fix(run_slurm): correct slinky HCA list + add vendor IB tuning vars#1
fix(run_slurm): correct slinky HCA list + add vendor IB tuning vars#1Johnsonms wants to merge 17 commits into
Conversation
- benchmarks/run_nccl_sharp_test.sh: shared script that auto-detects GPU type and saves results to benchmarks/<GPU_TYPE>/results/<timestamp>/ Supports single-node and multi-node (--nodes) via Slurm srun. Tests 4 configs: ring, nvls, collnet_sharp, nvls_collnet. - benchmarks/README.md: usage, config table, and H100 result summary - benchmarks/H100/results/: all AllReduce benchmark runs on 8x H100 Key finding: NVLS gives +30% busBW (479 vs 368 GB/s); IB SHARP (CollNet) shows no improvement on this cluster for float32 AllReduce.
…tion Supports 5 modes: local (no args), --node, --nodes, --all, --exclude. Prints output to screen in real time matching job_nccl_benchmark.sh style. - Local mode: runs binary directly (no srun), 8 GPUs on current node - Cluster mode: runs via srun with auto-detected GPU type and results saved to benchmarks/<GPU_TYPE>/results/<timestamp>/ - Sbatch chained mode: one job per config via --dependency=afterok to avoid slurmstepd zombie issues on K8s clusters (slurmd=PID1) Includes 2-node H100 results (ring/nvls/collnet_sharp/nvls_collnet). README updated with full usage and output format documentation.
Dockerfile builds a portable container (CUDA 12.9 + NCCL + OpenMPI + all nccl-tests binaries) for use with Pyxis/enroot on Slurm. Benchmark entry points: - benchmarks/run_k8s.sh — Kubernetes Jobs - benchmarks/run_slurm.sh — Slurm sbatch (native or Pyxis container) - benchmarks/run_mpi.sh — direct MPI, no scheduler Shared library (benchmarks/lib/common.sh): - NCCL_TEST_MATRIX: 3 ops × 3 configs (ring, nvls, collnet_sharp) - GPU auto-detection, peak busBW extraction, summary table with baseline diff - Docker registry auth: .env auto-load, K8s imagePullSecret, local/remote docker login Also includes: - tools/ scripts for image build/push/pull/containerd-import - run_nccl_test.sh (container), run_nccl_native.sh (host libs), setup_and_build.sh - .env.example + .gitignore entry for credential management - benchmarks/README.md with full docs, test matrix, B200/H100 results
…, and Slurm runner updates
- stragglers/find_stragglers.py + README.md: 6-phase NCCL/IB cluster diagnostic. Discovers idle nodes, classifies bootstrap failures, z-score performance grouping, parallel-encoding localization, inter-link cross-pair check, marginal-candidate retest, and optional IB-layer per-HCA sweep with K=2 disjoint matchings. - baselines/use3a-ss/healthy_4n.json: auto-seeded cluster baseline (4n ring + collnet_sharp medians/stddev) for z-score classification. - benchmarks/B200/collective-scaling/: existing per-group sweep scripts (submit_all.sh, submit_per4_groups.sh, parse_and_report.py, resubmit_failed.sh).
3 runs (2 valid) confirmed 8 bad nodes: gpu-162, 164, 195, 196, 214, 215, 227, 228. IB layer clean across all runs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…uster - ulimit -l unlimited (no-op if blocked; auto-activates when admin fixes cgroup) - UCX_TLS=tcp + UCX_NET_DEVICES/OMPI_MCA_btl_tcp_if_include=eth0 to route MPI bootstrap through TCP instead of IB (avoids ibv_reg_mr memlock failure) - NCCL_SOCKET_IFNAME=eth0 for NCCL's own bootstrap socket - NCCL_IB_HCA restricted to 12 IB/SHARP HCAs; excludes mlx5_8 (absent) and mlx5_13 (RoCE) observed on slinky B200 nodes - NCCL_TIMEOUT=300 to prevent indefinite hangs on NVLS init failure - CUDA_DEVICE_MAX_CONNECTIONS=32 for GPU peer connection performance Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop mlx5_0-3 from the HCA list — they are NVSwitch/non-RDMA on slinky B200 nodes and break SHARP. Keep mlx5_4-7, 9-12 (the 8 IB HCAs); mlx5_8 absent and mlx5_13 RoCE remain excluded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Allows running a single test type (e.g. NCCL_TESTS=all_reduce) without editing the matrix. Unset → all entries run as before. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the multi-algo, multi-round implementation with a focused
2-round disjoint-pair sweep:
Round 1: all 2-node groups run ring all_reduce at 8G in parallel,
60s per-job watchdog timeout flags hangs as bad groups.
Round 2: each candidate from a bad group is paired with a healthy
H-node (distinct H per candidate so all run in parallel) to
localize the fault to a single node.
Adds baselines/flapping-airplanes/healthy_2n_ring.json so z-score
classification is stable across runs (with --rebaseline to refresh).
Hoists statistics + collections imports to module scope, drops the
unused ALGOS list, and uses `with open()` for file reads.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-locate run-day reports alongside the cluster baselines:
baselines/use3a-ss/
2026-05-02_nccl_collective_scaling_report.md
baselines/flapping-airplanes/
2026-05-04_nccl_collective_scaling_report.md
2026-05-09_nccl_collective_scaling_report.md
2026-05-11_nccl_collective_scaling_report.md
2026-05-11_nccl_update.md
2026-05-12_nccl_collective_scaling_report.md
Filenames now prefix YYYY-MM-DD_ to match the existing convention.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Canonical home for the benchmarking workflow assets we've developed across
multiple clusters. Co-locates:
.claude/skills/cluster-bringup/ Procedure for onboarding a new GPU cluster
.claude/skills/benchmark-report/ Scaffold a report in the right location
.claude/memory/ 14 reference memory files (per-cluster
state, debug ladders, conventions)
.claude/install.sh Symlinks the above into ~/.claude/ paths
CLAUDE.md Auto-loaded repo-level context for Claude
Code (conventions, routing, setup)
On a new machine: clone, run .claude/install.sh, restart Claude Code.
Memory and skills then travel with the repo via git pull instead of needing
a separate dotfiles repo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two independent misconfigs were suppressing inter-node NCCL bandwidth on the flapping-airplanes (slinky) B200 cluster: 1. NCCL_IB_HCA pointed at degraded rails. mlx5_5 has flipped to Ethernet and mlx5_9/10/11 trained down to 100G HDR since the May baseline; the hard-coded list caught those instead of the 8 healthy 400G NDR IB rails (mlx5_1,2,3,4,6,7,12,13). This capped every collective at ~192 GB/s. 2. Missing vendor NCCL IB tuning vars (QPS_PER_CONNECTION=2, AR_THRESHOLD=0, SPLIT_DATA_ON_QPS=0, PCI_RELAXED_ORDERING=1, IGNORE_CPU_AFFINITY=1). Even on healthy rails, ring/NVLS ran ~20-25% low (4n ring 307 vs 384 GB/s). Isolated to the tuning vars alone, independent of -g launch style. SHARP is unaffected. After both fixes, 4n/32-GPU results match the May 2026 records to ~1% (all_gather ring 376.2 vs 376.9; all_reduce ring 384.0 vs 389.3; SHARP 554 vs 550), all configs 0 wrong. Full analysis + before/after tables in baselines/flapping-airplanes/2026-07-11_nccl_collective_scaling_report.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Broly Security ScanNote Baseline snapshot is missing for this repo. Broly is running in PR-only fallback mode until the first scheduled baseline completes. This does not block the PR. Note Summary 18 actionable finding(s) in this PR
9 highest-priority actionable rows in the table below (critical/high first, then top medium).
Fix Suggestions🔧 Command injection via unsanitized user input interpolated into a shell script executed by `sbatch`. — benchmarks/run_nccl_native.sh:84Validate all user-supplied arguments against strict allowlists or escape them with 🔧 Command injection via unsanitized user input interpolated into a shell heredoc that is executed b... — benchmarks/run_nccl_test.sh:90Validate all user-supplied parameters against strict allowlists or escape shell metacharacters before interpolating them into the heredoc, and prefer passing values via sbatch 🔧 Command injection via unvalidated command-line arguments passed to shell execution. — tools/docker_build.sh:42Replace the string-based BUILD_CMD with a bash array and use 🔧 YAML injection via unsanitized user input in Kubernetes Job manifest generation. Multiple user-co... — benchmarks/run_nccl_k8s.sh:136Validate all user-supplied variables against strict allowlist patterns (e.g., alphanumeric, dash, dot for names and namespaces) before interpolating them into the YAML heredoc, or use a YAML generator library that handles escaping properly. Dismiss false positivesEach dismissable row has a Dismiss key (
|
There was a problem hiding this comment.
Broly found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
Three reproducible runs of the tuned run_slurm.sh at 4 nodes (27/27 configs 0 wrong, spread <=2.6%), compared against the same-cluster May 2026-05-12 4-node baseline. Ring and all_gather match May to ~1%; SHARP matches at equal message size (554 vs 550 GB/s @8GiB). Investigates the one >5% deviation (all_reduce NVLS, -10%): traced by elimination to the NCCL 2.28.9->2.30.7 NVLS multi-node path, not fabric or config. Ruled out message size (flat 348->353 over 8->32GiB), algorithm selection (NVLSTree 343, auto-NVLS 348, both << 388), and launch style (-g 8 gives 308, even lower). Ring (-1.3%) and SHARP (+0.5%) both match May, confirming fabric/HCA/tuning are healthy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| cat > "$tmp_script" <<EOF | ||
| #!/bin/bash | ||
| #SBATCH --job-name=${job_name} | ||
| #SBATCH --nodes=${NNODES} | ||
| #SBATCH --ntasks-per-node=${GPUS_PER_NODE} | ||
| #SBATCH --gpus-per-node=${GPUS_PER_NODE} | ||
| #SBATCH --cpus-per-task=8 | ||
| #SBATCH --mem=0 | ||
| #SBATCH --partition=${PARTITION} | ||
| #SBATCH --output=${outfile} | ||
| #SBATCH --time=${TIME_LIMIT} | ||
| #SBATCH --exclusive | ||
| #SBATCH --chdir=/tmp | ||
|
|
||
| source ${HPCX_INIT} && hpcx_load | ||
| export LD_LIBRARY_PATH=${NCCL_LIB}:\${LD_LIBRARY_PATH} | ||
|
|
||
| ${local_nccl_env} | ||
|
|
||
| srun --mpi=pmix ${binary} \\ | ||
| -b ${MIN_BYTES} -e ${MAX_BYTES} -f ${STEP_FACTOR} \\ | ||
| -g 1 -n ${ITERS} -w ${WARMUP} | ||
| EOF |
| sbatch_script="#!/bin/bash | ||
| #SBATCH --job-name=${job_name} | ||
| #SBATCH --nodes=${GROUP_SIZE} | ||
| #SBATCH --ntasks-per-node=${GPUS_PER_NODE} | ||
| #SBATCH --gpus-per-node=${GPUS_PER_NODE} | ||
| #SBATCH --cpus-per-task=8 | ||
| #SBATCH --mem=0 | ||
| #SBATCH --partition=${PARTITION} | ||
| #SBATCH --output=${outfile} | ||
| #SBATCH --time=${TIME_LIMIT} | ||
| #SBATCH --exclusive | ||
| #SBATCH --chdir=/tmp | ||
| #SBATCH --nodelist=${GROUP_LIST} | ||
|
|
||
| if [ -f ${HPCX_INIT} ]; then | ||
| source ${HPCX_INIT} && hpcx_load | ||
| elif [ -f /mnt/vast/dgxc-benchmarking-auto/hpcx-2.18/hpcx-init.sh ]; then | ||
| source /mnt/vast/dgxc-benchmarking-auto/hpcx-2.18/hpcx-init.sh && hpcx_load | ||
| elif [ -f /opt/hpcx/hpcx-init.sh ]; then | ||
| source /opt/hpcx/hpcx-init.sh && hpcx_load | ||
| else | ||
| echo \"ERROR: no hpcx-init.sh found on \$(hostname)\" >&2; exit 127 | ||
| fi | ||
| export LD_LIBRARY_PATH=${NCCL_LIB}:\${LD_LIBRARY_PATH:-} | ||
|
|
||
| ${nccl_env} | ||
|
|
||
| echo \"=== NCCL all_reduce | algo=${ALGO} | group ${GID} (${GROUP_SIZE} nodes / $((GROUP_SIZE * GPUS_PER_NODE)) GPUs) ===\" | ||
| echo \"Job ID: \${SLURM_JOB_ID}\" | ||
| echo \"Nodes: ${GROUP_LIST}\" | ||
| echo \"Actual: \$(scontrol show hostnames \$SLURM_JOB_NODELIST | tr '\\n' ' ')\" | ||
| echo \"Date: \$(date)\" | ||
| echo \"NCCL_ALGO=\${NCCL_ALGO:-<unset>} NCCL_NVLS_ENABLE=${NVLS} NCCL_COLLNET_ENABLE=${COLLNET}\" | ||
| echo \"\" | ||
|
|
||
| srun --mpi=pmix ${binary} \\ | ||
| -b ${MIN_BYTES} -e ${MAX_BYTES} -f ${STEP_FACTOR} \\ | ||
| -g 1 -n ${ITERS} -w ${WARMUP} | ||
|
|
||
| echo \"\" | ||
| echo \"=== Done: group ${GID} ===\" | ||
| " |
| SBATCH_SCRIPT=$(cat <<EOF | ||
| #!/bin/bash | ||
| #SBATCH --job-name=${JOB_NAME} | ||
| #SBATCH --partition=${PARTITION} | ||
| #SBATCH --nodes=${NODES} | ||
| #SBATCH --ntasks-per-node=${GPUS_PER_NODE} | ||
| #SBATCH --gpus-per-node=${GPUS_PER_NODE} | ||
| #SBATCH --time=${TIME_LIMIT} | ||
| #SBATCH --output=${OUTPUT_FILE} | ||
| #SBATCH --exclusive | ||
|
|
||
| echo "=== NCCL Test: ${TEST} ===" | ||
| echo "Nodes: ${NODES}, GPUs/node: ${GPUS_PER_NODE}, Total GPUs: ${TOTAL_GPUS}" | ||
| echo "Date: \$(date)" | ||
| echo "Nodelist: \${SLURM_NODELIST}" | ||
| echo "===============================" | ||
|
|
||
| # Source HPC-X for MPI + UCX | ||
| source /opt/hpcx/hpcx-init.sh | ||
| hpcx_load | ||
|
|
||
| # NCCL environment | ||
| export NCCL_DEBUG=INFO | ||
| export NCCL_IB_GID_INDEX=3 | ||
| export NCCL_IB_TIMEOUT=23 | ||
| export NCCL_IB_RETRY_CNT=7 | ||
|
|
||
| srun --mpi=pmix \\ | ||
| ${BINARY} \\ | ||
| -b ${MIN_BYTES} \\ | ||
| -e ${MAX_BYTES} \\ | ||
| -f ${STEP_FACTOR} \\ | ||
| -g 1 \\ | ||
| -n ${ITERS} \\ | ||
| -w ${WARMUP} \\ | ||
| ${EXTRA_ARGS} | ||
|
|
||
| echo "=== Done: \$(date) ===" | ||
| EOF | ||
| ) |
| job_yaml=$(cat <<EOF | ||
| apiVersion: batch/v1 | ||
| kind: Job | ||
| metadata: | ||
| name: ${job_name} | ||
| namespace: ${NAMESPACE} | ||
| spec: | ||
| ttlSecondsAfterFinished: ${TTL} | ||
| template: | ||
| spec: | ||
| restartPolicy: Never | ||
| nodeSelector: | ||
| ${NODE_SELECTOR_KEY}: "${NODE_SELECTOR_VAL}" | ||
| imagePullSecrets: | ||
| - name: ${PULL_SECRET} | ||
| containers: | ||
| - name: nccl-test | ||
| image: ${IMAGE} | ||
| imagePullPolicy: IfNotPresent | ||
| command: | ||
| - ${binary} | ||
| - "-b" | ||
| - "${MIN_BYTES}" | ||
| - "-e" | ||
| - "${MAX_BYTES}" | ||
| - "-f" | ||
| - "${STEP_FACTOR}" | ||
| - "-g" | ||
| - "${GPUS}" | ||
| - "-n" | ||
| - "${ITERS}" | ||
| - "-w" | ||
| - "${WARMUP}" | ||
| env:${env_block} | ||
| resources: | ||
| limits: | ||
| nvidia.com/gpu: "${GPUS}" | ||
| securityContext: | ||
| capabilities: | ||
| add: ["IPC_LOCK"] | ||
| EOF |
| && ldd /usr/local/bin/all_reduce_perf | grep -q 'libnccl' \ | ||
| && (all_reduce_perf --help 2>&1 | head -3; true) | ||
|
|
||
| WORKDIR /workspace |
| REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" | ||
| CLAUDE_DIR="$REPO_DIR/.claude" | ||
|
|
||
| WORK_DIR="${1:-$(pwd)}" |
| echo " Nodes: ${NNODES:-all available}" | ||
| echo " Results: ${RESULTS_DIR}" | ||
|
|
||
| JOB_ID=$(sbatch --parsable "${SBATCH_ARGS[@]}" --wrap " |
| # ──────────────────── parse args ──────────────────── | ||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --name) IMAGE_NAME="$2"; shift 2 ;; |
Adds a direct 32 GiB comparison (dedicated 32G matrix run 20260711_061228 + confirmatory SHARP samples), removing the earlier 8 GiB size caveat. Key resolution of the SHARP headline: the matrix collnet_sharp config auto-selects CollnetChain (~558 GB/s @32G); forcing CollnetDirect (May's headline algo) gives 621 GB/s, beating May's 605.90 (+2.5%). The earlier "SHARP lags at 32G" concern was a CollnetChain-vs-CollnetDirect selection artifact, not a regression. SAT env vars made no difference. Full 32G matched comparison: ring 385 vs 389 (-1.1%), all_gather ring/nvls within 1%, SHARP-CollnetDirect 621 vs 606 (+2.5%). NVLS remains -9.7% (the isolated NCCL 2.28->2.30 NVLS-path difference). All 9/9 configs 0 wrong. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --cuda) CUDA_VERSION="$2"; shift 2 ;; | ||
| --ubuntu) UBUNTU_VERSION="$2"; shift 2 ;; | ||
| --nccl-tests) NCCL_TESTS_VERSION="$2"; shift 2 ;; | ||
| --name) IMAGE_NAME="$2"; shift 2 ;; | ||
| --tag) IMAGE_TAG="$2"; shift 2 ;; | ||
| --no-cache) NO_CACHE="--no-cache"; shift ;; | ||
| --dry-run) DRY_RUN=1; shift ;; | ||
| -h|--help) usage ;; | ||
| *) echo "Unknown option: $1"; usage ;; | ||
| esac | ||
| done | ||
|
|
||
| FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" | ||
|
|
||
| BUILD_CMD="docker build \ | ||
| --build-arg CUDA_VERSION=${CUDA_VERSION} \ | ||
| --build-arg UBUNTU_VERSION=${UBUNTU_VERSION} \ | ||
| --build-arg NCCL_TESTS_VERSION=${NCCL_TESTS_VERSION} \ | ||
| ${NO_CACHE} \ | ||
| -t ${FULL_IMAGE} \ | ||
| ${REPO_ROOT}" | ||
|
|
||
| echo "======================================================" | ||
| echo " Building nccl-tests Docker image" | ||
| echo "======================================================" | ||
| echo " Image: ${FULL_IMAGE}" | ||
| echo " CUDA: ${CUDA_VERSION}" | ||
| echo " Ubuntu: ${UBUNTU_VERSION}" | ||
| echo " nccl-tests: ${NCCL_TESTS_VERSION}" | ||
| echo " Dockerfile: ${REPO_ROOT}/Dockerfile" | ||
| echo "======================================================" | ||
|
|
||
| if [[ $DRY_RUN -eq 1 ]]; then | ||
| echo "" | ||
| echo "--- DRY RUN ---" | ||
| echo "$BUILD_CMD" | ||
| exit 0 | ||
| fi | ||
|
|
||
| echo "" | ||
| $BUILD_CMD |
| SBATCH_SCRIPT=$(cat <<EOF | ||
| #!/bin/bash | ||
| #SBATCH --job-name=${JOB_NAME} | ||
| #SBATCH --partition=${PARTITION} | ||
| #SBATCH --nodes=${NODES} | ||
| #SBATCH --ntasks-per-node=${GPUS_PER_NODE} | ||
| #SBATCH --gpus-per-node=${GPUS_PER_NODE} | ||
| #SBATCH --time=${TIME_LIMIT} | ||
| #SBATCH --output=${OUTPUT_FILE} | ||
| #SBATCH --exclusive | ||
|
|
||
| echo "=== NCCL Test: ${TEST} ===" | ||
| echo "Nodes: ${NODES}, GPUs/node: ${GPUS_PER_NODE}, Total GPUs: ${TOTAL_GPUS}" | ||
| echo "Container: ${CONTAINER_IMAGE}" | ||
| echo "Date: \$(date)" | ||
| echo "Nodelist: \${SLURM_NODELIST}" | ||
| echo "===============================" | ||
|
|
||
| # NCCL environment | ||
| export NCCL_DEBUG=INFO | ||
| export NCCL_IB_GID_INDEX=3 | ||
| export NCCL_IB_TIMEOUT=23 | ||
| export NCCL_IB_RETRY_CNT=7 | ||
|
|
||
| srun \\ | ||
| --container-image="${CONTAINER_IMAGE}" \\ | ||
| --container-mounts="/run/mellanox:/run/mellanox" \\ | ||
| --mpi=pmix \\ | ||
| ${BINARY} \\ | ||
| -b ${MIN_BYTES} \\ | ||
| -e ${MAX_BYTES} \\ | ||
| -f ${STEP_FACTOR} \\ | ||
| -g 1 \\ | ||
| -n ${ITERS} \\ | ||
| -w ${WARMUP} \\ | ||
| ${EXTRA_ARGS} | ||
|
|
||
| echo "=== Done: \$(date) ===" | ||
| EOF |
| delta=$(awk "BEGIN {printf \"%.2f\", ${current} - ${baseline}}") | ||
| pct=$(awk "BEGIN {printf \"%.1f\", (${current} - ${baseline}) / ${baseline} * 100}") | ||
| [[ $(awk "BEGIN {print (${delta} >= 0)}") -eq 1 ]] \ |
| [[ -n "$MPIRUN_EXTRA" ]] && cmd+=" ${MPIRUN_EXTRA}" | ||
| cmd+=" ${binary} -b ${MIN_BYTES} -e ${MAX_BYTES} -f ${STEP_FACTOR}" | ||
| cmd+=" -g 1 -n ${ITERS} -w ${WARMUP}" | ||
|
|
||
| echo " CMD: ${cmd}" | ||
|
|
||
| if [[ $DRY_RUN -eq 1 ]]; then | ||
| echo " (dry-run: would save to ${outfile})" | ||
| return | ||
| fi | ||
|
|
||
| if eval "$cmd" 2>&1 | tee "${outfile}"; then |
| sbatch_script=$(cat <<EOF | ||
| #!/bin/bash | ||
| #SBATCH --job-name=${job_name} | ||
| #SBATCH --nodes=${NNODES} | ||
| #SBATCH --ntasks-per-node=${GPUS_PER_NODE} | ||
| #SBATCH --gpus-per-node=${GPUS_PER_NODE} | ||
| #SBATCH --cpus-per-task=8 | ||
| #SBATCH --mem=0 | ||
| #SBATCH --partition=${PARTITION} | ||
| #SBATCH --output=${outfile} | ||
| #SBATCH --time=${TIME_LIMIT} | ||
| #SBATCH --exclusive | ||
| #SBATCH --chdir=/tmp | ||
| ${nodelist_line} | ||
| ${account_line} | ||
|
|
||
| ${hpcx_line} | ||
| export LD_LIBRARY_PATH=${NCCL_LIB}:\${LD_LIBRARY_PATH} | ||
| # Unlock IB memory registration — required for NCCL IB/NVLS at scale. | ||
| # No-op if the cluster enforces the limit; works automatically once admin sets | ||
| # LimitMEMLOCK=infinity in the Slurm cgroup config. | ||
| ulimit -l unlimited 2>/dev/null || true | ||
| # Route MPI/UCX bootstrap through shared-mem + TCP (IB requires ulimit -l unlimited, | ||
| # blocked in K8s pods). NCCL owns actual data movement via NVLink/IB directly. | ||
| export UCX_TLS=self,sm,cuda_ipc,cuda_copy,tcp | ||
| export UCX_NET_DEVICES=eth0 | ||
| export OMPI_MCA_btl_tcp_if_include=eth0 | ||
| export NCCL_SOCKET_IFNAME=eth0 | ||
| export NCCL_TIMEOUT=300 | ||
| export CUDA_DEVICE_MAX_CONNECTIONS=32 | ||
| # Restrict to the 8 compute rails on slinky B200 nodes: the eight 400G NDR | ||
| # InfiniBand ports. Verified 2026-07-11 via /sys/class/infiniband/*/ports/1/{rate,link_layer}. | ||
| # NOTE: the fabric state drifts. The prior list (mlx5_4,5,6,7,9,10,11,12) was healthy in | ||
| # May 2026 (390 GB/s @8n) but by 2026-07-11 mlx5_5 had flipped to Ethernet/200G and | ||
| # mlx5_9,10,11 trained down to 100G HDR, halving inter-node BW (192 vs ~310 GB/s @4n). | ||
| # Re-verify rate/link_layer before trusting numbers; ideally auto-detect the 400G IB ports. | ||
| export NCCL_IB_HCA="=mlx5_1:1,mlx5_2:1,mlx5_3:1,mlx5_4:1,mlx5_6:1,mlx5_7:1,mlx5_12:1,mlx5_13:1" | ||
| # Vendor NCCL IB tuning (from the validated slinky working-config, 2026-05-09). | ||
| # Without these, ring/NVLS inter-node busbw is ~20-25% below the fabric's real capability | ||
| # (measured 2026-07-11: 4n ring 307 -> 384 GB/s @8GiB with these set; matches historical 389). | ||
| # QPS_PER_CONNECTION=2 + AR_THRESHOLD=0 are the ring-critical ones. SHARP is unaffected. | ||
| export NCCL_IB_QPS_PER_CONNECTION=2 | ||
| export NCCL_IB_SPLIT_DATA_ON_QPS=0 | ||
| export NCCL_IB_AR_THRESHOLD=0 | ||
| export NCCL_IB_PCI_RELAXED_ORDERING=1 | ||
| export NCCL_IGNORE_CPU_AFFINITY=1 | ||
|
|
||
| ${nccl_env} | ||
|
|
||
| srun --mpi=pmix ${container_args} ${binary} \ | ||
| -b ${MIN_BYTES} -e ${MAX_BYTES} -f ${STEP_FACTOR} \ | ||
| -g 1 -n ${ITERS} -w ${WARMUP} | ||
| EOF |
| TMPDIR=$(mktemp -d) | ||
| trap "rm -rf ${TMPDIR}" EXIT | ||
|
|
||
| git clone --depth 1 --branch "${NCCL_TESTS_VERSION}" \ |
Summary
Two independent
run_slurm.shmisconfigurations were suppressing inter-node NCCL bandwidth on the flapping-airplanes (slinky) B200 cluster. Fixing both brings 4-node/32-GPU collective performance back in line with the May 2026 historical records (to ~1%), all configs0 wrong.1.
NCCL_IB_HCApointed at degraded railsmlx5_5has flipped to Ethernet andmlx5_9/10/11trained down to 100G HDR since the May baseline. The hard-coded list caught those instead of the 8 healthy 400G NDR IB rails (mlx5_1,2,3,4,6,7,12,13) → every collective capped at ~192 GB/s. Repo memoryproject_nccl_working_config.mdshows the old list was healthy in May, so this is a fabric regression, not a static bug.2. Missing vendor NCCL IB tuning vars
Even on healthy rails, ring/NVLS ran ~20–25% low (4n ring 307 vs 384 GB/s). Restored by
NCCL_IB_QPS_PER_CONNECTION=2,NCCL_IB_AR_THRESHOLD=0,NCCL_IB_SPLIT_DATA_ON_QPS=0,NCCL_IB_PCI_RELAXED_ORDERING=1,NCCL_IGNORE_CPU_AFFINITY=1. Isolated to the tuning vars alone (independent of-glaunch style). SHARP is unaffected.Results — 4n/32-GPU peak busBW @8GiB (GB/s)
Full analysis, before/after tables, and reproducibility (3 HCA-fix runs + tuned run, all
0 wrong) inbaselines/flapping-airplanes/2026-07-11_nccl_collective_scaling_report.md.Action item (fabric, not this PR)
mlx5_5/9/10/11were 400G NDR IB in May and are now Ethernet/100G — worth a hardware/cabling/switch-port check on all 4 nodes.🤖 Generated with Claude Code