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)

You are here: OBSERVE · metrics (DCGM + engine) complement tracing1 · Data2 · Tune3 · Eval4 · Deploy5 · Observe6 · Retrain

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

DCGM cockpit: 12 metrics in 4 familiesCOMPUTEDCGM_FI_PROF_SM_OCCUPANCYDCGM_FI_PROF_PIPE_TENSOR_ACTIVEDCGM_FI_PROF_DRAM_ACTIVEIs compute working orwaiting on HBM?MEMORYDCGM_FI_DEV_FB_USEDDCGM_FI_DEV_FB_FREEDCGM_FI_DEV_NVLINK_BANDWIDTH_TOTALIs there VRAM left fornew requests?THERMAL · POWERDCGM_FI_DEV_GPU_TEMPDCGM_FI_DEV_POWER_USAGEDCGM_FI_DEV_CLOCK_THROTTLE_REASONSHealthy hardware orsilently limiting?HEALTHDCGM_FI_DEV_XID_ERRORSDCGM_FI_DEV_ECC_DBE_VOL_TOTALDCGM_FI_DEV_RETIRED_DBEIs the silicondegrading right now?Each family answers a different question · none suffices alone

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. from nvidia-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

MetricGreenAmberRedBase query (PromQL)
SM occupancy0.30–0.700.15–0.30< 0.10 sustainedDCGM_FI_PROF_SM_OCCUPANCY
Tensor active (decode)15–30 %< 10 %< 3 %DCGM_FI_PROF_PIPE_TENSOR_ACTIVE
DRAM active60–85 %85–95 %> 95 % with low KVDCGM_FI_PROF_DRAM_ACTIVE
FB used70–85 %86–92 %> 92 %100 * DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL
NVLink BW< 70 % cap70–90 % cap> 90 % capDCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL
GPU temp< 75 °C75–82 °C> 83 °CDCGM_FI_DEV_GPU_TEMP
Power usage< 90 % TDP90–98 % TDP> 98 % TDPDCGM_FI_DEV_POWER_USAGE
Throttle reasons0 or IdleApp/SWHW Therm/PowerDCGM_FI_DEV_CLOCK_THROTTLE_REASONS
XID errorsno changeany deltaincrease(DCGM_FI_DEV_XID_ERRORS[5m])
ECC DBE0> 0DCGM_FI_DEV_ECC_DBE_VOL_TOTAL
Retired pages< 44–8> 8DCGM_FI_DEV_RETIRED_DBE
KV cache used50–80 %80–92 %> 92 %vllm:gpu_cache_usage_perc
Requests waiting01–5 sustained> 10 sustainedvllm:num_requests_waiting
TTFT P95< SLO80–100 % SLO> SLOsee alert query above
TPOT P95< SLO80–100 % SLO> SLOhistogram_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 add request_id or 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_USAGE and vllm:request_success_total for 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

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).