Observabilidad GPU para inferencia LLM: las doce métricas DCGM y vLLM que dictan la salud de tu producción
Este post complementa los de Tracing LLM con OpenTelemetry GenAI (la capa de tracing por encima de las métricas), Capacity planning (qué se dimensionó y qué se debe vigilar) y Continuous batching (el mecanismo que explica varias de las métricas del motor).
TL;DR
La observabilidad de un cluster de inferencia LLM se construye sobre dos fuentes complementarias: las métricas del hardware GPU expuestas por DCGM (Data Center GPU Manager) Exporter —parte del NVIDIA GPU Operator— y las métricas del motor de inferencia (vLLM, SGLang, TensorRT-LLM) expuestas en /metrics Prometheus-compatibles. Ninguna de las dos basta sola. La métrica clásica de nvidia-smi llamada GPU utilization es engañosa para LLMs: marca alto cuando hay cualquier kernel ejecutándose, sin distinguir tensor cores ardiendo de SMs esperando por HBM. La cabina de pilotaje completa tiene doce métricas DCGM en cuatro familias (compute: DCGM_FI_PROF_SM_OCCUPANCY, DCGM_FI_PROF_PIPE_TENSOR_ACTIVE, DCGM_FI_PROF_DRAM_ACTIVE; memoria: DCGM_FI_DEV_FB_USED, DCGM_FI_DEV_FB_FREE, DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL; térmico-energético: DCGM_FI_DEV_GPU_TEMP, DCGM_FI_DEV_POWER_USAGE, DCGM_FI_DEV_CLOCK_THROTTLE_REASONS; salud: DCGM_FI_DEV_XID_ERRORS, DCGM_FI_DEV_ECC_DBE_VOL_TOTAL, DCGM_FI_DEV_RETIRED_DBE) y cinco métricas del motor vLLM (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). Cada una tiene un umbral verde/ámbar/rojo defendible, una PromQL para alerta, y al menos una falsa lectura habitual que confunde al operador junior. Las seis alertas críticas que cualquier cluster productivo debe disparar son: HBM > 92 %, throttle por térmico o por power, XID error, ECC double-bit, KV cache pool > 95 %, y TTFT P95 fuera de SLO durante 5 minutos. El objetivo de tener este panel: que el operador de turno diagnostique el origen de una degradación en menos de cinco minutos, sin abrir consola SSH a las GPUs. Cuando esto se cumple, el cluster ha pasado a operación profesional; mientras no, se opera por intuición.
Estás aquí: OBSERVE (la otra mitad del tracing)
El tracing —ya cubierto en Tracing LLM con OpenTelemetry GenAI— responde qué pasó en esta request concreta. Las métricas responden qué está pasando en el cluster en agregado. Son complementarias: una alerta del lado de métricas te dice “el clúster está degradando”, el tracing te dice “y esta es la traza concreta que te lo demuestra”. Un cluster sin tracing pero con métricas opera; un cluster sin métricas pero con tracing no opera, debuggea.
La analogía: la cabina de un avión moderno
En un avión comercial moderno, el panel de instrumentos del piloto tiene más de 70 indicadores activos. Si solo hubiese uno —el altímetro, por ejemplo— el avión volaría hacia el suelo en el primer momento de baja visibilidad. Hace falta el altímetro y el indicador de actitud, y el de velocidad, y el de viraje, y el de combustible, y los de presión de aceite de cada motor, y las temperaturas de salida de turbina. Cada uno responde una pregunta distinta. Y todos juntos cubren la pregunta operacional: ¿está el avión sano, está donde debe, y va donde queremos?
La observabilidad de un cluster de inferencia LLM funciona igual. Una sola métrica —“GPU utilization 99 %"— no responde nada útil. Es como mirar solo el cuentakilómetros del coche para diagnosticar por qué hace ruido el motor. La cabina completa es doce instrumentos del lado de hardware más cinco del lado del motor de inferencia, organizados en familias que responden preguntas distintas:
- Compute y eficiencia: ¿están los tensor cores haciendo el trabajo que esperamos o están esperando?
- Memoria: ¿queda VRAM para nuevas requests o estamos al borde del OOM?
- Térmico y energético: ¿el hardware está sano o está limitando el throughput silenciosamente?
- Salud y errores: ¿hay degradación del hardware en curso (ECC, XID, NVLink)?
- Motor de inferencia: ¿la cola crece, el KV pool está saturado, el SLO se está cumpliendo?
Las cuatro primeras responden a “¿la GPU está bien?”. La quinta responde a “¿está dando el servicio que prometimos?”. Las dos preguntas son distintas y ambas deben tener respuesta a un golpe de vista.
Por qué nvidia-smi GPU-Util engaña en LLMs
La métrica clásica que aparece en nvidia-smi como GPU-Util corresponde a DCGM_FI_DEV_GPU_UTIL. Su definición oficial: “porcentaje del tiempo durante el cual uno o más kernels estuvieron ejecutándose en la GPU”. El problema en LLMs: la fase de decode es memory-bound, no compute-bound. Cuando el motor de inferencia hace decode token a token, la GPU pasa el 90 % del tiempo esperando que la HBM termine de entregar los pesos del modelo y el KV cache. Hay un kernel corriendo (lectura de HBM); por tanto GPU-Util reporta valores cercanos al 100 %. Pero los tensor cores están parados — el cuello de botella es la memoria, no el compute.
Resultado práctico: el operador ve “GPU-Util 99 %” en Grafana y asume “GPU saturada, no se puede meter más carga”. Pero la realidad puede ser “compute al 25 %, HBM saturada al 95 %”, lo que cambia las decisiones operativas (quantization, batch size, paralelismo). La métrica clásica miente por simplificación.
Lo correcto es mirar las tres métricas de profiling DCGM del subsistema _FI_PROF_*:
DCGM_FI_PROF_SM_OCCUPANCY— ratio de warps activos sobre máximos por SM. ¿Hay trabajo paralelo?DCGM_FI_PROF_PIPE_TENSOR_ACTIVE— % de ciclos con tensor cores efectivamente activos. ¿Está el compute trabajando?DCGM_FI_PROF_DRAM_ACTIVE— % de ciclos con la HBM transfiriendo. ¿Está la memoria saturada?
Una decode-bound GPU típica de Llama 70B en H100 muestra: SM occupancy 35–55 %, tensor active 15–30 %, DRAM active 80–95 %. Esa es la “GPU saturada” real para LLMs. Las tres juntas distinguen los regímenes; cada una sola no dice nada accionable.
Cómo se montan en producción
La parte de plataforma se cubre en Cinco niveles de madurez (nivel 4 — GPU plane) y Siete fases de despliegue (fase F5). Para el observador, las piezas clave son:
NVIDIA GPU Operator. Manifiestos Helm que despliegan en cada nodo GPU: drivers, container toolkit, MIG manager y DCGM Exporter. Este último expone /metrics en formato Prometheus con todos los DCGM_FI_* listados arriba. Se scrapea desde el Prometheus interno del cluster.
Motor de inferencia. vLLM expone /metrics en el puerto 8000 (default) con métricas vllm:*. SGLang lo expone también con prefijo sglang:. TensorRT-LLM lo expone vía Triton Inference Server con prefijo nv_inference:. La convención básica de nombres es similar entre los tres motores; los umbrales y queries de este post asumen vLLM, pero se traducen.
ServiceMonitor / PodMonitor. Recurso del operador de Prometheus que indica qué scrapear. Ejemplo mínimo:
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: vllm-inference
spec:
selector:
matchLabels: { app: vllm }
podMetricsEndpoints:
- port: metrics
interval: 15s
Dashboards. El operador de NVIDIA publica dashboards Grafana de referencia para DCGM en nvidia/dcgm-exporter (repo oficial). vLLM publica uno en vllm-project/vllm (carpeta examples/). Ambos sirven como base; cada equipo añade los paneles propios de su SLO.
Las doce métricas DCGM organizadas por familia
Familia 1 — Compute
DCGM_FI_PROF_SM_OCCUPANCY — Ratio de warps activos por SM sobre el máximo posible. Valor entre 0 y 1.
- Verde: 0.30–0.70 (régimen típico LLM en decode).
- Ámbar: < 0.20 sostenido (batch demasiado pequeño, GPU infrautilizada en paralelismo).
- Rojo: 0.95 sostenido con DRAM_ACTIVE bajo (kernel patológico saturando SMs).
DCGM_FI_PROF_PIPE_TENSOR_ACTIVE — % de ciclos con tensor cores ejecutando. La métrica clave de “¿el compute está produciendo?”.
- Verde en prefill: 50–80 %.
- Verde en decode: 15–30 % (decode es memory-bound, no es síntoma de problema).
- Rojo: < 5 % sostenido en prefill o el motor no usa los tensor cores (mala config, formato incompatible).
DCGM_FI_PROF_DRAM_ACTIVE — % de ciclos con HBM transfiriendo datos. Métrica clave para detectar saturación de memoria.
- Verde en decode: 60–85 %.
- Ámbar: > 90 % sostenido (HBM cuello de botella firme — explica la TPOT alta).
- Rojo: > 95 % sostenido con KV cache pool < 70 % (algo está pidiendo HBM que no es el motor; investigar leaks).
Familia 2 — Memoria
DCGM_FI_DEV_FB_USED — Frame Buffer (HBM) usado en MiB.
- Verde: 70–85 % del total.
- Ámbar: 86–92 %.
- Rojo: > 92 % (riesgo de OOM en el siguiente paged-attention allocation).
PromQL para porcentaje sobre cluster: 100 * sum(DCGM_FI_DEV_FB_USED) / sum(DCGM_FI_DEV_FB_TOTAL).
DCGM_FI_DEV_FB_FREE — Frame Buffer libre. Complementaria de la anterior; útil para alertas absolutas (< 4096 MiB libres).
DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL — Bandwidth NVLink agregado en MB/s. Para topologías TP (tensor parallel) que cruzan GPUs vía NVLink, esta métrica revela si el reparto de paralelismo está saturando el bus.
- Verde: variable según topología. En 4×H100 SXM con NVLink 4.0, capacidad teórica 450 GB/s por GPU. Régimen TP=4 típico: 50–150 GB/s sostenido.
- Rojo: > 90 % capacidad sostenido (revisar si el modelo cabría con TP menor o pipeline parallel).
Familia 3 — Térmico y energético
DCGM_FI_DEV_GPU_TEMP — Temperatura del die en °C.
- Verde: < 75 °C.
- Ámbar: 75–82 °C.
- Rojo: > 83 °C (cerca del thermal throttle automático de H100; revisar ventilación, caudal de aire, temperatura de entrada al rack).
DCGM_FI_DEV_POWER_USAGE — Consumo en watts. Para H100 SXM, TDP nominal 700 W. Útil para tres cosas: detectar workload inusualmente bajo (sospechar idle o stall), facturar coste energético real, y disparar alertas si el draw se acerca al límite de la PDU.
DCGM_FI_DEV_CLOCK_THROTTLE_REASONS — Bitmap codificado con las razones de throttle activas. Es la métrica que silenciosamente explica las degradaciones de TPOT.
Bits relevantes:
0x0000000000000001— Idle (no es problema).0x0000000000000002— App clocks setting.0x0000000000000004— SW Power Cap (límite de software, p. ej. pornvidia-smi -pl).0x0000000000000008— HW Slowdown.0x0000000000000010— Sync Boost (NVIDIA Sync).0x0000000000000020— SW Thermal Slowdown (límite térmico de software).0x0000000000000040— HW Thermal Slowdown (límite térmico de hardware — emergencia).0x0000000000000080— HW Power Brake Slowdown (caída de tensión PSU).0x0000000000000100— Display Clock Setting.
Cualquier throttle salvo Idle con valor > 0 sostenido es alerta. La degradación de TPOT con DRAM_ACTIVE ya alto y throttle térmico activo es el clásico “el rack está mal ventilado, no es problema del motor”.
Familia 4 — Salud
DCGM_FI_DEV_XID_ERRORS — Contador acumulado de XID errors del driver. Los XID son códigos de evento crítico que NVIDIA documenta exhaustivamente (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; etc.). Cualquier incremento es alerta inmediata: muchos XID requieren reset del nodo o RMA de la GPU.
DCGM_FI_DEV_ECC_DBE_VOL_TOTAL — Errores ECC double-bit volátiles (no corregibles). A diferencia de los single-bit (que ECC corrige silenciosamente y se contabilizan en DCGM_FI_DEV_ECC_SBE_*), los double-bit corrompen datos. Cualquier valor > 0 es alerta crítica: la GPU debe ser drenada y revisada.
DCGM_FI_DEV_RETIRED_DBE — Páginas físicas de HBM retiradas por double-bit errors acumulados. NVIDIA retira páginas defectuosas automáticamente para prevenir corrupción futura. Más de 4–8 páginas retiradas en una GPU sugiere degradación del silicio: documentar y planificar reemplazo en próxima ventana de mantenimiento.
Las cinco métricas del motor de inferencia (vLLM)
Las métricas DCGM responden “¿está sana la GPU?”. Las del motor responden “¿está el servicio cumpliendo el SLO?”. Sin ellas, sabes que el hardware funciona pero no sabes si los clientes están contentos.
vllm:num_requests_running — Requests actualmente en el batch. Si llega al --max-num-seqs configurado y no baja, el motor está saturado en concurrencia (revisar VRAM y rebalancear vía autoscaler — ver Autoscaling LLM en Kubernetes).
vllm:num_requests_waiting — Requests en cola, sin entrar al batch. Cualquier valor > 0 sostenido durante minutos indica que el cluster no escala con la carga. Esta es la métrica primaria para HPA.
vllm:gpu_cache_usage_perc — % del KV cache pool usado.
- Verde: 50–80 %.
- Ámbar: 80–92 %.
- Rojo: > 92 % (riesgo de preempt-on-OOM: vLLM tirará requests para liberar memoria, lo que aumenta TTFT visiblemente).
vllm:time_to_first_token_seconds — Histograma de TTFT por request. Se consume como histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket[5m]))). Comparado contra el SLO de TTFT P95 dispara la alerta primaria de servicio.
vllm:time_per_output_token_seconds — Histograma de TPOT. Equivalente al anterior pero para fluidez de streaming. Comparado contra el SLO de TPOT P95 dispara la alerta secundaria.
Las seis alertas que deben pagear en producción
Cualquier cluster productivo serio dispara estas seis alertas a un canal con rotación de guardia. Sin estas, el SLO se cumple por suerte, no por proceso.
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 de {{ $labels.gpu }} en {{ $value }}% — riesgo OOM"
- 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 }} en throttle (reasons={{ $value }})"
- alert: GpuXidErrorDetected
expr: increase(DCGM_FI_DEV_XID_ERRORS[5m]) > 0
labels: { severity: critical }
annotations:
summary: "XID error en GPU {{ $labels.gpu }} — investigar inmediatamente"
- alert: GpuEccDoubleBit
expr: DCGM_FI_DEV_ECC_DBE_VOL_TOTAL > 0
labels: { severity: critical }
annotations:
summary: "ECC double-bit en GPU {{ $labels.gpu }} — drenar nodo"
- alert: VllmKvCachePoolNearFull
expr: vllm:gpu_cache_usage_perc > 0.95
for: 3m
labels: { severity: warning }
annotations:
summary: "KV cache pool > 95% en {{ $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 sobre SLO ({{ $value }}s > 1.5s)"
Estas seis cubren el 80 % de los incidentes que afectan a SLO. El 20 % restante exige investigación con tracing (ver Tracing LLM con OpenTelemetry GenAI).
Tabla maestra: umbrales y queries
| Métrica | Verde | Ámbar | Rojo | Query base (PromQL) |
|---|---|---|---|---|
| SM occupancy | 0.30–0.70 | 0.15–0.30 | < 0.10 sostenido | 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 % con KV bajo | 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 o Idle | App/SW | HW Therm/Power | DCGM_FI_DEV_CLOCK_THROTTLE_REASONS |
| XID errors | sin cambio | — | cualquier 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 sostenido | > 10 sostenido | vllm:num_requests_waiting |
| TTFT P95 | < SLO | 80–100 % SLO | > SLO | ver query alerta arriba |
| TPOT P95 | < SLO | 80–100 % SLO | > SLO | histogram_quantile(0.95, sum by(le)(rate(vllm:time_per_output_token_seconds_bucket[5m]))) |
Tres pitfalls que confunden al operador junior
Pitfall 1 — “GPU-Util al 99 % = saturada”. Como se explicó al inicio: DCGM_FI_DEV_GPU_UTIL se enciende con cualquier kernel. Lo correcto es mirar las tres _PROF_* (SM occupancy, tensor active, DRAM active) juntas. GPU util 99 % + tensor active 8 % + DRAM active 92 % = “saturada por memoria, no compute”; GPU util 99 % + tensor active 75 % + DRAM active 50 % = “saturada por compute, prefill heavy”. Las dos situaciones piden palancas distintas.
Pitfall 2 — confundir ECC single-bit (SBE) con double-bit (DBE). Los single-bit se corrigen silenciosamente y son inevitables en cualquier HBM bajo carga (radiación cósmica, fluctuaciones de tensión). Un contador SBE creciendo lentamente no es alerta — es física. El DBE sí: corrompe datos. Distinguir las dos métricas evita falsas alarmas y falsos negativos a partes iguales.
Pitfall 3 — alertar sobre num_requests_waiting > 0 sin contexto. Un valor instantáneo de 1 o 2 durante un pico es normal. Lo que importa es la cola sostenida: usar for: 5m con umbral 3–5. Sin esa ventana, el sistema satura el canal de alertas con ruido.
Aplicado a hardware on-premise típico
Para un cluster genérico de 4×H100 SXM 80 GB con NVLink intra-nodo:
- DCGM Exporter desplegado vía NVIDIA GPU Operator, un DaemonSet por nodo GPU.
- Prometheus interno con retención 30 días para métricas de alta frecuencia, 1 año para downsampled (Thanos/Mimir si el volumen lo justifica).
- Grafana con tres dashboards estándar: hardware GPU (DCGM), motor (vLLM), SLO (TTFT/TPOT/RPS contra objetivos escritos).
- Alertmanager con rotación de guardia y rate-limiting por silencio agrupado por nodo.
- Cardinalidad controlada:
gpu(id local),node,pod,model— no añadirrequest_idni labels de alta cardinalidad a métricas (eso es trabajo del tracing).
Volumen estimado para un cluster de 16 GPUs con scraping cada 15 s: ~2 millones de samples/min, ~25 GB/día de Prometheus crudo. Manejable con un Prometheus por cluster + retention; si el equipo escala a > 64 GPUs, considerar Thanos sidecar o VictoriaMetrics. Ver Catálogo de herramientas OSS LLMOps para alternativas equivalentes.
Lo que no hemos cubierto (próximos artículos)
- Tracing de cargas LLM: ya cubierto en Tracing LLM con OpenTelemetry GenAI.
- Autoscaling basado en estas métricas: ver Autoscaling LLM en Kubernetes.
- Runbooks de incident response: cómo cada una de estas alertas se traduce a acción concreta (drain, restart, RMA, escalado, rollback).
- Cost accounting: usar
DCGM_FI_DEV_POWER_USAGEyvllm:request_success_totalpara showback de coste por tenant. - Monitorización de fairness multi-tenant: cuando varios tenants comparten cluster, qué métricas detectan que uno está acaparando el KV cache.
Ver también
- Tracing LLM con OpenTelemetry GenAI — la otra mitad de la observabilidad.
- Capacity planning para inferencia LLM on-premise — qué se dimensionó y, por tanto, qué umbrales son defendibles aquí.
- Continuous batching — explica por qué
num_requests_running,num_requests_waitingygpu_cache_usage_percson las métricas operativas del motor. - Cinco niveles de madurez — la observabilidad LLM-aware vive en el nivel 4.
- Siete capas del stack de inferencia LLM on-premise — DCGM Exporter es pieza de la capa de plataforma.
- Autoscaling LLM en Kubernetes — usa estas métricas como input.
- Anatomía de las doce métricas DCGM y cinco vLLM — profundización con analogía y anomalía documentada en producción para cada métrica, con cifras de incidentes públicos (Meta Llama 3, Story of Two GPUs, issues vLLM, KBs Dell/Lenovo).
- Runbooks de incident response para LLM con Keep + Kafka — la traducción de cada alerta crítica a acción concreta (drain, reset, RMA, rollback) con workflow YAML, schema Kafka WORM y encaje en ISO 27035, ENS, NIS2, EU AI Act art. 73.
Referencias
- NVIDIA — DCGM Exporter (repo
nvidia/dcgm-exporter, métricas y unidades documentadas). - NVIDIA — DCGM Field Identifiers reference (lista completa de
DCGM_FI_*). - NVIDIA — XID Errors documentation (catálogo de códigos XID y procedimientos de remediación).
- NVIDIA — NVIDIA GPU Operator (Helm chart oficial).
- vLLM project —
examples/production_monitoring/(PromQL y dashboards Grafana de referencia). - Prometheus — Histogram and summary best practices (para construir queries de percentiles defendibles).
- NVIDIA — H100 Tensor Core GPU datasheet (TDP, HBM bandwidth, NVLink capacities).