Part II - GPU platform
GPU Nodes as a Platform Layer
Lesson 05 got the model weights into the pod — the multi-gigabyte tensors are now sitting on a volume the container can read. But weights are inert without a chip to run them on, and a freshly provisioned GPU node does not announce itself to Kubernetes the way a CPU does. Plain Kubernetes counts CPU and memory natively; it has no built-in idea of what a GPU is, how many a node has, or whether one is already in use. This lesson is about the platform plumbing that turns a bare GPU box into a node the scheduler can target — and the sharing modes that decide whether one physical card serves one tenant or seven. Get this layer wrong and your pod lands on a GPU node, starts, and then fails the moment it asks for CUDA.
New capability: Understand how a GPU becomes a schedulable Kubernetes resource — the driver/discovery/plugin stack the platform must install first — and how to choose a sharing mode (whole-GPU, MIG, time-slicing, DRA) by trading isolation against utilization.
nvidia.com/gpu: 1, and show exactly what information that single number throws away. (4) Compare the four sharing/isolation modes with a worked utilization example and an interactive packing widget. (5) Failure modes, a platform checklist, and the hand-off to multi-GPU topology.1 · The GPU is a stack, not a count
When you write resources: { limits: { cpu: "2" } }, Kubernetes already understands CPU intimately — the kubelet measures it, cgroups enforce it, the scheduler bin-packs it. A GPU gets none of this for free. The kernel does not know how to talk to the card without a vendor driver; the container runtime does not know how to expose the device files; and the Kubernetes scheduler has no field called "GPU" at all. Every one of those gaps is filled by a separate piece of software, and if any layer is missing the symptom is the same maddening one: the pod schedules fine and then cannot find a GPU.
So the mental model to carry through this lesson is: a GPU is a stack. Driver, runtime, device plugin (or DRA driver), node labels, topology, isolation mode, telemetry, and scheduling policy — each is a distinct layer, and "the GPU works" means all of them are present and agree. The book's Chapter 3 walks this stack precisely because most "my GPU pod doesn't work" incidents are platform incidents, not model incidents. The fix lives below the model server, not in it.
2 · The platform stack, layer by layer
Here is the full pipeline a request for a GPU traverses, from silicon to a process that can call CUDA. Each stage is a real component you install or rely on; the names matter because each is a separate failure point.
/dev/nvidia*) and libraries into containers. Without this the chip is dark.nvidia.com/gpu; the newer DRA driver exposes claim-based requests (move 4).nvidia.com/gpu: 8 (or named MIG profiles, or DRA device classes) and matches pod requests to nodes that have free units.nvidia-smi or initializes CUDA.Two of these names deserve a sharper definition at first use. Node Feature Discovery (NFD) is a Kubernetes add-on that detects hardware and system features on each node and exposes them as node labels; GPU Feature Discovery (GFD) is the GPU-specific companion that adds labels like nvidia.com/gpu.product=H100, nvidia.com/gpu.memory=81920, and nvidia.com/mig.strategy. A device plugin is the kubelet extension mechanism (a gRPC service) by which a vendor advertises non-CPU/memory resources; NVIDIA's device plugin is what makes nvidia.com/gpu a thing the scheduler can count and allocate.
Installing and version-matching all six layers by hand on every node is exactly the "snowflake node" trap. The NVIDIA GPU Operator exists to collapse it: it is a Kubernetes Operator that installs and lifecycle-manages the driver, container toolkit, device plugin, NFD/GFD, and DCGM telemetry (move 5) from a single cluster-level configuration. The platform lesson is that this stack is code — a declarative policy you version and review — not a checklist someone runs once over SSH and forgets.
CUDA error: forward compatibility. Because the GPU Operator pins these together, version skew is the strongest argument for letting it own the stack rather than assembling layers à la carte.3 · The resource model — and what a single number hides
With the device plugin in place, requesting a GPU looks deceptively like requesting CPU. Here is the canonical whole-GPU pod spec:
apiVersion: v1
kind: Pod
metadata:
name: llm-server
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
resources:
limits:
nvidia.com/gpu: 1 # whole GPU; this pod owns the device
Note that GPUs are requested only as a limit, and the count is an integer — you cannot ask for "0.5 GPU" through the basic device plugin. The pod that gets nvidia.com/gpu: 1 owns the entire physical device with full memory and compute isolation. Simple, but the scheduler made its decision knowing only the integer 1. It did not know whether that GPU has 24 GB or 80 GB of HBM, nor whether the node's two GPUs are NVLink-connected. A 70B-parameter model in 16-bit needs roughly 70 × 2 = 140 GB of weights plus KV cache — it will not fit on one 80 GB card, but nvidia.com/gpu: 1 happily schedules it anyway, and the pod OOMs at load time. The number was the wrong unit (move 1) made operational.
Hardware partitioning gives the scheduler richer units. With MIG (Multi-Instance GPU) — NVIDIA's feature that splits one physical GPU into hardware-isolated slices — a single A100 can be carved into up to seven instances, each with its own dedicated memory and compute. Those slices are advertised as named resources, so a pod requests a profile:
resources:
limits:
nvidia.com/mig-1g.10gb: 1 # one MIG slice: ~1/7 compute, 10 GB HBM
Now the resource name itself carries memory and compute size (1g.10gb = one compute slice, 10 GB), so the scheduler is no longer flying blind on capacity. That is the difference: nvidia.com/gpu: 1 says "a GPU, somewhere"; nvidia.com/mig-1g.10gb: 1 says "10 GB of isolated GPU." Topology — which GPUs are NVLink-adjacent — is still not in either request, which is the whole subject of lesson 07.
4 · Sharing modes — utilization against isolation
One H100 can cost on the order of $2–$4 / GPU-hour on a cloud, so leaving it 15% utilized by a small model is burning money. Sharing modes exist to raise utilization — but every form of sharing trades away some isolation, and isolation is the assumption your tenants are quietly relying on. The four modes, defined at first use:
The sharpest distinction to internalize: MIG isolates, time slicing does not. MIG draws hardware fences — separate memory controllers, separate compute partitions — so a misbehaving tenant in one slice physically cannot touch another's memory or steal its cycles. Time slicing just rotates the GPU's attention between processes that all see the same memory; it improves average utilization but provides zero protection, so a single pod that allocates too much HBM can crash every other pod on the device. Use time slicing for cooperative, low-stakes, bursty workloads; never use it where one tenant's bug must not become another tenant's outage.
| Mode | Isolation | Best for | Risk |
|---|---|---|---|
| Whole GPU | Full (own device) | Large models, strict latency SLOs | Low utilization on small jobs — paying for unused HBM/compute |
| MIG | Full (hardware slices) | Many medium jobs needing real isolation on A100/H100 | Fixed profiles can mismatch workload memory; not on all GPUs |
| Time slicing | None (shared memory) | Bursty, cooperative, low-load inference | Noisy neighbors, OOM contagion, latency jitter, fuzzy accounting |
| DRA claims | Depends on claim | Expressive requests as the API matures | Driver/version readiness; operators must debug ResourceClaims |
3g.40gb/2g.20gb profiles lets you place 3–4 isolated tenants, each guaranteed its own ~20 GB and protected from the others, lifting utilization past 70% with no contagion risk. Time slicing could pack even more pods onto the raw 80 GB, but they all share that pool — five 16 GB tenants total 80 GB with zero slack, so one model loading a slightly larger checkpoint OOM-kills a neighbor, and request bursts collide into latency spikes. Same hardware, three very different risk profiles: the right choice is set by your isolation requirement, not by the maximum pod count.DRA deserves an honest maturity note. DRA (Dynamic Resource Allocation) generalizes the rigid "count of named resources" model into ResourceClaims: a pod describes the device it wants (a GPU with at least 40 GB, or a specific sharing arrangement) and a DRA driver matches and allocates it, much like a PersistentVolumeClaim does for storage. It is the strategic direction for accelerators on Kubernetes, and the core API has graduated into stable Kubernetes releases (the v1.34–v1.36 line moved DRA forward substantially). But "the API is stable" is not "every vendor driver and every advanced sharing feature is production-ready in your cluster." In 2026 the practical readiness questions are still vendor driver support, scheduler integration, telemetry, and whether your on-call engineers know how to debug a ResourceClaim that fails to allocate. Adopt it deliberately; do not overstate it.
5 · Where this breaks and what to install
Failure modes
- Pod schedules but cannot see the device. The driver stack is incomplete — driver, toolkit, or device plugin missing or version-skewed — so the pod starts and then fails on CUDA init. Signal: the pod is
Runningbutnvidia-smiinside it errors, or the model server crashes with a CUDA forward-compatibility message while the node label says a GPU exists. - Sharing breaks tenant isolation. You switch on time slicing to lift utilization, and now one tenant's oversized checkpoint OOM-kills another's pod, or a batch job adds latency jitter to an interactive one. Signal: correlated OOMKills or p99 latency spikes across unrelated pods on the same GPU node — the dependency is the shared, un-isolated device.
- Scheduler sees only
nvidia.com/gpu: 1. The integer hides HBM size and topology, so a model that needs 140 GB schedules onto an 80 GB card, or tensor-parallel ranks land on GPUs with no NVLink between them. Signal: OOM at model load, or multi-GPU throughput far below spec because the placement ignored topology (lesson 07). - Snowflake node setup. Drivers installed by hand drift across nodes; a reimaged node comes back missing a layer. Signal: a workload works on some GPU nodes and not others with no code difference — the difference is undocumented node state.
Implementation checklist
- Install discovery (NFD + GFD) so every node advertises accelerator type, count, driver version, MIG state, and topology as labels — you cannot schedule on what the cluster cannot see.
- Choose the sharing mode per workload from isolation + utilization needs: whole-GPU for large/strict, MIG for many isolated medium jobs, time slicing only for cooperative bursty load, DRA where the driver is genuinely ready.
- Treat GPU Operator configuration as platform code — versioned, reviewed, applied declaratively — not a one-off SSH session that produces snowflake nodes.
- Validate DCGM (Data Center GPU Manager — NVIDIA's GPU telemetry) metrics and in-container runtime visibility before admitting tenant workloads, so you confirm the device is both healthy and visible end to end.
- When a GPU workload fails, debug bottom-up: node sees device → driver matches runtime → discovery labeled it → plugin/DRA advertised it → scheduler placed it on a compatible node → container sees the device file. Debug the model server last.
Checkpoint exercise
resources: block each pod would request, and name the one node label you would require discovery to expose to keep A off a card it cannot fit on. Then state what breaks if you put B on time slicing and B's checkpoint quietly grows by 5 GB.Where this points next
This lesson made a single GPU schedulable and decided how many tenants share it. But the resource model still hides the thing that dominates large-model performance: how multiple GPUs are wired together. A 70B model that does not fit on one card must be split across several, and whether those cards share NVLink or only PCIe changes throughput by an order of magnitude — yet nvidia.com/gpu: 2 says nothing about it. Lesson 07, Multi-GPU Inference and Topology, adds the machinery the scheduler needs to place co-dependent GPUs correctly: tensor parallelism across a node, topology-aware scheduling, and gang scheduling so all the ranks of one model land together or not at all.
nvidia.com/gpu: 1 grants a whole device with full isolation but throws away HBM size and topology; MIG advertises richer, hardware-isolated slices (nvidia.com/mig-1g.10gb), time slicing oversubscribes one card with no memory isolation, and DRA is the maturing claim-based future — choose among them by trading utilization against isolation, never just by maximum pod count. Validate DCGM telemetry and in-container device visibility before tenants arrive, and when GPU pods fail, debug the stack bottom-up before ever touching the model.Interview prompts
- Why doesn't Kubernetes understand a GPU the way it understands a CPU? (§1, §2 — CPU/memory are native and cgroup-enforced; a GPU needs a vendor driver, container toolkit, discovery, and a device plugin/DRA driver to become a schedulable resource, so any missing layer means the pod schedules but cannot see the device.)
- Walk the stack from hardware to a process calling CUDA. (§2 — hardware → driver+runtime/container toolkit → discovery (NFD/GFD labels type/count/driver/MIG/topology) → device plugin or DRA driver → scheduler resource model → pod device visibility.)
- What does
nvidia.com/gpu: 1hide, and why does it matter? (§1, §3 — it loses HBM size, GPU generation, isolation mode, and topology; a 140 GB model schedules onto an 80 GB card and OOMs, or tensor-parallel ranks land on non-NVLinked GPUs.) - Contrast MIG and time slicing precisely. (§4 — MIG hardware-partitions a GPU into isolated slices with dedicated memory/compute (real isolation); time slicing software-interleaves processes over one shared memory pool (no isolation), so a tenant can OOM or add jitter to neighbors.)
- What is DRA and how mature is it in 2026? (§4 — Dynamic Resource Allocation: claim-based ResourceClaims describing the device needed, allocated by a DRA driver; the core API is stable in recent Kubernetes but vendor-driver and feature readiness is still uneven — beta/maturing, don't overstate.)
- What does the NVIDIA GPU Operator do, and why call its config "platform code"? (§2 — it installs and lifecycle-manages driver, toolkit, device plugin, NFD/GFD, and DCGM from one declarative cluster policy, replacing hand-built snowflake nodes and preventing driver/runtime version skew.)
- A GPU pod is
Runningbut the model server can't find CUDA. How do you debug? (§5 — bottom-up: node sees device → driver matches runtime → discovery labeled it → plugin/DRA advertised it → scheduler placed it on a compatible node → container sees the device file; debug the model server last.)