Skip to content

algbw, busbw, and reading a nccl-tests result

S3·E5The 1.75x he called marketing · Customer HQ conference room, quote review, Thursday

S3·E5Analyze~30 minsources checked todayverified against nccl-tests master README and doc/PERFORMANCE.md (re-fetched 2026-09-07); NCCL 2.31.2 environment-variable reference; NVIDIA all2all/PXN technical blog

Builds on: NCCL environment variables on a RoCE fabric, GPUDirect RDMA: proving it is actually on

Before you read: what do you already know?

3 quick questions. Wrong answers are fine and expected; trying first makes the lesson stick.

After this lesson you can

  • Compute algbw and busbw from size time and rank count for AllReduce AllGather and Broadcast.
  • Explain why the correction factor differs per collective and why Broadcast and Reduce have none.
  • Analyse a size sweep to locate the move from latency-bound to bandwidth-bound behaviour.
  • Translate a busbw figure into a claim about which piece of hardware is the bottleneck and state what would falsify it.

Episode 5 — The 1.75x he called marketing

The situation · Customer HQ conference room, quote review, Thursday

The last meeting before sixty-four nodes are signed or slide a quarter. Their lead engineer taps the circled row on your printed nccl-tests output: algbw in one column, busbw in the next, the second 1.75 times the first. Politely, he says he does not buy numbers a tool invents: when a vendor’s benchmark reports a bandwidth larger than the one it measured, somebody is selling. The SE studies his cold coffee; the network lead says nothing, which is worse.

He is right to be suspicious and wrong about the cause; the meeting turns on saying why, fast. Algorithm bandwidth is the plain one — size divided by time — and it is useful to compute how much time any large operation would take by dividing the size by it.[1] Bus bandwidth exists because the theoretical peak algorithm bandwidth is not equal to the hardware peak bandwidth, usually depending on the number of ranks, so nccl-tests reports a second number that reflects how optimally the hardware is used.[1] For AllReduce the correction is 2*(n-1)/n[1] — at eight ranks, 1.75. Not invented; derived, and independent of whether NCCL chose a ring or a tree that day.[1]

You look at the printout again and find what you missed before sending it. It is a single row. Both -b and -e default to 32M, so a bare run is one point, not a sweep.[2]

A single-size result is a claim; a sweep is a measurement.

Segment 1 is the arguments that decide what you measured — the half you cannot defend.

1The arguments that decide what you measured

Before the arithmetic, the run. nccl-tests builds with plain make, or with paths as make CUDA_HOME=/path/to/cuda NCCL_HOME=/path/to/nccl, and with MPI as make MPI=1 MPI_HOME=/path/to/mpi, where NAME_SUFFIX= adds a suffix to the binaries.[2] The README excerpt confirms all_reduce_perf, comm_ops_perf (requires MPI) and device_api/gin (requires MPI, NCCL 2.30.7 or later, exactly two ranks); the other binaries built from src/ are the conventional ones but were not enumerated on the page fetched, so confirm the list with ls build/ after a build rather than quoting it.[2]

The defaults are where results go wrong quietly. -b, --minbytes and -e, --maxbytes both default to 32M, -i, --stepbytes to 1M, and -f, --stepfactor is disabled by default — so a bare all_reduce_perf is a single point at 32 MiB, not a sweep.[2] -n, --iters defaults to 20 timed iterations, -w, --warmup_iters to 1, -m, --agg_iters to 1.[2] -c, --check defaults to 1 correctness-check iteration; set -c 0 for clean timing runs.[2] -a, --average defaults to 1, where 0=Rank0, 1=Avg, 2=Min, 3=Max.[2] -g, --ngpus is GPUs per thread and defaults to 1, -t, --nthreads to 1, -o, --op to Sum, -d, --datatype to Float, -r, --root to 0.[2]

Reporting and control flags worth knowing: -J, --output_file writes a JSON report, -M, --memory reports memory usage, -U, --tuning reports NCCL tuning information, -I, --per_iter_timing gives CUDA-event per-iteration timings with -K, --per_iter_skip to drop leading samples, -T, --timeout in seconds is disabled by default, and -N, --run_cycles defaults to 1 with 0 meaning infinite.[2] NCCL_TESTS_SPLIT controls GPU partitioning for parallel operations, and when several operations run in parallel the reported bandwidth is per group.[2]

The canonical sweep for a fabric question is therefore explicit about all of it: all_reduce_perf -b 8 -e 8G -f 2 -g 8 -c 0.[2]

no errors
2 nodes × 8 GPUs = 16 ranks (MPI form)
mpirun -np 2 --map-by ppr:1:node  ./build/all_reduce_perf_mpi -g 8 -b 8 -e 8G -f 2 -c 0

-g follows the rank scrubber (8 GPUs per node assumed, XE9680-class). n in the busbw formula is the total rank count, not the GPUs on one node.

nccl-tests arguments
NCCL — device and interface selection
NCCL — QoS marking
NCCL — timeouts, retries, QPs, routing
NCCL — GPUDirect RDMA gating
NCCL — debug
algbw and busbw from your measured row
n = 16
correction factor for AllReduce = 2(n-1)/n = 1.88
Pick a flag to see what it does to the number

perftest "is a collection of tests written over uverbs intended for use as a performance micro-benchmark". Quote this when a customer treats a result as an application SLA: "The benchmarks generate a synthetic stream of operations… The benchmarks are not designed to emulate any real application traffic."

They are "usually installed to the /usr/bin/" and "may need to be ran with sudo when running from non root". A cryptic syndrome string on stderr comes from rdma-core, not from perftest.

perftest README (master)
review (3)
AllReduce — why that factor

t = (S/B) * (2*(n-1)/n), so B = algbw * 2*(n-1)/n — "independent of the algorithm used (ring, tree, or other) as long as they use point-to-point operations".

"The bus bandwidth should reflect the speed of the hardware bottleneck: NVLink, PCI, QPI, or network."

nccl-tests doc/PERFORMANCE.md
Enter a measured size and time, then scrub the rank count and switch collective to watch the correction factor and busbw move.

2algbw is arithmetic; busbw is a model

“NCCL tests report the average operation time in ms, and two bandwidths in GB/s: algorithm bandwidth and bus bandwidth.”[1] Algorithm bandwidth is the simple one: “Algorithm bandwidth is using the most commonly used formula for bandwidth : size (S) / time (t).”[1] Its use is equally simple — “It is useful to compute how much time any large operation would take by simply dividing the size of the operation by the algorithm bandwidth.”[1] If a customer asks how long their all-reduce will take, algbw answers it.

Bus bandwidth answers a different question: how well is the hardware being used. It exists because “the theoretical peak algorithm bandwidth is not equal to the hardware peak bandwidth, usually depending on the number of ranks”, and NCCL tests introduce it “to provide a number which reflects how optimally the hardware is used”.[1]

For AllReduce the derivation gives t = (S/B) * (2*(n-1)/n) and therefore B = algbw * (2*(n-1)/n).[1] Crucially, that derivation is “independent of the algorithm used (ring, tree, or other) as long as they use point-to-point operations (send/receive)” — so a busbw number does not depend on which algorithm NCCL chose that day.[1]

The full set of correction factors: AllReduce 2*(n-1)/n; ReduceScatter (n-1)/n where “S is the size in bytes of the total array, which for NCCL is equal to recvcount*sizeof(datatype)*n”; AllGather (n-1)/n with S = sendcount*sizeof(datatype)*n; AllToAll (n-1)/n; Broadcast and Reduce 1, because “all data has to get out of the root rank, hence the bottleneck is on the root rank”.[1]

Two consequences to state out loud in any report. A busbw figure without n and without the collective is uninterpretable, because both are inputs to the factor. And AllReduce is the only collective whose factor exceeds 1, which is why an 8-rank AllReduce busbw is 1.75× its algbw and a naive reader thinks the tool is inflating the number.

3Turning a number into a bottleneck claim

The sentence that makes busbw diagnostic: “The bus bandwidth should reflect the speed of the hardware bottleneck: NVLink, PCI, QPI, or network.”[1] That is the whole method. Compute busbw, then say which of those four it most nearly matches, then defend the choice.

The reading changes with the shape of the run. Within a single node, an 8-GPU AllReduce should approach the NVLink bandwidth, because the collective never leaves the node. Add a second node and the bottleneck moves to the per-GPU network bandwidth — so a drop at exactly two nodes is expected, and the question is whether the size of the drop matches the per-GPU link speed you were sold. Do the arithmetic openly: a 400 Gb/s rail per GPU is 50 GB/s nominal before protocol overhead, so a per-GPU busbw in that neighbourhood at large sizes is consistent with the fabric being the bottleneck and nothing being broken. Any tighter rule of thumb you have heard for the achievable fraction is field practice, not a published NVIDIA figure; label it as such.

Where you read the number also matters. “Time matters at small sizes (‘to measure the constant overhead (or latency)’), bandwidth at large sizes” — so a busbw figure taken at 8 KiB is measuring launch overhead, not hardware.[1] This is why the sweep exists and why the knee in the sweep is more informative than any single row.

Rails are the reason a two-node number can be worse than the link speed suggests. NVIDIA’s rail definition is that “NIC-0 from each DGX system is connected to the same leaf switch (L0), NIC-1s are connected to the same leaf switch (L1), and so on”, which “helps maximize all-reduce performance while minimizing network interference between flows”.[4] PXN — “PCI × NVLink” — has GPUs reach a NIC “through NVLink and then PCI” rather than through the CPU, which “couldn’t deliver full bandwidth”, and it “leverages NVIDIA NVSwitch connectivity between GPUs within the node to first move data on a GPU on the same rail as the destination, then send it to the destination without crossing rails”.[4] That is the mechanism NCCL_CROSS_NIC=0 is protecting on a rail-optimized fabric.[3]

4Cross-checking with a tool that is not NCCL

A busbw claim gets much stronger when a second, unrelated tool measures the same path. The natural cross-check is point-to-point GPU-to-GPU RDMA between the same two GPUs: NVIDIA’s own reference run is ib_write_bw --use_cuda=0 --use_cuda_dmabuf -d mlx5_0 -a -F --report_gbits -q 1 on the server, with the client adding -n 5000 and the server address.[6] Convert once and compare: perftest reports Gb/s with --report_gbits, nccl-tests reports GB/s, so divide the perftest figure by 8 before putting the two side by side.[5][1]

The logic of the comparison is what matters. If perftest between the two GPUs is healthy and NCCL busbw is not, the fabric carried full-rate RDMA on that path and the fault is in configuration or topology — marking, GID selection, cross-NIC policy, rank placement. If both are low, the fabric is implicated and you move to counters. If perftest cannot run in CUDA mode at all, you are back in the GPUDirect gates rather than in a performance question.[6]

Keep the two tools honest about their differences. perftest is a synthetic micro-benchmark whose README says the benchmarks “are not designed to emulate any real application traffic”, and its latency figures are half a round trip.[5] NCCL busbw is a modelled figure derived from a collective. They agree about a link, not about a workload, and saying so in the report is what stops a customer treating either number as a service-level guarantee.

When the pair disagrees and you need NCCL’s own account of what it did, the debug pair from the previous lesson is the evidence: NCCL_DEBUG=INFO with NCCL_DEBUG_SUBSYS=INIT,NET,GRAPH, written per rank with NCCL_DEBUG_FILE.[3] And before either tool, rping proves basic RoCE connectivity so that a slow number is not actually a broken path.[7]

Read a three-scale result set and name the bottleneck

Given: all_reduce_perf -b 8 -e 8G -f 2 -c 0. One node, 8 ranks: at 1 GiB, time 60.0 ms. Two nodes, 16 ranks: at 1 GiB, time 168.0 ms.

  1. Single node, algbw. S/t = 1073741824 bytes / 0.060 s ≈ 17.9 GB/s.[1]
  2. Single node, busbw. Factor for AllReduce is 2*(n-1)/n = 2 × 7/8 = 1.75, so busbw ≈ 31.3 GB/s.[1]
  3. Name the hardware. The collective never left the node, so the bottleneck candidate is NVLink (or PCIe if the GPUs are not NVLink-connected). Check nvidia-smi topo -m for NV# links before asserting NVLink.
  4. Two nodes, algbw. 1073741824 / 0.168 ≈ 6.4 GB/s.[1]
  5. Two nodes, busbw. Factor is 2 × 15/16 = 1.875, so busbw ≈ 12.0 GB/s.[1]
  6. Name the hardware for the second case. The bottleneck moved off the node, so busbw is now a network statement. busbw is a per-rank figure — the derivation assumes each rank has a bandwidth B to the outside world and n links of that bandwidth — so compare it against one rail, not against the node total. 12.0 GB/s per GPU is well under the 50 GB/s nominal of a 400 Gb/s rail, so the drop is larger than the topology alone explains.[1]
  7. Falsify or confirm before reporting. Run ib_write_bw --use_cuda=0 --use_cuda_dmabuf -d <nic> -a -F --report_gbits -q 1 between one GPU on each node and divide by 8 to compare in GB/s.[6][5] If perftest is near line rate, the finding is configuration: check the traffic class, the cross-NIC policy and the GDR level with NCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,NET,GRAPH.[3]
  8. Confirm the run was bandwidth-bound by reading the large end of the sweep, not the row you were handed.[1]

Acceptance: two algbw values, two busbw values with their factors written out, one named bottleneck per case, and one independent measurement that would falsify the claim.

The sweep he actually wanted

How it ended

You rerun it as all_reduce_perf -b 8 -e 8G -f 2 -c 0[2], and the shape settles the argument better than the algebra did: small sizes measure overhead, large sizes flatten, and bus bandwidth should reflect the speed of the hardware bottleneck — NVLink, PCI, QPI, or network.[1] What you say to the lead engineer: busbw is not a bigger number, it is a different question — algbw tells you how long your all-reduce takes, busbw tells you which piece of hardware you buy next. The order goes in that afternoon. The network lead’s notebook closes on its last page, and a week later an envelope arrives from the night shift: one strip of label tape, RUN THE SWEEP.

Lab

Dell-lab GPU host plus a second node if available. Read-only: builds and benchmark runs only.

  1. Pre-flight inventory. nvidia-smi topo -m, nvidia-smi -L, ibdev2netdev, ulimit -l, and the NCCL version via NCCL_DEBUG=VERSION. Record which GPU pairs show NV# links; that determines what a single-node busbw is allowed to be compared against.
  2. Build nccl-tests. make CUDA_HOME=/usr/local/cuda NCCL_HOME=/usr (adjust paths), then ls build/. Expected: at least all_reduce_perf. Record the actual binary list — the README excerpt confirms only three, so this is the authoritative list for your environment.
  3. Single-node sweep. ./build/all_reduce_perf -b 8 -e 8G -f 2 -g 8 -c 0. Expected: a table with size, time, algbw and busbw columns. Record busbw at the largest size and compute the factor by hand to confirm the tool’s arithmetic: 2*(n-1)/n with n = 8.
  4. Name the single-node bottleneck from step 1’s topology and the busbw from step 3. Write it as a claim with the evidence attached.
  5. Two-node run, if a second node is available: build the MPI variant with make MPI=1 MPI_HOME=..., then run the two-node all-reduce with the env block from the previous lesson (NCCL_IB_HCA, NCCL_IB_TC, debug pair). Record busbw at the largest size and compute the implied per-GPU network bandwidth by hand.
  6. Independent cross-check. Between one GPU on each node: ib_write_bw --use_cuda=<n> --use_cuda_dmabuf -d <nic> -a -F --report_gbits -q 1 on the server and the same plus -n 5000 and the server IP on the client. Divide by 8 to compare in GB/s.
  7. Verdict. If the two disagree by more than roughly ten percent, the fault is in the fabric or its configuration rather than in NCCL; use NCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,NET,GRAPH to see which device, GID and transport NCCL actually chose. Record both command lines and both numbers as the artifact.
  8. Deliverable. One page: the topology matrix, the single-node busbw with its factor written out, the two-node busbw with its factor, the perftest cross-check, and one named bottleneck per case with the evidence that would falsify it.

Retrieval check

10 questions from memory. Answer before looking anything up; misses become flashcards.

Explain it to a Dell SE

Explain to a Dell SE, in five sentences, why nccl-tests prints two bandwidth numbers and which one you would put in a customer report.

14 flashcards for this lesson — 0 in deck. Spaced review lives at /review.

Sources

Facts in this lesson were checked against nccl-tests master README and doc/PERFORMANCE.md (re-fetched 2026-09-07); NCCL 2.31.2 environment-variable reference; NVIDIA all2all/PXN technical blog. Dates are when each page was fetched.

  1. Performance reported by NCCL tests (nccl-tests doc/PERFORMANCE.md) · fetched 2026-09-07
  2. NVIDIA/nccl-tests README (master) · fetched 2026-09-07
  3. Environment Variables — NCCL 2.31.2 documentation · fetched 2026-09-07
  4. Doubling all2all Performance with NVIDIA Collective Communication Library 2.12 (NVIDIA Technical Blog) · fetched 2026-09-07
  5. OFED Performance Tests README (linux-rdma/perftest master) · fetched 2026-09-07
  6. GPUDirect RDMA and GPUDirect Storage — NVIDIA GPU Operator (v26.7.0) · fetched 2026-09-07
  7. Networking Troubleshooting — NCCL 2.31.2 documentation · fetched 2026-09-07

The same idea elsewhere

Other lessons that cover this ground, sometimes from another course's angle.