Lessons
18 — GPU node pools / MIG / time-slicing

Lesson 18 — AI/ML platform: GPU node pools, device plugins, MIG/time-slicing, SKU isolation

2026-09-26

What interviewers are really probing

Lesson 17 previewed heterogeneous SKUs and GPU tenancy as tradeoff axes. This lesson is the first deep AI/ML platform cut: can you operate GPU capacity as labeled, tainted, device-plugin-backed pools — not “nodes that happen to have accelerators”?

Interviewers at hyperscale (and AI platforms fueled by wafer-ai/gpu-perf-engineering-resources (opens in a new tab)) want evidence you have run GPU fleets under packing pressure and trust boundaries:

  • SKU isolation as a pool contract — A100 vs H100 vs L4 are different products; labels + taints + separate autoscaler min/max (L3, L4, L17)
  • Device plugin vs GPU Operator — who installs drivers, who advertises nvidia.com/gpu / mig-*, what breaks when the DaemonSet is unhealthy
  • MIG vs time-slicing vs exclusive — hardware memory/fault isolation vs SW oversubscribe with no mem/fault isolation vs 1:1 ownership
  • RuntimeClass + container toolkit — the nvidia runtime path; wrong RuntimeClass = silent CPU fallback or CrashLoop
  • Scheduler packing reality — requests, tolerations, nodeSelectors; Pending forever is a pool-contract bug until proven otherwise
  • Autoscaling gotchas — CA/NAP scales on pending GPU resources; MIG profile flips are drains; time-slice replicas fake headroom
  • Security floor on shared GPUs — admission, NetPol, signed driver/workload images, weight IAM so a notebook on a shared L4 cannot read another tenant’s checkpoints (L11–L14)

Interview sentence: “I treat GPUs as SKU pools with taints and labels, let the GPU Operator own the device stack, choose exclusive / MIG / time-slice by isolation need — never time-slice across trust boundaries — and keep admission, NetPol, signed images, and per-pool weight IAM on the floor.”

Core diagram: SKU pools → plugin → resources → sharing modes → security floor

GPU SKU pools, labels/taints, NVIDIA GPU Operator and device plugin, resource ads, exclusive vs MIG vs time-slicing, scheduler packing, security floor

Read it top-down: SKU pools (labeled + tainted) feed the NVIDIA GPU Operator stack (driver, container toolkit, device plugin, GFD, DCGM). The plugin advertises nvidia.com/gpu, nvidia.com/mig-*, or nvidia.com/gpu.shared. You pick a sharing mode — exclusive (full GPU), MIG (HW memory/fault isolation), or time-slicing (SW replicas, no mem/fault isolation) — then the scheduler packs via nodeSelector/affinity + tolerations + RuntimeClass. Everything sits on a security floor: admission requiring SKU contracts, device-plugin RBAC, signed driver/workload images, NetworkPolicy on GPU namespaces, and WI/IRSA so weights never leak across tenants on shared nodes. Topology (NVLink/RDMA/NCCL) is Lesson 19 — preview only here.

Idiosyncrasies / operator depth

1. GPU node pools are products, not instance types

Contract pieceOperator practiceFootgun
Labelssku=h100, accelerator=nvidia-h100-80gb, pool=train-exclusiveRelying only on node.kubernetes.io/instance-type (opaque to app teams)
Taintsnvidia.com/gpu=true:NoSchedule or sku=h100:NoScheduleUntainted GPU nodes become free CPU capacity for junk Deployments
TolerationsRequired on every GPU Pod template (Helm values, Kyverno mutate)One chart without toleration → Pending; “GPUs are broken” tickets
Separate CA/NAPPer-pool min/max/spot policyOne mixed pool: H100 jobs land on L4 or block behind notebooks
Golden image + SKU overlayOne CIS baseline AMI; driver/ABI differences via Operator, not 12 AMIs (L17)Snowflake AMIs per SKU forever

Interview sentence: “SKU isolation lives in labels, taints, and autoscaler pools — not in hoping the scheduler invents topology.”

2. NVIDIA device plugin vs GPU Operator

PieceRoleGotcha
k8s-device-pluginAds nvidia.com/gpu (and MIG resources); allocates devices to podsAlone does not install drivers or configure toolkit
GPU OperatorClusterPolicy CRD → DaemonSets: driver, toolkit, device-plugin, GFD, DCGM, MIG managerPrivileged / hostPath by design on driver DS — gate who can edit ClusterPolicy
GFDLabels product, memory, MIG capability, countStale labels after MIG flip until plugin/GFD restart
Container toolkitInjects GPU devices into containers via RuntimeClassMissing toolkit → pods schedule then fail to see /dev/nvidia*
DCGM exporterHealth + metrics for capacity/SEVMetrics without alert on XID / ROW remap = flying blind

Prefer Operator-managed stacks at fleet scale (L3 lifecycle). Pin Operator/chart versions; treat driver upgrades as drain waves (Temporal saga territory from L16).

3. Exclusive vs MIG vs time-slicing

ModeIsolationWhen it winsWhen it loses
ExclusiveFull GPU to one podTraining, SLO inference, unknown memory footprintDensity; idle H100s eating budget
MIGHW partition: dedicated SM + memory + fault isolationMulti-tenant inference on A100/H100; regulated tenants sharing a nodeProfile rigidity; resize = drain; not all SKUs support MIG
Time-slicingSW replicas; no memory or fault isolationDev notebooks, batch within one trust domainCross-tenant; production SLO; any OOM kills neighbors’ luck

Operator truth from NVIDIA docs: time-slicing trades MIG’s memory/fault isolation for higher share counts. Never put hostile tenants on the same time-sliced GPU. GKE can combine MIG partitions with time-sharing inside a partition — still same-trust only (L14).

Interview sentence: “Exclusive for train/SLO; MIG when multi-tenant needs HW walls; time-slice only inside one trust boundary — never as a security control.”

4. Resource names and what the scheduler actually sees

AdvertisementMeaning
nvidia.com/gpuWhole GPU (or time-sliced replica if config renames/overrides)
nvidia.com/mig-1g.5gb, mig-2g.10gb, …MIG profiles (strategy single vs mixed)
nvidia.com/gpu.sharedTime-slice when renameByDefault: true
Allocatable vs capacityPlugin may advertise replicas ≫ physical GPUs — fake headroom if apps ignore GPU memory

Pods must request the exact resource name the pool ads. Requesting nvidia.com/gpu on a MIG-only node with strategy that hides whole GPUs → Pending.

5. RuntimeClass lockdown

# Workloads must opt into nvidia runtime — do not make it cluster default for all pods
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: nvidia
handler: nvidia

Admission should require runtimeClassName: nvidia (or your org’s name) for any pod requesting GPU resources, and forbid privileged + hostPID combos except the Operator’s own DaemonSets (L11).

6. Autoscaling gotchas (the pager at 2am)

SymptomReality
Pending GPU pods, CA idlePod missing toleration / selector; CA never sees schedulable pending
CA scales wrong SKUNode templates not labeled; NAP defaults to cheapest GPU
MIG config change “hangs”Nodes need cordon/drain; in-place MIG reconfig is a lifecycle event
Time-slice “plenty of GPUs” then OOMReplicas ≠ memory; enforce app-level GPU memory limits
Spot GPU reclaim mid-trainCheckpoint discipline + exclusive reserved pools for critical train (L13)

7. Compose with prior security lessons

GPU pools are where L12–L14 become physical: signed images land on nodes that can read weight buckets. Shared pools need stricter IAM and NetPol, not looser — density is not an excuse to widen blast radius (L1).

Concrete commands & config

GKE-style GPU node pool (SKU + taint)

# Illustrative — separate pools per SKU
gcloud container node-pools create gpu-h100-exclusive \
  --cluster=cell-a --region=us-east1 \
  --machine-type=a3-highgpu-8g \
  --accelerator=type=nvidia-h100-80gb,count=8 \
  --num-nodes=0 --enable-autoscaling --min-nodes=0 --max-nodes=16 \
  --node-taints=sku=h100:NoSchedule,nvidia.com/gpu=true:NoSchedule \
  --node-labels=sku=h100,pool=train-exclusive,accelerator=nvidia-h100-80gb \
  --spot  # only if checkpointed; prefer on-demand for SLO train
 
gcloud container node-pools create gpu-l4-shared \
  --cluster=cell-a --region=us-east1 \
  --machine-type=g2-standard-8 \
  --accelerator=type=nvidia-l4,count=1,gpu-sharing-strategy=time-sharing,max-shared-clients-per-gpu=4 \
  --num-nodes=0 --enable-autoscaling --min-nodes=0 --max-nodes=32 \
  --node-taints=sku=l4-shared:NoSchedule \
  --node-labels=sku=l4,pool=interactive-shared,cloud.google.com/gke-gpu-sharing-strategy=time-sharing

Helm: NVIDIA GPU Operator (sketch)

helm repo add nvidia https://helm.ngc.nvidia.com/nvidia && helm repo update
helm upgrade --install gpu-operator nvidia/gpu-operator \
  -n gpu-operator --create-namespace \
  --set driver.enabled=true \
  --set mig.strategy=mixed \
  --set devicePlugin.config.name=time-slicing-config \
  --set operator.defaultRuntime=containerd

Device plugin time-slicing ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: time-slicing-config
  namespace: gpu-operator
data:
  any: |-
    version: v1
    sharing:
      timeSlicing:
        renameByDefault: true
        failRequestsGreaterThanOne: true
        resources:
          - name: nvidia.com/gpu
            replicas: 4

Label nodes that should apply it: nvidia.com/device-plugin.config=any. Keep MIG pools on different nodes with nvidia.com/mig.config=all-balanced (or your profile) — do not time-slice and MIG-chaos the same unlabeled pool.

Workload: exclusive H100 train

apiVersion: batch/v1
kind: Job
metadata:
  name: train-llama-cell-a
  namespace: model-training
spec:
  template:
    spec:
      runtimeClassName: nvidia
      serviceAccountName: weight-writer
      restartPolicy: OnFailure
      nodeSelector:
        sku: h100
        pool: train-exclusive
      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
          resources:
            limits:
              nvidia.com/gpu: "8"

Workload: MIG inference slice

apiVersion: apps/v1
kind: Deployment
metadata:
  name: infer-small
  namespace: model-serving
spec:
  template:
    spec:
      runtimeClassName: nvidia
      serviceAccountName: weight-reader
      nodeSelector:
        sku: a100-mig
      tolerations:
        - key: sku
          value: a100-mig
          effect: NoSchedule
      containers:
        - name: server
          image: registry.example.com/ml/infer@sha256:REPLACE
          resources:
            limits:
              nvidia.com/mig-1g.5gb: "1"

kubectl inventory (operator morning checks)

# Pool contract
kubectl get nodes -L sku,pool,accelerator,nvidia.com/gpu.product,nvidia.com/mig.capable
 
# What the plugin ads
kubectl describe node -l sku=h100 | sed -n '/Allocatable:/,/System Info:/p'
kubectl get cm -n gpu-operator time-slicing-config -o yaml
 
# Operator health
kubectl get pods -n gpu-operator -o wide
kubectl get clusterpolicies.nvidia.com
kubectl get runtimeclass
 
# Pending GPU truth
kubectl get pods -A -o custom-columns=NS:.metadata.namespace,N:.metadata.name,ST:.status.phase \
  | grep Pending
kubectl describe pod -n model-training TRAIN_POD | sed -n '/Events/,$p'
 
# DCGM / GPU feature labels
kubectl get nodes -l nvidia.com/gpu.count -o json \
  | jq '.items[] | {name:.metadata.name, labels:.metadata.labels | with_entries(select(.key|startswith("nvidia.com/")))}'

Admission sketch: require SKU + RuntimeClass for GPU requests

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: gpu-pool-contract
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
  validations:
    - expression: >
        !has(object.spec.containers) ||
        !object.spec.containers.exists(c,
          has(c.resources) && has(c.resources.limits) &&
          (c.resources.limits.exists(k, k.startsWith("nvidia.com/")))) ||
        (has(object.spec.runtimeClassName) && object.spec.runtimeClassName == "nvidia" &&
         has(object.spec.nodeSelector) && has(object.spec.nodeSelector.sku))
      message: "GPU pods require runtimeClassName=nvidia and nodeSelector.sku"

Failures & fixes

SymptomLikely causeFix / mitigation
Pod Pending: 0/N nodes are available: insufficient nvidia.com/gpuWrong resource name (gpu vs mig-* vs gpu.shared); pool at max; missing CA templateMatch ads; scale pool; fix NAP SKU labels
Pod Pending: untolerated taintChart omitted tolerationsKyverno mutate tolerations by namespace; golden Helm defaults
Scheduled but no /dev/nvidia*Toolkit/RuntimeClass missing; wrong handlerFix RuntimeClass; check toolkit DS; admission require nvidia
CrashLoop: driver mismatchNode kernel vs driver container ABIPin Operator/driver; precompiled drivers; drain-wave upgrades
MIG resources missing after configGFD/plugin not restarted; wrong mig.strategyVerify nvidia.com/mig.config; restart plugin; use MIG manager
Time-slice OOM / XID stormsNo mem isolation; hostile or unbounded appsSplit trust domains; prefer MIG; set app GPU memory limits
CA scales L4 for H100 JobShared autoscaler / unlabeled templatesSeparate node pools + labels; NAP constraints per SKU
Notebook reads other tenant weightsNode SA / WI too broad on shared poolPer-workload WI/IRSA; bucket IAM; never node-wide s3:* (L13)
Privileged escape via GPU DS patternTenants copy Operator securityContextPSS restricted on tenant NS; only Operator NS allowed privileged
“GPU utilization low” with Pending trainInteractive shared pool starved exclusive capacityQuotas + separate pools; fair-share queues (upcoming L20)
Drain stuck on GPU nodeLong train without eviction budgetPDB + checkpoint; Temporal drain saga (L16)

Security & hardening

GPU nodes are high-value targets: they hold model weights in memory/disk cache, run privileged device stacks, and often share physical accelerators across teams. Soft density without a floor is how you get cross-tenant weight theft and driver-level escapes.

ControlWhyPractice
SKU pool isolationBlast walls between train / serve / interactive (L14)Separate pools + taints; no default-scheduler freeloading on GPU nodes
Admission requires sku + RuntimeClassStops accidental or malicious GPU schedulingValidatingAdmissionPolicy / Kyverno; deny :latest; digest pins (L12)
Device plugin / Operator RBACClusterPolicy edit ≈ fleet GPU rootLeast privilege; break-glass only; audit ClusterPolicy changes
Privileged / device accessDriver DS needs privilege; tenants must notPSS restricted on model-* NS; allow privileged only in gpu-operator
Signed driver + toolkit + workload imagesCompromised GPU stack = host compromiseCosign verify; private registry; SBOM for Operator charts (L12)
RuntimeClass lockdownPrevents silent non-GPU runtimes and host escape pathsNamed nvidia RuntimeClass; admission-enforced for GPU resource requests
No cross-tenant weight access on shared GPUsTime-slice/MIG density ≠ shared IAMPer-pod WI/IRSA; bucket IAM + CMEK; separate reader/writer SAs (L13)
NetworkPolicy on GPU namespacesExfil of weights / pivot to trainDefault-deny; allow private registry + weight VPC endpoints only (L9, L14)
Secrets / WI-IRSA for pulls onto GPU nodesLong-lived keys on GPU nodes are standing incidentsExternal Secrets / CSI; short-lived tokens; never HF tokens in images
PSS + no hostPath for tenantshostPath + GPU ≈ hostDeny hostPath/hostNetwork/privileged for tenant GPU pods
Time-slicing trust ruleNo mem/fault isolationSame trust boundary only; regulated / prod → MIG or exclusive
Shared vs dedicated poolsShared interactive pools still pull private weightsDedicated train/serve pools for prod weights; shared pools use scrubbed/public fixtures or tightly scoped IAM

AI/ML weight & registry hardening on GPU pools (required)

ScenarioWeight / registry controls
Dedicated exclusive train poolWriter SA + CMEK bucket; signed checkpoints; node pulls via WI only; no public HF
MIG multi-tenant servePer-tenant reader SA; digest-pinned model refs; NetPol per NS; no shared emptyDir for weights across pods
Time-sliced interactivePrefer non-prod weights; if prod-like, per-user IAM + short TTL; never node instance role with broad s3:GetObject
All poolsPrivate registry for runtime + model images; cosign; admission blocks unsigned; provenance attestations on promote (L12/L13)
# Sketch: shared GPU NS still gets the floor
apiVersion: policy.platform/v1
kind: GpuPoolGate
metadata:
  name: gpu-floor
spec:
  match:
    nodeSelectorKeys: ["sku"]
  require:
    runtimeClass: nvidia
    imageDigestPin: true
    cosignVerify: true
    podSecurity: restricted
    networkPolicy: deny-by-default
    weightBucketPublicAccess: false
    cmek: true
    workloadIdentity: true
    timeSliceSameTrustOnly: true
    forbidNodeWideObjectIAM: true

Punchline: MIG gives hardware walls; time-slicing does not. IAM, admission, NetPol, and signing are what stop a shared L4 notebook from becoming a weight exfil path — SKU taints alone are not enough.

Interview soundbites

  • “GPUs are SKU pools with taints — not unlabeled capacity.”
  • “Operator owns the device stack; the plugin only ads what the Operator configured.”
  • “Exclusive for train/SLO; MIG for HW multi-tenant; time-slice only inside one trust domain.”
  • “Time-slicing is density, not isolation — no mem, no fault walls.”
  • “Pending GPU pods are a pool-contract bug until proven otherwise.”
  • “MIG profile changes are drains, not ConfigMap daydreams.”
  • “Replicas inflate allocatable; GPU memory is still finite.”
  • “RuntimeClass nvidia is required, not decorative.”
  • “Tenant NS stays PSS restricted — only gpu-operator may be privileged.”
  • “Per-pod WI/IRSA for weights; never a node-wide models: role on shared pools.”*
  • “Golden OS baseline; heterogeneity in labels and device plugins.”
  • “Next: Lesson 19 — topology: NVLink, RDMA, NCCL — packing without fabric is half a design.”

How this maps to the JD

Hyperscale + AI/ML platform JDs that mention GPU fleets, inference/training, and 10K+ nodes are asking whether you can productize accelerators: pools, device plugins, sharing modes, and security. This lesson connects L1/L3/L4 (cells, lifecycle, Borg packing instincts), L11–L14 (admission, supply chain, weights, deploy isolation), L15 (pool IaC), and L17 (SKU vs golden image) into an operator-ready GPU pool story. Fuel: wafer-ai/gpu-perf-engineering-resources (opens in a new tab). Next up — Lesson 19: GPU topology (NVLink/NVSwitch, multi-NIC RDMA, GPUDirect, NCCL) so packing respects the fabric, not just the resource name.

Watch (talks & demos)

Real talks — watch after Failures & fixes.

Mastering GPU Management in Kubernetes Using the Operator Pattern (Shiva Krishna Merla & Kevin Klues, NVIDIA / KubeCon)

End-to-end GPU Operator lifecycle: drivers, toolkit, device plugin, MIG/MPS profiles, monitoring — the operator depth behind ClusterPolicy and fleet GPU stacks.

Open on YouTube (opens in a new tab)

A Deep Dive on Supporting Multi-Instance GPUs in Containers and Kubernetes (Kevin Klues, NVIDIA)

How MIG is exposed to containers and Kubernetes, resource naming, and lifecycle of MIG devices on a node — essential before you promise “multi-tenant A100” in an interview.

Open on YouTube (opens in a new tab)

Scaling GPU Clusters Without Melting Down (Alay Patel & Ryan Hallisey, NVIDIA / KubeCon EU 2025)

Control-plane cost of large GPU fleets, scheduler/apiserver pressure, KWOK simulation, and DRA direction — why GPU scale is a platform problem, not only a device-plugin problem.

Open on YouTube (opens in a new tab)

References

Quick self-check

Out loud, in under two minutes:

  1. What labels and taints define a GPU SKU pool — and what happens if you skip the taint?
  2. What does the GPU Operator manage that the device plugin alone does not?
  3. Compare exclusive vs MIG vs time-slicing on memory isolation, fault isolation, and trust.
  4. Why can time-slice “allocatable GPUs” look healthy while training OOMs?
  5. Name three admission / IAM controls that must hold on a shared GPU pool when pods pull model weights.
  6. What breaks if a GPU pod omits runtimeClassName: nvidia or the matching SKU toleration?