<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Nccl on vishctl</title><link>https://vishctl.dev/tags/nccl/</link><description>Recent content in Nccl on vishctl</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 05 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://vishctl.dev/tags/nccl/index.xml" rel="self" type="application/rss+xml"/><item><title>Kubernetes GPU Platform Engineering: From nvidia-smi to NCCL</title><link>https://vishctl.dev/posts/kubernetes-gpu-platform-engineering/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0800</pubDate><guid>https://vishctl.dev/posts/kubernetes-gpu-platform-engineering/</guid><category>kubernetes</category><category>gpu</category><category>nvidia</category><category>ai-infra</category><category>rdma</category><category>nccl</category><description>A simple, layered guide to GPUs on Kubernetes: GPU Operator, NUMA and PCIe, RDMA, SR-IOV and Multus, NVLink and NCCL, with a troubleshooting question for every layer.</description><content:encoded><![CDATA[<p>Your Pod says <code>Running</code>. Kubernetes says the GPU is allocated. Yet the training job crawls, or vLLM fails the moment it spreads across eight GPUs.</p>
<p>Nothing in Kubernetes looks broken. So where is the problem?</p>
<p>Usually in a layer Kubernetes can&rsquo;t see: the driver, the CPU socket the GPU is wired to, the network card, or the link between GPUs.</p>
<p>This guide climbs those layers in the order your data crosses them, from one GPU in one Pod to a job spread across nodes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">GPU in a Pod → topology inside the node → network between nodes → GPU-to-GPU traffic
</span></span></code></pre></div><p>For each layer you get two things: <strong>what it is, and what to check when it breaks.</strong></p>
<h2 id="gpu-access-first">GPU access first</h2>
<p>Start with the checkpoints that make a GPU usable inside a Pod. This is a troubleshooting checklist; the components do not all sit on one runtime data path.</p>
<figure class="post-screenshot"><a href="https://vishctl.dev/images/posts/kubernetes-gpu-platform/01-gpu-stack-chain.svg">
    <img loading="lazy" src="https://vishctl.dev/images/posts/kubernetes-gpu-platform/01-gpu-stack-chain.svg"
         alt="Eight GPU access checkpoints from hardware to the CUDA application, highlighted in sequence"/> </a><figcaption>
            <p>Eight checkpoints from hardware visibility to application performance. Start at the first failing check.</p>
        </figcaption>
</figure>

<p>Once the application can use the GPU, the question changes: can it move data fast enough? That takes us beyond this checklist into CPU, memory, GPU, and network locality.</p>
<h2 id="1-nvidia-gpu-operator-and-gpu-scheduling">1. NVIDIA GPU Operator and GPU scheduling</h2>
<h3 id="the-pieces">The pieces</h3>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Piece</th>
            <th scope="col">What it does</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><strong>NVIDIA driver</strong></td>
            <td>Lets the OS talk to the physical GPU</td>
        </tr>
        <tr>
            <td><strong>Container Toolkit</strong></td>
            <td>Lets the container runtime expose the allocated GPU and NVIDIA libraries inside a container</td>
        </tr>
        <tr>
            <td><strong>Device Plugin</strong></td>
            <td>Discovers GPUs and tells kubelet about them</td>
        </tr>
        <tr>
            <td><strong>GPU Feature Discovery</strong></td>
            <td>Adds node labels describing the GPU</td>
        </tr>
        <tr>
            <td><strong>GPU Operator</strong></td>
            <td>Installs and manages most of the above</td>
        </tr>
    </tbody>
  </table>
</div>
<p>Think of it as a hand-off:</p>
<figure class="mermaid-figure">
  <div class="mermaid">flowchart LR
    A["Host GPU works<br/>(nvidia-smi)"] e1@--> B["Container Toolkit"]
    B e2@--> C["Container can<br/>access its GPU"]
    A e3@--> D["Device Plugin"]
    D e4@--> E["kubelet advertises<br/>nvidia.com/gpu: 8"]
    E e5@--> F["Scheduler can<br/>count GPUs"]
    e1@{ animate: true }
    e2@{ animate: true }
    e3@{ animate: true }
    e4@{ animate: true }
    e5@{ animate: true }
    classDef k8s fill:#13264a,stroke:#38bdf8,color:#e6f6ff;
    classDef gpu fill:#173f59,stroke:#22d3ee,color:#ecfeff;
    classDef net fill:#281d48,stroke:#a78bfa,color:#f1edff;
    classDef host fill:#302410,stroke:#fbbf24,color:#fff3cf;
    class A,C gpu;
    class B,D,E,F k8s;</div>
  <figcaption>Each box hands the GPU to the next one. If a hand-off fails, everything after it fails too.</figcaption>
</figure>

<h3 id="the-driver">The driver</h3>
<p>The OS needs a working NVIDIA driver to talk to the GPU. If <code>nvidia-smi</code> fails, start below Kubernetes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">hardware → PCIe → OS → driver
</span></span></code></pre></div><h3 id="the-device-plugin-and-the-scheduling-request">The device plugin and the scheduling request</h3>
<p>The device plugin exposes GPUs as a Kubernetes <strong>extended resource</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">nvidia.com/gpu: 8
</span></span></code></pre></div><p>Kubelet then reports that capacity to the control plane. A Pod asks for a GPU like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">nvidia.com/gpu</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span></code></pre></div><p>Now the scheduler can choose a node with enough available GPU resources. Kubelet and the device plugin handle allocation of the actual devices on that node.</p>
<h3 id="gpu-model-awareness">GPU model awareness</h3>
<p><code>nvidia.com/gpu</code> is just a count. It does not say H100, H200, or L40S. To target a specific model, use <strong>GPU Feature Discovery</strong> labels with node affinity, so a rule such as &ldquo;requires H200 nodes&rdquo; becomes possible.</p>
<h3 id="the-operator-does-not-schedule">The Operator does not schedule</h3>
<blockquote>
<p>The GPU Operator does not schedule GPUs.</p>
</blockquote>
<p>It installs and manages the software stack (driver, Container Toolkit, Device Plugin, Feature Discovery, monitoring, MIG-related parts, depending on configuration). The Kubernetes scheduler still does the scheduling, using the resources the device plugin advertises.</p>
<h3 id="gpu-taints">GPU taints</h3>
<p>You may taint GPU nodes so ordinary workloads stay off them. GPU workloads then need:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">GPU request + toleration + affinity or node selection
</span></span></code></pre></div><p>A toleration means <em>allowed</em>, not <em>forced onto that node</em>.</p>
<h3 id="one-pod-many-nodes">One Pod, many nodes?</h3>
<p>No. Ordinary Kubernetes scheduling does not split a Pod across nodes. A Pod asking for 8 GPUs needs all 8 on <strong>one</strong> node. Multi-node workloads use several Pods coordinated by a framework or operator.</p>
<h3 id="mig">MIG</h3>
<p>MIG (Multi-Instance GPU) splits a supported NVIDIA GPU into isolated instances. It helps with smaller workloads, better utilization, and isolation. A job that needs the whole device may still prefer the full GPU.</p>
<h3 id="troubleshooting-tree">Troubleshooting tree</h3>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Symptom</th>
            <th scope="col">What to check</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>nvidia-smi</code> fails</td>
            <td>Hardware, PCIe, driver, OS</td>
        </tr>
        <tr>
            <td><code>nvidia-smi</code> works, <code>nvidia.com/gpu</code> missing</td>
            <td>Device plugin, kubelet registration, GPU Operator integration, runtime integration</td>
        </tr>
        <tr>
            <td><code>nvidia.com/gpu</code> exists, Pod <code>Pending</code></td>
            <td>GPU capacity, CPU/RAM, taints, node affinity, topology, storage, cordon, other scheduler constraints</td>
        </tr>
        <tr>
            <td>Pod scheduled, no GPU inside the container</td>
            <td>Container Toolkit, runtime configuration, device allocation, driver compatibility</td>
        </tr>
        <tr>
            <td>GPU visible, but NCCL or vLLM fails</td>
            <td>CUDA libraries, NCCL, topology, distributed network, RDMA</td>
        </tr>
    </tbody>
  </table>
</div>
<p>Also watch more than utilization: GPU memory, temperature, power, health or errors, and allocation.</p>
<h3 id="burn-this-into-memory">Burn this into memory</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">nvidia-smi          = host sees GPU
</span></span><span class="line"><span class="cl">Device Plugin       = Kubernetes sees GPU
</span></span><span class="line"><span class="cl">nvidia.com/gpu      = scheduler can account for GPU
</span></span><span class="line"><span class="cl">Container Toolkit   = container can access GPU
</span></span><span class="line"><span class="cl">GPU Operator        = manages the NVIDIA Kubernetes GPU software lifecycle
</span></span></code></pre></div><h2 id="2-numa-and-pcie-topology">2. NUMA and PCIe topology</h2>
<p>A visible GPU is only the first check. Next, look at the route data takes to reach it.</p>
<h3 id="what-is-numa">What is NUMA?</h3>
<p>NUMA means <strong>Non-Uniform Memory Access</strong>. Many large servers have several CPU sockets, each with its own local memory. A socket can also contain more than one NUMA node; the two-socket example below is a simplified layout. Reading local memory is generally faster than reading memory attached to the other socket.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NUMA Node 0                 NUMA Node 1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">CPU Socket 0                CPU Socket 1
</span></span><span class="line"><span class="cl">   │                            │
</span></span><span class="line"><span class="cl">Local RAM                    Local RAM
</span></span></code></pre></div><h3 id="why-it-matters-for-ai">Why it matters for AI</h3>
<p>GPUs and NICs hang off a specific socket too:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">CPU Socket 0                 CPU Socket 1
</span></span><span class="line"><span class="cl"> ├── RAM 0                    ├── RAM 1
</span></span><span class="line"><span class="cl"> ├── GPU 0                    ├── GPU 2
</span></span><span class="line"><span class="cl"> ├── GPU 1                    ├── GPU 3
</span></span><span class="line"><span class="cl"> └── NIC 0                    └── NIC 1
</span></span></code></pre></div><p>A good path keeps everything together: <code>CPU0 + RAM0 + GPU0 + NIC0</code>. A bad path makes data cross sockets, adding latency and eating inter-socket bandwidth.</p>
<figure class="post-screenshot"><a href="https://vishctl.dev/images/posts/kubernetes-gpu-platform/02-numa-paths.svg">
    <img loading="lazy" src="https://vishctl.dev/images/posts/kubernetes-gpu-platform/02-numa-paths.svg"
         alt="Two CPU sockets. A short cyan path connects GPU 0 and NIC 0 on socket 0. A long amber path loops from GPU 0 across the inter-socket link to NIC 1."/> </a><figcaption>
            <p>Same GPU, same NIC count. The route is what changes.</p>
        </figcaption>
</figure>

<h3 id="pcie">PCIe</h3>
<p>PCIe is the general-purpose high-speed connection between the CPU and devices such as GPUs, NICs, and NVMe drives.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">CPU
</span></span><span class="line"><span class="cl"> ├── PCIe → GPU
</span></span><span class="line"><span class="cl"> ├── PCIe → NIC
</span></span><span class="line"><span class="cl"> └── PCIe → NVMe
</span></span></code></pre></div><h3 id="kubernetes-and-topology">Kubernetes and topology</h3>
<p>With the conventional device-plugin setup described here, the default scheduler mainly sees resource counts, rather than the socket of each GPU. Fine-grained locality is handled <strong>on the node</strong>, by kubelet and its configured resource managers:</p>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Piece</th>
            <th scope="col">Role</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><strong>Topology Manager</strong></td>
            <td>Collects hints from resource managers so CPU and device allocations line up when possible</td>
        </tr>
        <tr>
            <td><strong>CPU Manager</strong></td>
            <td>With the <code>static</code> policy, containers in Guaranteed Pods with whole-number CPU requests can get exclusive CPUs</td>
        </tr>
        <tr>
            <td><strong>Memory Manager</strong></td>
            <td>With the <code>Static</code> policy, provides NUMA-aware memory allocation for Guaranteed Pods</td>
        </tr>
        <tr>
            <td><strong>Guaranteed QoS</strong></td>
            <td>CPU and memory requests equal their limits in every container; required for exclusive CPU allocation</td>
        </tr>
    </tbody>
  </table>
</div>
<p>Guaranteed QoS alone does not align resources. The node needs the relevant manager policies and device topology hints. A strict Topology Manager policy can reject a Pod on the node if its locality requirements cannot be met. See the <a href="https://kubernetes.io/docs/concepts/resource-management/resource-managers/">Kubernetes resource manager guide</a> and <a href="https://kubernetes.io/docs/tasks/administer-cluster/topology-manager/">Topology Manager policies</a>.</p>
<p>Two more places locality shows up:</p>
<ul>
<li><strong>SR-IOV:</strong> a VF inherits locality from its parent NIC. If the GPU is near NUMA 0 but the VF is near NUMA 1, traffic crosses sockets.</li>
<li><strong>GPUDirect RDMA:</strong> it benefits from a sensible GPU and NIC PCIe/NUMA layout.</li>
</ul>
<h3 id="useful-commands">Useful commands</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">lscpu
</span></span><span class="line"><span class="cl">numactl --hardware
</span></span><span class="line"><span class="cl">nvidia-smi topo -m
</span></span></code></pre></div><p><code>nvidia-smi topo -m</code> prints a matrix of how every GPU and NIC connects to the others. It is the quickest way to see the real topology.</p>
<h3 id="performance-troubleshooting-order">Performance troubleshooting order</h3>
<figure class="mermaid-figure">
  <div class="mermaid">flowchart TD
    A["GPU health"] e1@--> B["GPU topology"]
    B e2@--> C["CPU affinity"]
    C e3@--> D["NUMA"]
    D e4@--> E["PCIe"]
    E e5@--> F["NIC locality"]
    F e6@--> G["NVLink / NVSwitch"]
    G e7@--> H["NCCL"]
    H e8@--> I["RDMA"]
    I e9@--> J["Network fabric"]
    e1@{ animate: true }
    e2@{ animate: true }
    e3@{ animate: true }
    e4@{ animate: true }
    e5@{ animate: true }
    e6@{ animate: true }
    e7@{ animate: true }
    e8@{ animate: true }
    e9@{ animate: true }
    classDef k8s fill:#13264a,stroke:#38bdf8,color:#e6f6ff;
    classDef gpu fill:#173f59,stroke:#22d3ee,color:#ecfeff;
    classDef net fill:#281d48,stroke:#a78bfa,color:#f1edff;
    classDef host fill:#302410,stroke:#fbbf24,color:#fff3cf;
    class A,B,G,H gpu;
    class C,D,E host;
    class F,I,J net;</div>
  <figcaption>A useful investigation order, rather than a strict dependency chain. Compare topology and transport evidence as you go.</figcaption>
</figure>

<h3 id="burn-this-into-memory-1">Burn this into memory</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NUMA              = CPU/memory locality
</span></span><span class="line"><span class="cl">PCIe              = device connection path
</span></span><span class="line"><span class="cl">Topology Manager  = coordinates resource locality on the node
</span></span><span class="line"><span class="cl">CPU Manager       = can assign exclusive CPUs
</span></span><span class="line"><span class="cl">Memory Manager    = can align memory with NUMA locality
</span></span></code></pre></div><h2 id="3-rdma-infiniband-roce-connectx">3. RDMA, InfiniBand, RoCE, ConnectX</h2>
<p>Locality matters within a server. Once a job spans servers, the network becomes part of the GPU communication path too.</p>
<h3 id="what-is-rdma">What is RDMA?</h3>
<p>RDMA means <strong>Remote Direct Memory Access</strong>. It lets one server read or write another server&rsquo;s registered memory with very little CPU and kernel involvement.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Server A memory ↔ RDMA ↔ Server B memory
</span></span></code></pre></div><p>The key point:</p>
<blockquote>
<p>RDMA is a communication capability, not a network type by itself.</p>
</blockquote>
<h3 id="why-distributed-ai-benefits-from-it">Why distributed AI benefits from it</h3>
<p>Distributed GPU jobs move huge amounts of data. If the network is slow, the GPU waits, and an expensive accelerator sits idle. This matters for tensor parallelism, training collectives, model parallelism, and large inference workloads.</p>
<h3 id="the-names">The names</h3>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Name</th>
            <th scope="col">One line</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><strong>RDMA</strong></td>
            <td>The communication model</td>
        </tr>
        <tr>
            <td><strong>InfiniBand</strong></td>
            <td>A purpose-built HPC fabric that supports RDMA</td>
        </tr>
        <tr>
            <td><strong>RoCE</strong></td>
            <td>RDMA over Converged Ethernet: RDMA on an Ethernet network</td>
        </tr>
        <tr>
            <td><strong>ConnectX</strong></td>
            <td>NVIDIA&rsquo;s family of high-performance network adapters</td>
        </tr>
        <tr>
            <td><strong>NCCL</strong></td>
            <td>NVIDIA Collective Communications Library, the software that moves GPU data</td>
        </tr>
    </tbody>
  </table>
</div>
<h3 id="gpudirect-rdma">GPUDirect RDMA</h3>
<p>Without GPUDirect RDMA, data is staged through host memory on both servers:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Server A: GPU memory → host memory → RDMA NIC
</span></span><span class="line"><span class="cl">                                       ↓
</span></span><span class="line"><span class="cl">                              InfiniBand / RoCE
</span></span><span class="line"><span class="cl">                                       ↓
</span></span><span class="line"><span class="cl">Server B: GPU memory ← host memory ← RDMA NIC
</span></span></code></pre></div><p>With GPUDirect RDMA, the sending NIC reads GPU memory directly and the receiving NIC writes directly into the other GPU&rsquo;s memory. Both paths below use RDMA across the network; the difference is whether they need host-memory staging.</p>
<figure class="mermaid-figure">
  <div class="mermaid">flowchart LR
    subgraph S["Without GPUDirect RDMA"]
        direction TB
        G1["Server A<br/>GPU memory"] e1@-->|GPU-to-host copy| H1["Server A<br/>Host memory"]
        H1 e2@--> N1["Server A<br/>RDMA NIC"]
        N1 e3@--> F1["InfiniBand / RoCE fabric"]
        F1 e4@--> N2["Server B<br/>RDMA NIC"]
        N2 e5@--> H2["Server B<br/>Host memory"]
        H2 e6@-->|Host-to-GPU copy| G2["Server B<br/>GPU memory"]
    end
    subgraph D["With GPUDirect RDMA"]
        direction TB
        G3["Server A<br/>GPU memory"] e7@-->|Direct PCIe access| N3["Server A<br/>RDMA NIC"]
        N3 e8@--> F2["InfiniBand / RoCE fabric"]
        F2 e9@--> N4["Server B<br/>RDMA NIC"]
        N4 e10@-->|Direct PCIe access| G4["Server B<br/>GPU memory"]
    end
    e1@{ animate: true }
    e2@{ animate: true }
    e3@{ animate: true }
    e4@{ animate: true }
    e5@{ animate: true }
    e6@{ animate: true }
    e7@{ animate: true }
    e8@{ animate: true }
    e9@{ animate: true }
    e10@{ animate: true }
    S ~~~ D
    classDef gpu fill:#173f59,stroke:#22d3ee,color:#ecfeff;
    classDef net fill:#281d48,stroke:#a78bfa,color:#f1edff;
    classDef host fill:#302410,stroke:#fbbf24,color:#fff3cf;
    class G1,G2,G3,G4 gpu;
    class H1,H2 host;
    class N1,N2,N3,N4,F1,F2 net;</div>
  <figcaption>Full path from Server A&#39;s GPU to Server B&#39;s GPU. Left: staging through host memory on both servers. Right: GPUDirect RDMA avoids those copies.</figcaption>
</figure>

<p>Topology still matters here. A GPU and NIC far apart lose much of the benefit.</p>
<h3 id="roce-needs-a-careful-network">RoCE needs a careful network</h3>
<p>RDMA over Ethernet only behaves well on a carefully engineered network. Terms worth recognizing: <strong>PFC</strong>, <strong>ECN</strong>, <strong>DCQCN</strong>. The idea is congestion and loss management so RDMA stays fast and predictable.</p>
<h3 id="pod-networking-for-ai">Pod networking for AI</h3>
<p>One common design for a distributed AI Pod uses two interfaces:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">eth0 = normal Kubernetes networking
</span></span><span class="line"><span class="cl">net1 = high-performance RDMA interface
</span></span></code></pre></div><p>One way to build that second path is Multus + SR-IOV + an RDMA-capable VF. Other deployments use shared RDMA devices or host networking. The next section explains the VF design; section 5 shows how NCCL uses the available paths.</p>
<h3 id="troubleshooting-distributed-performance">Troubleshooting distributed performance</h3>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Symptom</th>
            <th scope="col">Think about</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Single node works, multi-node is bad</td>
            <td>NCCL transport, RDMA interfaces, ConnectX, link speed, errors and drops, congestion, GPUDirect, NUMA/PCIe topology, fabric</td>
        </tr>
        <tr>
            <td>Normal Pod network works, distributed GPU communication fails</td>
            <td>The secondary RDMA path may be broken while ordinary CNI is fine: VF allocation, RDMA device visibility, ConnectX health, RoCE or InfiniBand, NCCL</td>
        </tr>
    </tbody>
  </table>
</div>
<h3 id="burn-this-into-memory-2">Burn this into memory</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">RDMA            = low-overhead remote memory communication
</span></span><span class="line"><span class="cl">InfiniBand      = HPC fabric supporting RDMA
</span></span><span class="line"><span class="cl">RoCE            = RDMA over Ethernet
</span></span><span class="line"><span class="cl">ConnectX        = high-performance adapter
</span></span><span class="line"><span class="cl">GPUDirect RDMA  = efficient GPU memory ↔ RDMA NIC communication
</span></span></code></pre></div><h2 id="4-sr-iov-multus-dpu-smartnic">4. SR-IOV, Multus, DPU, SmartNIC</h2>
<h3 id="sr-iov">SR-IOV</h3>
<p>SR-IOV means <strong>Single Root I/O Virtualization</strong>. One physical PCIe device exposes many hardware-backed virtual functions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Physical NIC
</span></span><span class="line"><span class="cl">    │
</span></span><span class="line"><span class="cl">    ├── PF
</span></span><span class="line"><span class="cl">    ├── VF1
</span></span><span class="line"><span class="cl">    ├── VF2
</span></span><span class="line"><span class="cl">    ├── VF3
</span></span><span class="line"><span class="cl">    └── VF4
</span></span></code></pre></div><ul>
<li><strong>PF (Physical Function):</strong> the full physical function, visible to the host. It configures SR-IOV and creates VFs.</li>
<li><strong>VF (Virtual Function):</strong> a lightweight PCIe function that can be handed to a workload.</li>
</ul>
<p>A NIC exposes a <strong>finite</strong> number of VFs, and Kubernetes treats them as schedulable resources.</p>
<h3 id="the-kubernetes-sr-iov-stack">The Kubernetes SR-IOV stack</h3>
<figure class="mermaid-figure">
  <div class="mermaid">flowchart TD
    A["NIC PF"] e1@--> B["VFs"]
    B e2@--> C["SR-IOV Network Device Plugin"]
    C e3@--> D["Kubernetes extended resources"]
    D e4@--> E["Scheduler selects a node<br/>with VF capacity"]
    E e5@--> K["kubelet + Device Plugin<br/>allocate a VF"]
    K --> F["SR-IOV CNI"]
    F e6@--> G["VF configured in the Pod network namespace"]
    e1@{ animate: true }
    e2@{ animate: true }
    e3@{ animate: true }
    e4@{ animate: true }
    e5@{ animate: true }
    e6@{ animate: true }
    classDef k8s fill:#13264a,stroke:#38bdf8,color:#e6f6ff;
    classDef gpu fill:#173f59,stroke:#22d3ee,color:#ecfeff;
    classDef net fill:#281d48,stroke:#a78bfa,color:#f1edff;
    classDef host fill:#302410,stroke:#fbbf24,color:#fff3cf;
    class A,B,G net;
    class C,D,E,K,F k8s;</div>
  <figcaption>Two different jobs: the Device Plugin allocates the VF, the SR-IOV CNI attaches it.</figcaption>
</figure>

<p>Do not mix up the two:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Device Plugin = discover / advertise / allocate the device resource
</span></span><span class="line"><span class="cl">SR-IOV CNI    = attach / configure the network interface
</span></span></code></pre></div><h3 id="multus">Multus</h3>
<p>Multus is a <strong>meta-CNI</strong>. It lets a Pod have more than one network attachment.</p>
<figure class="mermaid-figure">
  <div class="mermaid">flowchart TD
    P["Pod"] e1@--> E0["eth0"]
    P e2@--> N1["net1"]
    E0 e3@--> C["Cilium"]
    N1 e4@--> S["SR-IOV CNI"]
    C e5@--> K["Normal K8s networking<br/>API, DNS, Services"]
    S e6@--> R["VF / RDMA<br/>high-performance traffic"]
    e1@{ animate: true }
    e2@{ animate: true }
    e3@{ animate: true }
    e4@{ animate: true }
    e5@{ animate: true }
    e6@{ animate: true }
    classDef k8s fill:#13264a,stroke:#38bdf8,color:#e6f6ff;
    classDef gpu fill:#173f59,stroke:#22d3ee,color:#ecfeff;
    classDef net fill:#281d48,stroke:#a78bfa,color:#f1edff;
    classDef host fill:#302410,stroke:#fbbf24,color:#fff3cf;
    class P,E0,C,K k8s;
    class N1,S,R net;</div>
  <figcaption>Multus orchestrates the attachments. It is not the fast datapath itself.</figcaption>
</figure>

<p>In this design, the traffic is separated like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">eth0 = API / DNS / Services / management traffic
</span></span><span class="line"><span class="cl">net1 = high-performance RDMA traffic
</span></span></code></pre></div><h3 id="sr-iov-is-not-rdma">SR-IOV is not RDMA</h3>
<p>SR-IOV gives a Pod a direct-ish, hardware-backed virtual interface. RDMA needs a suitable NIC, firmware and driver, RDMA stack, and fabric. They often work together, but they are different things.</p>
<h3 id="troubleshooting">Troubleshooting</h3>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Symptom</th>
            <th scope="col">Think about</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Pod <code>Pending</code>: <code>Insufficient &lt;sriov-resource&gt;</code></td>
            <td>No allocatable VF, a resource advertisement problem, device plugin issue, or scheduler constraints</td>
        </tr>
        <tr>
            <td>Pod scheduled but no secondary interface</td>
            <td>Multus, <code>NetworkAttachmentDefinition</code>, SR-IOV CNI, configuration</td>
        </tr>
        <tr>
            <td><code>net1</code> exists but RDMA fails</td>
            <td>VF driver, RDMA device exposure, RoCE or InfiniBand, NCCL, fabric</td>
        </tr>
        <tr>
            <td>Device plugin unhealthy</td>
            <td>VF resources may vanish from Kubernetes accounting</td>
        </tr>
        <tr>
            <td>PF down</td>
            <td>Many VFs can be affected at once</td>
        </tr>
    </tbody>
  </table>
</div>
<h3 id="smartnic-vs-dpu">SmartNIC vs DPU</h3>
<ul>
<li><strong>SmartNIC:</strong> a programmable, high-performance NIC that can offload networking, security, or storage work.</li>
<li><strong>DPU:</strong> a more capable infrastructure processor: NIC + onboard CPU cores + offload engines. NVIDIA BlueField is a common example.</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ConnectX   = high-performance NIC
</span></span><span class="line"><span class="cl">BlueField  = DPU with networking + onboard compute/offload
</span></span></code></pre></div><p>These are optional infrastructure components in this story. A DPU does not replace the GPU:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">GPU = model compute
</span></span><span class="line"><span class="cl">DPU = infrastructure / network / security / storage offload
</span></span></code></pre></div><h3 id="burn-this-into-memory-3">Burn this into memory</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Multus         = multiple interfaces
</span></span><span class="line"><span class="cl">SR-IOV         = hardware virtualization through PF/VFs
</span></span><span class="line"><span class="cl">Device Plugin  = advertise/allocate VF
</span></span><span class="line"><span class="cl">SR-IOV CNI     = attach/configure VF
</span></span><span class="line"><span class="cl">ConnectX       = high-speed NIC
</span></span><span class="line"><span class="cl">BlueField      = NIC + onboard compute/offload
</span></span><span class="line"><span class="cl">RDMA           = communication capability
</span></span></code></pre></div><h2 id="5-nvlink-nvswitch-nccl">5. NVLink, NVSwitch, NCCL</h2>
<p>We now have GPU access, local topology, and a path between servers. NCCL brings those pieces together for communication between GPUs.</p>
<p>Large AI workloads need GPUs to swap data quickly: activations, partial results, gradients, tensor-parallel data.</p>
<h3 id="the-links">The links</h3>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Piece</th>
            <th scope="col">What it is</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><strong>PCIe</strong></td>
            <td>General-purpose device interconnect</td>
        </tr>
        <tr>
            <td><strong>NVLink</strong></td>
            <td>NVIDIA&rsquo;s high-bandwidth interconnect for supported GPUs, faster and more specialized than PCIe</td>
        </tr>
        <tr>
            <td><strong>NVSwitch</strong></td>
            <td>A switching fabric that connects many NVLink GPUs. It is not an Ethernet switch</td>
        </tr>
        <tr>
            <td><strong>NCCL</strong></td>
            <td>The software library that does the actual GPU communication</td>
        </tr>
    </tbody>
  </table>
</div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">GPU0 ─┐
</span></span><span class="line"><span class="cl">GPU1 ─┼── NVSwitch fabric
</span></span><span class="line"><span class="cl">GPU2 ─┼
</span></span><span class="line"><span class="cl">GPU3 ─┘
</span></span></code></pre></div><h3 id="nccl-collectives">NCCL collectives</h3>
<p>NCCL provides optimized group operations:</p>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Operation</th>
            <th scope="col">What happens</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><strong>AllReduce</strong></td>
            <td>Everyone contributes values, everyone receives the reduced result</td>
        </tr>
        <tr>
            <td><strong>AllGather</strong></td>
            <td>Everyone gathers everyone&rsquo;s pieces</td>
        </tr>
        <tr>
            <td><strong>ReduceScatter</strong></td>
            <td>Data is reduced, then split across participants</td>
        </tr>
        <tr>
            <td><strong>Broadcast</strong></td>
            <td>One participant sends data to all</td>
        </tr>
    </tbody>
  </table>
</div>
<figure class="post-screenshot"><a href="https://vishctl.dev/images/posts/kubernetes-gpu-platform/03-allreduce-ring.svg">
    <img loading="lazy" src="https://vishctl.dev/images/posts/kubernetes-gpu-platform/03-allreduce-ring.svg"
         alt="Four GPUs in a ring holding 1, 2, 3 and 4. Dots circulate between them, then every GPU shows the result 10."/> </a><figcaption>
            <p>Sum AllReduce: each GPU contributes a value and every GPU gets 10. This illustrates the result, not the full ring algorithm.</p>
        </figcaption>
</figure>

<h3 id="which-path-does-nccl-use">Which path does NCCL use?</h3>
<p>NCCL sits above the physical paths and selects transports based on topology, support, and configuration:</p>
<ul>
<li><strong>Same server:</strong> NVLink, NVSwitch, or PCIe, depending on topology.</li>
<li><strong>Across servers, with GPUDirect RDMA:</strong> GPU → RDMA NIC (such as ConnectX) → InfiniBand or RoCE → RDMA NIC → GPU.</li>
<li><strong>Fallback paths:</strong> RDMA can use host-memory staging when direct GPU access is unavailable, and NCCL can use IP sockets when the RDMA transport is unavailable or disabled. A working job does not prove it is using the fast path.</li>
</ul>
<figure class="mermaid-figure">
  <div class="mermaid">flowchart TD
    N["NCCL"] e1@--> I["Inside one server"]
    N e2@--> X["Across servers"]
    I e3@--> L["NVLink / NVSwitch / PCIe"]
    X e4@--> G["GPUDirect RDMA"]
    G e5@--> C1["ConnectX"]
    C1 e6@--> F["InfiniBand / RoCE"]
    F e7@--> C2["ConnectX"]
    C2 e8@--> G2["Remote GPU"]
    e1@{ animate: true }
    e2@{ animate: true }
    e3@{ animate: true }
    e4@{ animate: true }
    e5@{ animate: true }
    e6@{ animate: true }
    e7@{ animate: true }
    e8@{ animate: true }
    classDef k8s fill:#13264a,stroke:#38bdf8,color:#e6f6ff;
    classDef gpu fill:#173f59,stroke:#22d3ee,color:#ecfeff;
    classDef net fill:#281d48,stroke:#a78bfa,color:#f1edff;
    classDef host fill:#302410,stroke:#fbbf24,color:#fff3cf;
    class N,I,X k8s;
    class L,G,G2 gpu;
    class C1,F,C2 net;</div>
  <figcaption>The intended fast paths. Verify the selected transport in NCCL logs; fallback paths can also work.</figcaption>
</figure>

<p>Set <code>NCCL_DEBUG=INFO</code> on a diagnostic run and inspect the selected network transport. Compare a collective benchmark such as <a href="https://github.com/NVIDIA/nccl-tests">nccl-tests</a> on one node and across nodes with the same GPU count and message sizes. The <a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/env.html">NCCL environment variable reference</a> describes transport controls and debug logging.</p>
<h3 id="vllm-and-tensor-parallelism">vLLM and tensor parallelism</h3>
<p>Tensor-parallel execution needs constant GPU-to-GPU communication. With a weak interconnect, the model becomes <strong>communication-bound</strong>: the GPUs have capacity, but they spend their time waiting on each other.</p>
<h3 id="narrow-it-down-by-scale">Narrow it down by scale</h3>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">What works</th>
            <th scope="col">What fails</th>
            <th scope="col">Think about</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Nothing</td>
            <td>One GPU</td>
            <td>GPU, driver, CUDA, app</td>
        </tr>
        <tr>
            <td>One GPU</td>
            <td>8 GPUs, same node</td>
            <td>NCCL, NVLink/NVSwitch, PCIe/topology</td>
        </tr>
        <tr>
            <td>8 GPUs, same node</td>
            <td>Multi-node</td>
            <td>NCCL, RDMA, ConnectX, RoCE or InfiniBand, fabric</td>
        </tr>
    </tbody>
  </table>
</div>
<p>This is the trap to remember:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Pod Running  ✓
</span></span><span class="line"><span class="cl">GPU allocated ✓
</span></span><span class="line"><span class="cl">AI performance still terrible
</span></span></code></pre></div><p>Kubernetes can look perfectly healthy while the bottleneck sits in the communication path below the application.</p>
<h3 id="burn-this-into-memory-4">Burn this into memory</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">PCIe            = general device interconnect
</span></span><span class="line"><span class="cl">NVLink          = high-bandwidth NVIDIA GPU/device link
</span></span><span class="line"><span class="cl">NVSwitch        = switching fabric for NVLink GPUs
</span></span><span class="line"><span class="cl">NCCL            = optimized GPU collective communication library
</span></span><span class="line"><span class="cl">ConnectX        = high-performance NIC
</span></span><span class="line"><span class="cl">RDMA            = low-overhead remote memory communication
</span></span><span class="line"><span class="cl">GPUDirect RDMA  = efficient GPU ↔ NIC path
</span></span></code></pre></div><h2 id="the-one-page-version">The one-page version</h2>
<p>When a GPU workload misbehaves, walk up the layers and stop at the first one that fails.</p>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Layer</th>
            <th scope="col">Question</th>
            <th scope="col">First tool</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Driver</td>
            <td>Does the host see the GPU?</td>
            <td><code>nvidia-smi</code></td>
        </tr>
        <tr>
            <td>Device Plugin</td>
            <td>Does Kubernetes see it?</td>
            <td>Node <code>allocatable</code>: <code>nvidia.com/gpu</code></td>
        </tr>
        <tr>
            <td>Container Toolkit</td>
            <td>Can the container use it?</td>
            <td><code>nvidia-smi</code> inside the Pod</td>
        </tr>
        <tr>
            <td>Topology</td>
            <td>Are GPU, CPU, and NIC close together?</td>
            <td><code>nvidia-smi topo -m</code>, <code>numactl --hardware</code></td>
        </tr>
        <tr>
            <td>Multus + SR-IOV</td>
            <td>Does the Pod have <code>net1</code>?</td>
            <td><code>ip a</code> in the Pod</td>
        </tr>
        <tr>
            <td>RDMA</td>
            <td>Does the fast path work?</td>
            <td><code>rdma link</code>, <code>ibv_devinfo</code>, fabric counters</td>
        </tr>
        <tr>
            <td>NCCL</td>
            <td>Do the GPUs talk fast?</td>
            <td>NCCL logs and tests, single node then multi-node</td>
        </tr>
    </tbody>
  </table>
</div>
<p>The GPU is only one part of the platform. Most of the hard problems live in the paths between the parts.</p>
<p><strong>Related posts:</strong> to practice GPU scheduling without any hardware, try the <a href="https://vishctl.dev/posts/simulate-gpu-vllm-llm-d-on-laptop/">laptop GPU simulation lab</a>.</p>
]]></content:encoded></item></channel></rss>