GPU node health
Per-node GPU sanity checks + a fleet-wide three-step audit.
Two things on this page:
- Per-node checks — copy-ready commands for the ten things you should look at when a single node is in question.
- Fleet audit — paste three kubectl outputs and find nodes with missing or unreachable GPU pods.
Per-node checklist
GPU presence + driver
nvidia-smi -L
Should list 8 H100 / H200 / GH200 (depending on SKU). If it errors with "NVML: Unknown Error" the driver / fabric-manager is broken.
GPU full status
nvidia-smi
Look for ECC errors in the lower table, persistence-mode = On, and no dashes ("--") under temp.
Per-GPU PCIe link speed/width
nvidia-smi --query-gpu=index,pcie.link.gen.current,pcie.link.width.current --format=csv
On H100 expect Gen5 x16. Gen3 / x8 = bus is parked or the slot is misseated.
NVLink topology
nvidia-smi nvlink --status
8-GPU NVSwitch: every GPU should have 18 links Up. Anything Disabled = needs fabric-manager investigation.
NVSwitch peer test (DCGM)
dcgmi diag -r 1
Run after a node has been fully drained. Catches ECC double-bit errors, NVLink down, fabric-manager not running.
Persistence mode
nvidia-smi -pm 1
Critical for keeping the kernel module loaded between container starts. Should be enabled at boot via nvidia-persistenced.
Fabric manager
systemctl status nvidia-fabricmanager
Required on NVSwitch boards (DGX, HGX H100). Must match the running driver version.
IB devices + state
ibstat | grep -E "CA '|State|Rate"
On 8-rail H100 box: 8 CAs, all "State: Active", all "Rate: 400 Gb/sec".
IB RDMA test (peer)
# server ib_send_bw -d mlx5_0 -F # client ib_send_bw -d mlx5_0 -F <peer-ip>
Confirms the rail is actually carrying RDMA, not just link-up.
Container Toolkit hook
nvidia-container-cli info
If this fails the runtime can't inject /dev/nvidia* into containers — every workload will see "no GPU available".
Fleet audit
Step 1 · All cluster nodes
command
kubectl get nodes --selector='!node-role.kubernetes.io/login' -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}'Step 2 · Pod → node mapping
command
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.nodeName}{"\n"}{end}' | grep <pod-prefix>Step 3 · kubectl exec — reach each pod
command
for node in $(kubectl get nodes --selector='!node-role.kubernetes.io/login' -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}'); do
pod=$(kubectl get pods -A --field-selector spec.nodeName=$node \
--no-headers -o custom-columns=":metadata.namespace,:metadata.name" 2>/dev/null \
| grep "<pod-prefix>" | head -1)
if [ -z "$pod" ]; then continue; fi
ns=$(echo "$pod" | awk '{print $1}'); name=$(echo "$pod" | awk '{print $2}')
out=$(kubectl exec -n "$ns" "$name" -- echo ok 2>&1)
if [ $? -eq 0 ]; then echo "OK: $name"; else echo "FAIL: $name => $out"; fi
done