Proving the fabric: perftest and nccl-tests
S5·E1The number nobody can defend · Dell solution center lab, Round Rock, day three of a five-day proof of concept
Builds on: Counters and errors that mean something, ibdiagnet: the command you run before escalating
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
- Run ib_write_bw and ib_write_lat correctly on an InfiniBand fabric and state what each number means.
- Apply the perftest preconditions - a running subnet manager and identical options on both sides - before quoting any result.
- Isolate a GPUDirect problem from a fabric problem by adding --use_cuda to an already-clean host-to-host run.
- Read the NCCL_IB environment variables that matter on InfiniBand and reject the RoCE-era reflexes that do not apply.
Episode 1 - The number nobody can defend
The customer’s ML lead - imaging diagnostics, board meeting Friday, a 64-node order behind it - joins at eight with one slide: the fabric does half of what they paid for. On it, two terminal blocks: the client ran ib_write_bw with -a, the server ran it without. Beside you the Dell SE reheats the same coffee for the third time, promise spreadsheet open. The network lead has said four words: “show me the counter.”
Nothing on that slide is a measurement. The perftest README is blunt about it: the same options must be passed to both server and client, and different versions may not be compatible, so use the same perftest version on both sides.[1] And the fabric has a precondition Ethernet people forget: a subnet manager must run on the switch or on one of the nodes before the benchmarks start.[1][2]
Why so pedantic? Because RDMA moves data without the kernel in the path, so no operating-system counter can tell you what the wire did. perftest exists to produce one defensible number per layer - bandwidth measured by the client on a unidirectional run, latency measured as a round trip and reported as half of it - so an argument about a fabric ends in evidence instead of opinion.[1]
Different flags on each side turn a number into a rumour with a decimal point.
You ask for one thing before anyone touches a cable: ibstat from both hosts, and a rerun with identical flags.[6] The night-shift operator labelled both hosts before breakfast, so nobody argues about which is which.
1Nothing is measured until the fabric is up
Benchmarks on InfiniBand have a precondition that Ethernet people forget: “When running the benchmarks over an Infiniband fabric, a Subnet Manager must run on the switch or on one of the nodes in your fabric, prior to starting the benchmarks.”[1] NVIDIA’s own NCCL troubleshooting page opens with the same check - run sudo sminfo, and if it fails “start the opensm service on at least one node”.[2] A host SM confirms success by writing SUBNET UP into both /var/log/messages and /var/log/opensm.log.[5]
The second precondition is the port itself. Read ibstat, which “displays basic information obtained from the local IB driver” including LID, SMLID, port state, link width active and port physical state.[6] NVIDIA tells you exactly which four fields to read before benchmarking: port state is Active and not Down, Init or Armed; physical state is LinkUp; the link layer matches the fabric; and the rate “matches your fabric specification without unexpected downshift”.[2] A port stuck in Initializing trained physically but no SM has programmed it - that is an SM problem, not a cable problem, and no benchmark will run through it.[6][5] For the whole fabric at once, iblinkinfo “reports link info for each port in an IB fabric, node by node” and -d prints only nodes with a port in the Down state.[8]
Diagram source (Mermaid)
flowchart TD
A[Slow training job reported] --> B{sminfo returns one master}
B -- no --> B1[SM problem - start opensm or enable the switch SM]
B -- yes --> C{ibstat Active LinkUp expected rate}
C -- no --> C1[Port or link problem - iblinkinfo and mlxlink]
C -- yes --> D{ib_write_bw host to host near line rate}
D -- no --> D1[Fabric problem - stop touching NCCL]
D -- yes --> E{same run with use_cuda near line rate}
E -- no --> E1[GPUDirect or PCIe topology]
E -- yes --> F[Application layer - read NCCL_DEBUG INFO first]2perftest, exactly as documented
The suite is ib_send_bw/ib_send_lat, ib_read_bw/ib_read_lat, ib_write_bw/ib_write_lat and ib_atomic_*, built from source with ./autogen.sh, ./configure, make, make install.[1] You start the server with no address and the client with the server’s address, “IPv4 or IPv6 address. You can use the IPoIB address if IPoIB is configured.”[1]
# server
ib_write_bw -d mlx5_0 -a
# client
ib_write_bw -d mlx5_0 -a <server_ip>The defaults you must be able to quote: -p/--port is 18515, -c/--connection is RC, -d is the first device found, -i/--ib-port is 1, -s/--size is 1, -n/--iters is 1000 with a minimum of 100, and -a/--all runs “sizes from 2 till 2^23”.[1] Bandwidth tests add -b bidirectional, -t/--tx-depth default 128, -q/--qp default 1 and --run_infinitely, which prints results every five seconds.[1]
Three methodology facts decide whether a number is defensible. “The SAME OPTIONS must be passed to both server and client.”[1] “On all unidirectional bandwidth benchmarks, the client measures the bandwidth.”[1] Latency benchmarks “measure round-trip time but report half of that as one-way latency” and print minimum, median and maximum, where “Typically, the first value measured is the maximum value, due to warmup effects”.[1] Raising iterations far above the default “may have negative impact on the measured performance”, so use -N (No Peak) when you do.[1]
Two more traps. Perftest “automatically detects the NUMA node of the IB device and binds the benchmark thread and memory allocations to that node”, and “The active NUMA node is reported in the test output header”; --numa_node is mutually exclusive with --pin_cores and --disable_numa and the conflict is “rejected at parse time”.[1] And versions must match: “Different versions of perftest may not be compatible with each other. Please use the same perftest version on both sides.”[1]
3Adding the GPU without losing the plot
GPUDirect is a second measurement, not a replacement for the first. NVIDIA prescribes the exact pair: run ib_write_bw -d <device> -a on the server and ib_write_bw -d <device> <server> -a on the client, then “repeat with --use_cuda=<gpu_id> on both sides” to isolate GPUDirect RDMA.[2][1] A gap that appears only in the second run is a GPU-to-NIC path problem - PCIe topology, GPUDirect support, or the NIC and GPU sitting on different roots - and not a fabric problem.
MLX5_SCATTER_TO_CQE=0 ./ib_write_bw -d ib_dev --use_cuda=<gpu index> -aThat prefix is the documented recipe for the most common GPUDirect failure: “If GPUDirect is not working, (e.g. you see ‘Couldn’t allocate MR’ error message), consider disabling Scatter to CQE feature. Set the environmental variable MLX5_SCATTER_TO_CQE=0.”[1] DMA-BUF support adds --use_cuda_dmabuf and requires CUDA Toolkit 11.7 or later with NVIDIA open GPU kernel modules 515 or later.[1] Build-side detail worth knowing before a customer’s build fails: “As of perftest release 25.07 the build system automatically detects the location of cuda.h”, so CUDA_H_PATH is no longer required and is deprecated in the 25.10 release.[1]
The other class of failure is pinned memory. NCCL WARN Call to ibv_reg_mr failed and NCCL WARN Call to ibv_create_qp failed both mean the process cannot lock enough memory.[2] The fix is two lines in /etc/security/limits.conf:
* soft memlock unlimited
* hard memlock unlimitedthen log out and back in - or restart the scheduler job with the new limits - and verify with ulimit -l.[2] This is worth checking before a customer’s first run rather than after, because a Slurm job inherits the limits of the daemon that launched it, not of your login shell.
4NCCL over InfiniBand: the few variables that matter
nccl-tests builds with make, or make CUDA_HOME=/path/to/cuda NCCL_HOME=/path/to/nccl, and with MPI=1 MPI_HOME=... for multi-node runs.[4] The single-node eight-GPU sweep is the one to memorise:
./build/all_reduce_perf -b 8 -e 128M -f 2 -g 8
# multi-node, 64 ranks over MPI
mpirun -np 64 -N 8 ./build/all_reduce_perf -b 8 -e 8G -f 2 -g 1The flag defaults explain why an unqualified run tells you nothing: -b/--minbytes and -e/--maxbytes both default to 32M, -f/--stepfactor is disabled by default, -g/--ngpus is 1, -n/--iters is 20 and -w/--warmup_iters is 1.[4] The bus-bandwidth column is documented separately in the repository’s own doc/PERFORMANCE.md.[4]
On the environment side, four variables carry most of the weight on InfiniBand. NCCL_IB_TIMEOUT is “computed as 4.096 µs × 2^timeout” and defaults to 20 since NCCL 2.23, having been 18 since 2.14.[3] NCCL_IB_ADAPTIVE_ROUTING is “Enabled (1) by default on IB networks. Disabled (0) by default on RoCE networks”.[3] NCCL_IB_HCA selects which adapters to use, with the format <hca>[:<port>[:<rail>[:<plane>]]] and comma-separated lists.[3] NCCL_IB_QPS_PER_CONNECTION is “Number of IB queue pairs to use for each connection between two ranks”, default 1 in the range 1 to 128.[3]
The anti-pattern to name explicitly: do not set NCCL_IB_GID_INDEX. It “defines the Global ID index used in RoCE mode” with a default of -1, and NVIDIA states that for NCCL 2.21 and later the GID index is selected automatically and should not be set by hand.[3][2] It is a reflex carried over from RoCE deployments and it is the most common way a working InfiniBand job is broken by tuning.
When you do need to look inside, NCCL_DEBUG=INFO with NCCL_DEBUG_SUBSYS=INIT,NET prints which adapter each rank selected - the answer to “are our eight rails actually being used” - and sudo ibping -S on the remote node followed by sudo ibping <lid> locally is the minimal connectivity proof underneath it.[3][2]
One host, 60 seconds: you need LID, SMLID, port state, link width active and physical state straight from the local IB driver — from a binary, not a shell script.
A customer sentence. Pick the one tool that answers it.
Answer, then the panel gives the man-page wording, a working invocation, and where the evidence lands. Nothing here is trivia: every prompt is a sentence a customer has actually said.
ib_write_latib_write_bwibpingibstatibdiagnetibnodesiblinkinfo5The proof pack
What you send back after a performance escalation should be reproducible by the customer without you. Four artefacts do it: the sminfo output showing exactly one master; ibstat from both hosts showing Active, LinkUp and the expected rate; the ib_write_bw -a client output with its NUMA header line and the exact command line used on both sides; and the same run again with --use_cuda if GPUs are in scope.[2][6][1]
Add two numbers that make the result auditable rather than anecdotal. ulimit -l on the host that ran the test, because a pinned-memory limit silently caps large-message performance and produces ibv_reg_mr failures under load.[2] And a counter baseline: perfquery reads PortCounters and the extended counters from the PMA, and its unit trap must be in your note - “components that represent Data (e.g. PortXmitData and PortRcvData) indicate octets divided by 4 rather than just octets”.[10] A customer who multiplies by four and gets line rate has learned something; one who forgets has opened a second ticket.
This is also the shape the certification asks for. The NCP-AIN Troubleshooting Tools domain is 20% of the exam and names ib_write_lat, ib_write_bw, ibping, ibstat, ibdiagnet, ibnodes and iblinkinfo directly, alongside the objective “Verify low-latency interconnects”.[9] The drill and the deliverable are the same work.
Complaint: “Two XE9680 nodes on the new NDR fabric get about half the bandwidth we expected on our data-loader.”
- Preconditions.
sudo sminfoon both nodes returns one master with the same LID.ibstaton both showsState: Active,Physical state: LinkUp, and the expected rate.[2][6] If the state wereInitializing, stop here - no SM has programmed the port.[5] - Version parity.
ib_write_bw --versionon both hosts. Same build, or the numbers are not comparable.[1] - Host to host. Server:
ib_write_bw -d mlx5_0 -a. Client:ib_write_bw -d mlx5_0 -a 10.10.1.11. Identical flags, and the client’s output is the measurement.[1] Note the NUMA node line from the header.[1] - Read it honestly. If the large-message end of the
-asweep is far below line rate, this is a fabric result and the ticket moves toiblinkinfoandibdiagnet. If it is at rate, the fabric is exonerated in writing. - GPU path. Repeat both sides with
--use_cuda=0. If this run is short while step 3 was not, the gap is GPUDirect or PCIe topology.[2] If it fails with “Couldn’t allocate MR”, re-run withMLX5_SCATTER_TO_CQE=0prefixed.[1] - Limits.
ulimit -lon both. Anything but unlimited gets the twomemlocklines in/etc/security/limits.confand a re-login before you believe any large-message number.[2] - Application layer, last.
NCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,NETon the real job and read which HCA each rank chose.[3]
Deliverable: the six outputs plus one paragraph that says which layer the evidence exonerates.
Same complaint on a different pod: one node is a PowerEdge with a single ConnectX-7 and the other is a customer machine you cannot log into. Fill the blanks.
- The one precondition you can check from your own node alone is ____ and the command is ____.
- You cannot run a two-sided test. The perftest facility that still gives you a local number is ____.
- The customer can run one command for you. It should be ____, and the reason you choose that one over
ibstatis ____. - Their result arrives with no command line attached. Your first reply asks for ____ because ____.
- Their latency figure is exactly twice what you expected. The most likely explanation is ____.
- Before accepting any bandwidth number from them, the two host-side values you request are ____ and ____.
A Dell customer with 16 XE9680 nodes reports that all-reduce throughput dropped by a third after a maintenance window in which they patched the hosts and updated adapter firmware on half the fleet. Nothing changed in the fabric.
Produce: (a) the ordered list of checks you will ask for, cheapest decisive first, with the exact command line for each; (b) which single result would let you close the ticket as an application issue; (c) which single result would make it a fabric issue; (d) the two things about the maintenance window that could produce this symptom without any fabric change at all, and the command that distinguishes them.
Acceptance: every command is quoted exactly as the documentation writes it, both sides of every two-sided test carry identical flags, no step asks the customer to change a setting before a measurement has justified it, and your answer to (d) names a host-side cause rather than a switch-side guess.
One rerun, two numbers, no argument
By Thursday afternoon the rerun is on the screen: identical flags on both sides, the same perftest build, the client’s number a hair under line rate, and the active NUMA node printed in the output header.[1] The same pair with --use_cuda on both sides comes back short, and that gap is the GPU-to-NIC path, not the fabric.[2][1] The network lead copies both numbers into his notebook and deletes his cabling complaint. What you say to the ML lead: “Your fabric is at rate. The missing bandwidth is between your GPU and your NIC, and one run shows it.” Then the SE slides over the quote for sixty-four of these nodes - no spine line item, one cable length for the whole room - and asks whether Friday still works.
Lab
Two Dell-lab ports in IB mode, back to back or through a lab switch, with opensm running on one host.
Pre-flight inventory, all read-only: ibstat, ibstatus, ibdev2netdev, sminfo, iblinkinfo, ulimit -l, and ib_write_bw --version on both hosts. Save all seven outputs - they are your baseline and your rollback reference.[6][8][2]
- Confirm the preconditions before anything else: exactly one master from
sminfo, andActiveplusLinkUpplus the expected rate fromibstaton both hosts.[2][6] If a port readsInitializing, start or fix the SM before continuing.[5] - Server:
ib_write_bw -d mlx5_0 -a. Client:ib_write_bw -d mlx5_0 -a <server_ip>. Record the client output and the header’s NUMA line, and compare the large-message end of the sweep against the port’s line rate.[1] - Repeat with
ib_write_lat -d mlx5_0on both sides and record minimum, median and maximum. State in your notes that the figure is half a round trip.[1] - Mutating step: if
ulimit -lis not unlimited, add the twomemlocklines to/etc/security/limits.confand re-login. Rollback: remove the two lines you added and re-login; the file is the only thing changed and a diff against the pre-flight copy restores it exactly.[2] - Repeat step 2 with
--use_cuda=<gpu>on both sides if the host has a GPU. If it fails with “Couldn’t allocate MR”, prefixMLX5_SCATTER_TO_CQE=0and note that the variable was needed - it belongs in the customer report.[1] - Optional, in a customer or partner lab with GPUs:
./build/all_reduce_perf -b 8 -e 128M -f 2 -g 8, then re-run the real job withNCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,NETand record which HCA each rank picked.[4][3] Change no NCCL variable during this session; the purpose is to observe defaults, not to tune.
Rehearse the mechanics with no InfiniBand at all, then write down what does not transfer.
- Build perftest once on the host, or in a single container launched with
--network host --device /dev/infiniband --cap-add=IPC_LOCK:git clone https://github.com/linux-rdma/perftest.git, thencd perftest/ && ./autogen.sh && ./configure && make. Expected:ib_write_bwandib_write_latbinaries. If not: install the RDMA development headers first - the configure step names what is missing.[1] - Create a soft-RoCE device so the verbs API has something to bind to:
sudo rdma link add rxe0 type rxe netdev eth0, thenrdma link show. Expected:link rxe0/1 state ACTIVE. Rollback:sudo rdma link delete rxe0. If the module is absent:sudo modprobe rdma_rxefirst. Create the device in the same namespace where you will run both binaries - an rdma link belongs to the network namespace that created it. - Run the pair with identical flags. Server:
ib_write_bw -d rxe0 -a. Client:ib_write_bw -d rxe0 -a 127.0.0.1. Expected: a full size sweep to 2^23 with the client printing the bandwidth column.[1] If either side reportsCouldn't find any IB devices, the process cannot seerxe0: run server and client in the same namespace as therdma link add, or re-launch the container with--network host --device /dev/infiniband, and confirm withibv_devinfobefore retrying. If it hangs at connect instead, port 18515 is blocked.[1] - Repeat with
ib_write_lat -d rxe0on both sides. Expected: minimum, median and maximum columns. Note which one is largest and connect it to the warmup statement in the README.[1] - Run
./run_perftest_loopbackfrom the repository and read the output header. Expected: the active NUMA node line.[7][1] Record it - this is the header field you will ask a customer to include. - Deliberately break the contract: run the client with
-aand the server without it. Record what happens, then write one paragraph naming three things in this whole exercise that do not transfer to InfiniBand - no subnet manager was involved, the transport is soft-RoCE in software rather than a ConnectX ASIC, and the numbers reflect a loopback path rather than a fabric.[1]
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, how you prove an InfiniBand pod is healthy before anyone blames the fabric for a slow training job.
Sources
Facts in this lesson were checked against perftest README (linux-rdma master), NCCL user guide env + networking troubleshooting pages, nccl-tests README, all re-fetched 2026-09-07. Dates are when each page was fetched.
- perftest README - linux-rdma/perftest master · fetched 2026-09-07
- Networking Troubleshooting - NCCL user guide · fetched 2026-09-07
- NCCL Environment Variables - NCCL user guide · fetched 2026-09-07
- nccl-tests README - NVIDIA/nccl-tests master · fetched 2026-09-07
- opensm(8) man page source - linux-rdma/opensm master · fetched 2026-09-07
- ibstat(8) man page source - rdma-core master · fetched 2026-09-07
- linux-rdma/perftest repository root listing (GitHub API) · fetched 2026-09-07
- iblinkinfo(8) man page source - rdma-core master · fetched 2026-09-07
- NVIDIA-Certified Professional: AI Networking (NCP-AIN) certification page · fetched 2026-09-07
- perfquery(8) man page source - rdma-core master · fetched 2026-09-07
The same idea elsewhere
Other lessons that cover this ground, sometimes from another course's angle.
- Scenario: "the 400G NIC only does 180 Gb/s"RoCE course · Same ground: perftest, numa and NCCL_IB_TC
- GPUDirect RDMA: dma-buf, nvidia-peermem and GDSK8sNet course · Same ground: gpudirect, numa and NCCL_IB_TC
- Triage in order: symptom to counter to causeRoCE course · Same ground: gpudirect, perftest and NCCL_IB_TC