Lessons
19 — GPU topology / NVLink / RDMA / NCCL

Lesson 19 — AI/ML platform: GPU topology — NVLink/NVSwitch, multi-NIC RDMA, GPUDirect, NCCL

2026-09-27

What interviewers are really probing

Lesson 18 productized GPUs as SKU pools. This lesson asks the next operator question: can you pack those GPUs without fabric awareness — or do you treat NVLink domains, RDMA rails, and NCCL topology as first-class capacity?

Interviewers at hyperscale AI platforms (fuel: wafer-ai/gpu-perf-engineering-resources (opens in a new tab)) want evidence you have debugged silent 10× slowdowns, not only “8× nvidia.com/gpu Pending”:

  • Packing without fabric awareness — scheduler sees GPUs; NCCL sees NVLink vs PCIe hops and which RDMA NIC is actually wired (L1, L4)
  • NVLink vs PCIe — intra-node peer bandwidth; commodity PCIe boxes ≠ HGX/DGX NVSwitch domains
  • NVSwitch domains — all-to-all GPU fabric inside a node (or multi-node NVLink / ComputeDomain on NVL racks)
  • Multi-NIC RDMA (RoCE / InfiniBand) — rail-aligned NICs; why eth0-only CNI kills training (L9, L8)
  • GPUDirect RDMA / Storage — NIC DMA into GPU HBM (GDR) and storage paths (GDS); host bounce buffers as the silent tax
  • NCCL topology discovery — rings vs trees vs NVLS; NCCL_DEBUG, topology XML, NCCL_IB_HCA
  • Topology-aware scheduling — labels, affinity, DRA / ComputeDomain; anti-affinity that destroys bandwidth; gang/queues preview → Lesson 20 upcoming
  • Security floor on fast paths — RDMA privilege, fabric isolation, weight IAM on topology nodes (L11–L14)

Interview sentence: “I treat NVLink domains and RDMA rails as capacity — labels and affinity pack ranks into the fabric, NCCL env pins the right HCAs, GPUDirect avoids host bounce, and I never open node-wide weight IAM or hostNetwork just because the train fabric is fast.”

Core diagram: GPUs ↔ NVLink → host/PCIe → RDMA NICs → fabric → NCCL → security floor

GPU topology: NVLink/NVSwitch domain, PCIe/host bridge, multi-NIC RDMA with GPUDirect, cluster fabric, NCCL collective path, topology-aware scheduling, security floor

Read it top-down: An NVLink/NVSwitch domain (HGX/DGX/NVL) gives GPUs a fast all-to-all path. Traffic that leaves the domain hits PCIe/host (NUMA hops) unless GPUDirect RDMA lets the NIC DMA GPU HBM directly. Multi-NIC RDMA (RoCE/IB rails) carries inter-node collectives; eth0 is for the pod CNI — not NCCL. The cluster fabric (rail alignment, PFC/ECN, jumbo MTU) either keeps rings local or silently stripes them across racks/AZs. NCCL discovers topology, builds rings/trees, and is pinned with env vars. Topology-aware scheduling (labels, affinity, DRA) decides whether packing respects the fabric. Everything sits on a security floor: RDMA device privilege lockdown, separate train vs inference fabrics, WI/IRSA + CMEK for weights on topology nodes, signed NCCL/CUDA/driver images, admission on privileged RDMA pods.

Idiosyncrasies / operator depth

1. NVLink / NVSwitch vs PCIe hops

PathWhat you getOperator truth
NVLink + NVSwitch (HGX/DGX)Full-mesh / high-BW GPU↔GPU inside the domainnvidia-smi topo -m shows NV# / NVSwitch; NCCL prefers P2P here
NVLink bridge pairs (some commodity)Fast pairs, slow cross-pair“8 GPU node” ≠ one domain — rings may bounce PCIe
PCIe onlyHost root-complex hopsFine for inference fan-out; painful for large all-reduce
Multi-node NVLink (MNNVL)Rack-scale domains (e.g. NVL72)Needs IMEX / ComputeDomain / DRA — not just device plugin ads (NVIDIA DRA GPU docs (opens in a new tab))

Interview sentence: “I read nvidia-smi topo before I believe an 8-GPU SKU is one NVLink domain — PCIe hops inside a ‘GPU node’ are a packing bug waiting to page.”

2. Multi-NIC RDMA / GPUDirect — why eth0-only CNI kills training

PieceRoleFootgun
RoCE / IB HCAsRDMA verbs for NCCL inter-nodeUsing the wrong NIC → TCP fallback or wrong rail
GPUDirect RDMA (GDR)NIC DMA ↔ GPU HBM; skip host bounceNeeds nvidia-peermem / DMA-BUF; NUMA distance + NCCL_NET_GDR_LEVEL
GPUDirect Storage (GDS)Storage↔GPU paths for checkpoints / datasetsDifferent stack than GDR; still needs least-privilege device access (L13)
Secondary network (NAD / Multus)Attach RDMA netdev to train pods (L9)eth0-only CNI: NCCL never sees mlx5_* → hang or abysmal BW
Rail alignmentRank i prefers NIC iMisaligned rails = incast / congestion even with “enough” BW

Cloud fabric lessons (L5/L6) still apply: VPC MTU, sticky routes, and interconnect choice show up as NCCL timeouts if ignored.

Interview sentence: “Training pods get a dedicated RDMA interface via Multus/NAD; eth0 stays for kubelet/CNI — if NCCL lands on eth0, the job is already broken.”

3. NCCL env, topology XML, rings vs trees

KnobWhy operators care
NCCL_DEBUG=INFO / TRACEFirst page of every hang ticket — shows chosen graph, NIC, GDR
NCCL_TOPO_DUMP_FILE / XMLCapture discovered topology; compare across nodes in a Job
NCCL_IB_HCA / NCCL_SOCKET_IFNAMEPin HCAs / exclude eth0
NCCL_NET_GDR_LEVELHow far GDR is allowed across PCIe/NUMA
NCCL_P2P_LEVEL / NCCL_ALGOP2P depth; Ring vs Tree selection (size-dependent)
NCCL_IB_DISABLE=1Emergency TCP — proves fabric vs app, never leave on in prod
Rings vs treesRings BW-friendly for large messages; trees often win latency / larger scales; NVLS when NVLink Switch features apply

Interview sentence: “I debug NCCL with DEBUG=INFO and a topo dump before I blame PyTorch — most ‘framework hangs’ are wrong NIC, GDR off, or ranks striped across racks.”

4. Topology labels, DRA, scheduler hints (preview L20)

MechanismUse
Node labelssku, rack, block, nvlink-domain, rdma-rail-count via GFD + your own inventory
Pod affinityPack multi-GPU / multi-pod jobs into same rack/block
Anti-affinityGreat for HA inference; toxic for train ranks that need NVLink/RDMA locality
DRA / ComputeDomainClaim topology-aware GPU (+ MNNVL) resources instead of opaque nvidia.com/gpu counts
Volcano / Kueue gangAll-or-nothing admission so you don’t half-schedule a job across bad topology — Lesson 20

Interview sentence: “Topology labels make the fabric schedulable; gang scheduling makes it atomic — affinity without gang still leaves you with half a ring on a slow rack.”

5. Stripe across racks / AZs = silent 10× slowdown

MistakeSymptomReality
Default-scheduler spreads podsJob “runs” but step time explodesCross-rack BW ≪ NVLink / local RDMA
AZ-aware anti-affinity on train“HA training”Collectives become WAN-ish; checkpoints become the reliability story instead
Mixed SKU domains in one JobNCCL graph weirdness / fallbackIsolate SKUs (L18); don’t invent a hybrid ring
Ignoring PFC/ECN/MTUFlaky RoCE, PFC stormsFabric ops is part of GPU ops — not “someone else’s network” (L8)

Interview sentence: “If step time regresses after a reschedule with the same GPU count, I assume topology first — racks and rails, not CUDA kernels.”

Concrete commands & config

Inventory: topology on the node

# Intra-node GPU / NIC topology matrix
nvidia-smi topo -m
nvidia-smi nvlink -s
nvidia-smi nvlink -e   # error counters — non-zero is a page
 
# RDMA devices & link
ibstatus 2>/dev/null || rdma link show
ibdev2netdev
ethtool -i eth1
ethtool -l eth1   # combined queues; often pinned per NUMA
 
# Is peermem available for GDR?
lsmod | grep -E 'nvidia_peermem|ib_core'
dmesg | grep -i -E 'peermem|gdr' | tail

kubectl: labels, RDMA plugin, NAD

# Topology labels you should see / enforce
kubectl get nodes -l sku=h100 \
  -L sku,rack,block,nvlink-domain,rdma-rail-count,nvidia.com/gpu.product
 
# RDMA device plugin / SR-IOV resources (illustrative names)
kubectl describe node GPU_NODE | sed -n '/Allocatable:/,/System Info:/p' | grep -iE 'rdma|mlx|pci'
 
# Multus NetworkAttachmentDefinition for train fabric
kubectl get net-attach-def -A
kubectl get pods -n model-training -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations.k8s\.v1\.cni\.cncf\.io/networks}{"\n"}{end}'
# Sketch: secondary RDMA network for train pods (Multus)
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: rdma-train-rail
  namespace: model-training
spec:
  config: '{
    "cniVersion": "0.3.1",
    "type": "host-device",
    "device": "eth1",
    "ipam": { "type": "whereabouts", "range": "192.168.100.0/24" }
  }'
---
# RDMA shared-device plugin resource (pattern — pin to your plugin)
# Ads something like rdma/rdma_shared_device_a on nodes with mlx5_*

Job with topology constraints + NCCL env

apiVersion: v1
kind: ConfigMap
metadata:
  name: nccl-train-env
  namespace: model-training
data:
  NCCL_DEBUG: "INFO"
  NCCL_IB_HCA: "=mlx5_0,mlx5_1,mlx5_2,mlx5_3"
  NCCL_SOCKET_IFNAME: "eth1"
  NCCL_NET_GDR_LEVEL: "5"
  NCCL_P2P_LEVEL: "SYS"
  NCCL_IB_DISABLE: "0"
  NCCL_TOPO_DUMP_FILE: "/tmp/nccl_topo.xml"
---
apiVersion: batch/v1
kind: Job
metadata:
  name: train-topo-aware
  namespace: model-training
spec:
  parallelism: 4
  completions: 4
  template:
    metadata:
      annotations:
        k8s.v1.cni.cncf.io/networks: rdma-train-rail
      labels:
        app: train-topo
        trust-domain: train-prod
    spec:
      runtimeClassName: nvidia
      serviceAccountName: weight-writer
      restartPolicy: OnFailure
      nodeSelector:
        sku: h100
        pool: train-exclusive
        # Pack into one fabric island
        block: cell-a-block-3
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: train-topo
              topologyKey: topology.kubernetes.io/zone  # tighten to rack label in prod
        # Do NOT spread train ranks with podAntiAffinity on rack
      tolerations:
        - key: sku
          value: h100
          effect: NoSchedule
        - key: nvidia.com/gpu
          value: "true"
          effect: NoSchedule
      containers:
        - name: trainer
          image: registry.example.com/ml/trainer@sha256:REPLACE
          envFrom:
            - configMapRef:
                name: nccl-train-env
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              add: ["IPC_LOCK"]   # often required for RDMA reg; still not privileged
              drop: ["ALL"]
          resources:
            limits:
              nvidia.com/gpu: "8"
              # rdma/rdma_shared_device_a: "1"   # if using RDMA device plugin
          volumeMounts:
            - name: dshm
              mountPath: /dev/shm
      volumes:
        - name: dshm
          emptyDir:
            medium: Memory
            sizeLimit: 16Gi

nccl-tests smoke (operator acceptance)

# Inside a topology-correct Job / debug pod with RDMA + GPUs
export NCCL_DEBUG=INFO NCCL_IB_HCA==mlx5_0,mlx5_1
./build/all_reduce_perf -b 8 -e 2G -f 2 -g 8
# Compare busbw to node SKU expectations; cliff = wrong NIC / no GDR / cross-rack
 
# Capture topo for the postmortem bundle
ls -l /tmp/nccl_topo.xml

Admission sketch: RDMA pods are not free privilege

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: rdma-train-floor
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
  validations:
    - expression: >
        !has(object.metadata.annotations) ||
        !has(object.metadata.annotations["k8s.v1.cni.cncf.io/networks"]) ||
        object.metadata.annotations["k8s.v1.cni.cncf.io/networks"] != "rdma-train-rail" ||
        (has(object.spec.serviceAccountName) &&
         object.spec.serviceAccountName != "default" &&
         has(object.spec.runtimeClassName) &&
         object.spec.runtimeClassName == "nvidia" &&
         !(has(object.spec.hostNetwork) && object.spec.hostNetwork) &&
         has(object.spec.nodeSelector) &&
         has(object.spec.nodeSelector.sku))
      message: "RDMA train pods require nvidia RuntimeClass, non-default SA, sku selector; hostNetwork forbidden"

Failures & fixes

SymptomLikely causeFix / mitigation
NCCL hang at init / watchdog abortWrong NIC; eth0 only; IB verbs blocked; MTU mismatchPin NCCL_IB_HCA + Multus RDMA; verify ibstatus; align MTU 9000 end-to-end
Busbw ~TCP / host NIC speedsGDR off; peermem missing; NCCL_NET_GDR_LEVEL too lowLoad nvidia-peermem; raise GDR level; confirm GPU↔NIC topo
Job “healthy” but 5–10× slower after rescheduleRanks striped across racks/AZsRequired affinity on rack/block; forbid train anti-affinity on zone
nvidia-smi topo shows SYS between GPUs you expected NV#Not an NVSwitch domain; PCIe fallbackDon’t sell as HGX-equivalent; adjust rings / SKU pool
RoCE flaps / PFC stormLossless config drift; ECN mis-set; noisy neighborFabric runbook (L8); isolate train VRF/VLAN
Mixed A100+H100 in one process groupScheduler ignored SKUPool taints (L18); admission require sku
Pending: RDMA resource missingDevice plugin / SR-IOV VF exhaustionCap concurrent train Jobs; separate inference from train VFs
Checkpoint I/O tanks step timeNo GDS / saturated eth0 path for weightsSeparate data path; GDS where supported; WI to CMEK bucket (L13)
Security finding: hostNetwork train podsShortcut to “see” RDMANAD + device plugin; deny hostNetwork in admission
Tenant inference pod on train railShared CNI / open NetPolSeparate NAD/VRF; default-deny; allowlist train NS only (L9, L14)
Weight exfil from topo nodeNode instance role broad because “GDR nodes need storage”Per-pod WI/IRSA; never node-wide s3:* / gs://*

Security & hardening

Fast fabrics amplify blast radius. GPUDirect and RDMA put device memory and NIC DMA on paths that traditionally needed privilege. Topology nodes also pull model weights — do not trade IAM for bandwidth.

ControlWhyPractice
RDMA / GPUDirect privilegeMemory registration wants CAP_IPC_LOCK; VFIO / device plugins expose /dev/infiniband/*Add only needed caps; forbid privileged + hostNetwork for tenants; Operator/device-plugin NS is the exception (L11)
Device access lockdownShared RDMA devices ≈ shared DMARDMA device plugin with resource quota; no raw hostPath to ib devices for notebooks
Isolate train vs inference fabricsInference tenants must not snoop / DoS train railsSeparate CNI/NAD/VRF/VLAN; NetworkPolicy default-deny across trust domains (L9, L14)
NetPol on GPU namespacesWeights and gradients on the wireAllow DNS + private registry + weight VPC endpoints + RDMA fabric; deny east-west to interactive NS
Weights on topology-aware nodesTrain nodes still download checkpointsWI/IRSA per workload; CMEK buckets; signed digests; private registries — never widen node-wide IAM “because train nodes need GDR” (L13)
Signing / provenance for NCCL·CUDA·driver imagesCompromised collective stack = host/GPU compromiseCosign verify; pin digests; SBOM on GPU Operator + train images (L12)
Admission on privileged RDMA podsCopy-paste from GPU Operator is the classic escapeValidatingAdmissionPolicy / Kyverno; PSS restricted on model-*; break-glass only in gpu-operator / rdma-system
Secrets for registry / HFLong-lived tokens on GPU nodesExternal Secrets / CSI; short-lived OIDC; no HF tokens in images
IMEX / MNNVL domainsMulti-node NVLink memory export is a trust boundaryScoped ComputeDomains per Job; tear down with the gang; don’t leave rack-wide open import

AI/ML weight & registry hardening on topology nodes (required)

ScenarioWeight / registry controls
Exclusive NVLink train poolWriter SA + CMEK; digest-pinned trainer image; WI only — GDR does not imply node object-store admin
Multi-rail RDMA JobSame IAM as single-rail; fabric speed ≠ storage IAM; checkpoint path via GDS/WI still least privilege
Shared interactive near train racksDifferent NAD; no train-rail attachment; scrubbed weights or per-user short TTL IAM
All topology nodesPrivate registry for CUDA/NCCL/runtime; cosign; admission blocks unsigned; provenance on promote (L12/L13)
# Sketch: topology floor — fast path, narrow trust
apiVersion: policy.platform/v1
kind: GpuTopologyGate
metadata:
  name: topo-floor
spec:
  match:
    nodeSelectorKeys: ["nvlink-domain", "rdma-rail-count"]
  require:
    runtimeClass: nvidia
    imageDigestPin: true
    cosignVerify: true
    podSecurity: restricted
    hostNetwork: false
    rdmaViaNADOrDevicePlugin: true
    capabilitiesAllowed: ["IPC_LOCK"]
    networkPolicy: deny-by-default
    trainFabricNetPolIsolation: true
    weightBucketPublicAccess: false
    cmek: true
    workloadIdentity: true
    forbidNodeWideObjectIAM: true
    hfTokensInImages: false

Punchline: GPUDirect and NVLink are performance features. IAM, admission, NetPol, and signed images are what stop a topology node from becoming a weight exfil or DMA trampoline — fabric labels alone are not a security boundary.

Interview soundbites

  • “GPUs without topology are half a capacity plan.”
  • “Read nvidia-smi topo — NVSwitch domains aren’t marketing.”
  • “eth0 is for the CNI; NCCL rides RDMA rails via Multus/NAD.”
  • “GPUDirect skips the host bounce; peermem + GDR level make or break busbw.”
  • “NCCL_DEBUG=INFO before you blame PyTorch.”
  • “Anti-affinity that spreads train ranks across AZs is a self-inflicted 10×.”
  • “Pack NVLink domains; gang-schedule so you don’t half-place a ring.”
  • “RoCE needs PFC/ECN/MTU discipline — GPU ops owns the fabric story with netops.”
  • “CAP_IPC_LOCK ≠ privileged ≠ hostNetwork.”
  • “Never widen node IAM because the node has GDR.”
  • “Train fabric and inference fabric are different trust zones.”
  • “Next: gang, Kueue/Volcano, fair-share — topology-aware admission at queue time.”

How this maps to the JD

Hyperscale + AI/ML platform JDs that mention GPU fleets, distributed training, and high-speed interconnects are asking whether you can operate the fabric under the GPUs: NVLink/NVSwitch, multi-NIC RDMA, GPUDirect, NCCL, and topology-aware placement — with the same security bar as any other cell. This lesson extends L18 (pools) with L8/L9 (CNI, multi-NIC, NetPol), L5/L6 (cloud fabric), L11–L14 (admission, supply chain, weights, deploy isolation), and L1/L4 (fleet packing instincts). Fuel: wafer-ai/gpu-perf-engineering-resources (opens in a new tab). Next up — Lesson 20: training/inference scheduling — gang, Kueue/Volcano, fair-share queues — so topology constraints become queueable contracts, not hope.

Watch (talks & demos)

Real talks — watch after Failures & fixes.

Lightning Talk: Mind the Topology — Smarter Scheduling for AI Workloads on Kubernetes (Roman Baron)

Why default scheduling that only sees GPU counts places distributed jobs across racks/blocks with unequal bandwidth — and how topology-aware placement keeps collectives on the fast path. Direct interview fuel for “silent 10× after reschedule.”

Open on YouTube (opens in a new tab)

NCCL Explained: How NVIDIA's GPU Communication Library Powers Distributed Deep Learning

Intra-node P2P over NVLink vs inter-node RDMA / GPUDirect, rings vs trees, and protocol choices — the mental model behind NCCL_DEBUG graphs and busbw cliffs.

Open on YouTube (opens in a new tab)

Keynote: Community-Driven Evolution of the Kubernetes Network Driver Model / DRA for AI Networking (Lionel Jouin & Antonio Ojea)

DRA as the common language for GPUs and secondary/high-speed networks — why Multus-era attachments are evolving toward topology-aware resource claims for AI fabrics.

Open on YouTube (opens in a new tab)

References

Quick self-check

Out loud, in under two minutes:

  1. How do you tell from the node whether eight GPUs are one NVSwitch domain or PCIe-bridged pairs?
  2. Why does an eth0-only CNI attachment break multi-node NCCL — and what do you attach instead?
  3. What does GPUDirect RDMA change in the data path versus a host bounce buffer?
  4. Name three NCCL env vars you set before declaring a hang “framework bug.”
  5. How can pod anti-affinity produce a silent multi-× slowdown for training?
  6. List four security controls that must still hold on topology/RDMA nodes when jobs pull model weights.