GPUDirect RDMA: proving it is actually on
S3·E3Whichever slot was easiest to reach · Customer data center, cold aisle, day three of the PoC
Builds on: What ib_write_bw actually measures, NUMA, data validation, and doca_perftest
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
- Read an nvidia-smi topo -m matrix and classify each NIC-to-GPU pair by the distance vocabulary NCCL uses.
- Distinguish the DMA-BUF path from the legacy nvidia-peermem path by their stated requirements.
- Diagnose which of the five silent GPUDirect killers applies from host evidence rather than from symptoms.
- Design the host-memory versus GPU-memory A/B that proves whether GPUDirect is actually in use.
Episode 3 — Whichever slot was easiest to reach
The cold aisle is loud enough that you and the platform lead are half shouting over a laptop on a cart. The training run works — that is the problem. It works at a speed nobody signs a purchase order for, and every box on their checklist is ticked: drivers in, OFED in, RoCE up, host memory steady overnight. The GPU-memory benchmark comes back at a fraction of the host one, the lead wants to know which switch to blame; procurement, on speaker from the parking lot, asks only whether the answer changes the build sheet.
None of the switches. GPUDirect RDMA is the technology introduced in Kepler-class GPUs and CUDA 5.0 that enables a direct path for data exchange between the GPU and a third-party peer device using standard features of PCI Express.[2] It exists to delete exactly that fraction: without it every byte stages through host memory, and with it the transfer never involves the CPU. One physical condition has to hold first — the two devices must share the same upstream PCI Express root complex.[2]
You run nvidia-smi topo -m, which displays a matrix of connections between all GPUs and NICs in the system along with CPU and memory affinities.[4] Two pairs read SYS: connection traversing PCIe as well as the SMP interconnect between NUMA nodes.[4] The rest read PXB. Nothing is broken. Two cards were installed in whichever slot was easiest to reach.
No switch setting shortens a distance you built into the chassis.
Segment 1 begins with that matrix: distance decides what is possible.
1Distance decides whether GPUDirect can happen at all
GPUDirect RDMA is “a technology introduced in Kepler-class GPUs and CUDA 5.0 that enables a direct path for data exchange between the GPU and a third-party peer device using standard features of PCI Express”.[2] The hard topology rule is one sentence: “The two devices must share the same upstream PCI Express root complex.”[2] NVIDIA describes three cases — PCIe switches only (optimal), a single CPU or IOH (works but degraded), and CPU/IOH connected via QPI/HT, which “may be extremely limited or unreliable”.[2]
You read that off one command. nvidia-smi topo -m “Displays a matrix of connections between all GPUs and NICs (including their data-direct devices if applicable) in the system along with CPU/memory affinities for the GPUs”.[4] The legend, verbatim: X is Self; PIX is “Connection traversing a single PCIe switch”; PXB is “Connection traversing multiple PCIe switches (without traversing the PCIe Host Bridge)”; PHB is “Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU)”; NODE is “Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node”; SYS is “Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)”; NV# is “Connection traversing a bonded set of # NVLinks”.[4]
That is not just a diagnostic vocabulary — it is the same vocabulary NCCL_NET_GDR_LEVEL uses. The variable “Defines maximum distance between NIC and GPU” for GDR to be used, accepting LOC, PIX, PXB, PHB, SYS or the legacy integers 0–4, with the default auto-selected by architecture.[9] So the matrix and the environment variable are read together: the matrix tells you the distance, the variable tells you the threshold NCCL will accept.
On Dell hardware the distance is a slot decision. The XE9680’s dedicated NIC/SmartNIC/DPU slots are 31 on Processor 2 and 40 on Processor 1, while slots 32/33 and 34/35 hang off Processor 2 and 36/37 and 38/39 off Processor 1.[11] A customer who filled 31 and 40 first with ConnectX-7s has spread two cards across two sockets before any software was installed.
Diagram source (Mermaid)
flowchart TD; A["nvidia-smi topo -m: NIC to GPU distance"] -->|SYS or NODE| Z1["Move the card or repair the pairing. Same root complex is a hard requirement."]; A -->|PIX PXB or PHB| B["Peer-memory path present? lsmod plus driver install order"]; B -->|neither DMA-BUF nor peermem| Z2["Install path wrong. GPU driver before OFED means reinstall the GPU driver."]; B -->|present| C["ulimit -l unlimited, or IPC_LOCK in the pod?"]; C -->|limited| Z3["ibv_create_qp or ibv_reg_mr will fail. Fix limits.conf then re-login."]; C -->|unlimited| D["IOMMU 1:1 or off? /proc/cmdline and dmesg"]; D -->|Translated| Z4["Non 1:1 translation is incompatible with GPUDirect RDMA."]; D -->|off or passthrough| E["PCIe ACS on the bridges? lspci -vvv ACSCtl"]; E -->|enabled on bare metal| Z5["Disable with setpci, or enable ATS if this is a VM."]; E -->|clear| F["Prove it: ib_write_bw host memory versus --use_cuda --use_cuda_dmabuf"]
2Two paths to the same thing: DMA-BUF and nvidia-peermem
There are two mechanisms and NVIDIA’s GPU Operator documentation states their requirements as a table.[1] For DMA-BUF: “An Open Kernel module driver is required”, “CUDA 11.7 or higher”, “Turing architecture data center, Quadro RTX, and RTX GPU or higher”, “MLNX_OFED or DOCA-OFED are optional”, and Linux kernel “5.12 or higher”.[1] For the legacy nvidia-peermem: “Any supported driver”, “No minimum version” of CUDA, “All data center, Quadro RTX, and RTX GPU or higher”, “MLNX_OFED or DOCA-OFED are required”, and no minimum kernel.[1] NVIDIA’s recommendation is explicit: “NVIDIA recommends using the DMA-BUF rather than using the nvidia-peermem kernel module from the GPU Driver.”[1]
The peer-memory module itself “provides direct peer-to-peer read and write access to the NVIDIA GPU’s video memory” for NVIDIA InfiniBand-based HCAs, so RDMA applications avoid a bounce through host memory.[5] It is “a drop-in replacement for nv_peer_mem”, and only one of the two may be loaded at a time; the documented migration order is service nv_peer_mem stop, lsmod | grep nv_peer_mem, rmmod nv_peer_mem, then uninstall the package.[5][6]
Two traps live here. First, ordering: “If the NVIDIA GPU driver is installed before MOFED, the GPU driver must be uninstalled and installed again” so that nvidia-peermem compiles against MLNX_OFED’s RDMA APIs.[5] On a freshly imaged Dell node this is the single most common reason GPUDirect is silently unused. Second, persistence: it is loaded with modprobe nvidia-peermem, and “there is no service to automatically load nvidia-peermem.ko”.[5] A /etc/modules-load.d/ entry is the usual workaround, but the NVIDIA README does not document it — say so rather than presenting it as vendor guidance.
Verification is lsmod | grep nvidia, where the expected modules include nvidia_fs and nvidia_peermem.[1] Do not confuse gdrcopy with GPUDirect RDMA: gdrcopy is a separate library checked with lsmod | grep gdrdrv.[6] And on a modern DMA-BUF system there may be no module to find at all — NCCL “auto-detects and enables DMA-BUF support” so “modern systems using DMA-BUF with open-source drivers don’t require this module”.[3]
3The three silent killers on a healthy-looking host
Memory lock. The symptoms are NCCL WARN Call to ibv_create_qp failed and NCCL WARN Call to ibv_reg_mr failed.[8] The fix is * soft memlock unlimited and * hard memlock unlimited in /etc/security/limits.conf; “Users must log out/reboot or update Slurm job launch settings. Verify with ulimit -l.”[8] In Kubernetes the equivalent is securityContext.capabilities.add: ["IPC_LOCK"], which appears in both of NVIDIA’s sample pods.[10][1]
IOMMU. The constraint is verbatim: “GPUDirect RDMA currently relies upon all physical addresses being the same from the different PCI devices’ point of view. This makes it incompatible with IOMMUs performing any form of translation other than 1:1.”[2] NCCL’s own troubleshooting is blunter, quoting the CUDA Programming Guide that “the IOMMU must be disabled on Linux bare-metal systems to prevent silent device memory corruption”.[3] Check with cat /proc/cmdline and dmesg | grep -i -E "iommu|dmar|default domain"; the concerning strings are Default domain type: Translated and DMAR: IOMMU enabled.[3]
PCIe ACS. Access Control Services forces peer transactions up to the root complex, which is the opposite of what GPUDirect wants. Detect with sudo lspci -vvv | grep ACSCtl; disable one device with sudo setpci -s 03:00.0 ECAP_ACS+0x6.w=0000; NVIDIA publishes a loop for every bridge in the box.[3]
for BDF in `lspci -d "*:*:*" | awk '{print $1}'`; do
sudo setpci -v -s ${BDF} ECAP_ACS+0x6.w > /dev/null 2>&1
if [ $? -ne 0 ]; then continue; fi
sudo setpci -v -s ${BDF} ECAP_ACS+0x6.w=0000
doneFor virtual machines the answer is different: “For virtual machines, ACS cannot be disabled; instead, enable ATS in network adapters for optimal performance.”[3] One more container-and-VM rule from the same page: ensure /sys is properly mounted, because that is what provides PCI topology discovery, and a virtual PCI topology can degrade performance significantly.[3]
Sample runs, exits cleanly, but zero throughput
4The proof: the host-memory versus GPU-memory A/B
Everything above is a prerequisite. The only thing that proves GPUDirect is in use is a pair of measurements. NVIDIA’s own GPU Operator documentation runs it as a server line ib_write_bw --use_cuda=0 --use_cuda_dmabuf -d mlx5_0 -a -F --report_gbits -q 1 with the client adding -n 5000 and the server address.[1] Run the identical line without the CUDA flags to get the host-memory baseline: if the GPU number approximately equals the host number, GPUDirect is on; if it is a fraction of it, the transfer is staging through host memory.[1][7]
perftest’s CUDA support has moved recently. “As of perftest release 25.07 the build system automatically detects the location of cuda.h. Passing CUDA_H_PATH to the configure script is therefore no longer required… The variable will depracted in the 25.10 release.”[7] DMA-BUF has its own build requirements, quoted verbatim: “1) CUDA Toolkit 11.7 or later. 2) NVIDIA Open-Source GPU Kernel Modules version 515 or later.”[7] Usage is ib_send_bw -d mlx5_0 --use_cuda=0 --use_cuda_dmabuf.[7]
Two flags make the run reproducible on a rail-optimised box. --use_cuda_bus_id=<cuda full BUS id> selects the GPU by full PCIe address instead of by index, which matters because CUDA ordinals move; --cuda_mem_type=<value> chooses 0(device,default), 1(managed) or 4(malloc).[7]
And the known issue worth memorising: “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”, as in MLX5_SCATTER_TO_CQE=0 ./ib_write_bw -d ib_dev --use_cuda=<gpu index> -a.[7]
Evidence supplied by the customer: nvidia-smi topo -m shows GPU3 to mlx5_2 as SYS and GPU3 to mlx5_6 as PXB. ib_write_bw host memory reads 372 Gb/s; with --use_cuda=3 --use_cuda_dmabuf -d mlx5_2 it reads 121 Gb/s.
- Classify the pairing.
SYSis “Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)”, which violates the shared-root-complex rule.[4][2] The customer is testing GPU3 against a NIC on the other socket. - Note the correct pair exists:
mlx5_6atPXBis “Connection traversing multiple PCIe switches (without traversing the PCIe Host Bridge)” — a valid GDR pairing.[4] - Re-run the A/B against the right NIC:
ib_write_bw --use_cuda=3 --use_cuda_dmabuf -d mlx5_6 -a -F --report_gbits -q 1on the server, client adds-n 5000and the address.[1] - If the number is now close to the host figure, the finding is a pairing error, not a platform fault. If it is still a fraction, walk the remaining gates in order:
lsmodand install order,ulimit -l, IOMMU, ACS.[1][5][8][3] - If it fails outright with
Couldn't allocate MR, retry withMLX5_SCATTER_TO_CQE=0before concluding anything about the fabric.[7] - Write the finding as measured versus inferred: measured is the four numbers and the matrix; inferred is which slot change would fix it, which needs the XE9680 slot-to-socket map to state.[11]
Acceptance: the report names the distance label for both pairings, gives four bandwidth figures with their full command lines, and separates the measured pairing error from the inferred physical remedy.
SYSmeans the path crosses ________, which breaks the rule that both devices must share the same ________.PXBmeans the path crosses ________ without traversing the ________.- The proof run is
ib_write_bw ________ ________ -d <right nic> -a -F --report_gbits -q 1, compared against the same line ________. - If the GPU number is roughly equal to the host number then ________; if it is a fraction then ________.
- Remaining gates in order: ________, ________, ________, ________.
- On
Couldn't allocate MR, set ________ before blaming the fabric.
A customer’s node shows every GPU-to-NIC pair as PHB, lsmod lists no nvidia_peermem, ulimit -l returns unlimited, /proc/cmdline contains iommu=pt, and ib_write_bw --use_cuda=0 --use_cuda_dmabuf runs at roughly the host-memory number. They ask you to file a defect because peermem is missing. Write the reply. Acceptance: you say whether GPUDirect is working and on what evidence, you explain why the missing module is not a defect on this system, you say what PHB costs relative to PIX without inventing a number, and you name the one thing you would still check before closing the ticket.
Same line, twice
The two SYS cards move to slots under the right socket — procurement was right for once, the build sheet changes, and she wants that in writing before Thursday. On the rest you walk the gates in order: peer path present, ulimit -l unlimited[8], IOMMU not translating[2], ACS clear on the bridges.[3] Then the only thing that actually proves it: the same ib_write_bw line with and without the CUDA flags, GPU number against host number.[1] They match. On the nodes you reimage, the GPU driver goes in after OFED, not before.[5] Every pair is clean now. The eight-node training job still stalls.
Lab
Dell-lab GPU host. Mostly read-only; step 6 is the only mutating step and its rollback is named.
- Pre-flight inventory, all read-only.
nvidia-smi topo -m,nvidia-smi -q | grep -i -A3 BAR1,lsmod | grep -E "nvidia|gdrdrv",ulimit -l,cat /proc/cmdline,dmesg | grep -i -E "iommu|dmar|default domain",ibdev2netdev,cat /sys/class/net/<if>/device/numa_node. Record every output before changing anything. - Choose the pair. From the matrix pick the GPU and the NIC with the best distance label and write it down:
PIXorPXBis what you want,PHBis workable,NODEorSYSmeans stop and check the slot population. - Host-memory baseline. Server:
ib_write_bw -d mlx5_0 -a -F --report_gbits -q 1. Client: the same line plus-n 5000and the server address. Expected: a size sweep in Gb/sec. This is the reference number. - GPU-memory run. Server:
ib_write_bw --use_cuda=0 --use_cuda_dmabuf -d mlx5_0 -a -F --report_gbits -q 1. Client: the same plus-n 5000and the address. Expected: large sizes approximately match step 3. If it is roughly a third, GDR is not being used and you go back to the gates. - If step 4 fails with
Couldn't allocate MR: re-run asMLX5_SCATTER_TO_CQE=0 ib_write_bw --use_cuda=0 --use_cuda_dmabuf -d mlx5_0 -a -F --report_gbits -q 1. Record whether the workaround was needed; that fact belongs in the customer report. - Mutating, only if
ulimit -lis notunlimited: capture the current/etc/security/limits.confwithcp /etc/security/limits.conf /root/limits.conf.bak, add the two memlock lines, open a new session, re-checkulimit -l, repeat step 4. Rollback:cp /root/limits.conf.bak /etc/security/limits.confand open a new session. Do not change IOMMU or ACS on the lab host in this lab; both are reboot-scope or BIOS-scope changes and belong in a change-control window. - Repeat step 4 against a deliberately mismatched NIC (the one your matrix shows as the worst label for that GPU) and record the pair of numbers. That contrast, not the absolute figure, is the artifact.
- Deliverable. A table with GPU, NIC, distance label, host-memory Gb/s and GPU-memory Gb/s for two pairings, plus the state of all five gates as read in step 1.
No GPU required. This is a diagnosis drill plus two commands that are safe on the containerlab host.
- Read three topology matrices supplied with the lesson (a rail-optimised XE9680, a single-socket box, and a box whose cards were placed by convenience). For each pair, write the distance label and whether GPUDirect can engage, using only the legend:
PIX,PXB,PHB,NODE,SYS. Expected: you never argue from performance, only from the label. - For each matrix name the
NCCL_NET_GDR_LEVELvalue that would force GDR on that pair, and the value that would disable it everywhere (LOC). Expected: you can state that the variable’s default is auto-selected by architecture rather than a fixed number. - ACS detection on the lab host, read-only.
sudo lspci -vvv | grep ACSCtlandsudo lspci | grep PLX. Expected: a list of bridges withACSCtllines showingSrcValid,TransBlkand friends as+or-. If the grep is empty: the host has no ACS-capable bridges and you record that as the result. - Write, but do not run, the exact remediation: the single-device
setpci -s <bdf> ECAP_ACS+0x6.w=0000and the loop over every BDF. State in one sentence why you would not run it on a shared lab host and what happens to the setting at the next reboot. - IOMMU state, read-only.
cat /proc/cmdlineanddmesg | grep -i -E "iommu|dmar|default domain". Expected: you can classify the host as off, passthrough or translating, and quote the string that told you. - Memlock, in a throwaway container.
ulimit -linside the container, then add* soft memlock unlimitedand* hard memlock unlimitedto/etc/security/limits.confinside that container, open a new shell and re-check. Rollback: discard the container. Expected: the value changes only after a new login session. - Write the pod-level equivalent as YAML from memory:
securityContext.capabilities.add: ["IPC_LOCK"], and say which of the two bare-metal steps it replaces.
Retrieval check
10 questions from memory. Answer before looking anything up; misses become flashcards.
Explain it to a Dell SE
Explain to a Dell platform engineer, in five sentences, what GPUDirect RDMA actually does and the two host settings most likely to be silently switching it off.
Sources
Facts in this lesson were checked against NVIDIA GPU Operator GPUDirect RDMA page (v26.7.0), CUDA GPUDirect RDMA 13.3, NCCL 2.31.2 GPU troubleshooting, nvidia-smi topo legend, NVIDIA Linux driver 575.57.08 nvidia-peermem README, perftest master README — all re-fetched 2026-09-07. Dates are when each page was fetched.
- GPUDirect RDMA and GPUDirect Storage — NVIDIA GPU Operator (v26.7.0) · fetched 2026-09-07
- GPUDirect RDMA 13.3 documentation — Overview · fetched 2026-09-07
- GPU Troubleshooting — NCCL 2.31.2 documentation · fetched 2026-09-07
- nvidia-smi documentation (topo subcommand and legend) · fetched 2026-09-07
- GPUDirect RDMA Peer Memory Client — NVIDIA Linux driver 575.57.08 README · fetched 2026-09-07
- Installation | NVIDIA GPUDirect RDMA User Manual · fetched 2026-09-07
- OFED Performance Tests README (linux-rdma/perftest master) · fetched 2026-09-07
- Networking Troubleshooting — NCCL 2.31.2 documentation · fetched 2026-09-07
- Environment Variables — NCCL 2.31.2 documentation · fetched 2026-09-07
- MacVLAN Network with RDMA Shared Device — NVIDIA Network Operator v25.7.0 quick start · fetched 2026-09-07
- Dell PowerEdge XE9680 Technical Guide (Regulatory Model E90S) · fetched 2026-09-07
- PowerEdge BIOS Performance and Workload Tuning Guide for 16G (Dell InfoHub) · fetched 2026-09-07
The same idea elsewhere
Other lessons that cover this ground, sometimes from another course's angle.
- GPUDirect RDMA: dma-buf, nvidia-peermem and GDSK8sNet course · Same ground: nvidia-peermem, gpudirect and buf
- Rail-optimized fabrics on Dell hardwareElsewhere in this course · Same ground: gdr, topology and nic
- vfio-pci for VMs, and the DRA SR-IOV driverK8sNet course · Same ground: iommu, rdma and stated