·
Last changedVersion:
This version passed the hardening gate. Hardening applied 8 targeted changes and preserved all other text. Classified findings before hardening: 5. Classified findings closed: 2. Classified findings remaining: 3. Unclassified scanner findings remaining: 0. Review findings closed: 8. Review findings remaining: 12.
3
2
3
Expand | File | Group | Subtype | Severity | Status |
|---|---|---|---|---|---|
SKILL.md | Sensitive Data & Secret Exposure | Hardcoded secret | Medium | Open | |
references/templates.md | Privilege Escalation & Host / Cloud Compromise | Privilege escalation | Medium | Open | |
BENCHMARK.md | Sensitive Data & Secret Exposure | Agent snooping | Medium | Open | |
SKILL.md | Malicious Code & Command Execution | — | Low | Closed | |
SKILL.md | Supply Chain & Install-Time Trust | Install-time download and execute | Low | Closed |
5 findings
Convert single-node uv run python -m torch.distributed.run commands into multi-node Slurm sbatch scripts with Enroot container support, and debug common multi-node failures.
When converting or debugging Bridge multi-node jobs, answer in this order:
initialize.py: #SBATCH --ntasks-per-node=8 and a direct srun ... uv run python <script> ... launch. Do not wrap these jobs in
python -m torch.distributed.run.RANK, WORLD_SIZE, LOCAL_RANK,
MASTER_ADDR, and MASTER_PORT from SLURM variables during
initialize.py distributed init.HF_HOME, UV_CACHE_DIR, and NEMO_HOME.Failures: to find the first failed rank and nodencclUniqueId, timeout, or crash on rank 0| Approach | ntasks-per-node | Process spawning | Best for |
|---|---|---|---|
srun-native (preferred) | 8 | Slurm spawns 8 tasks/node | Conversion, inference, Bridge scripts |
uv run torch.distributed (legacy) | 1 |
| MLM pretrain_gpt.py |
Prefer srun-native — simpler, avoids shell escaping issues with TRAIN_CMD. Megatron Bridge auto-derives RANK, WORLD_SIZE, LOCAL_RANK, MASTER_ADDR, MASTER_PORT from SLURM env vars (SLURM_PROCID, SLURM_NTASKS, SLURM_LOCALID, SLURM_NODELIST) via common_utils.py helpers called during initialize.py distributed init, so you never need to set them manually.
Use a shared filesystem for the repository, data, logs, HF_HOME, UV_CACHE_DIR, and NEMO_HOME. NEMO_HOME must not use the container-local default (/root/.cache/nemo) for multi-node SFT/PEFT jobs, because packed-sequence data prepared on node 0 must be visible to the other nodes.
Keep credentials out of sbatch templates and logs. Provide HF_TOKEN, GH_TOKEN, and WANDB_API_KEY through the scheduler environment or a restricted secrets file, and never hardcode token values in the script body. For copy-paste environment and sbatch templates, read references/templates.md.
Slurm spawns all processes directly. No torch.distributed.run, no TRAIN_CMD escaping.
Use a two-phase srun pattern: first run a single-process uv sync to populate the shared cache, then launch the full multi-node job. The full copy-paste version lives in references/templates.md.
uv sync once on a single node/process, building all wheels into the shared cache on Lustreuv sync is a fast no-op (everything is cached) — safe to run on all ranks without sleep guardsinitialize.py + common_utils.py auto-set RANK, WORLD_SIZE, LOCAL_RANK, MASTER_ADDR, MASTER_PORT from SLURM env varsHF_TOKEN, HF_HOME, UV_CACHE_DIR exported at sbatch level are inherited by srun tasksexamples/models/glm/glm_45v/slurm_sft.sh, examples/models/minimax/minimax_m2/slurm_conversion.shUse when the script requires torch.distributed.run (e.g., MLM pretrain_gpt.py) or when Bridge's initialize.py is not in the call path.
Critical: --ntasks-per-node=1, NOT 8. uv run python -m torch.distributed.run --nproc_per_node=8 spawns 8 processes per node. Using ntasks-per-node=8 causes EADDRINUSE port collisions (8 tasks x 8 procs = 64 per node).
Replace single-node:
With multi-node (inside TRAIN_CMD string):
Use the same two-phase pattern: first a single-process srun to warm the uv cache, then the full run.
Set runtime variables inside the container, but do not inject token values into a long bash -c string. Export credentials through the scheduler or source a restricted secrets file before the job starts. Keep HF_HOME, UV_CACHE_DIR, and NEMO_HOME on shared storage.
Use the two-phase launch template in references/templates.md, keeping #SBATCH --ntasks-per-node=1 for this legacy approach.
salloc + srun)For ad-hoc testing (inference, conversion debugging), always follow these 3 steps:
Then run commands with uv run (uses the synced virtualenv):
Pitfalls with interactive allocation:
| Error | Cause | Fix |
|---|---|---|
| Missing | Always include |
| Wrong partition name | Use |
| Partition not available for account | Check combos: |
|
| Remove |
| Container doesn't have | Use a container with |
| Container's | Redirect: |
| Container's pre-installed megatron-core conflicts with local | Install local: |
Check the log for these patterns (in order):
When a multi-node job fails:
num_experts / EP fits in GPU memory with headroomsalloc -N 2 -p interactive to iterate faster than sbatch queuedist.barrier() — "crash on rank 0"Symptom: All ranks on node 2+ show:
Root causes (check in order):
| Cause | How to verify | Fix |
|---|---|---|
| Error is in | Increase timeout: |
|
| Catch |
Rank 0 OOM during export |
| Increase EP or nodes |
Network issue between nodes | Error only on cross-node ranks | Check |
The save_artifacts problem: When trust_remote_code=True, rank 0 runs save_artifacts() (downloads tokenizer, config, custom modeling code) while all other ranks skip directly to dist.barrier(). If save_artifacts is slow or crashes, other ranks timeout.
Fix for ImportError in save_artifacts (hf_pretrained/base.py):
Symptom: torch.OutOfMemoryError: CUDA out of memory during model loading or forward pass.
Key insight: TP does NOT reduce expert memory. Only EP splits experts across GPUs.
Sizing formula:
MiniMax-M2 example (256 experts, ~230GB fp8 → ~460GB bf16):
| Config | Nodes | GPUs | Experts/GPU | Result |
|---|---|---|---|---|
TP=2, EP=4 | 1 | 8 | 64 | OOM (too many experts) |
TP=2, EP=8 | 2 | 16 | 32 | Works for roundtrip (weight-only), OOM for inference |
TP=1, EP=16 | 2 | 16 | 16 | Works for inference |
TP=2, EP=32 | 8 | 64 | 8 | Comfortable for training |
Rules of thumb:
ModuleNotFoundError: No module named 'megatron.core.tensor_parallel'Cause: Container's pre-installed megatron-core conflicts with local 3rdparty/Megatron-LM.
Fix: Add uv sync before running:
Symptom: Roundtrip completes but shows ❌ for all expert weights and raises ValueError: Weight mismatch detected.
Cause: Original HF weights are FP8, Megatron stores in BF16. Exported weights are BF16. Comparison against original FP8 exceeds atol=1e-1.
WORLD_SIZE Not Set with srunSymptom: Script exits with "must be launched with torchrun".
Cause: Scripts check os.environ.get("WORLD_SIZE") which torchrun sets but srun doesn't.
Fix: Also check SLURM_NTASKS:
Bridge's common_utils.py helpers (called by initialize.py) populate env vars from SLURM:
Two-phase srun for uv sync: Run a single-process srun first to warm the cache, then the full multi-node srun. The second uv sync is a fast no-op since everything is already cached on the shared filesystem.
--no-container-mount-home is an srun flag, NOT an #SBATCH directive.
Escaping inside TRAIN_CMD: Since TRAIN_CMD is a double-quoted string, escape inner $ for Slurm variables that must expand at runtime (not sbatch time):
\${SLURM_PROCID}, \${SLURM_JOB_NUM_NODES}, \${SLURM_NODEID}$LOGDIR, $WORKDIR expand at sbatch time — no escaping needed.Bridge rm -rf nemo_experiments: Add before training to avoid stale checkpoint auto-resume.
MLM needs PYTHONPATH: For pretrain_gpt.py scripts, add inside TRAIN_CMD:
Node count heuristic: Total GPUs = NNODES * 8.
NEMO_HOME on shared filesystem for multi-node SFT: The default nemo cache (/root/.cache/nemo)
is container-local. Multi-node SFT with packed sequences prepares .npy files on one node
that are invisible to others. Set export NEMO_HOME=<SHARED_FS>/cache/nemo so packed data
is shared. Without this, ranks on other nodes fail with TypeError: 'NoneType' object is not an iterator.
For copyable sbatch scaffolding and Bridge/MLM-specific TRAIN_CMD bodies, read
references/templates.md.
Get started with chainctlchainctl skill install public/nvidia/nemo-mbridge-multi-node-slurm:latest
Bring Chainguard's hardening pipeline to your first-party skills — custom rulesets, automated remediation, full audit trail.
skills.cgr.dev/public/github.com/nvidia/skills/nemo-mbridge-multi-node-slurm:latest
Apache-2.0
BENCHMARK.md
HARDENING.md
LICENSE
SKILL.md
evals/evals.json
references/templates.md
skill-card.md
skill.oms.sig