Run preflight without a container#
⚠ Run the node-smoke test first.
preflightopens a globaltorch.distributedrendezvous, so a single sick node (wedged driver, leaked rank holding HBM, partial NIC enumeration, time-sync drift, etc.) can stall the whole job for up to--dist-timeout-secseconds — long before any cross-node bandwidth number is produced. The node-smoke test catches those exact failure modes without a rendezvous in ~30–60 s and emits a SLURM-readyfailing_nodes.txtyou can pipe straight intosrun --exclude=. Treat node-smoke as a hard prerequisite; only runpreflighton the nodes node-smoke marked PASS. See §0 “Which test should I run?” for the side-by-side comparison and the recommended 3-step workflow.
This guide explains how to run Primus’s preflight cluster-diagnostic tool directly on the host (no Docker / Podman), via the standard Primus launcher.
Git clone the Primus repository to a shared filesystem that all nodes can read.
git clone --recurse-submodules https://github.com/AMD-AGI/Primus.git
cd Primus
Recommended (through the primus-cli SLURM wrapper):
For some clusters, you may need to explicitly request CPU and GPU resources with srun -N <nodes> -c <cpus-per-node> --gpus-per-node=<gpus-per-node>.
runner/primus-cli slurm srun -N <NNODES> --ntasks-per-node=1 -- direct -- preflight [PREFLIGHT_ARGS...]
Equivalent (bare srun, useful when composing with custom srun flags):
srun -N <NNODES> --ntasks-per-node=1 runner/primus-cli direct -- preflight [PREFLIGHT_ARGS...]
Both forms produce the same workload on the same ranks. The wrapper form is recommended because it auto-resolves MASTER_ADDR / MASTER_PORT / NNODES / NODE_RANK / GPUS_PER_NODE once on the launching node and passes them to every rank via --env, applies any slurm.* config defaults (partition / time / etc.) from your YAML, and is the same pattern used for train / benchmark / node_smoke. See § Wrapper vs. bare-srun below for the exact precedence / caveats.
primus-cli direct activates an optional Python virtualenv (VENV_ACTIVATE), auto-derives the distributed environment variables (NNODES, NODE_RANK, MASTER_ADDR, MASTER_PORT, GPUS_PER_NODE) from SLURM_* when running inside a SLURM allocation, and then launches the preflight Python subcommand via torchrun (one worker per GPU). It is the recommended entry point when:
You’re running on a SLURM cluster but cannot (or don’t want to) use the container-based path.
Your nodes share a Python virtual environment on a network-mounted filesystem.
You want a single-node sanity check with no extra configuration.
0. Which test should I run?#
Primus ships two complementary cluster screens. Pick the right one — and ideally run them in this order.
Aspect |
|
|
|---|---|---|
Purpose |
“Which nodes are healthy enough to run anything?” |
“What is the actual cross-node performance on the surviving nodes?” |
Rendezvous |
None — every node independent |
Global |
Wall clock |
~30–60 s for 6 nodes (Tier 1+2) |
A few minutes; scales with N for inter-node tests |
Granularity |
Per-node PASS/FAIL |
Per-rank perf measurements |
Safety |
A stuck node cannot wedge its peers |
A single hung NIC can stall the whole rendezvous |
Output |
Per-node JSON + cluster md + SLURM-ready |
Markdown + PDF perf report |
Entry point |
|
|
Quick-start guide |
This doc, §3+ |
Recommended workflow#
Before running any of the commands below, complete the one-time setup:
Python virtualenv on a shared filesystem — see §2 Set up the Python virtual environment, then point the launcher at it via
export VENV_ACTIVATE=...(details in §2 → Tell the launcher where the venv is).NCCL / fabric environment variables — usually the defaults in
base_env.share fine, but multi-NIC nodes may needNCCL_IB_HCA/NCCL_IB_GID_INDEX/NCCL_SOCKET_IFNAMEoverrides. See §4 Cluster-specific NCCL configuration for known-good values per fabric (Broadcom, Pensando Pollara/AINIC).
Through the primus-cli slurm srun -- direct -- wrapper (recommended):
# 1) Prune broken nodes with node-smoke (fast, no rendezvous).
runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \
-- direct -- node_smoke --tier2-perf
# 2) Re-allocate excluding the bad nodes, and run preflight --quick
# for a fast cross-node sanity check.
runner/primus-cli slurm srun -N <good-nnodes> -c 128 --gpus-per-node=8 \
--ntasks-per-node=1 \
--exclude=$(paste -sd, output/preflight/failing_nodes.txt) \
-- direct -- preflight --quick
# 3) Optional: full preflight on the same set if --quick numbers
# look off, or if you want the full bandwidth matrix.
runner/primus-cli slurm srun -N <good-nnodes> -c 128 --gpus-per-node=8 \
--ntasks-per-node=1 \
--exclude=$(paste -sd, output/preflight/failing_nodes.txt) \
-- direct -- preflight
Equivalent with bare srun (works identically; useful when scripting around custom srun flags that don’t compose with the wrapper):
srun -N "$SLURM_NNODES" --ntasks-per-node=1 \
runner/primus-cli direct -- node_smoke --tier2-perf
srun -N <good-nnodes> -c 128 --gpus-per-node=8 --ntasks-per-node=1 \
--exclude=$(paste -sd, output/preflight/failing_nodes.txt) \
runner/primus-cli direct -- preflight --quick
srun -N <good-nnodes> -c 128 --gpus-per-node=8 --ntasks-per-node=1 \
--exclude=$(paste -sd, output/preflight/failing_nodes.txt) \
runner/primus-cli direct -- preflight
Why this ordering matters:
A single broken node can stall a
torch.distributed.init_process_group()for--dist-timeout-secseconds (default 120), so feeding a known-good list to preflight is much faster.node-smokecatches things preflight cannot — leaked / foreign processes, wedged drivers, partial NIC enumeration, time-sync drift, RDMA roll-call issues — that produce misleading preflight failures.preflight --quickadds the cross-node bandwidth signal thatnode-smokedeliberately does not measure.
1. Prerequisites#
A working AMD ROCm installation on every node.
Network reachability between nodes (Ethernet for bootstrap, RDMA / InfiniBand recommended for perf tests).
A Python ≥ 3.10 virtual environment on a shared filesystem that all nodes can read (the same path is sourced on every node).
The Primus repository checked out somewhere readable from every node.
2. Set up the Python virtual environment#
The environment must live on a path visible from every node (e.g. NFS-mounted home, Lustre, or any shared filesystem). All nodes will source the same activation script.
You can use any tool you like; uv is the fastest. Either of the following works.
What you actually need to install#
The preflight and node-smoke tools deliberately use only a small subset of Primus’s full dependency tree. You do not need to install the entire requirements.txt — that pulls in trainer / dataset / experiment-tracking packages that neither tool ever imports.
Package |
Required for |
Skip when |
|---|---|---|
|
Both tools — perf measurements ( |
Never (mandatory). |
|
|
You always pass |
|
|
Same as above. |
|
|
You don’t pass |
Everything else in the preflight / node-smoke code path is Python stdlib (os, subprocess, socket, argparse, dataclasses, json, time, …) — no extra installs needed.
Option A — uv (recommended), minimal install#
mkdir -p ~/envs/preflight
cd ~/envs/preflight
uv venv --python 3.12
source .venv/bin/activate
# 1) ROCm-built PyTorch (pin to your ROCm version; rocm7.1 shown here)
uv pip install torch torchvision --index-url https://download.pytorch.org/whl/rocm7.1 --no-cache-dir
# 2) Optional: only if you want preflight PDF reports (omit to use --disable-pdf)
uv pip install markdown2 weasyprint
# 3) Optional: only if you want preflight --plot bar charts
uv pip install matplotlib
Option B — python -m venv, minimal install#
mkdir -p ~/envs/preflight
python3.12 -m venv ~/envs/preflight/.venv
source ~/envs/preflight/.venv/bin/activate
pip install torch torchvision --index-url https://download.pytorch.org/whl/rocm7.1 --no-cache-dir
pip install markdown2 weasyprint # optional, for preflight PDFs
pip install matplotlib # optional, for preflight --plot
Option C — full Primus runtime (only if you also want the rest of Primus)#
cd /path/to/Primus
uv pip install -r requirements.txt # or: pip install -r requirements.txt
This installs every Primus runtime dependency (trainer, dataset loaders, experiment trackers, …). Use only if you’re going to run more than just preflight / node-smoke from this environment.
Per-tool minimum install matrix#
If you want the absolute smallest footprint, install only what your intended invocations need:
Invocation |
|
|
|
|
|---|---|---|---|---|
|
required |
— |
— |
— |
|
required |
— |
— |
— |
|
required |
required |
required |
— |
|
required |
— |
— |
— |
|
required |
required |
required |
— |
|
required |
required (unless |
required (unless |
required |
Tell the launcher where the venv is#
primus-cli direct reads the VENV_ACTIVATE environment variable. When set, it sources the path before launching the Python process; when unset, it is a no-op (the container path, which uses the container’s bundled Python, leaves this unset):
export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate
VENV_ACTIVATE is the only optional environment variable specific to the direct flow. Everything else has a sensible default; distributed-env variables (NNODES, NODE_RANK, MASTER_ADDR, …) are auto-derived from SLURM when not pre-exported.
3. Run preflight#
Single node (no SLURM)#
export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate
# Info report only (fast)
runner/primus-cli direct -- preflight --host --gpu --network
# Info + perf report
runner/primus-cli direct -- preflight
# Perf report only
runner/primus-cli direct -- preflight --perf-test
When SLURM is not detected the script defaults to NNODES=1, NODE_RANK=0, MASTER_ADDR=localhost. Any of those can be overridden by exporting them before calling the script.
Multi-node without SLURM (parallel SSH)#
When no scheduler is available (bare-metal, cloud VMs, lab nodes), launch
primus-cli direct on each node yourself via SSH. The script works
identically — you just pre-export the distributed variables that SLURM
would normally provide.
Requirements#
All nodes share the same filesystem (or at least the same Primus checkout + venv path).
Nodes can reach each other on a data-plane network interface (not the management NIC).
SSH key-based access to each node from the launching host.
Required environment variables#
Variable |
Description |
|---|---|
|
Total number of nodes |
|
This node’s rank ( |
|
IP of rank-0 node on the data-plane interface |
|
Rendezvous port (default |
|
GPUs per node (default |
|
Data-plane NIC name (e.g. |
|
Same as |
|
Path to virtualenv |
Warning:
NCCL_SOCKET_IFNAMEauto-detection often picks a management interface (e.g.enp28s0np0,eno8303) instead of the high-bandwidth data NIC. For multi-node runs this causesinit_process_groupto hang or NCCL to fail silently. Always set it explicitly.
Identifying the correct data-plane interface#
# On any node, find the interface whose IP matches the MASTER_ADDR subnet:
ip -4 addr show | grep "10.245.134"
# → enp159s0np0 inet 10.245.134.129/24
# Or check which interface routes to the master:
ip route get 10.245.134.129 | awk '{print $5; exit}'
Multi-node via SLURM#
primus-cli direct auto-detects a SLURM allocation (via SLURM_JOB_ID) and derives all distributed variables from SLURM_* automatically. Pre-exported values always win, so the same launcher script also works inside the primus-cli slurm srun ... -- direct -- ... chain (where slurm-entry has already set these via --env):
Variable |
Resolved as |
|---|---|
|
|
|
|
|
|
|
|
|
|
Run it as a single task per node (the script invokes torchrun internally, which spawns one worker per GPU):
Verify NCCL / network env first. The script sets sensible
NCCL_* defaults viabase_env.sh, but auto-detection can pick the wrong device on multi-NIC nodes. Always confirmNCCL_IB_HCA,NCCL_IB_GID_INDEX,NCCL_SOCKET_IFNAME, andGLOO_SOCKET_IFNAME(set to the same value asNCCL_SOCKET_IFNAME) are correct for your fabric, andexportoverrides before running. See §4 for cluster-specific values.
export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate
# export NCCL_IB_HCA=rdma1,rdma2,rdma3,rdma4,rdma5,rdma6,rdma7
# export NCCL_IB_GID_INDEX=3
# export NCCL_SOCKET_IFNAME=eno0
# export GLOO_SOCKET_IFNAME=eno0
# Recommended: through the primus-cli SLURM wrapper.
runner/primus-cli slurm srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 \
--nodelist <nodes> --ntasks-per-node=1 \
-- direct -- preflight --perf-test
# Or, equivalently, with bare srun:
srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --nodelist <nodes> \
--ntasks-per-node=1 \
runner/primus-cli direct -- preflight --perf-test
Wrapper vs. bare-srun#
Both forms target the same primus-cli-direct.sh launcher and produce identical workloads. The difference is only in how the SLURM context is constructed:
Aspect |
|
Bare |
|---|---|---|
|
Resolved once on the launching node via `scontrol show hostnames “$SLURM_NODELIST” |
head -n1 |
|
Set explicitly by |
Derived from |
|
Honored (partition, time, ntasks-per-node, etc. from the active YAML). |
Not consulted — you pass every flag explicitly to |
Default wall-time |
|
None — |
|
Required: |
N/A — there’s only one path. |
|
Not auto-added. Pass it on the CLI (before the first |
Not auto-added. Pass it as an |
Best for |
Production / repeatable runs. Same pattern as |
Ad-hoc runs where you want to compose with arbitrary |
For the rest of this doc the examples use bare srun for brevity, but every example also works with the wrapper form by substituting srun <flags> runner/primus-cli direct -- → runner/primus-cli slurm srun <flags> -- direct --.
Key srun flags#
Flag |
Why it’s necessary |
|---|---|
|
Allocate all CPU cores per task. Without this, SLURM may default to 1 core, which starves the RCCL network proxy threads and can cause >30× slowdown on perf tests. Set this to your node’s core count. |
|
Grants GPU device access ( |
|
One launcher invocation per node; |
|
Wall-clock limit. Full perf tests on 8N usually finish well under 10 min. |
Tip — check core count:
srun -N 1 --gpus-per-node=8 bash -c 'nproc'
4. Cluster-specific NCCL configuration#
primus-cli direct sources runner/helpers/envs/base_env.sh, which sets sensible defaults for NCCL_* and auto-detects NCCL_IB_HCA / NCCL_SOCKET_IFNAME. Pre-exported values from your shell take precedence, so the standard pattern is:
export VAR=value
runner/primus-cli direct -- preflight ...
Broadcom NICs (no AINIC)#
Most clusters fall here. The defaults from base_env.sh are usually fine, but the two values most commonly worth overriding are:
export NCCL_CROSS_NIC=1 # default in base_env.sh is 0
export NCCL_PXN_DISABLE=0 # default in base_env.sh is 1
srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --nodelist <nodes> \
--ntasks-per-node=1 \
runner/primus-cli direct -- preflight --perf-test
Pensando Pollara (AINIC) RDMA#
export USING_AINIC=1
export NCCL_IB_GID_INDEX=1 # AINIC uses index 1 (default in base_env.sh is 3)
export NCCL_PXN_DISABLE=0
srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --nodelist <nodes> \
--ntasks-per-node=1 \
runner/primus-cli direct -- preflight
primus-cli directdoes accept--env KEY=VALUEon its own command line (placed before--), in addition to the conventionalexport/srun --export=approaches.
5. Launcher flags vs. preflight flags#
Anything you place after the -- separator is forwarded verbatim to the preflight Python tool. The launcher (primus-cli-direct.sh) consumes a small set of flags before --. The one most users care about is --silent.
Launcher-only flags (before --)#
Flag |
Effect |
|---|---|
|
Back-pocket knob: redirect the launcher’s and the Python tool’s |
|
Verbose launcher logging ( |
|
Print the resolved configuration and final |
|
Force |
|
Inject an env var into the Python process (in addition to anything |
|
Redirect the captured tee log to a specific path (default: |
See runner/primus-cli direct --help for the full set.
Forwarded preflight flags (after --, most common)#
See Preflight for the full list. The most common are:
Mode selection:
--host,--gpu,--network,--perf-test,--tests,--quickTest tuning:
--comm-sizes-mb,--intra-comm-sizes-mb,--inter-comm-sizes-mb,--intra-group-sizes,--inter-group-sizes,--ring-p2p-sizes-mbReporting:
--dump-path,--report-file-name,--disable-pdf,--plotReliability:
--comm-cleanup-delay-sec,--dist-timeout-sec
If you do not pass --report-file-name, preflight auto-generates a unique one of the form:
preflight-${NNODES}N-YYYYMMDD-HHMMSS
This guarantees that each run lands in its own files and prevents stale leftovers from earlier runs from being mistaken for fresh output. The auto-name logic now lives in the Python tool itself, so every call site (host srun ... primus-cli direct, primus-cli slurm ... -- direct, primus-cli slurm ... -- container) gets the same fresh name.
Examples#
The examples below all assume one of the two equivalent shell-prefix conventions. Pick whichever matches your habits — every example block in this section works with either definition:
export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate
# Recommended: through the primus-cli SLURM wrapper. Auto-resolves
# MASTER_ADDR/NNODES/NODE_RANK once on the launching node and propagates
# them via --env; honors slurm.* config defaults.
SRUN="runner/primus-cli slurm srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --ntasks-per-node=1 --nodelist <nodes> --"
# Then in every example below, replace `$SRUN runner/primus-cli direct --`
# with just `$SRUN direct --`. (The wrapper expects the entry-mode keyword
# `direct` as the first token after the inner `--`.)
# Equivalent: bare srun. NNODES/NODE_RANK/MASTER_ADDR get derived inside
# primus-cli-direct.sh's STEP 4.7 directly from SLURM_*; same net effect.
SRUN="srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --ntasks-per-node=1 --nodelist <nodes>"
The examples in this section use the bare-srun form below for brevity (since $SRUN runner/primus-cli direct -- preflight reads naturally as one command line). To use the wrapper form instead, substitute $SRUN runner/primus-cli direct -- → $SRUN direct -- after exporting SRUN to the wrapper variant.
A. Mode selection#
# Default: info report + every perf test
$SRUN runner/primus-cli direct -- preflight
# Info-only (fast, no torch.distributed rendezvous)
$SRUN runner/primus-cli direct -- preflight --host --gpu --network --disable-pdf
# Perf-only, every test
$SRUN runner/primus-cli direct -- preflight --perf-test
# Fast pre-launch sanity preset (gemm + intra-AR + inter-AR @ 64,1024 MB,
# full intra-node group, full N-node inter group, low warmup/iter)
$SRUN runner/primus-cli direct -- preflight --quick
Note: Mixing perf-mode flags (
--perf-test/--tests/--quick) with info selectors (--host/--gpu/--network) makes preflight drop the info selectors with aWARN. Run two invocations if you want both reports.
B. Test selection (--tests)#
# Only GEMM
$SRUN runner/primus-cli direct -- preflight --tests gemm
# Only the inter-node bandwidth tests
$SRUN runner/primus-cli direct -- preflight --tests inter-allreduce,inter-alltoall
# Only the inter-node ring P2P
$SRUN runner/primus-cli direct -- preflight --tests inter-ring-p2p
# Combine: GEMM + inter-AR with overridden sizes/groups
$SRUN runner/primus-cli direct -- preflight \
--tests gemm,inter-allreduce \
--comm-sizes-mb 64,1024 \
--inter-group-sizes all
Valid --tests tokens: gemm, intra-allreduce, intra-alltoall, inter-allreduce, inter-alltoall, inter-p2p, inter-ring-p2p, all. Unknown tokens fail fast (before NCCL init).
C. Message sizes#
# One CSV applied to both intra and inter
$SRUN runner/primus-cli direct -- preflight --tests intra-allreduce,inter-allreduce \
--comm-sizes-mb 8,128
# Different sizes for intra vs inter (override wins over --comm-sizes-mb)
$SRUN runner/primus-cli direct -- preflight --tests intra-allreduce,inter-allreduce \
--comm-sizes-mb 8,128 --intra-comm-sizes-mb 4,32
# Inter-only override (also covers inter-p2p when enabled)
$SRUN runner/primus-cli direct -- preflight --tests inter-allreduce,inter-p2p \
--comm-sizes-mb 8,128 --inter-comm-sizes-mb 16,512
D. Group sizes#
# Custom intra-node group sizes (each must divide LOCAL_WORLD_SIZE)
$SRUN runner/primus-cli direct -- preflight \
--tests intra-allreduce \
--intra-group-sizes 4,8
# Custom inter-node groups: 2-node pairs and the full N-node group
$SRUN runner/primus-cli direct -- preflight \
--tests inter-allreduce \
--inter-group-sizes 2,all
Note: for
inter-alltoallonly, every requested per-group node count is internally capped at 16 (real-world MoE training rarely dispatches across more nodes; seepreflight.md§5.2 for the rationale). The other inter-node tests use the requested sizes unchanged. So on a 128-node cluster,--tests inter-alltoall --inter-group-sizes allactually runs at 16-node sub-groups, while--tests inter-allreduce --inter-group-sizes allruns at 128 nodes as written.
E. Ring P2P sizes#
$SRUN runner/primus-cli direct -- preflight \
--tests inter-ring-p2p \
--ring-p2p-sizes-mb 5,20,80
F. Plotting#
# Generate per-test bandwidth bar charts under <dump-path>/<test>/*.png
$SRUN runner/primus-cli direct -- preflight \
--tests intra-allreduce,inter-allreduce --plot
G. Reliability knobs#
# Bump the per-phase cleanup delay. Default 2.0 is sufficient at every
# cluster size for the comm shapes preflight exercises (inter-alltoall
# is internally capped at 16 nodes; see preflight.md §5.2). Only bump
# this on very flaky networks or unusual kernel TIME_WAIT settings.
$SRUN runner/primus-cli direct -- preflight --quick --comm-cleanup-delay-sec 5
# Fail fast if torch.distributed rendezvous can't complete in 30s
$SRUN runner/primus-cli direct -- preflight --perf-test --dist-timeout-sec 30
Operating clusters at ≥ 128 nodes? See
preflight.md§7 for the recommended OS-level tuning (wideningip_local_port_range) and per-test invocation patterns. With the §5.2 inter-alltoall cap in place, a default invocation is safe at every cluster size; the §7.3 OS tuning remains best-practice for any RDMA host.
H. Reporting & output layout#
# Quick info-only check on 4 nodes, no PDF
$SRUN runner/primus-cli direct -- preflight --host --gpu --network --disable-pdf \
--report-file-name info-4N
# Perf test only, silenced (CI-friendly), explicit name. Note that --silent
# is consumed by primus-cli-direct.sh and must appear BEFORE the `--`
# separator; everything after `--` is forwarded to the preflight Python tool.
$SRUN runner/primus-cli direct --silent -- preflight --perf-test \
--report-file-name nightly-4N-perf
# Archive each run under its own directory
$SRUN runner/primus-cli direct -- preflight --quick \
--dump-path /shared/preflight-archive/$(date +%Y%m%d-%H%M%S)
I. Backward-compat aliases#
These still work and are equivalent to flags above. Use them only when retrofitting older scripts.
# Same as --host --gpu --network
$SRUN runner/primus-cli direct -- preflight --check-host --check-gpu --check-network
# Same as --inter-group-sizes all AND drops inter-p2p
$SRUN runner/primus-cli direct -- preflight --perf-test --no-split-nodes-subgroup
J. Combined “production-ready” pre-launch screen#
# 1) Smoke first to prune broken nodes (note: --silent goes BEFORE `--`)
srun -N "$SLURM_NNODES" --ntasks-per-node=1 \
runner/primus-cli direct --silent -- node_smoke --tier2-perf
# 2) Quick perf sanity on the survivors
srun -N <good-nnodes> -c 128 --gpus-per-node=8 --ntasks-per-node=1 \
--exclude=$(paste -sd, output/preflight/failing_nodes.txt) \
runner/primus-cli direct --silent -- preflight --quick \
--comm-cleanup-delay-sec 5 --dist-timeout-sec 60 \
--report-file-name screen-$(date +%Y%m%d-%H%M%S)
6. Outputs#
Reports are written to --dump-path (default: output/preflight/), with the basename from --report-file-name and a _perf suffix for performance reports:
File |
Produced by |
Notes |
|---|---|---|
|
|
Info report |
|
same, unless |
Info report PDF |
|
|
Perf report (GEMM + comm) |
|
same, unless |
Perf report PDF |
Only rank 0 writes the report. After preflight completes, the Python tool prints the absolute path of every report file it produced to stdout. Under --silent these prints go to /dev/null along with everything else (one of the trade-offs of using --silent); without --silent the announcement is visible live. Sample output:
[Primus:Preflight] Report: /home/.../Primus/output/preflight/preflight-4N-20260428-201925.md
[Primus:Preflight] Report: /home/.../Primus/output/preflight/preflight-4N-20260428-201925_perf.md
7. Environment variable reference#
Variables read by primus-cli direct itself:
Variable |
Required |
Default |
Purpose |
|---|---|---|---|
|
no |
— |
Path to the venv |
|
no |
|
Number of nodes. Pre-exported always wins. |
|
no |
|
This node’s rank. Pre-exported always wins. |
|
no |
|
GPUs per node |
|
no |
|
Rendezvous host. Pre-exported always wins. |
|
no |
|
Rendezvous port |
Variables consumed downstream by primus-cli direct / base_env.sh (set them via export):
Variable |
Default in |
When to override |
|---|---|---|
|
auto-detected |
Force a specific Ethernet interface for bootstrap |
|
auto-detected |
Force specific RDMA HCAs |
|
|
|
|
|
|
|
|
|
|
unset |
|
|
unset |
|
8. Troubleshooting#
[ERROR] [direct] VENV_ACTIVATE is set but file does not exist: ...#
VENV_ACTIVATE was set in the environment but the path it points at doesn’t exist on this node. This is a fail-fast guard to prevent a silent fallback to system Python (which usually has the wrong torch / no ROCm). Either fix the path:
export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate
… or unset it to fall back to the container / system Python:
unset VENV_ACTIVATE
If the path looks right but the file still appears missing, confirm the venv lives on a filesystem visible from the node SLURM scheduled you onto.
[Primus:Preflight] FAIL: No GPUs detected#
The Python process inside the venv can’t find ROCm. Diagnose with:
srun --nodes=1 --nodelist=<node> bash -c '
echo "=== PATH ==="; echo $PATH
echo "=== LD_LIBRARY_PATH ==="; echo $LD_LIBRARY_PATH
echo "=== rocm-smi ==="; rocm-smi --showid 2>&1
echo "=== Python torch check ==="
source ~/envs/preflight/.venv/bin/activate
python3 -c "import torch; print(\"hip:\", torch.version.hip); print(\"available:\", torch.cuda.is_available()); print(\"count:\", torch.cuda.device_count())"
'
If LD_LIBRARY_PATH is empty, set it explicitly:
export LD_LIBRARY_PATH=/opt/rocm/lib:${LD_LIBRARY_PATH:-}
Report announcement points at stale files#
This shouldn’t happen with the current Python tool — the auto-generated unique report name (preflight-${NNODES}N-<timestamp>) ensures every run gets a fresh path. If you explicitly pass --report-file-name X, you’re responsible for choosing a name that doesn’t collide with prior runs.
Slow perf tests (~30× expected)#
Almost always a symptom of insufficient CPU cores. Pass -c <cores-per-node> to srun so RCCL’s network proxy threads have CPU to spawn on. Verify with srun -N 1 --gpus-per-node=8 bash -c 'nproc'.
Using conda instead of venv#
primus-cli direct does source "$VENV_ACTIVATE", which works for venv/uv but not directly for conda. Two options:
Create a venv inside the conda env and point
VENV_ACTIVATEat that venv’s activate script.Write a small shim activate script (e.g.
~/envs/conda-shim.sh) that activates conda and the desired env, then pointVENV_ACTIVATEat it:
# ~/envs/conda-shim.sh
source "$HOME/miniconda3/etc/profile.d/conda.sh"
conda activate <env_name>
“Address already in use” during perf tests#
This means a node’s kernel ephemeral-port pool was momentarily exhausted while preflight was building many communicators in a short window, so an outgoing bind() could not find a free port. It is a preflight-specific artifact of repeated communicator setup/teardown — a real training job builds its communicators once and reuses them — not a training failure mode. See preflight.md §7 for details.
Preflight has two complementary defenses:
The inter-node alltoall sub-group is internally capped at 16 nodes (see
preflight.md§5.2) — the only test that, at large scale, opens enough simultaneous connections to approach the per-node ephemeral-port pool. The cap eliminates this failure mode by construction.A global barrier +
--comm-cleanup-delay-secsleep (default 2 s) is inserted after every comm destroy, primarily for cross-rank synchronization across the destroy → setup transition.
If you still see Address already in use (e.g. on a network with an unusually narrow ephemeral-port range), the directly relevant OS-level tuning is widening that range — best-practice for any RDMA host:
# Widen the ephemeral port range from ~28k to ~64k. This is the
# OS knob that directly addresses the binding constraint.
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"
As a fallback, raise the per-phase delay:
# Bump the per-phase delay (default 2 s) on a particularly stressed
# network. Rarely needed in practice with the §5.2 alltoall cap.
runner/primus-cli direct -- preflight --comm-cleanup-delay-sec 5
See preflight.md §7 for persistence and recommended large-cluster invocation patterns (split tests into separate runs, etc.).
If the error occurs at init_process_group (before tests even start), it typically means a previous job left the rendezvous port (MASTER_PORT, default 1234) in TIME_WAIT. Either wait ~60 s or use a different port:
export MASTER_PORT=1235
Capturing full output#
The launcher already writes a complete log to logs/log_<timestamp>.txt (configurable via --log_file PATH), even under --silent. If you also want a copy at the call site, redirect there:
srun ... runner/primus-cli direct -- preflight --perf-test \
2>&1 | tee preflight-$(date +%Y%m%d-%H%M%S).log
9. Automated node bisection (finding the bad node in an NCCL hang)#
When a cluster-wide preflight run hangs or fails, use
tools/preflight_bisect/bisect.py to
run preflight --perf-test on smaller Slurm node subsets until suspect nodes
are isolated.
Prerequisites#
Working non-container preflight setup from the sections above, with
VENV_ACTIVATEexported from a shared filesystem path.Run from the SLURM login/head node, where both
scontrolandsrunare available.Run from inside a Slurm allocation, or provide a Slurm nodelist explicitly.
Example from inside an allocation#
export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate
mkdir -p output
python tools/preflight_bisect/bisect.py \
--nodelist "$SLURM_NODELIST" \
--output-dir "output/bisect-$(date +%Y%m%d-%H%M%S)" \
--trial-timeout-sec 600 \
--slurm-time 00:15:00 \
--preflight-env USING_AINIC=1 \
--preflight-env NCCL_IB_GID_INDEX=1 \
--preflight-env NCCL_CROSS_NIC=1 \
--preflight-env NCCL_PXN_DISABLE=0 \
2>&1 | tee output/bisect-latest.log
Adjust the --preflight-env lines to match your cluster. Per-trial logs and a
final summary.txt are written under --output-dir.
Note: Set
--trial-timeout-sechigh enough for a healthy subset to finish. Too small a timeout can turn slow-but-good trials into false failures, causing the bisection to explore extra paths.Note:
--preflight-env KEY=VALUEvalues are concatenated into a singlesrun --export=ALL,...argument, so values must not contain commas or whitespace. Keep comma-containing values as normal exported environment variables.
10. See also#
Preflight—full reference for the
preflightsubcommand and its flagsCLI User Guide—container-based and
primus-cli slurmworkflowsrunner/primus-cli-direct.sh—the direct launcher itself (primus-cli directdispatches here)primus/tools/preflight/—preflight implementationtools/preflight_bisect/bisect.py—bisect wrapper for narrowing down failing nodes in multi-node preflight runs