Container-based dev environment setup and dependency management for Megatron-LM. Covers acquiring and launching the CI container, uv package management, and updating uv.lock.
18,041 stars
0 votes
0 copies
1 view
Added September 3, 2026
testingbashnodedockertestinggit
Security analysis
C67/100
criticalAccesses sensitive system or user directories
mediumInstalls packages at runtime which could introduce malicious dependencies
Installs into .claude/skills of the current project.
Are you the author of Mcore Build And Dependency?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/nvidia-mcore-build-and-dependency)
---
name: mcore-build-and-dependency
description: Container-based dev environment setup and dependency management for Megatron-LM. Covers acquiring and launching the CI container, uv package management, and updating uv.lock.
license: Apache-2.0
when_to_use: Adding, removing, or updating a dependency; editing pyproject.toml or uv.lock; uv.lock merge conflict; setting up a dev environment; pulling or building the CI container; container build errors; uv errors; 'how do I install', 'uv sync fails', 'ModuleNotFoundError'.
metadata:
author: Oliver Koenig <okoenig@nvidia.com>
---
# Build & Dependency Guide
The core principle: **build and develop inside containers** — the CI container
ships the correct CUDA toolkit, PyTorch build, and pre-compiled native extensions
(TransformerEngine, DeepEP, …) that cannot be reproduced on a bare host.
## Answer-First Constants
For text-only dependency or container questions, give these repo-specific facts
up front before the longer workflow:
- Run dependency work inside the Megatron-LM CI container, not on the host.
- The container venv is `/opt/venv`, already on `PATH`.
- Default `dev` uses `docker/.ngc_version.dev` and the `dev` uv group; `lts`
uses `docker/.ngc_version.lts` and the `lts` uv group. The `container::lts`
PR label selects the LTS path; otherwise CI uses `dev`.
- **`lts` is opt-in only when the user explicitly asks for it.** It is the older
long-term-support base, not a routine second lane — never attach
`container::lts`, build the LTS image, or run the `lts` uv group on your own
initiative, not even for a container or dependency change.
- Install commands inside the container: `uv sync --locked --group dev --group test`,
`uv sync --locked --only-group linting`, or
`uv sync --locked --group lts --group test`.
- Dependency edits use `uv add <package>` followed by `uv lock`, both inside
the container.
- `docker/Dockerfile.ci.dev` has `main` and `jet` stages. The `jet` stage needs
an internal secret; local/public builds should pass `--target main`.
---
## Why Containers
Megatron-LM depends on CUDA, NCCL, PyTorch with GPU support, TransformerEngine,
and optional components like ModelOpt and DeepEP. Installing these on a bare host
is fragile and hard to reproduce. The project ships Dockerfiles that pin every
dependency.
**Use the container as your development environment.** This guarantees:
- Identical CUDA / NCCL / cuDNN versions across all developers and CI.
- `uv.lock` resolves the same way locally and in CI.
- GPU-dependent operations (training, testing) work out of the box.
---
## dev vs lts
Two image variants exist, each with its own Dockerfile, selected by the
`container::lts` PR label. The defining difference is the **base container**:
`dev` tracks the latest NGC PyTorch release, while `lts` ("long-term support")
pins the previous, still-supported NGC PyTorch/CUDA release. `container::lts`
exists to verify a change still works on that older base — the dependency
differences below follow from it, they are not the point.
| Variant | Base image pin | Dockerfile | Where deps live | When used |
|---------|---------------|------------|-----------------|-----------|
| **`dev`** | `docker/.ngc_version.dev` (latest NGC release) | `docker/Dockerfile.ci.dev` | `pyproject.toml` `dev` extra (uv-resolved) | Default — CI, local development, most PRs |
| **`lts`** | `docker/.ngc_version.lts` (older long-term-support release) | `docker/Dockerfile.ci.lts` | `docker/lts/requirements.txt` (pinned, sourced from main's `uv.lock` at AUT-479) | Backward-compat lane — verify the change still runs on the older NGC base; extras not carried on it (ModelOpt, the CUDA-13 TransformerEngine build) are dropped |
> LTS deps used to live in `[project.optional-dependencies].lts` in
> `pyproject.toml`. They were moved into `docker/lts/requirements.txt` so
> `pyproject.toml` can host meaningful module-level extras without colliding
> with the LTS pin set. To bump an LTS dependency, edit the version in
> `docker/lts/requirements.txt` and rebuild `docker/Dockerfile.ci.lts`.
**Use `dev` for everything. `lts` is off-limits unless the user explicitly asks
for it.** CI runs `dev` by default, and that is the only variant you touch on
your own initiative. Treat `container::lts` as a high barrier, not a fallback: do
**not** attach the label, build `docker/Dockerfile.ci.lts`, or run the `lts` uv
group unless the user has explicitly requested LTS validation — not even for a
container or dependency change. When they do ask, `container::lts` verifies the
change still works on the older long-term-support PyTorch/CUDA base that LTS
users run. The `@pytest.mark.flaky_in_dev` marker skips tests in the `dev`
environment; `@pytest.mark.flaky` skips them in `lts`.
---
## Step 1 — Acquire an Image
**Option A — NVIDIA-internal: pull a CI-built image**
> ⚠️ Requires access to the internal GitLab instance.
> See @tools/trigger_internal_ci.md for setup (adding the git remote, obtaining a token).
The internal GitLab CI publishes images to its container registry.
Derive the registry host from your configured `gitlab` remote — the same
host you use for `trigger_internal_ci.py`:
```bash
# Derive host from your 'gitlab' remote:
GITLAB_HOST=$(git remote get-url gitlab | sed 's/.*@\(.*\):.*/\1/')
docker pull ${GITLAB_HOST}/adlr/megatron-lm/mcore_ci_dev:main
```
**Option B — Build from scratch (works for everyone)**
> ⚠️ `Dockerfile.ci.dev` has two stages: `main` and `jet`. The `jet` stage
> requires an internal build secret and will fail without it. Always pass
> `--target main` to stop at the public stage.
```bash
# dev image (default)
docker build \
--target main \
--build-arg FROM_IMAGE_NAME=$(cat docker/.ngc_version.dev) \
--build-arg IMAGE_TYPE=dev \
-f docker/Dockerfile.ci.dev \
-t megatron-lm:local .
# lts image (uses a dedicated Dockerfile; no IMAGE_TYPE arg)
docker build \
--target main \
--build-arg FROM_IMAGE_NAME=$(cat docker/.ngc_version.lts) \
-f docker/Dockerfile.ci.lts \
-t megatron-lm:local-lts .
```
Which image variant is used is controlled by the PR label `container::lts`;
absent that label, `dev` is used.
---
## Step 2 — Launch the Container
**Option A — Local Docker runtime**
```bash
docker run --rm --gpus all \
-v $(pwd):/workspace \
-w /workspace \
megatron-lm:local \
bash -c "<your command>"
```
**Option B — Slurm cluster (for those without a local Docker runtime)**
NVIDIA clusters typically use [Pyxis](https://github.com/NVIDIA/pyxis) +
[enroot](https://github.com/NVIDIA/enroot). Request an interactive session:
```bash
srun \
--nodes=1 --gpus-per-node=8 \
--container-image megatron-lm:local \
--container-mounts $(pwd):/workspace \
--container-workdir /workspace \
--pty bash
```
For clusters that require a `.sqsh` archive first:
```bash
enroot import -o megatron-lm.sqsh dockerd://megatron-lm:local
srun \
--nodes=1 --gpus-per-node=8 \
--container-image $(pwd)/megatron-lm.sqsh \
--container-mounts $(pwd):/workspace \
--container-workdir /workspace \
--pty bash
```
---
## Dependency Management
Dependencies are declared in `pyproject.toml`. The venv lives at `/opt/venv`
inside the container (already on `PATH`).
> **All `uv` operations must be run inside the container.**
> Never run `uv sync` / `uv pip install` on the host.
### uv Dependency Groups
| Group | Purpose |
|-------|---------|
| `training` | Runtime training extras |
| `dev` | Full dev environment (TransformerEngine, ModelOpt, …) |
| `test` | pytest, coverage, nemo-run |
| `linting` | ruff, black, isort, pylint |
| `build` | Cython, pybind11, nvidia-mathdx |
> The previous `lts` extra has been emptied. LTS deps are pinned in
> `docker/lts/requirements.txt` rather than `pyproject.toml`. Do not add new
> packages under `[project.optional-dependencies].lts`.
Install commands (inside the container):
```bash
# Full dev + test environment
uv sync --locked --group dev --group test
# Linting only
uv sync --locked --only-group linting
```
The LTS environment is reproduced by building `docker/Dockerfile.ci.lts`
end-to-end; there is no `uv sync`-only equivalent because the LTS deps no
longer live in `pyproject.toml`. The LTS top-level pin set is in
`docker/lts/requirements.txt`; bump versions there and rebuild the image.
Several dependencies are sourced directly from git (TransformerEngine, nemo-run,
FlashMLA, Emerging-Optimizers, nvidia-resiliency-ext). The locked `uv.lock` file
pins exact revisions; update it with `uv lock` when changing `pyproject.toml`.
### Adding a New Dependency
Follow this three-step workflow:
1. **Acquire a container image** — see [Step 1](#step-1--acquire-an-image) above.
2. **Launch the container interactively** — see [Step 2](#step-2--launch-the-container) above.
3. **Update the lock file inside the container**, then commit it:
```bash
# Inside the container:
uv add <package> # adds to pyproject.toml and resolves
uv lock # regenerates uv.lock
# Exit the container, then on the host:
git add pyproject.toml uv.lock
git commit -S -s -m "build: add <package> dependency"
```
### Resolving a merge conflict in uv.lock
`uv.lock` is machine-generated; never resolve conflicts manually. Instead:
```bash
git checkout origin/main -- uv.lock # take main's version as the base
# then inside the container:
uv lock # re-resolve on top of your pyproject.toml changes
```
---
## Common Pitfalls
| Problem | Cause | Fix |
|---------|-------|-----|
| `uv sync --locked` fails | Dependency conflict or stale `uv.lock` | Re-run `uv lock` inside the container and commit updated lock |
| `ModuleNotFoundError` after pip install | pip installed outside the uv-managed venv | Use `uv add` and `uv sync`, never bare `pip install` |
| `uv: command not found` inside container | Wrong container image | Use the `megatron-lm` image built from `Dockerfile.ci.dev` |
| `No space left on device` during uv ops | Cache fills container's `/root/.cache/` | Mount a host cache dir via `-v $HOME/.cache/uv:/root/.cache/uv` |
| `docker build` fails with secret-related error | `Dockerfile.ci.dev` has a `jet` stage that requires an internal secret | Add `--target main` to stop before the `jet` stage |
| `access forbidden` when pulling | Registry URL includes an explicit port (e.g. `:5005`) | Use `${GITLAB_HOST}/adlr/...` with no port — the sed extracts the hostname only |