Why DCGM
nvidia-smi is fine for a quick look at one node. For a fleet of inference nodes you need metrics over time, per GPU and per pod, with alerts. NVIDIA's DCGM Exporter reads metrics from the Data Center GPU Manager and exposes them in Prometheus format, by default on port 9400 at /metrics.
Step 1: deploy it
Kubernetes with GPU Operator
The NVIDIA GPU Operator deploys DCGM Exporter by default. Check it is running:
kubectl get pods -n gpu-operator -l app=nvidia-dcgm-exporter -o wide
kubectl get svc -n gpu-operator | grep dcgm
Kubernetes without the operator (Helm)
helm repo add gpu-helm-charts https://nvidia.github.io/dcgm-exporter/helm-charts
helm repo update
helm install dcgm-exporter gpu-helm-charts/dcgm-exporter -n monitoring --create-namespace
Make sure it schedules only on GPU nodes (node selector or tolerations to match your GPU node taints).
Standalone host (Docker)
docker run -d --gpus all --cap-add SYS_ADMIN --restart unless-stopped \
-p 9400:9400 --name dcgm-exporter nvcr.io/nvidia/k8s/dcgm-exporter:<tag>
Pick the image tag that matches your driver and DCGM version from NVIDIA's NGC catalog.
Quick check:
curl -s localhost:9400/metrics | grep -E "^DCGM_FI_DEV_(GPU_UTIL|FB_USED)" | head
Step 2: scrape with Prometheus
With the Prometheus Operator, a ServiceMonitor (the Helm chart can create one):
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: dcgm-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app.kubernetes.io/name: dcgm-exporter
namespaceSelector:
any: true
endpoints:
- port: metrics
interval: 15s
Label names differ between chart versions; match your service's labels (kubectl get svc -n <ns> --show-labels).
In Kubernetes, DCGM Exporter adds namespace, pod and container labels for GPUs assigned to pods, so you can see which workload is on which GPU.
Step 3: the metrics that matter
| Metric | What it tells you |
|---|---|
DCGM_FI_DEV_GPU_UTIL | % of time a kernel was running; coarse "is it busy" |
DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_FREE | Framebuffer (GPU memory) used/free, in MiB |
DCGM_FI_DEV_GPU_TEMP | GPU temperature (°C) |
DCGM_FI_DEV_POWER_USAGE | Power draw (W) |
DCGM_FI_DEV_SM_CLOCK | SM clock; drops under thermal or power throttling |
DCGM_FI_DEV_XID_ERRORS | Last XID error code: driver/hardware faults |
DCGM_FI_PROF_GR_ENGINE_ACTIVE | Graphics/compute engine activity, more precise than GPU_UTIL (profiling metric) |
DCGM_FI_PROF_DRAM_ACTIVE | Memory bandwidth usage; LLM decoding is often memory-bound |
Profiling (DCGM_FI_PROF_*) metrics depend on GPU model and the exporter's metric config file; enable them in the counters CSV if they don't appear.
Tip
Inference servers such as vLLM pre-allocate most GPU memory for the KV cache at startup.
FB_USEDat 90% is normal for them and not a leak. Watch the server's own KV-cache usage metric for real memory pressure, and useFB_USEDto spot unexpected processes.
Step 4: useful PromQL
# Average utilisation per node over 15 minutes
avg by (Hostname) (avg_over_time(DCGM_FI_DEV_GPU_UTIL[15m]))
# GPU memory used % per GPU
100 * DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE)
# Which pod is on which GPU, with memory used
sum by (namespace, pod, gpu) (DCGM_FI_DEV_FB_USED)
# Idle GPUs: under 5% for 6 hours (expensive capacity doing nothing)
max_over_time(DCGM_FI_DEV_GPU_UTIL[6h]) < 5
Step 5: alerts
groups:
- name: gpu
rules:
- alert: GpuXidError
expr: DCGM_FI_DEV_XID_ERRORS > 0
for: 1m
labels: {severity: critical}
annotations:
summary: "XID {{ $value }} on {{ $labels.Hostname }} GPU {{ $labels.gpu }}"
- alert: GpuHot
expr: DCGM_FI_DEV_GPU_TEMP > 85
for: 10m
labels: {severity: warning}
- alert: GpuMetricsMissing
expr: absent(DCGM_FI_DEV_GPU_UTIL)
for: 10m
labels: {severity: warning}
- alert: GpuIdleLong
expr: max_over_time(DCGM_FI_DEV_GPU_UTIL[6h]) < 5
labels: {severity: info}
Pick temperature thresholds from your GPU's specification; data centre GPUs throttle at different points.
XID errors to know
| XID | Usual meaning | Action |
|---|---|---|
| 13, 31, 43 | Application fault (bad kernel, illegal memory access) | Usually the workload; check app logs |
| 48, 63, 64, 94, 95 | ECC / memory errors, row remapping | Drain node, check nvidia-smi -q -d ECC,ROW_REMAPPER, possibly reset or replace |
| 79 | GPU fell off the bus | Drain and reboot; repeated = hardware ticket |
| 74 | NVLink error | Check NVLink status; hardware ticket if persistent |
Check NVIDIA's XID documentation for your driver; meanings are refined between releases.
Troubleshooting the exporter
| Problem | Fix |
|---|---|
| No metrics, pod CrashLoopBackOff | Driver/DCGM version mismatch; use an exporter tag matching the driver |
Metrics but no pod labels | Kubelet pod-resources socket not mounted; use the chart defaults |
DCGM_FI_PROF_* missing | Not enabled in the counters CSV, or not supported on that GPU |
| Gaps every few minutes | Scrape interval shorter than collection interval, or exporter under CPU limits |
Prevent it next time
- Treat DCGM Exporter as part of the GPU node image or operator install, not an add-on.
- Build one Grafana board per cluster: utilisation, memory, temperature, power, XID, plus inference latency from the serving layer.
- Route XID alerts to the platform team with a runbook to cordon and drain the node.
- Review idle-GPU alerts weekly; idle GPUs are the biggest waste in an inference platform.