GPU observability for LLM inference: the twelve DCGM and vLLM metrics that dictate the health of your production
Contents
This post complements those on LLM tracing with OpenTelemetry GenAI (the tracing layer above the metrics), Capacity planning (what was sized and what must be watched) and Continuous batching (the mechanism that explains several of the engine metrics).
TL;DR
Observability for an LLM inference cluster is built on two complementary sources: the GPU hardware metrics exposed by the DCGM (Data Center GPU Manager) Exporter, part of the NVIDIA GPU Operator, and the metrics from the inference engine (vLLM, SGLang, TensorRT-LLM) exposed on a Prometheus-compatible /metrics. Neither is enough on its own. The classic nvidia-smi metric called GPU utilization is misleading for LLMs: it reads high whenever any kernel is running, without distinguishing tensor cores burning from SMs waiting on HBM. The full cockpit has twelve DCGM metrics in four families (compute: DCGM_FI_PROF_SM_OCCUPANCY, DCGM_FI_PROF_PIPE_TENSOR_ACTIVE, DCGM_FI_PROF_DRAM_ACTIVE; memory: DCGM_FI_DEV_FB_USED, DCGM_FI_DEV_FB_FREE, DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL; thermal-power: DCGM_FI_DEV_GPU_TEMP, DCGM_FI_DEV_POWER_USAGE, DCGM_FI_DEV_CLOCK_THROTTLE_REASONS; health: DCGM_FI_DEV_XID_ERRORS, DCGM_FI_DEV_ECC_DBE_VOL_TOTAL, DCGM_FI_DEV_RETIRED_DBE) and five vLLM engine metrics (vllm:num_requests_running, vllm:num_requests_waiting, vllm:gpu_cache_usage_perc, vllm:time_to_first_token_seconds, vllm:time_per_output_token_seconds). Each one has a defensible green/amber/red threshold, a PromQL query for alerting, and at least one common false reading that confuses the junior operator. The six critical alerts that any production cluster must fire are: HBM > 92 %, thermal or power throttle, XID error, ECC double-bit, KV cache pool > 95 %, and TTFT P95 outside SLO for 5 minutes. The point of having this panel: that the operator on shift diagnoses the origin of a degradation in under five minutes, without opening an SSH console to the GPUs. When that holds, the cluster has moved to professional operation; until then, it is operated on intuition.
You are here: OBSERVE (the other half of tracing)
Tracing, already covered in LLM tracing with OpenTelemetry GenAI, answers what happened in this specific request. Metrics answer what is happening across the cluster in aggregate. They are complementary: an alert on the metrics side tells you “the cluster is degrading”, tracing tells you “and here is the specific trace that proves it”. A cluster without tracing but with metrics operates; a cluster without metrics but with tracing does not operate, it debugs.
The analogy: the cockpit of a modern aircraft
In a modern commercial aircraft, the pilot’s instrument panel has more than 70 active indicators. If there were only one, the altimeter, say, the aircraft would fly into the ground at the first moment of low visibility. You need the altimeter and the attitude indicator, and the airspeed indicator, and the turn indicator, and the fuel gauge, and the oil pressure gauges for each engine, and the turbine exit temperatures. Each answers a different question. And together they cover the operational question: is the aircraft healthy, is it where it should be, and is it going where we want?
Observability for an LLM inference cluster works the same way. A single metric, “GPU utilization 99 %”, answers nothing useful. It is like looking only at the car’s odometer to diagnose why the engine is making a noise. The full cockpit is twelve instruments on the hardware side plus five on the inference engine side, organised into families that answer different questions:
- Compute and efficiency: are the tensor cores doing the work we expect, or are they waiting?
- Memory: is there VRAM left for new requests, or are we on the edge of OOM?
- Thermal and power: is the hardware healthy, or is it silently limiting throughput?
- Health and errors: is there hardware degradation under way (ECC, XID, NVLink)?
- Inference engine: is the queue growing, is the KV pool saturated, is the SLO being met?
The first four answer “is the GPU fine?”. The fifth answers “is it delivering the service we promised?”. The two questions are different and both must have an answer at a glance.
Why nvidia-smi GPU-Util misleads with LLMs
The classic metric that appears in nvidia-smi as GPU-Util corresponds to DCGM_FI_DEV_GPU_UTIL. Its official definition: “percentage of time during which one or more kernels were executing on the GPU”. The problem with LLMs: the decode phase is memory-bound, not compute-bound. When the inference engine decodes token by token, the GPU spends 90 % of the time waiting for the HBM to finish delivering the model weights and the KV cache. There is a kernel running (the HBM read), so GPU-Util reports values close to 100 %. But the tensor cores are idle: the bottleneck is memory, not compute.
Practical result: the operator sees “GPU-Util 99 %” in Grafana and assumes “GPU saturated, no more load can go in”. But the reality may be “compute at 25 %, HBM saturated at 95 %”, which changes the operational decisions (quantisation, batch size, parallelism). The classic metric lies by oversimplifying.
The right move is to look at the three DCGM profiling metrics of the _FI_PROF_* subsystem:
DCGM_FI_PROF_SM_OCCUPANCY: ratio of active warps over the maximum per SM. Is there parallel work?DCGM_FI_PROF_PIPE_TENSOR_ACTIVE: % of cycles with tensor cores effectively active. Is compute working?DCGM_FI_PROF_DRAM_ACTIVE: % of cycles with HBM transferring. Is memory saturated?
A typical decode-bound GPU running Llama 70B on an H100 shows: SM occupancy 35–55 %, tensor active 15–30 %, DRAM active 80–95 %. That is the real “saturated GPU” for LLMs. The three together tell the regimes apart; none of them alone says anything actionable.
How they are wired up in production
The platform side is covered in Five maturity levels (level 4, the GPU plane) and Seven deployment phases (phase F5). For the observer, the key pieces are:
NVIDIA GPU Operator. Helm manifests that deploy on each GPU node: drivers, container toolkit, MIG manager and DCGM Exporter. The last one exposes /metrics in Prometheus format with all the DCGM_FI_* listed above. It is scraped from the cluster’s internal Prometheus.
Inference engine. vLLM exposes /metrics on port 8000 (default) with vllm:* metrics. SGLang exposes it too, with the sglang: prefix. TensorRT-LLM exposes it through Triton Inference Server with the nv_inference: prefix. The basic naming convention is similar across the three engines; the thresholds and queries in this post assume vLLM, but they translate.
ServiceMonitor / PodMonitor. The Prometheus operator resource that says what to scrape. Minimal example:
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: vllm-inference
spec:
selector:
matchLabels: { app: vllm }
podMetricsEndpoints:
- port: metrics
interval: 15s
Dashboards. The NVIDIA operator publishes reference Grafana dashboards for DCGM in nvidia/dcgm-exporter (the official repo). vLLM publishes one in vllm-project/vllm (the examples/ folder). Both work as a base; each team adds the panels specific to its own SLO.
The twelve DCGM metrics organised by family
Family 1 — Compute
DCGM_FI_PROF_SM_OCCUPANCY: ratio of active warps per SM over the maximum possible. Value between 0 and 1.
- Green: 0.30–0.70 (typical LLM decode regime).
- Amber: < 0.20 sustained (batch too small, GPU under-used in parallelism).
- Red: 0.95 sustained with low DRAM_ACTIVE (pathological kernel saturating the SMs).
DCGM_FI_PROF_PIPE_TENSOR_ACTIVE: % of cycles with tensor cores executing. The key metric for “is compute producing?”.
- Green in prefill: 50–80 %.
- Green in decode: 15–30 % (decode is memory-bound, this is not a symptom of a problem).
- Red: < 5 % sustained in prefill, or the engine is not using the tensor cores at all (bad config, incompatible format).
DCGM_FI_PROF_DRAM_ACTIVE: % of cycles with HBM transferring data. The key metric for detecting memory saturation.
- Green in decode: 60–85 %.
- Amber: > 90 % sustained (HBM is a firm bottleneck, which explains high TPOT).
- Red: > 95 % sustained with KV cache pool < 70 % (something other than the engine is asking for HBM; investigate leaks).
Family 2 — Memory
DCGM_FI_DEV_FB_USED: Frame Buffer (HBM) used, in MiB.
- Green: 70–85 % of total.
- Amber: 86–92 %.
- Red: > 92 % (risk of OOM on the next paged-attention allocation).
PromQL for the cluster-wide percentage: 100 * sum(DCGM_FI_DEV_FB_USED) / sum(DCGM_FI_DEV_FB_TOTAL).
DCGM_FI_DEV_FB_FREE: free Frame Buffer. Complementary to the previous one; useful for absolute alerts (< 4096 MiB free).
DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL: aggregate NVLink bandwidth in MB/s. For TP (tensor parallel) topologies that cross GPUs over NVLink, this metric reveals whether the parallelism split is saturating the bus.
- Green: varies with topology. On 4×H100 SXM with NVLink 4.0, theoretical capacity is 450 GB/s per GPU. A typical TP=4 regime: 50–150 GB/s sustained.
- Red: > 90 % of capacity sustained (check whether the model would fit with lower TP or with pipeline parallel).
Family 3 — Thermal and power
DCGM_FI_DEV_GPU_TEMP: die temperature in °C.
- Green: < 75 °C.
- Amber: 75–82 °C.
- Red: > 83 °C (close to the H100’s automatic thermal throttle; check ventilation, air flow, rack inlet temperature).
DCGM_FI_DEV_POWER_USAGE: draw in watts. For the H100 SXM, nominal TDP is 700 W. Useful for three things: spotting an unusually low workload (suspect idle or a stall), billing real energy cost, and firing alerts if the draw approaches the PDU limit.
DCGM_FI_DEV_CLOCK_THROTTLE_REASONS: encoded bitmap with the active throttle reasons. This is the metric that silently explains TPOT degradations.
Relevant bits:
0x0000000000000001: Idle (not a problem).0x0000000000000002: App clocks setting.0x0000000000000004: SW Power Cap (software limit, e.g. fromnvidia-smi -pl).0x0000000000000008: HW Slowdown.0x0000000000000010: Sync Boost (NVIDIA Sync).0x0000000000000020: SW Thermal Slowdown (software thermal limit).0x0000000000000040: HW Thermal Slowdown (hardware thermal limit, an emergency).0x0000000000000080: HW Power Brake Slowdown (PSU voltage drop).0x0000000000000100: Display Clock Setting.
Any throttle other than Idle with a sustained value > 0 is an alert. TPOT degradation with DRAM_ACTIVE already high and thermal throttle active is the classic “the rack is badly ventilated, it is not the engine’s fault”.
Family 4 — Health
DCGM_FI_DEV_XID_ERRORS: cumulative counter of driver XID errors. XIDs are critical event codes that NVIDIA documents exhaustively (XID 13: graphics engine exception; XID 31: GPU memory page fault; XID 43: reset channel verif error; XID 79: GPU has fallen off the bus; XID 95: uncontained ECC error; and so on). Any increment is an immediate alert: many XIDs require a node reset or an RMA of the GPU.
DCGM_FI_DEV_ECC_DBE_VOL_TOTAL: volatile double-bit ECC errors (uncorrectable). Unlike single-bit ones (which ECC corrects silently and which are counted in DCGM_FI_DEV_ECC_SBE_*), double-bit errors corrupt data. Any value > 0 is a critical alert: the GPU must be drained and inspected.
DCGM_FI_DEV_RETIRED_DBE: physical HBM pages retired because of accumulated double-bit errors. NVIDIA retires faulty pages automatically to prevent future corruption. More than 4–8 retired pages on one GPU suggests silicon degradation: document it and plan a replacement in the next maintenance window.
The five inference engine metrics (vLLM)
The DCGM metrics answer “is the GPU healthy?”. The engine metrics answer “is the service meeting the SLO?”. Without them, you know the hardware works but you do not know whether the clients are happy.
vllm:num_requests_running: requests currently in the batch. If it reaches the configured --max-num-seqs and does not come down, the engine is saturated on concurrency (check VRAM and rebalance through the autoscaler, see Autoscaling LLM on Kubernetes).
vllm:num_requests_waiting: requests queued, not yet in the batch. Any value > 0 sustained over minutes means the cluster is not scaling with the load. This is the primary metric for HPA.
vllm:gpu_cache_usage_perc: % of the KV cache pool in use.
- Green: 50–80 %.
- Amber: 80–92 %.
- Red: > 92 % (risk of preempt-on-OOM: vLLM will drop requests to free memory, which raises TTFT visibly).
vllm:time_to_first_token_seconds: histogram of TTFT per request. Consumed as histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket[5m]))). Compared against the TTFT P95 SLO, it fires the primary service alert.
vllm:time_per_output_token_seconds: histogram of TPOT. Equivalent to the previous one but for streaming smoothness. Compared against the TPOT P95 SLO, it fires the secondary alert.
The six alerts that must page in production
Any serious production cluster fires these six alerts to a channel with an on-call rotation. Without them, the SLO is met by luck, not by process.
groups:
- name: gpu-llm-critical
rules:
- alert: GpuHbmNearOom
expr: 100 * (DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL) > 92
for: 2m
labels: { severity: critical }
annotations:
summary: "HBM on {{ $labels.gpu }} at {{ $value }}% — OOM risk"
- alert: GpuThermalOrPowerThrottle
expr: (DCGM_FI_DEV_CLOCK_THROTTLE_REASONS != 0) and ignoring(reason) (DCGM_FI_DEV_CLOCK_THROTTLE_REASONS != 1)
for: 1m
labels: { severity: warning }
annotations:
summary: "GPU {{ $labels.gpu }} throttling (reasons={{ $value }})"
- alert: GpuXidErrorDetected
expr: increase(DCGM_FI_DEV_XID_ERRORS[5m]) > 0
labels: { severity: critical }
annotations:
summary: "XID error on GPU {{ $labels.gpu }} — investigate immediately"
- alert: GpuEccDoubleBit
expr: DCGM_FI_DEV_ECC_DBE_VOL_TOTAL > 0
labels: { severity: critical }
annotations:
summary: "ECC double-bit on GPU {{ $labels.gpu }} — drain node"
- alert: VllmKvCachePoolNearFull
expr: vllm:gpu_cache_usage_perc > 0.95
for: 3m
labels: { severity: warning }
annotations:
summary: "KV cache pool > 95% on {{ $labels.instance }}"
- alert: VllmTtftP95OutOfSlo
expr: histogram_quantile(0.95, sum by(le, instance)(rate(vllm:time_to_first_token_seconds_bucket[5m]))) > 1.5
for: 5m
labels: { severity: warning }
annotations:
summary: "TTFT P95 over SLO ({{ $value }}s > 1.5s)"
These six cover 80 % of the incidents that affect the SLO. The remaining 20 % demand investigation with tracing (see LLM tracing with OpenTelemetry GenAI).
Master table: thresholds and queries
| Metric | Green | Amber | Red | Base query (PromQL) |
|---|---|---|---|---|
| SM occupancy | 0.30–0.70 | 0.15–0.30 | < 0.10 sustained | DCGM_FI_PROF_SM_OCCUPANCY |
| Tensor active (decode) | 15–30 % | < 10 % | < 3 % | DCGM_FI_PROF_PIPE_TENSOR_ACTIVE |
| DRAM active | 60–85 % | 85–95 % | > 95 % with low KV | DCGM_FI_PROF_DRAM_ACTIVE |
| FB used | 70–85 % | 86–92 % | > 92 % | 100 * DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL |
| NVLink BW | < 70 % cap | 70–90 % cap | > 90 % cap | DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL |
| GPU temp | < 75 °C | 75–82 °C | > 83 °C | DCGM_FI_DEV_GPU_TEMP |
| Power usage | < 90 % TDP | 90–98 % TDP | > 98 % TDP | DCGM_FI_DEV_POWER_USAGE |
| Throttle reasons | 0 or Idle | App/SW | HW Therm/Power | DCGM_FI_DEV_CLOCK_THROTTLE_REASONS |
| XID errors | no change | — | any delta | increase(DCGM_FI_DEV_XID_ERRORS[5m]) |
| ECC DBE | 0 | — | > 0 | DCGM_FI_DEV_ECC_DBE_VOL_TOTAL |
| Retired pages | < 4 | 4–8 | > 8 | DCGM_FI_DEV_RETIRED_DBE |
| KV cache used | 50–80 % | 80–92 % | > 92 % | vllm:gpu_cache_usage_perc |
| Requests waiting | 0 | 1–5 sustained | > 10 sustained | vllm:num_requests_waiting |
| TTFT P95 | < SLO | 80–100 % SLO | > SLO | see alert query above |
| TPOT P95 | < SLO | 80–100 % SLO | > SLO | histogram_quantile(0.95, sum by(le)(rate(vllm:time_per_output_token_seconds_bucket[5m]))) |
Three pitfalls that confuse the junior operator
Pitfall 1 — “GPU-Util at 99 % = saturated”. As explained at the start, DCGM_FI_DEV_GPU_UTIL lights up with any kernel. The right move is to look at the three _PROF_* metrics (SM occupancy, tensor active, DRAM active) together. GPU util 99 % + tensor active 8 % + DRAM active 92 % means “saturated by memory, not compute”; GPU util 99 % + tensor active 75 % + DRAM active 50 % means “saturated by compute, prefill heavy”. The two situations call for different levers.
Pitfall 2 — confusing single-bit ECC (SBE) with double-bit (DBE). Single-bit errors are corrected silently and are unavoidable in any HBM under load (cosmic radiation, voltage fluctuations). An SBE counter growing slowly is not an alert, it is physics. DBE is: it corrupts data. Telling the two metrics apart avoids false alarms and false negatives in equal measure.
Pitfall 3 — alerting on num_requests_waiting > 0 without context. An instantaneous value of 1 or 2 during a spike is normal. What matters is a sustained queue: use for: 5m with a threshold of 3–5. Without that window, the system floods the alert channel with noise.
Applied to typical on-premise hardware
For a generic cluster of 4×H100 SXM 80 GB with intra-node NVLink:
- DCGM Exporter deployed through the NVIDIA GPU Operator, one DaemonSet per GPU node.
- Internal Prometheus with 30-day retention for high-frequency metrics, 1 year for downsampled ones (Thanos/Mimir if the volume justifies it).
- Grafana with three standard dashboards: GPU hardware (DCGM), engine (vLLM), SLO (TTFT/TPOT/RPS against written targets).
- Alertmanager with an on-call rotation and rate limiting through silences grouped by node.
- Cardinality kept under control:
gpu(local id),node,pod,model. Do not addrequest_idor other high-cardinality labels to metrics; that is tracing’s job.
Estimated volume for a 16-GPU cluster scraped every 15 s: around 2 million samples/min, around 25 GB/day of raw Prometheus. Manageable with one Prometheus per cluster plus retention; if the team scales beyond 64 GPUs, consider a Thanos sidecar or VictoriaMetrics. See OSS LLMOps tooling catalogue for equivalent alternatives.
What we have not covered (upcoming articles)
- Tracing of LLM workloads: already covered in LLM tracing with OpenTelemetry GenAI.
- Autoscaling based on these metrics: see Autoscaling LLM on Kubernetes.
- Incident response runbooks: how each of these alerts translates into concrete action (drain, restart, RMA, scaling, rollback).
- Cost accounting: using
DCGM_FI_DEV_POWER_USAGEandvllm:request_success_totalfor per-tenant cost showback. - Multi-tenant fairness monitoring: when several tenants share a cluster, which metrics detect that one of them is hogging the KV cache.
See also
FinOps and multi-tenancy on the GPU cluster: who pays for what — DCGM utilisation as the cluster’s FinOps governance metric.
LLM tracing with OpenTelemetry GenAI — the other half of observability.
Capacity planning for on-premise LLM inference — what was sized and, therefore, which thresholds are defensible here.
Continuous batching — explains why
num_requests_running,num_requests_waitingandgpu_cache_usage_percare the engine’s operational metrics.Five maturity levels — LLM-aware observability lives at level 4.
Seven layers of the on-premise LLM inference stack — DCGM Exporter is a piece of the platform layer.
Autoscaling LLM on Kubernetes — uses these metrics as input.
Anatomy of the twelve DCGM and five vLLM metrics — a deeper dive with an analogy and a documented production anomaly for each metric, with figures from public incidents (Meta Llama 3, Story of Two GPUs, vLLM issues, Dell/Lenovo KBs).
Incident response runbooks for LLM with Keep + Kafka — the translation of each critical alert into concrete action (drain, reset, RMA, rollback) with workflow YAML, a WORM Kafka schema and the fit with ISO 27035, ENS, NIS2 and EU AI Act art. 73.
NVLink, NVSwitch and NCCL: the cable every token travels through — the NVLink counters (
nvidia-smi nvlink -e, TX/RX bytes per link, CRC errors) that these DCGM metrics expose: a slow all-reduce shows up in a cable error counter before it shows up in API latency.GPU utilisation as a FinOps lever — from the DCGM metrics (GPU_UTIL vs SM_ACTIVE, MFU) to the cost of idle and the cost per token.
References
- NVIDIA — DCGM Exporter (repo
nvidia/dcgm-exporter, documented metrics and units). - NVIDIA — DCGM Field Identifiers reference (complete list of
DCGM_FI_*). - NVIDIA — XID Errors documentation (catalogue of XID codes and remediation procedures).
- NVIDIA — NVIDIA GPU Operator (official Helm chart).
- vLLM project —
examples/production_monitoring/(reference PromQL and Grafana dashboards). - Prometheus — Histogram and summary best practices (for building defensible percentile queries).
- NVIDIA — H100 Tensor Core GPU datasheet (TDP, HBM bandwidth, NVLink capacities).