Skip to content

fix(run_slurm): correct slinky HCA list + add vendor IB tuning vars#1

Open
Johnsonms wants to merge 17 commits into
masterfrom
johnson-dev
Open

fix(run_slurm): correct slinky HCA list + add vendor IB tuning vars#1
Johnsonms wants to merge 17 commits into
masterfrom
johnson-dev

Conversation

@Johnsonms

Copy link
Copy Markdown
Collaborator

Summary

Two independent run_slurm.sh misconfigurations 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 configs 0 wrong.

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) → every collective capped at ~192 GB/s. Repo memory project_nccl_working_config.md shows 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 -g launch style). SHARP is unaffected.

Results — 4n/32-GPU peak busBW @8GiB (GB/s)

Collective Config Buggy Fixed (both) Historical (May)
all_reduce ring 192.8 384.0 389.3 ✓
all_reduce collnet_sharp 320.0 554.4 550.6 ✓
all_gather ring 192.7 376.2 376.9 ✓✓
all_gather nvls 193.0 376.3 377.0 ✓✓
reduce_scatter ring 192.6 384.1

Full analysis, before/after tables, and reproducibility (3 HCA-fix runs + tuned run, all 0 wrong) in baselines/flapping-airplanes/2026-07-11_nccl_collective_scaling_report.md.

Action item (fabric, not this PR)

mlx5_5/9/10/11 were 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

Johnsonms and others added 15 commits March 5, 2026 23:42
- 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
- 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-code-security-scanner

broly-code-security-scanner Bot commented Jul 11, 2026

Copy link
Copy Markdown

Broly Security Scan

Note

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

  • 🟠 4 high
  • 🟡 12 medium
  • 🔵 2 low

9 highest-priority actionable rows in the table below (critical/high first, then top medium).

Severity Scanner Issue Location Dismiss Verdict
🟠 HIGH SAST Command injection via unsanitized user input
interpolated into a shell script executed by
sbatch.
benchmarks/run_nccl_native.sh:84 d5 🔺 TRUE_POSITIVE · Confidence: HIGH
🟠 HIGH SAST Command injection via unsanitized user input
interpolated into a shell heredoc that is executed
b...
benchmarks/run_nccl_test.sh:90 d6 🔺 TRUE_POSITIVE · Confidence: HIGH
🟠 HIGH SAST Command injection via unvalidated command-line
arguments passed to shell execution.
tools/docker_build.sh:42 d34 🔺 TRUE_POSITIVE · Confidence: HIGH
🟠 HIGH SAST YAML injection via unsanitized user input in
Kubernetes Job manifest generation. Multiple
user-co...
benchmarks/run_nccl_k8s.sh:136 d4 🔺 TRUE_POSITIVE · Confidence: HIGH
🟡 MEDIUM DOCKERFILE Container runs as root with no USER directive
specified.
Dockerfile:74 d26 Needs review
🟡 MEDIUM SAST Command injection via awk in print_summary().
The variables $current and $baseline are de...
benchmarks/lib/common.sh:211 d37 🔺 TRUE_POSITIVE · Confidence: HIGH
🟡 MEDIUM SAST Command injection via the --exclude argument.
The args.exclude value is passed to
`subprocess...
stragglers/find_stragglers.py:78 d32 🔺 TRUE_POSITIVE · Confidence: HIGH
🟡 MEDIUM SAST Command injection via unsanitized command-line
arguments (--nodes and --exclude) that are
int...
benchmarks/B200/collective-scaling/submit_all.sh:59 d35 🔺 TRUE_POSITIVE · Confidence: HIGH
🟡 MEDIUM SAST Command injection via unsanitized command-line
arguments interpolated into a generated sbatch
scr...
benchmarks/B200/collective-scaling/submit_per4_groups.sh:48 d1 🔺 TRUE_POSITIVE · Confidence: HIGH

Fix Suggestions

🔧 Command injection via unsanitized user input interpolated into a shell script executed by `sbatch`. — benchmarks/run_nccl_native.sh:84

Validate all user-supplied arguments against strict allowlists or escape them with printf '%q' before interpolating into the heredoc, and prefer passing values via environment variables or a temporary file with fixed formatting rather than direct string interpolation.


# Validate all numeric args and sanitize string args before heredoc interpolation:
for v in NODES GPUS_PER_NODE ITERS MIN_BYTES MAX_BYTES; do
[[ "$v" =~ ^[0-9]+$ ]] || { echo "Invalid $v: $v" >&2; exit 1; }
done
PARTITION=$(printf '%q' "$PARTITION")
JOB_NAME=$(printf '%q' "$JOB_NAME")
OUTPUT_FILE=$(printf '%q' "$OUTPUT_FILE")
TIME_LIMIT=$(printf '%q' "$TIME_LIMIT")
🔧 Command injection via unsanitized user input interpolated into a shell heredoc that is executed b... — benchmarks/run_nccl_test.sh:90

Validate all user-supplied parameters against strict allowlists or escape shell metacharacters before interpolating them into the heredoc, and prefer passing values via sbatch --export or environment variables rather than text interpolation.


# Validate numeric parameters
for var in NODES GPUS_PER_NODE MIN_BYTES MAX_BYTES ITERS WARMUP; do
if [[ -n "${!var}" ]] && ! [[ "${!var}" =~ ^[0-9]+$ ]]; then
echo "Error: $var must be a positive integer" >&2; exit 1
fi
done
# Validate string parameters against safe character set
for var in TEST PARTITION CONTAINER_IMAGE OUTPUT_DIR TIME_LIMIT EXTRA_ARGS; do
🔧 Command injection via unvalidated command-line arguments passed to shell execution. — tools/docker_build.sh:42

Replace the string-based BUILD_CMD with a bash array and use "${array[@]}" for execution, or validate each argument against a strict regex pattern before interpolation.


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}")
if [[ $DRY_RUN -eq 1 ]]; then
🔧 YAML injection via unsanitized user input in Kubernetes Job manifest generation. Multiple user-co... — benchmarks/run_nccl_k8s.sh:136

Validate 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.


# Add validation before YAML generation (insert after line 101, before derived values):
validate_name() { [[ "$1" =~ ^[a-zA-Z0-9]([-a-zA-Z0-9.]*[a-zA-Z0-9])?$ ]] || { echo "ERROR: Invalid value: $1"; exit 1; }; }
validate_int() { [[ "$1" =~ ^[0-9]+$ ]] || { echo "ERROR: Expected integer: $1"; exit 1; }; }
validate_size() { [[ "$1" =~ ^[0-9]+[KMGT]?$ ]] || { echo "ERROR: Invalid size: $1"; exit 1; }; }
[[ -n "$JOB_NAME" ]] && validate_name "$JOB_NAME"
validate_name "$NAMESPACE"
validate_name "$TEST"
validate_int "$GPUS_PER_NODE"; validate_int "$STEP_FACTOR"; validate_int "$ITERS"; validate_int "$WARMUP"

Dismiss false positives

Each dismissable row has a Dismiss key (d1, d2, …) in the table above. Reply to this comment (or post on the PR) with /broly dismiss d2: your reason to mark a false positive, or /broly undismiss d2 to reverse it. Broly records every dismissal in the section below this comment (updated in place) and suppresses the finding on the next scan.

Key Finding
d4 🟠 HIGH · YAML injection via unsanitized user input in Kubernetes Job manifest generati... · benchmarks/run_nccl_k8s.sh:136
d5 🟠 HIGH · Command injection via unsanitized user input interpolated into a shell script... · benchmarks/run_nccl_native.sh:84
d6 🟠 HIGH · Command injection via unsanitized user input interpolated into a shell heredo... · benchmarks/run_nccl_test.sh:90
d34 🟠 HIGH · Command injection via unvalidated command-line arguments passed to shell exec... · tools/docker_build.sh:42
d8 🟡 MEDIUM · Path traversal via unsanitized command-line argument allows arbitrary file ov... · .claude/install.sh:21
d26 🟡 MEDIUM · Container runs as root with no USER directive specified. · Dockerfile:74
d35 🟡 MEDIUM · Command injection via unsanitized command-line arguments (--nodes and `--ex... · benchmarks/B200/collective-scaling/submit_all.sh:59
d1 🟡 MEDIUM · Command injection via unsanitized command-line arguments interpolated into a ... · benchmarks/B200/collective-scaling/submit_per4_groups.sh:48
d36 🟡 MEDIUM · Command injection via unsanitized command-line argument · benchmarks/fa4-bench/run.sh:70
d37 🟡 MEDIUM · Command injection via awk in print_summary(). The variables $current an... · benchmarks/lib/common.sh:211
d23 🟡 MEDIUM · YAML injection via unsanitized user input in Kubernetes Job manifest generati... · benchmarks/run_k8s.sh:130
d38 🟡 MEDIUM · Command injection via unsanitized user input passed to eval · benchmarks/run_mpi.sh:166
d39 🟡 MEDIUM · Command injection via unsanitized user input interpolated into a generated sb... · benchmarks/run_slurm.sh:198
d40 🟡 MEDIUM · Command injection via unsanitized user input in git clone and cp commands · benchmarks/setup_and_build.sh:67
d15 🟡 MEDIUM · Command injection via unsanitized command-line arguments written into a gener... · benchmarks/submit_scaleout.sh:73
d32 🟡 MEDIUM · Command injection via the --exclude argument. The args.exclude value is p... · stragglers/find_stragglers.py:78
d33 🔵 LOW · Command injection via unsanitized command-line argument (NNODES) passed to ... · benchmarks/gpu-health/run.sh:45
d20 🔵 LOW · Command injection via unsanitized command-line arguments passed to shell comm... · tools/containerd_import.sh:40

Broly — SAST (zai-org/GLM-5.2) · Secrets · SCA · GH Actions (zizmor) · Containers · SBOM · Powered by Together AI

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Comment on lines +73 to +95
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
Comment on lines +152 to +193
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} ===\"
"
Comment thread benchmarks/run_slurm.sh Fixed
Comment on lines +84 to +123
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
)
Comment thread benchmarks/run_k8s.sh
Comment on lines +130 to +170
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
Comment thread Dockerfile
&& ldd /usr/local/bin/all_reduce_perf | grep -q 'libnccl' \
&& (all_reduce_perf --help 2>&1 | head -3; true)

WORKDIR /workspace
Comment thread Dockerfile Fixed
Comment thread .claude/install.sh
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>
Comment thread tools/docker_build.sh
Comment on lines +42 to +84
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
Comment on lines +90 to +128
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
Comment thread benchmarks/lib/common.sh
Comment on lines +211 to +213
delta=$(awk "BEGIN {printf \"%.2f\", ${current} - ${baseline}}")
pct=$(awk "BEGIN {printf \"%.1f\", (${current} - ${baseline}) / ${baseline} * 100}")
[[ $(awk "BEGIN {print (${delta} >= 0)}") -eq 1 ]] \
Comment thread benchmarks/run_mpi.sh
Comment on lines +166 to +177
[[ -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
Comment thread benchmarks/run_slurm.sh
Comment on lines +198 to +250
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}" \
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants