Core Fundamentals
The substrate every other chapter sits on. Linux, networking, storage, containers, Kubernetes, distributed runtimes, and what "production debugging" actually looks like on a GPU fleet.
Linux for GPU hosts
GPU nodes are Linux machines first. The unusual parts are NUMA topology, hugepages, kernel modules, and the device files. The fundamentals you must be fluent in:
- cgroups v2 & namespaces — how container isolation actually works. GPU access flows through bind-mounted device nodes (
/dev/nvidia*) and a hook. - NUMA topology — H100 nodes have multiple NUMA domains. PCIe lanes attach to specific sockets. Cross-NUMA traffic is slow.
nvidia-smi topo -mshows the PCIe / NVLink topology;numactl --hardwareshows the CPU side. - Hugepages — RDMA and some CUDA paths benefit from hugepage backing. Misconfigured hugepages cause silent perf cliffs.
- Kernel modules —
nvidia,nvidia_uvm,nvidia_drm,nvidia_peermem. A bad mismatch between the loaded module and userspace driver is a top-5 outage source. - Systemd units —
nvidia-persistencedkeeps the driver warm;nvidia-fabricmanagersets up NVLink/NVSwitch. - journalctl + dmesg — the first place driver and Xid errors land. You should reflexively check both during a GPU incident.
# The interview-grade Linux/GPU triage cheatsheet
nvidia-smi # are GPUs visible, healthy, temp/power sane?
nvidia-smi topo -m # NVLink / PCIe topology
nvidia-smi -q -d ECC,POWER,CLOCK # detail per device
dmesg | grep -i -E 'nvidia|xid|nvrm' # driver-level errors
journalctl -u nvidia-fabricmanager # NVSwitch fabric state
cat /proc/driver/nvidia/version # loaded driver version vs userspace
lspci | grep -i nvidia # PCIe enumeration sanity
numactl --hardware # NUMA layout
ibstat # InfiniBand link state (if applicable)
NVIDIA driver errors get a numeric Xid. Xid 79 = GPU fell off bus (often hardware), Xid 31 = MMU fault (often app), Xid 13/63 = graphics engine fault, Xid 74 = NVLink error, Xid 119/120 = GSP RPC timeout. You don't need to memorize them all — you do need to know they exist and where to look them up.
Networking and the fabric
GPU training is bottlenecked by interconnect bandwidth, not compute, more often than people expect. Three layers:
- Intra-node — NVLink between GPUs on the same chassis (900 GB/s aggregate on H100 SXM). NVSwitch enables all-to-all at full bandwidth.
- PCIe — host-to-GPU, NIC-to-GPU. Gen5 x16 ≈ 64 GB/s. GPUDirect RDMA lets a NIC DMA directly into GPU memory, bypassing the host.
- Inter-node fabric — InfiniBand HDR/NDR (200/400 Gbps) or RoCE (RDMA over Converged Ethernet). NCCL uses this for cross-node all-reduce.
For inference, the fabric matters less unless you're doing tensor parallelism across nodes. For training, it's load-bearing.
| Symptom | Likely networking culprit |
|---|---|
| All-reduce throughput half of expected | Wrong NCCL transport selected (TCP instead of IB), or PCIe lane downgrade |
| Step time variance node-to-node | Network congestion, stragglers, or one NIC at degraded speed |
| NCCL hangs at startup | Subnet manager issue, firewall, NCCL_SOCKET_IFNAME misconfigured |
| Cross-node training much slower than single-node | Missing GPUDirect RDMA, falling back to host-staged transfers |
Storage for model weights & checkpoints
A 70B-parameter model in FP16 is ~140 GB. A 405B model is ~810 GB. Sharded checkpoints during training generate terabytes per checkpoint. Three storage tiers:
- Object storage (S3, GCS, R2) — durable, cheap, slow first-byte. Source of truth for weights.
- Network filesystem (Lustre, Weka, FSx) — high parallel throughput, good for shared training scratch.
- Local NVMe — fastest, ephemeral. Used for staging weights into a serving pod, for KV-cache spill, for fast checkpoint write-then-flush.
A practical pattern for inference: bake or stage weights to local NVMe on pod start, then mmap them. Loading a 70B model over the network for every pod restart is a pager-class mistake.
Containers and the NVIDIA toolkit
Containers see GPUs because nvidia-container-toolkit injects device nodes and libraries via an OCI hook. The flow:
- Container runtime (containerd, CRI-O, Docker) invokes the OCI hook.
- Hook reads
NVIDIA_VISIBLE_DEVICESandNVIDIA_DRIVER_CAPABILITIES. - Bind-mounts
/dev/nvidia*,libcuda.so, and friends into the container namespace. - Container starts;
nvidia-smiworks inside.
The classic failure: the host driver is updated but the toolkit / device plugin doesn't restart cleanly, and new pods come up without GPUs. The drain-and-replace runbook lives in chapter 13.
The host driver provides a CUDA runtime. Apps built against CUDA toolkit version X work on driver versions ≥ X (forward-compatible). Driver Y < CUDA toolkit version X fails at load with CUDA driver version is insufficient. This is the most common new-cluster gotcha.
Kubernetes basics for GPU workloads
K8s itself doesn't know about GPUs. The NVIDIA device plugin (a DaemonSet) advertises nvidia.com/gpu as an allocatable resource. A pod requesting nvidia.com/gpu: 2 gets two whole GPUs bound exclusively.
apiVersion: v1
kind: Pod
metadata:
name: vllm-server
spec:
nodeSelector:
accelerator: nvidia-h100
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
priorityClassName: inference-online
containers:
- name: vllm
image: vllm/vllm-openai:latest
args: ["--model", "meta-llama/Llama-3.1-70B-Instruct",
"--tensor-parallel-size", "4"]
resources:
limits:
nvidia.com/gpu: 4
memory: "256Gi"
cpu: "32"
volumeMounts:
- name: model-cache
mountPath: /root/.cache/huggingface
- name: shm
mountPath: /dev/shm
volumes:
- name: model-cache
hostPath: { path: /mnt/nvme/hf-cache }
- name: shm
emptyDir: { medium: Memory, sizeLimit: 64Gi }
Key primitives the role asks about:
- NodeSelector / nodeAffinity / taints / tolerations for accelerator placement.
- PriorityClass & preemption for fair scheduling between research and production.
- ResourceQuota & LimitRange per namespace for quota enforcement.
- PodDisruptionBudget so cluster operations don't murder live inference.
- HorizontalPodAutoscaler with custom metrics (queue depth, GPU util) — not just CPU.
- Gang scheduling controllers — Volcano, Kueue, Yunikorn — for "schedule all N pods or none."
Distributed runtimes
"Distributed" in this role means two different things, and you should be able to talk about both.
Training-side
- PyTorch DDP — data parallel, one process per GPU, NCCL all-reduce on gradients.
- FSDP — fully sharded data parallel, shards optimizer state + grads + params.
- DeepSpeed ZeRO 1/2/3 — analogous sharding stages.
- Megatron-LM — tensor + pipeline parallelism for very large models.
- Ray — task-level distributed compute, often used for orchestration around training.
Inference-side
- vLLM — server with paged attention and continuous batching.
- Triton Inference Server — multi-model multi-backend serving.
- Ray Serve — flexible multi-stage pipelines.
- TensorRT-LLM — compiled, often combined with Triton.
The depth is in chapter 04.
What "production debugging" means for GPU infra
For this role, "production debugging" almost always means one of these:
| Surface | Tools you reach for |
|---|---|
| "My inference latency p99 doubled at 14:00" | Prometheus (queue depth, GPU util, batch size histogram), traces, recent deploys, KV-cache pressure |
| "My training job hangs at NCCL init" | NCCL_DEBUG=INFO, IB link state, NCCL topology dump, firewall, NCCL_SOCKET_IFNAME |
| "This kernel is slow" | Nsight Systems (timeline), Nsight Compute (kernel-level), PyTorch profiler |
| "OOM at step 47 of training" | Memory snapshot, gradient checkpointing settings, batch size, activation memory, fragmentation |
| "Pods land on node, get no GPU" | Device plugin DaemonSet health, driver/toolkit version, dmesg Xids, container runtime hook |
| "Throughput regressed after driver upgrade" | Bisect: revert driver on canary, profile, compare NCCL/CUDA versions, check P2P state |
Interviewers love asking debug scenarios because they reveal whether you've actually held the pager. The strongest answer always starts: "First I'd look at what changed. Then I'd narrow the blast radius..."
The cost-perf-reliability triangle
Every nontrivial design call in this role lives inside this triangle. Pick any two; the third bends.
- Maximize throughput, hold reliability → cost goes up (more reserved capacity, less spot).
- Minimize cost, hold throughput → reliability gets shakier (spot preemption, packing, less headroom).
- Hold cost & reliability → throughput is fixed; you can't grow free.
When asked a design question, name the triangle out loud before answering. "I'm going to optimize for tail latency and reliability here, accepting ~20% higher steady-state cost, because this serves a customer-facing path." That single sentence raises your read.