<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Operaciones on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/operaciones/</link><description>Recent content in Operaciones on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Tue, 02 Jun 2026 04:30:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/operaciones/index.xml" rel="self" type="application/rss+xml"/><item><title>Entornos mixtos NVIDIA + Intel para inferencia LLM: del cluster H100 central al NUC en la sucursal</title><link>https://blog.lo0.es/posts/entornos-mixtos-nvidia-intel-servidores-nucs/</link><pubDate>Tue, 02 Jun 2026 04:30:00 +0200</pubDate><guid>https://blog.lo0.es/posts/entornos-mixtos-nvidia-intel-servidores-nucs/</guid><description>&lt;blockquote>
&lt;p>Este post complementa los de &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning para inferencia LLM on-premise&lt;/a> (que asumía cluster NVIDIA puro), &lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">Siete capas del stack&lt;/a> (que tampoco entraba en heterogeneidad de hardware) y &lt;a href="https://blog.lo0.es/posts/router-inferencia-llm-gateway-l7/">El router de inferencia LLM&lt;/a> (donde el routing por capability cobra todo su sentido cuando hay hardware mixto). Es la pieza que faltaba para hablar de &amp;ldquo;soberanía de hardware&amp;rdquo; sin reducirla a &amp;ldquo;qué fabricante elegir&amp;rdquo;.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Un cluster productivo de inferencia LLM en 2026 puede dejar de ser monolítico NVIDIA si acepta heterogeneidad como decisión arquitectónica. La motivación no es teoría sino &lt;strong>tres ventajas operativas medibles&lt;/strong>. (1) &lt;strong>Coste&lt;/strong>: un Intel Xeon 6 con AMX (Advanced Matrix Extensions) entrega 7B INT4 a ~80 tok/s sirviendo embeddings y reranker a una fracción del coste de dedicar una H100 a esa tarea; el &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">capacity planning&lt;/a> cierra mejor con Intel CPU manejando lo barato e NVIDIA H100 el LLM grande. (2) &lt;strong>Soberanía y diversificación de cadena de suministro&lt;/strong>: NVIDIA tiene ~94 % del mercado de AI accelerators (noviembre 2025), single-vendor dependency con todos sus riesgos; Intel fabrica en Europa (Leixlip operativa, Magdeburg planeada) frente a NVIDIA design-only con foundry TSMC, lo que para una organización española/europea con exigencia ENS / NIS2 / EU AI Act es un argumento de hedge real. (3) &lt;strong>Edge&lt;/strong>: un Intel NUC con CPU Lunar Lake (NPU 48 TOPS) o Panther Lake (NPU 50 TOPS + Xe3 120 TOPS = 180 TOPS plataforma) corre modelos 7B INT4 a velocidad usable, lo que abre el patrón &amp;ldquo;sucursal con inferencia local + DC central para casos complejos&amp;rdquo;. Hardware Intel relevante en junio 2026: &lt;strong>Intel Gaudi 3&lt;/strong> (128 GB HBM2e, 1835 TFLOPS BF16/FP8, 3.67 TB/s; competidor directo a H100 — Intel reclama +20 % en Llama 2 70B pero Signal65 publicó H200 9× sobre Gaudi 3 en Llama 3.1 405B, hay que citar ambos; Falcon Shores &lt;strong>cancelado&lt;/strong> enero 2025, Jaguar Shores 2026 como apuesta de reinicio, &lt;strong>Gaudi 4 confirmado que no existirá&lt;/strong>); &lt;strong>Intel Xeon 6 con AMX&lt;/strong> (hasta 288 cores E-core en Sierra Forest o 86 P-core en Granite Rapids, 1024 FLOPS BF16/ciclo/core con AMX, Intel reclama 2.7× tok/s vs EPYC 9965 en vLLM CPU backend); &lt;strong>Intel Arc Pro B60&lt;/strong> (Battlemage, 24 GB GDDR6, 456 GB/s, 197 TOPS INT8, lanzado septiembre 2025 — variante dual-GPU 48 GB y rack &amp;ldquo;Battlematrix&amp;rdquo; con 8× = 192 GB VRAM); &lt;strong>Intel NUC con NPU&lt;/strong> (Lunar Lake 48 TOPS, Arrow Lake similar, Panther Lake 50 TOPS CES 2026; realista para 7-13B INT4, no para los 30-70B que Intel afirma en su marketing). Software: &lt;strong>OpenVINO 2025.3&lt;/strong> con GenAI API y vLLM-OpenVINO; &lt;strong>IPEX-LLM&lt;/strong> con integraciones a llama.cpp, vLLM, HF, LangChain; &lt;strong>vLLM CPU backend&lt;/strong> con AMX; &lt;strong>llama.cpp SYCL&lt;/strong> (mejor que Vulkan en Arc). Cuatro patrones canónicos: embeddings + reranker en Intel al lado del LLM en NVIDIA; guardrails + PII redact en NUC near edge; speculative drafter en NUC cerca del usuario y target en H100; dev workstations NUC. Observabilidad unificada vía DCGM + habana-metric-exporter + intel-gpu-exporter + Intel PCM federados en Prometheus. Pitfalls: tokenizer mismatch entre engines, latencia round-trip edge↔central, FP8 Hopper ≠ INT8 AMX en calidad, sincronización de versiones. Aplicado a un cluster genérico: DC central 4×H100 SXM + sidecar Xeon 6 AMX + 6-12 NUCs Intel en sucursales. &lt;strong>Disclaimer crítico&lt;/strong>: a junio 2026 no hay casos públicos verificables de despliegue mixto NVIDIA + Intel en banca o gobierno europeo; el patrón es &lt;strong>arquitectura emergente&lt;/strong> y recomendable, no práctica establecida con histórico industrial.&lt;/p>
&lt;h2 id="estás-aquí-deploy-con-heterogeneidad-como-decisión">Estás aquí: DEPLOY (con heterogeneidad como decisión)&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="estás aquí: Deploy con hardware heterogéneo">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#7ad88f;stroke-width:3}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#mxm)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#mxm)}&lt;/style>
&lt;defs>&lt;marker id="mxm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">Estás aquí: DEPLOY · hardware heterogéneo NVIDIA + Intel como decisión arquitectónica&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box active"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box idle"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box idle"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="la-analogía-la-fábrica-con-varias-máquinas-distintas">La analogía: la fábrica con varias máquinas distintas&lt;/h2>
&lt;p>Una fábrica seria tiene &lt;strong>varias máquinas con propósitos distintos&lt;/strong>, no una sola máquina universal. Una prensa hidráulica de 200 toneladas para troquelado pesado; un torno de banco para piezas de revolución; una impresora 3D para prototipos rápidos; un robot de pick-and-place para SMD. Cada máquina hace lo que hace &lt;strong>mejor que las demás en su nicho&lt;/strong>, y el gerente de planta dimensiona el mix según el portfolio real de productos, no según moda. Comprar tres prensas hidráulicas porque &amp;ldquo;son las más impresionantes&amp;rdquo; cuando el 60 % del trabajo son piezas de revolución es derrochar capital — el torno es más barato, más rápido para su nicho y libera la prensa para lo que de verdad la necesita.&lt;/p>
&lt;p>Un cluster de inferencia LLM con NVIDIA H100 dedicada a hacer &lt;strong>embeddings de un corpus RAG&lt;/strong> está usando una prensa hidráulica para taladrar pernos. La H100 es magnífica para LLM 70B en BF16 con concurrencia 40+; para embeddings de un documento de 800 tokens en bge-m3, lo que necesitas es un Intel Xeon 6 con AMX a una fracción del coste y consumo eléctrico. Un cluster que quiera servir guardrails ligeros (Llama Guard 4 8B) en cada request, con presupuesto de 50 ms, tampoco necesita ese guardrail en una H100 — un Intel NUC con NPU 48 TOPS cubre el caso con margen.&lt;/p>
&lt;p>La fábrica heterogénea no es elegancia teórica: es &lt;strong>maximizar utilización útil del capital fijo&lt;/strong>. El cluster heterogéneo de inferencia LLM tampoco lo es.&lt;/p>
&lt;h2 id="tres-razones-operativas-para-la-heterogeneidad">Tres razones operativas para la heterogeneidad&lt;/h2>
&lt;h3 id="razón-1--coste">Razón 1 — coste&lt;/h3>
&lt;p>Una H100 SXM 80 GB en operación 24/7 consume ~700 W (medición real al wall ~697 W con vLLM Llama 3.1 405B batch=4) y representa entre 25 000 € y 35 000 € de hardware amortizado. Un Intel Xeon 6 con AMX (Granite Rapids 86 cores o Sierra Forest 288 cores E) consume 350-500 W para el socket y cuesta una fracción. La operativa: la H100 está reservada para el LLM grande (Llama 70B BF16 o FP8, donde su HBM3 y FP8 tensor cores valen su peso); el Xeon AMX absorbe embeddings (bge-m3, e5-large), reranker (bge-reranker-v2-m3), modelos pequeños (Llama 3.2 1B / 3B INT4) y batch processing offline. Es la misma lógica del &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">capacity planning&lt;/a> llevada un paso más allá: en vez de presupuestar VRAM de KV cache solo en H100, presupuestar cada workload en el silicio donde su arithmetic intensity case mejor.&lt;/p>
&lt;h3 id="razón-2--soberanía-y-diversificación-de-la-cadena-de-suministro">Razón 2 — soberanía y diversificación de la cadena de suministro&lt;/h3>
&lt;p>A noviembre 2025, NVIDIA tiene aproximadamente &lt;strong>94 % del mercado de AI accelerators&lt;/strong>. Esa concentración es riesgo. Para una organización con exigencia ENS / NIS2 / EU AI Act, depender de un único proveedor con foundry concentrada en Taiwán (TSMC) introduce vulnerabilidades de cadena de suministro que regulaciones recientes (NIS2, supply chain provisions) están empezando a exigir documentar y mitigar. &lt;strong>Intel diversifica&lt;/strong>: tiene fabs propias en Europa (Leixlip operativa en Irlanda; Magdeburg planeada en Alemania, con financiación EU Chips Act), lo que para un cliente público español o europeo es argumento contractual real, no marketing.&lt;/p>
&lt;p>Disclaimer obligatorio: &lt;strong>el roadmap Intel post-Falcon Shores es inestable&lt;/strong>. Intel canceló Falcon Shores en enero 2025 y relegó Gaudi 4 a &amp;ldquo;no existirá&amp;rdquo;; la apuesta de re-arranque es Jaguar Shores en 2026 como plataforma rack-scale, todavía sin specs públicas confirmadas. La diversificación es estratégicamente correcta, &lt;strong>pero asumir continuidad de roadmap Intel al nivel del de NVIDIA en 2026 sería ingenuo&lt;/strong>. La estrategia operativa: Intel para cargas donde el lock-in es menor (CPU para embeddings, NUC para edge ligero — sustituibles por AMD/Apple/SiFive si Intel pivot otra vez), NVIDIA para el LLM grande donde la madurez del software stack todavía no tiene rival.&lt;/p>
&lt;h3 id="razón-3--edge">Razón 3 — edge&lt;/h3>
&lt;p>El patrón de &amp;ldquo;todo viaja al DC central&amp;rdquo; rompe en tres casos: latencia (sucursal a 100+ ms del DC, inaceptable para chat), soberanía de datos (prompts con datos personales / clasificados que no deben salir del perímetro local), y operación offline (sucursal con conectividad intermitente). El Intel NUC con CPU moderna (Lunar Lake / Arrow Lake / Panther Lake) trae &lt;strong>NPU 48-50 TOPS + iGPU Xe2/Xe3 100-180 TOPS&lt;/strong> en un equipo de 0.5-1.5 L de volumen y 30-65 W de consumo. Modelos 7B INT4 corren a velocidad usable; con quantization más agresiva (Q3_K) cabe Llama 13B. Para sucursales con RAG sobre corpus local + LLM 7B + guardrails, el NUC es perfecto.&lt;/p>
&lt;h2 id="hardware-intel-relevante-junio-2026">Hardware Intel relevante (junio 2026)&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Pieza&lt;/th>
&lt;th>Memoria&lt;/th>
&lt;th>Performance clave&lt;/th>
&lt;th>Lanzamiento&lt;/th>
&lt;th>Estado&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Intel Gaudi 3&lt;/td>
&lt;td>128 GB HBM2e, 3.67 TB/s&lt;/td>
&lt;td>1835 TFLOPS BF16/FP8; 1200 GB/s networking&lt;/td>
&lt;td>abr-2024&lt;/td>
&lt;td>Activo; sucesor Jaguar Shores 2026 (no Gaudi 4)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel Xeon 6 (Granite Rapids)&lt;/td>
&lt;td>DDR5 + MRDIMM&lt;/td>
&lt;td>86 P-cores, AMX 1024 FLOPS BF16/ciclo/core&lt;/td>
&lt;td>2024-2025&lt;/td>
&lt;td>Activo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel Xeon 6 (Sierra Forest)&lt;/td>
&lt;td>DDR5&lt;/td>
&lt;td>288 E-cores&lt;/td>
&lt;td>2024&lt;/td>
&lt;td>Activo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel Arc Pro B60 (Battlemage)&lt;/td>
&lt;td>24 GB GDDR6, 456 GB/s&lt;/td>
&lt;td>197 TOPS INT8; 12.28 TFLOPS FP32&lt;/td>
&lt;td>sep-2025&lt;/td>
&lt;td>Activo; variante dual 48 GB, rack 8× = 192 GB&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel Data Center GPU Max&lt;/td>
&lt;td>128 GB HBM&lt;/td>
&lt;td>sucesor de Ponte Vecchio&lt;/td>
&lt;td>descontinuado&lt;/td>
&lt;td>&lt;strong>Descontinuado&lt;/strong> ene-2026&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel NUC (Lunar Lake)&lt;/td>
&lt;td>DDR5x&lt;/td>
&lt;td>NPU 48 TOPS + Xe2 67 TOPS = 120 TOPS plataforma&lt;/td>
&lt;td>2024&lt;/td>
&lt;td>Activo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel NUC (Arrow Lake)&lt;/td>
&lt;td>DDR5&lt;/td>
&lt;td>NPU 13 TOPS + Xe iGPU&lt;/td>
&lt;td>2024&lt;/td>
&lt;td>Activo (menos NPU que Lunar)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel NUC (Panther Lake)&lt;/td>
&lt;td>DDR5x&lt;/td>
&lt;td>NPU 50 TOPS + Xe3 120 TOPS = 180 TOPS plataforma&lt;/td>
&lt;td>CES ene-2026&lt;/td>
&lt;td>En despliegue&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="intel-gaudi-3--la-nota-crítica-sobre-el-marketing">Intel Gaudi 3 — la nota crítica sobre el marketing&lt;/h3>
&lt;p>Intel publica que Gaudi 3 entrega &lt;strong>+20 % throughput vs H100 en Llama 2 70B inferencia&lt;/strong> y &lt;strong>2× price/performance&lt;/strong>. La cifra aparece en whitepaper oficial y en presentaciones de lanzamiento. Sin embargo, &lt;strong>Signal65 (firma independiente)&lt;/strong> publicó en 2025 que &lt;strong>H200 supera a Gaudi 3 por factor 9× en Llama 3.1 405B&lt;/strong>. La discrepancia es relevante: ambos números pueden ser ciertos para sus benchmarks específicos (Llama 2 70B FP16 vs Llama 3.1 405B FP8) pero la conclusión operativa cambia radicalmente según con cuál te quedes.&lt;/p>
&lt;p>Recomendación de este post: tratar Gaudi 3 como &lt;strong>opción válida para Llama-class 70B en BF16/FP8&lt;/strong> donde Intel reclama paridad o ventaja, no para modelos de frontera 200B+ donde NVIDIA mantiene márgen claro. Y considerar el riesgo de roadmap: Gaudi 4 no existirá; el sucesor de la línea es Jaguar Shores 2026 con arquitectura rack-scale completamente nueva — discontinuidad, no evolución.&lt;/p>
&lt;h3 id="intel-xeon-6-con-amx--el-caballo-de-batalla-cpu">Intel Xeon 6 con AMX — el caballo de batalla CPU&lt;/h3>
&lt;p>Las &lt;strong>Advanced Matrix Extensions (AMX)&lt;/strong> son la pieza no obvia. Cada core P-core de Granite Rapids ejecuta hasta &lt;strong>1024 FLOPS BF16 por ciclo&lt;/strong> vía AMX, lo que convierte un Xeon 6 con 64-86 cores en un acelerador de matriz respetable para modelos pequeños/medianos. Cifras reales reportadas: &lt;strong>Llama 3.2 INT4 a ~57 tok/s con AMX vs 28 tok/s sin AMX&lt;/strong> (factor 2× clean). En servir 7B INT4 con vLLM CPU backend + AMX, Intel reclama &lt;strong>2.7× tok/s vs EPYC 9965&lt;/strong>, cifra con sesgo de Intel pero corroborada cualitativamente por LMSYS en su despliegue DeepSeek R1 671B sobre Xeon 6 + SGLang.&lt;/p>
&lt;p>Caso de uso operativo: &lt;strong>embeddings y reranker&lt;/strong> en un sidecar Xeon 6 al lado del cluster H100. Modelos como &lt;code>bge-m3&lt;/code> (embedding multilingüe) o &lt;code>bge-reranker-v2-m3&lt;/code> corren a throughput aceptable en CPU AMX; no merecen H100 dedicada. Liberar la H100 para el LLM 70B aumenta el RPS efectivo del cluster sin comprar más GPUs.&lt;/p>
&lt;h3 id="intel-arc-pro-b60-y-battlematrix">Intel Arc Pro B60 y Battlematrix&lt;/h3>
&lt;p>Lanzada en septiembre 2025, la Arc Pro B60 (Battlemage) trae &lt;strong>24 GB GDDR6 con 456 GB/s de bandwidth&lt;/strong> y &lt;strong>197 TOPS INT8&lt;/strong> a 200 W. Variante de Maxsun con dual-GPU 48 GB. La configuración rack &amp;ldquo;Battlematrix&amp;rdquo; combina 8 unidades = &lt;strong>192 GB VRAM agregada&lt;/strong> — el punto interesante: a un coste muy inferior a una H100 SXM 80 GB, lo que la hace candidata para LLM 30-70B INT4-INT8 servidos vía OpenVINO o llama.cpp SYCL.&lt;/p>
&lt;p>Phoronix verificó que en SYCL la Arc Pro B70 alcanza &lt;strong>paridad con Radeon PRO W7900&lt;/strong> (generación anterior AMD) en DeepSeek R1 Llama 8B &lt;code>pp512&lt;/code>. Vulkan backend pierde fuerte (~1/4 del rendimiento de SYCL); para Arc Pro siempre SYCL.&lt;/p>
&lt;h3 id="intel-nuc-con-npu--el-edge-node">Intel NUC con NPU — el edge node&lt;/h3>
&lt;p>Los Intel NUC con CPU Lunar Lake (Core Ultra Series 2) traen NPU 4 con &lt;strong>48 TOPS&lt;/strong> y total plataforma &lt;strong>120 TOPS&lt;/strong> sumando iGPU Xe2 y CPU AVX. Panther Lake (CES enero 2026) sube a NPU 5 = 50 TOPS + Xe3 120 TOPS = &lt;strong>180 TOPS plataforma&lt;/strong>.&lt;/p>
&lt;p>Intel afirma que Panther Lake &amp;ldquo;ejecuta modelos 30-70B locales&amp;rdquo;. Comprobación realista: &lt;strong>es marketing&lt;/strong>. El 30-70B INT4 cabe en RAM (DDR5x 32-64 GB) pero la velocidad sostenida con quant Q4_K_M en un NUC ronda 2-8 tok/s; cómodo para uso ocasional, no para servir tráfico. &lt;strong>El sweet spot real del NUC es 7B INT4 a 20-40 tok/s&lt;/strong> sobre iGPU/NPU, perfecto para sucursal de cliente con consultas casuales.&lt;/p>
&lt;h2 id="software-intel--la-pila-relevante">Software Intel — la pila relevante&lt;/h2>
&lt;p>&lt;strong>OpenVINO 2025.3&lt;/strong> (junio 2026) es la pieza central. Soporta deploy con un comando vía OVMS CLI con descarga automática desde HF Hub; integra &lt;code>OpenVINO GenAI&lt;/code> con API C++/Python para pipelines generativas; expone API compatible con vLLM v1 (&lt;code>vLLM-OpenVINO&lt;/code>). Soporte de modelos GGUF: DeepSeek Distill, Qwen 2/2.5, Llama 3. Optimizaciones: Sage Attention (primer token con prompts largos), KV-cache compression por canal.&lt;/p>
&lt;p>&lt;strong>Intel Extension for PyTorch (IPEX)&lt;/strong> — versión XPU 2.8.10+xpu — añade backends Intel a PyTorch. &lt;strong>IPEX-LLM&lt;/strong> es el subproyecto que integra con llama.cpp, Ollama, HuggingFace, LangChain, LlamaIndex, vLLM y DeepSpeed. Mayo 2025: corrió DeepSeek V3/R1 671B y Qwen3MoE 235B en 1-2 Arc A770/B580 con FlashMoE.&lt;/p>
&lt;p>&lt;strong>vLLM CPU backend&lt;/strong> — el branch CPU de vLLM con optimizaciones AMX. Para 7B INT4 en Xeon 4ª gen con AMX: 12-50 tok/s; con Xeon Gold 6530 + INT4: ~80 tok/s. Cifras académicas (arXiv 2410.04466).&lt;/p>
&lt;p>&lt;strong>llama.cpp SYCL&lt;/strong> — el backend recomendado para Arc; Vulkan funciona pero ronda 1/4 del rendimiento SYCL en Arc B580. SYCL alcanza paridad con AMD generación anterior.&lt;/p>
&lt;p>&lt;strong>Habana SynapseAI&lt;/strong> — stack de Gaudi 3. PyTorch bridge &lt;code>habana_frameworks.torch&lt;/code> registra device &lt;code>hpu&lt;/code>; integración con &lt;code>torch.compile&lt;/code>. &lt;strong>No&lt;/strong> es port completo a oneAPI sino integración parcial via oneMKL. Implica que el ecosistema Gaudi mantiene cierta separación del oneAPI general de Intel — relevante de cara al hipotético Jaguar Shores y unificación futura.&lt;/p>
&lt;h2 id="los-cuatro-patrones-canónicos">Los cuatro patrones canónicos&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 340" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="cuatro patrones canónicos NVIDIA + Intel">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.e{fill:#dfe9f5;stroke:#356}.g{fill:#eef0d0;stroke:#7a3}.s{fill:#f4e3cf;stroke:#a63}.d{fill:#ead8f5;stroke:#634}.title{font:600 13px sans-serif;fill:#222}.h{font:700 12px sans-serif;fill:#222}.l{font:11px sans-serif;fill:#222}.n{font:italic 10px sans-serif;fill:#444}&lt;/style>
&lt;text x="410" y="20" text-anchor="middle" class="title">Cuatro patrones canónicos de uso mixto NVIDIA + Intel&lt;/text>
&lt;rect x="20" y="40" width="380" height="120" class="b e"/>
&lt;text x="30" y="62" class="h">1 · EMBEDDINGS + RERANKER EN INTEL&lt;/text>
&lt;text x="30" y="82" class="l">Sidecar Xeon 6 AMX (o Arc Pro B60) sirve bge-m3 +&lt;/text>
&lt;text x="30" y="98" class="l">bge-reranker-v2-m3 al lado del H100 con LLM 70B.&lt;/text>
&lt;text x="30" y="118" class="n">Libera H100 del trabajo barato; mejora RPS efectivo&lt;/text>
&lt;text x="30" y="132" class="n">sin comprar GPU adicional. Pattern más maduro.&lt;/text>
&lt;rect x="420" y="40" width="380" height="120" class="b g"/>
&lt;text x="430" y="62" class="h">2 · GUARDRAILS + PII EN NUC NEAR EDGE&lt;/text>
&lt;text x="430" y="82" class="l">NUC Lunar/Panther Lake en sucursal ejecuta&lt;/text>
&lt;text x="430" y="98" class="l">Llama Guard 4 + Presidio antes del round-trip.&lt;/text>
&lt;text x="430" y="118" class="n">PII jamás sale del perímetro local;&lt;/text>
&lt;text x="430" y="132" class="n">latencia 50-150ms en lugar de 200-500ms.&lt;/text>
&lt;rect x="20" y="170" width="380" height="120" class="b s"/>
&lt;text x="30" y="192" class="h">3 · SPECULATIVE DRAFTER EN NUC&lt;/text>
&lt;text x="30" y="212" class="l">Llama 3.2 1B INT4 en NUC cerca del usuario;&lt;/text>
&lt;text x="30" y="228" class="l">target Llama 70B en H100 central acepta/rechaza.&lt;/text>
&lt;text x="30" y="248" class="n">TTFT cae ~50% si tasa de aceptación &amp;gt; 60%.&lt;/text>
&lt;text x="30" y="262" class="n">Requiere drafter idéntico tokenizer-wise.&lt;/text>
&lt;rect x="420" y="170" width="380" height="120" class="b d"/>
&lt;text x="430" y="192" class="h">4 · DEV WORKSTATIONS NUC&lt;/text>
&lt;text x="430" y="212" class="l">Dev/CI corre tests sobre Llama 3.2 3B en NUC;&lt;/text>
&lt;text x="430" y="228" class="l">prod despliega tras green CI a cluster H100.&lt;/text>
&lt;text x="430" y="248" class="n">Iteración 10× más barata; valida lógica end-to-end&lt;/text>
&lt;text x="430" y="262" class="n">sin gastar GPU productiva.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h3 id="patrón-1--embeddings--reranker-en-intel">Patrón 1 — embeddings + reranker en Intel&lt;/h3>
&lt;p>El más maduro y el más fácil de adoptar. En un sistema RAG típico, cada request del usuario invoca:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Embedding del query&lt;/strong> (50 ms en H100, 80 ms en Xeon AMX, 30 ms en Arc Pro B60).&lt;/li>
&lt;li>&lt;strong>Búsqueda vectorial&lt;/strong> (Qdrant / Milvus / Chroma; latencia ~10-30 ms).&lt;/li>
&lt;li>&lt;strong>Reranker sobre top-k candidatos&lt;/strong> (60 ms en H100, 100-150 ms en Xeon AMX).&lt;/li>
&lt;li>&lt;strong>LLM&lt;/strong> sobre prompt aumentado (200-500 ms TTFT, 30-50 ms/token).&lt;/li>
&lt;/ol>
&lt;p>Los pasos 1 y 3 son &lt;strong>memory-bound + relativamente pequeños&lt;/strong> (modelos 100M-1B): Xeon 6 con AMX (Arc Pro B60 más rápida pero ya GPU dedicada) hace el trabajo a un coste de hardware una fracción del de una H100 dedicada. El paso 4 sigue en NVIDIA porque ahí es donde su arquitectura tensor + HBM3 + FP8 vale lo que cuesta.&lt;/p>
&lt;p>&lt;strong>Implicación operativa&lt;/strong>: un Xeon 6 sidecar (~40 cores, ~10-15 k€) sirviendo embeddings + reranker libera el equivalente de 1-2 H100 de carga &amp;ldquo;barata&amp;rdquo;, recuperando esa capacidad para el LLM grande. ROI en sizing claro.&lt;/p>
&lt;h3 id="patrón-2--guardrails--pii-redact-en-nuc-near-edge">Patrón 2 — guardrails + PII redact en NUC near edge&lt;/h3>
&lt;p>Una sucursal bancaria, un consultorio médico o una oficina jurídica genera prompts con &lt;strong>datos personales o clasificados&lt;/strong>. Mandar esos prompts al DC central (aunque sea on-premise corporativo) puede chocar con políticas de retención local o con compliance específico (GDPR, secreto profesional).&lt;/p>
&lt;p>Patrón: el &lt;strong>NUC en la sucursal&lt;/strong> ejecuta dos pasos críticos antes del round-trip:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>PII redact&lt;/strong> con Presidio (CPU-only, rápido) o Llama Guard 4 8B en NPU + iGPU del NUC. Reemplaza nombres, NIFs, números de cuenta por placeholders.&lt;/li>
&lt;li>&lt;strong>Guardrails ligeros&lt;/strong> (PromptGuard 2 86M, Llama Guard 4 8B) en NPU + iGPU. Filtra prompt injection, jailbreak, contenido prohibido.&lt;/li>
&lt;/ol>
&lt;p>Solo después, el prompt redacted viaja al DC central para que el LLM grande responda. La respuesta se devuelve al NUC, que &lt;strong>re-hidrata&lt;/strong> los placeholders con los valores reales antes de mostrarla al usuario. Los datos sensibles nunca abandonan la sucursal.&lt;/p>
&lt;p>Costes: NUC Panther Lake ~1500-2500 €/unidad, escalable a docenas de sucursales sin coste de GPU central adicional. Latencia: 50-150 ms del paso edge antes del round-trip de 200-500 ms del DC.&lt;/p>
&lt;h3 id="patrón-3--speculative-decoding-drafter-en-nuc">Patrón 3 — speculative decoding drafter en NUC&lt;/h3>
&lt;p>&lt;a href="https://blog.lo0.es/posts/speculative-decoding-fundamentos/">Speculative decoding&lt;/a> usa un &lt;strong>drafter pequeño&lt;/strong> que propone γ tokens y un &lt;strong>target grande&lt;/strong> que los acepta/rechaza en un único forward pass. Si el drafter está geográficamente cerca del usuario (NUC en sucursal) y el target en el DC central, la latencia percibida del cliente cae aún más.&lt;/p>
&lt;p>&lt;strong>Setup&lt;/strong>: drafter Llama 3.2 1B INT4 en NUC + target Llama 3.1 70B FP8 en H100 central. El NUC genera γ=4 tokens en ~50 ms locales; el target los verifica en una pasada (40-80 ms incluyendo round-trip); si tasa de aceptación &amp;gt; 60 %, &lt;strong>TTFT efectivo cae ~50 %&lt;/strong> vs Llama 70B sin speculative.&lt;/p>
&lt;p>Restricción importante: &lt;strong>drafter y target deben compartir tokenizer&lt;/strong>. Llama 3.2 1B y Llama 3.1 70B tienen tokenizer compatible. Mezclar Llama drafter con Qwen target rompe el patrón.&lt;/p>
&lt;h3 id="patrón-4--dev-workstations-nuc">Patrón 4 — dev workstations NUC&lt;/h3>
&lt;p>El dev / CI iterando sobre prompts, evals, retrieval logic, no necesita GPU productiva para validar correctness. Un NUC con Llama 3.2 3B INT4 corre los tests funcionales end-to-end (incluyendo embeddings + retrieval + LLM + guardrails) en una décima parte del coste de iterar sobre una H100. &lt;strong>Solo el último smoke test pre-prod usa el cluster productivo&lt;/strong>.&lt;/p>
&lt;p>Patrón maduro en organizaciones con muchos desarrolladores y GPU productiva escasa. La iteración 10× más rápida y barata se traduce en velocidad de feature delivery.&lt;/p>
&lt;h2 id="observabilidad-unificada-en-cluster-heterogéneo">Observabilidad unificada en cluster heterogéneo&lt;/h2>
&lt;p>El &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">post de observabilidad GPU&lt;/a> cubría DCGM Exporter para NVIDIA. En cluster mixto hace falta más:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Pieza hardware&lt;/th>
&lt;th>Exporter&lt;/th>
&lt;th>Métricas clave&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>NVIDIA H100/A100&lt;/td>
&lt;td>&lt;code>nvidia/dcgm-exporter&lt;/code>&lt;/td>
&lt;td>DCGM_FI_DEV_* + DCGM_FI_PROF_*&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel Gaudi 3&lt;/td>
&lt;td>&lt;code>HabanaAI/habana-metric-exporter&lt;/code>&lt;/td>
&lt;td>habana_hpu_utilization, habana_hbm_used&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel Arc Pro&lt;/td>
&lt;td>&lt;code>intel/intel-gpu-exporter&lt;/code> (no oficial; existen alternativas)&lt;/td>
&lt;td>xe_engine_utilization, xe_memory_used&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel Xeon CPU + AMX&lt;/td>
&lt;td>&lt;code>prometheus/node-exporter&lt;/code> + Intel PCM&lt;/td>
&lt;td>cpu_amx_utilization (vía PCM)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Intel NUC (NPU+iGPU)&lt;/td>
&lt;td>&lt;code>intel/intel-gpu-exporter&lt;/code> + custom NPU exporter&lt;/td>
&lt;td>npu_utilization, xe_iGPU&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Todos federados en un único Prometheus + Grafana. Las dashboards se organizan por &lt;strong>familia de hardware&lt;/strong> (NVIDIA, Intel server, Intel edge) más una vista agregada &amp;ldquo;cluster heterogéneo&amp;rdquo; con SLO por tenant que combina los cuatro.&lt;/p>
&lt;p>Cardinalidad: ~1.5-2× la del cluster NVIDIA puro. Manejable con Thanos / Mimir para retención larga.&lt;/p>
&lt;h2 id="routing-por-capability--del-router-l7-al-heterogéneo">Routing por capability — del router L7 al heterogéneo&lt;/h2>
&lt;p>El &lt;a href="https://blog.lo0.es/posts/router-inferencia-llm-gateway-l7/">router de inferencia LLM&lt;/a> deja de ser un selector de versiones del mismo modelo para convertirse en un &lt;strong>dispatcher por capability&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">models&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;llama-70b-chat&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;vllm-llama70b.inference.svc:8000&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">backend&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nvidia-h100&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">chat, tool_use, json_mode]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;embedding-multilingual&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;ipex-bge-m3.inference.svc:8080&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">backend&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">intel-xeon-amx&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">embeddings]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;reranker-multilingual&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;ipex-bge-reranker.inference.svc:8080&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">backend&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">intel-xeon-amx&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">reranking]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;guardrail-prompt-injection&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;openvino-llama-guard.edge-suc01.local:8080&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">backend&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">intel-nuc-edge&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">guardrails, redact-pii]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">region&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sucursal-01&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;llama-3b-draft&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;openvino-llama-3b.edge-suc01.local:8080&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">backend&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">intel-nuc-edge&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">speculative-drafter]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">region&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sucursal-01&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">target_model&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;llama-70b-chat&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El router resuelve &lt;code>model=embedding-multilingual&lt;/code> → Intel Xeon; &lt;code>model=llama-70b-chat&lt;/code> → H100; &lt;code>model=guardrail-prompt-injection&lt;/code> con &lt;code>region=sucursal-01&lt;/code> → NUC local. Si el NUC de la sucursal cae, &lt;strong>failover&lt;/strong> a una réplica equivalente en el DC central, asumiendo el coste de latencia.&lt;/p>
&lt;p>LiteLLM Proxy, NVIDIA Dynamo y Envoy AI Gateway soportan este routing por capability. La pieza no obvia: el router debe conocer el &lt;strong>tokenizer compatible&lt;/strong> entre drafter y target para el patrón 3, lo que se modela en metadata adicional del catálogo.&lt;/p>
&lt;h2 id="pitfalls-específicos">Pitfalls específicos&lt;/h2>
&lt;p>&lt;strong>Tokenizer mismatch entre engines.&lt;/strong> OpenVINO con un GGUF de Llama 3.2 y vLLM con el mismo Llama 3.2 nominal pueden usar tokenizers ligeramente distintos (chat template, special tokens). Validar identidad de tokens con &lt;code>tokenizer.encode(&amp;quot;hola&amp;quot;)&lt;/code> en ambos lados antes de asumir intercambiabilidad. Para speculative decoding, &lt;strong>un solo token diferente rompe el patrón&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Latencia round-trip edge ↔ central.&lt;/strong> El patrón 2 y 3 asumen que el NUC y el DC están en la misma WAN corporativa con latencia controlada. Si la sucursal está sobre 4G/5G con jitter de 100-200 ms, el speculative drafter no compensa nada — al revés, añade latencia. Medir antes de prometer.&lt;/p>
&lt;p>&lt;strong>FP8 Hopper ≠ INT8 AMX en calidad de salida.&lt;/strong> El operador asume que una request que en H100 corre FP8 y en Xeon AMX corre INT8 producirá la misma salida. &lt;strong>No es cierto&lt;/strong>: las dos quantizaciones tienen perfiles de degradación distintos. Si el sistema espera idempotencia (e.g., evals con golden output), validar offline que la versión Intel reproduce el comportamiento esperado dentro de tolerancia.&lt;/p>
&lt;p>&lt;strong>Sincronización de versiones de modelo entre sitios.&lt;/strong> El modelo en el DC central se actualiza, pero los NUCs de las sucursales mantienen la versión vieja del drafter o del guardrail durante semanas. Resultado: comportamiento divergente entre sucursales sin diagnóstico fácil. Política: &lt;strong>modelo central y modelo edge avanzan juntos&lt;/strong> o con ventana documentada; el &lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">canary&lt;/a> se extiende a la flota de NUCs.&lt;/p>
&lt;p>&lt;strong>Roadmap Intel inestable.&lt;/strong> Falcon Shores cancelado, Gaudi 4 no existirá, Jaguar Shores 2026 todavía sin specs públicas confirmadas. Comprar Gaudi 3 hoy es razonable si el caso de uso justifica los 18-24 meses de amortización; comprometer arquitectura a 5+ años sobre Intel accelerator es apuesta más arriesgada que la equivalente NVIDIA — al menos hasta que Jaguar Shores se materialice con software stack maduro.&lt;/p>
&lt;p>&lt;strong>Vacío de despliegues productivos públicos.&lt;/strong> A junio 2026, los despliegues Gaudi 3 confirmados son IBM Cloud, Dell AI Factory y un puñado de early adopters (Bharti Airtel, Bosch, Naver). &lt;strong>No hay caso público verificable de cluster mixto NVIDIA + Intel en banca o gobierno europeo&lt;/strong>. Este patrón es &lt;strong>arquitectura emergente recomendada&lt;/strong>, no práctica con histórico industrial. El primer adoptante asume coste de validación que un segundo adoptante evita.&lt;/p>
&lt;h2 id="aplicado-a-un-cluster-on-premise-genérico">Aplicado a un cluster on-premise genérico&lt;/h2>
&lt;p>Para una organización con un cluster genérico de inferencia LLM heterogéneo:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>DC central&lt;/strong>: 4 nodos × 4×H100 SXM 80 GB con NVLink intra-nodo = 16 H100. Sirve LLM grandes (Llama 70B, Mixtral 8×22B, Qwen 72B) en BF16 o FP8.&lt;/li>
&lt;li>&lt;strong>Sidecar Xeon 6&lt;/strong>: 2-4 servidores Xeon 6 (Granite Rapids 64-86 cores) con AMX, 512 GB DDR5, en el mismo rack que el cluster H100. Sirve embeddings (bge-m3), reranker (bge-reranker-v2-m3), modelos pequeños (Llama 3.2 1B/3B) en vLLM CPU backend con AMX.&lt;/li>
&lt;li>&lt;strong>Sidecar Arc Pro&lt;/strong> (opcional): 1-2 servidores con 4-8× Arc Pro B60 24 GB cada uno (Battlematrix), para modelos 13-30B INT8 vía OpenVINO. Útil si el coste por LLM mediano debe bajar de la H100.&lt;/li>
&lt;li>&lt;strong>NUCs edge en sucursales&lt;/strong>: 1-2 NUCs Panther Lake por sucursal, con NPU 50 TOPS + Xe3 120 TOPS, sirviendo Llama Guard 4 + Presidio + drafter Llama 3.2 1B INT4 vía OpenVINO. Conectividad WAN corporativa con latencia &amp;lt; 80 ms hacia el DC.&lt;/li>
&lt;/ul>
&lt;p>Volumen estimado: cluster central ~120 kW de pico GPU + ~10-15 kW de sidecars Intel. Edge: ~50 W por NUC, despreciable comparado con coste de oficinas.&lt;/p>
&lt;p>Observabilidad: Prometheus federado en el DC + scrape pull desde los NUCs (vía VPN corporativa). Dashboards &amp;ldquo;GPU NVIDIA fleet&amp;rdquo;, &amp;ldquo;Intel server fleet&amp;rdquo;, &amp;ldquo;Intel edge fleet&amp;rdquo; más una vista &amp;ldquo;SLO consolidado&amp;rdquo;.&lt;/p>
&lt;p>Router: LiteLLM Proxy o NVIDIA Dynamo en el DC, con catálogo de modelos extendido para incluir backends Intel y regiones (sucursal-01, sucursal-02, &amp;hellip;). Failover edge→central documentado.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-posts">Lo que no hemos cubierto (próximos posts)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Benchmarks reproducibles&lt;/strong> de Llama 70B en Gaudi 3 vs H100 SXM en hardware equivalente — el material que falta para tomar decisiones con datos propios, no de Intel ni de Signal65.&lt;/li>
&lt;li>&lt;strong>AMD ROCm en el mix&lt;/strong>: cómo entran MI300X / MI355X en este patrón heterogéneo y qué cambia el catálogo del router.&lt;/li>
&lt;li>&lt;strong>Apple Silicon como edge&lt;/strong>: M3/M4 Max con Neural Engine ~38 TOPS + GPU 40-core, hardware equivalente al NUC Panther Lake pero con software stack distinto (MLX).&lt;/li>
&lt;li>&lt;strong>Optimización de coste energético&lt;/strong>: cómo &lt;code>nvidia-smi -pl 500W&lt;/code> + Intel TDP cap en Xeon 6 reduce factura un 25-30 % con 15-20 % de pérdida de throughput.&lt;/li>
&lt;li>&lt;strong>CI/CD de modelos para flota edge&lt;/strong>: cómo el rolling update de un Llama Guard llega a 50 NUCs de sucursales sin que ninguna pierda servicio.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning para inferencia LLM on-premise&lt;/a> — el sizing que esta heterogeneidad permite optimizar tarea por tarea, no para todo en H100.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">Siete capas del stack de inferencia LLM on-premise&lt;/a> — las siete capas aplican igual sobre hardware heterogéneo; los backends son intercambiables si el contrato OpenAI-compatible se respeta.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/router-inferencia-llm-gateway-l7/">El router de inferencia LLM&lt;/a> — el router por capability es la pieza central del patrón heterogéneo.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a> — extiende a Gaudi, Arc, Xeon AMX y NPU edge.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">Quantization para inferencia LLM&lt;/a> — FP8 Hopper, INT8 AMX, INT4 GGUF — la base de por qué los hardware mixtos exigen validación cruzada.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/speculative-decoding-fundamentos/">Speculative decoding&lt;/a> — el patrón 3 del post; cómo el drafter near edge cierra latencia.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">Guardrails y safety en LLMs&lt;/a> y &lt;a href="https://blog.lo0.es/posts/llm-guard-fundamentos/">LLM Guard&lt;/a> — los modelos que viven en el NUC del patrón 2.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/catalogo-herramientas-oss-llmops/">Catálogo OSS para LLMOps&lt;/a> — fichas de OpenVINO, IPEX-LLM, vLLM CPU backend.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/oss-vs-hyperscalers-llmops/">OSS vs hyperscalers&lt;/a> — el análisis paralelo de lock-in que sostiene el argumento de diversificación.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;p>&lt;strong>Intel Gaudi 3&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Intel — &lt;em>Gaudi 3 AI Accelerator White Paper&lt;/em>. &lt;a href="https://cdrdv2-public.intel.com/817486/gaudi-3-ai-accelerator-white-paper.pdf">https://cdrdv2-public.intel.com/817486/gaudi-3-ai-accelerator-white-paper.pdf&lt;/a>&lt;/li>
&lt;li>Intel — Hot Chips 2024 Gaudi 3 deep dive. &lt;a href="https://hc2024.hotchips.org/assets/program/conference/day1/60_HC2024.Intel.RomanKaplan.Gaudi3-0826.pdf">https://hc2024.hotchips.org/assets/program/conference/day1/60_HC2024.Intel.RomanKaplan.Gaudi3-0826.pdf&lt;/a>&lt;/li>
&lt;li>Signal65 / DataCenterDynamics — &lt;em>NVIDIA H200 outperforms Intel Gaudi 3 by factor of 9× across first Llama 3.1 405B benchmark test&lt;/em>. &lt;a href="https://www.datacenterdynamics.com/en/news/nvidia-h200-outperforms-intel-gaudi-3-by-factor-of-nine-across-first-llama-31-405b-benchmark-test-exclusive/">https://www.datacenterdynamics.com/en/news/nvidia-h200-outperforms-intel-gaudi-3-by-factor-of-nine-across-first-llama-31-405b-benchmark-test-exclusive/&lt;/a>&lt;/li>
&lt;li>IEEE Spectrum — &lt;em>Intel Gaudi 3 review&lt;/em>. &lt;a href="https://spectrum.ieee.org/intel-gaudi-3">https://spectrum.ieee.org/intel-gaudi-3&lt;/a>&lt;/li>
&lt;li>Tom&amp;rsquo;s Hardware — &lt;em>Intel cancels Falcon Shores GPU; Jaguar Shores to be successor&lt;/em>. &lt;a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/intel-cancels-falcon-shores-gpu-for-ai-workloads-jaguar-shores-to-be-successor">https://www.tomshardware.com/tech-industry/artificial-intelligence/intel-cancels-falcon-shores-gpu-for-ai-workloads-jaguar-shores-to-be-successor&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Intel Xeon 6 + AMX&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Intel — &lt;em>Xeon 6 (Granite Rapids) Product Brief&lt;/em>. &lt;a href="https://www.intel.com/content/dam/www/central-libraries/us/en/documents/2025-02/xeon-6-granite-rapids-product-brief.pdf">https://www.intel.com/content/dam/www/central-libraries/us/en/documents/2025-02/xeon-6-granite-rapids-product-brief.pdf&lt;/a>&lt;/li>
&lt;li>OpenMetal — &lt;em>Intel AMX AI Inference Performance&lt;/em>. &lt;a href="https://openmetal.io/resources/blog/intel-amx-ai-inference-performance/">https://openmetal.io/resources/blog/intel-amx-ai-inference-performance/&lt;/a>&lt;/li>
&lt;li>LMSYS — &lt;em>Intel Xeon 6 + SGLang for DeepSeek R1 671B&lt;/em>. &lt;a href="https://www.lmsys.org/blog/2025-07-14-intel-xeon-optimization/">https://www.lmsys.org/blog/2025-07-14-intel-xeon-optimization/&lt;/a>&lt;/li>
&lt;li>arXiv 2410.04466 — &lt;em>CPU-LLM benchmarks with AMX&lt;/em>.&lt;/li>
&lt;li>Intel community blog — &lt;em>Accelerating vLLM Inference on Intel Xeon 6 Processor&lt;/em>.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Intel Arc Pro Battlemage&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Intel — &lt;em>Arc Pro B60 Graphics Specifications&lt;/em>. &lt;a href="https://www.intel.com/content/www/us/en/products/sku/243916/intel-arc-pro-b60-graphics/specifications.html">https://www.intel.com/content/www/us/en/products/sku/243916/intel-arc-pro-b60-graphics/specifications.html&lt;/a>&lt;/li>
&lt;li>StorageReview — &lt;em>Intel Arc Pro B60 Battlematrix Preview: 192GB VRAM for On-Premise AI&lt;/em>. &lt;a href="https://www.storagereview.com/review/intel-arc-pro-b60-battlematrix-preview-192gb-of-vram-for-on-premise-ai">https://www.storagereview.com/review/intel-arc-pro-b60-battlematrix-preview-192gb-of-vram-for-on-premise-ai&lt;/a>&lt;/li>
&lt;li>Phoronix — &lt;em>Intel Arc Pro B-series review&lt;/em>. &lt;a href="https://www.phoronix.com/review/intel-arc-pro-b-series">https://www.phoronix.com/review/intel-arc-pro-b-series&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Intel NUC / NPU&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>HotHardware — &lt;em>Intel CES 2026 Panther Lake is a Go&lt;/em>. &lt;a href="https://hothardware.com/news/intel-ces-2026-panther-lake-is-a-go">https://hothardware.com/news/intel-ces-2026-panther-lake-is-a-go&lt;/a>&lt;/li>
&lt;li>TechPowerUp — &lt;em>Intel Panther Lake Technical Deep Dive&lt;/em>.&lt;/li>
&lt;li>arXiv 2412.11053 — &lt;em>NITRO: LLM inference on laptop NPU&lt;/em>.&lt;/li>
&lt;li>Intel — &lt;em>AI PC brings larger LLM development to your desk&lt;/em>.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Software&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>OpenVINO — &lt;em>Release Notes 2025.3&lt;/em>. &lt;a href="https://www.intel.com/content/www/us/en/developer/articles/release-notes/openvino/2025-3.html">https://www.intel.com/content/www/us/en/developer/articles/release-notes/openvino/2025-3.html&lt;/a>&lt;/li>
&lt;li>HuggingFace — &lt;em>Deploy with OpenVINO&lt;/em>. &lt;a href="https://huggingface.co/blog/deploy-with-openvino">https://huggingface.co/blog/deploy-with-openvino&lt;/a>&lt;/li>
&lt;li>Intel — &lt;em>Intel Extension for PyTorch XPU 2.8.10&lt;/em>. &lt;a href="https://intel.github.io/intel-extension-for-pytorch/xpu/latest/tutorials/releases.html">https://intel.github.io/intel-extension-for-pytorch/xpu/latest/tutorials/releases.html&lt;/a>&lt;/li>
&lt;li>IPEX-LLM — &lt;code>github.com/intel/ipex-llm&lt;/code>.&lt;/li>
&lt;li>Habana — &lt;em>SynapseAI PyTorch Theory of Operations&lt;/em>. &lt;a href="https://docs.habana.ai/en/latest/PyTorch/PyTorch_Gaudi_Theory_of_Operations.html">https://docs.habana.ai/en/latest/PyTorch/PyTorch_Gaudi_Theory_of_Operations.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Market context&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>MLCommons — &lt;em>MLPerf Inference v6.0 benchmark results&lt;/em>. &lt;a href="https://www.spheron.network/blog/mlperf-inference-v6-benchmark-results-2026/">https://www.spheron.network/blog/mlperf-inference-v6-benchmark-results-2026/&lt;/a>&lt;/li>
&lt;li>Intel newsroom — &lt;em>Gaudi 3 Expanded Availability&lt;/em>. &lt;a href="https://newsroom.intel.com/artificial-intelligence/intel-gaudi-3-expands-availability-drive-ai-innovation-scale">https://newsroom.intel.com/artificial-intelligence/intel-gaudi-3-expands-availability-drive-ai-innovation-scale&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Sources: las URLs completas están enlazadas en línea sobre cada referencia.&lt;/p></description></item><item><title>Runbooks de incident response para inferencia LLM: cada alerta a una acción concreta con Kafka y Keep</title><link>https://blog.lo0.es/posts/runbooks-incident-response-llm-keep-kafka/</link><pubDate>Tue, 02 Jun 2026 04:30:00 +0200</pubDate><guid>https://blog.lo0.es/posts/runbooks-incident-response-llm-keep-kafka/</guid><description>&lt;blockquote>
&lt;p>Este post cierra la trilogía de observabilidad que abrieron &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a> (qué métricas) y &lt;a href="https://blog.lo0.es/posts/anatomia-metricas-dcgm-vllm-anomalias/">Anatomía de las doce métricas DCGM y cinco vLLM&lt;/a> (qué anomalía documentada por métrica). Aquí cada anomalía recibe su acción concreta y se encaja en la maquinaria de gestión de incidentes que compliance exige.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Las alertas de &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">observabilidad GPU&lt;/a> son inútiles sin un procedimiento codificado por cada una; el operador que las interpreta a mano cada vez opera por intuición. La combinación correcta tiene &lt;strong>tres piezas indispensables&lt;/strong>. (1) &lt;strong>Catálogo de runbooks&lt;/strong>: para cada una de las seis alertas críticas (&lt;code>GpuHbmNearOom&lt;/code>, &lt;code>GpuThermalOrPowerThrottle&lt;/code>, &lt;code>GpuXidErrorDetected&lt;/code>, &lt;code>GpuEccDoubleBit&lt;/code>, &lt;code>VllmKvCachePoolNearFull&lt;/code>, &lt;code>VllmTtftP95OutOfSlo&lt;/code>), severity, mitigación inmediata, evidencia que capturar &lt;strong>antes&lt;/strong> de remediar, acción de resolución, criterio de cierre y trigger de postmortem. (2) &lt;strong>Pipeline reproducible&lt;/strong>: Prometheus + DCGM → Alertmanager → &lt;strong>Kafka como event bus&lt;/strong> (topics &lt;code>gpu.alerts.enriched&lt;/code>, &lt;code>incidents.lifecycle&lt;/code>, &lt;code>audit.actions&lt;/code> con retención WORM) → &lt;strong>Keep como workflow engine&lt;/strong> (workflows declarativos YAML versionados en git) → ejecutores Kubernetes jobs / scripts / ChatOps. (3) &lt;strong>Encaje formal en gestión de incidentes&lt;/strong> según el corpus normativo: &lt;strong>ISO/IEC 27035&lt;/strong> fases &lt;code>identify → report → assess → respond → learn&lt;/code>; &lt;strong>ENS&lt;/strong> controles &lt;code>op.exp.7&lt;/code> (gestión de incidentes), &lt;code>op.exp.8&lt;/code> (registro de actividad), &lt;code>op.exp.10&lt;/code> (notificación a usuarios); &lt;strong>NIS2&lt;/strong> art. 23 con notificación temprana &lt;strong>24 h&lt;/strong>, notificación formal &lt;strong>72 h&lt;/strong> e informe final &lt;strong>1 mes&lt;/strong>; &lt;strong>EU AI Act&lt;/strong> art. 73 para incidente grave de un sistema de alto riesgo, plazos &lt;strong>2 a 15 días&lt;/strong> según severity; &lt;strong>ISO/IEC 42001&lt;/strong> cláusula 10 (mejora continua del AIMS). La taxonomía de acción es &lt;strong>mitigación inmediata&lt;/strong> (drain, throttle, scale-down: contiene el daño en segundos) → &lt;strong>diagnóstico&lt;/strong> (captura de evidencia con &lt;code>nvidia-smi -q&lt;/code>, &lt;code>dmesg&lt;/code>, vLLM &lt;code>/metrics&lt;/code> snapshot, traza OTel relacionada; sin esto el postmortem no es defensible) → &lt;strong>resolución&lt;/strong> (restart, reset, RMA, rollback) → &lt;strong>postmortem&lt;/strong> (RCA por 5-whys, plan de prevención, actualización del runbook). Kafka aporta el &lt;strong>audit trail inmutable&lt;/strong> que ENS y EU AI Act exigen — cada acción ejecutada por Keep o por humano se publica como evento en &lt;code>audit.actions&lt;/code> con timestamp, actor, decisión y evidencia, retenido WORM mínimo 6 meses. Keep aporta los &lt;strong>workflows como código&lt;/strong>: este post incluye tres workflows completos (XID con drain + ticket Jira, ECC DBE con paginación inmediata y bloqueo del nodo en scheduler, canary rollback automático por TTFT P95 fuera de SLO). Cuatro anti-patrones cierran el material: alertas sin runbook (la mayoría), runbook sin captura de evidencia previa (perpetúa el incidente porque la causa raíz se pierde), escalada por antigüedad en vez de severity (operador junior gestiona ECC DBE), ausencia de gate humano para acciones destructivas (Keep ejecutando &lt;code>nvidia-smi --gpu-reset&lt;/code> sin confirmación). Aplicable a un cluster genérico de 4×H100 SXM con Kafka y Keep ya desplegados.&lt;/p>
&lt;h2 id="estás-aquí-observe--deploy-incident-response-cierra-el-bucle">Estás aquí: OBSERVE → DEPLOY (incident response cierra el bucle)&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="incident response: bucle Observe-Deploy">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#c9a8e9;stroke-width:3}.semiactive{fill:#cfead0;stroke-width:2}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#rbm)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#rbm)}.loop{stroke:#c33;stroke-width:1.8;fill:none;stroke-dasharray:5 3;marker-end:url(#rbmc)}&lt;/style>
&lt;defs>&lt;marker id="rbm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;marker id="rbmc" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#c33"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">Incident response: cierra el bucle de OBSERVE a DEPLOY (acción)&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box semiactive"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box active"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box idle"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="loop" d="M530,40 C500,5 480,5 460,40"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="la-analogía-la-sala-de-control-de-un-reactor-nuclear">La analogía: la sala de control de un reactor nuclear&lt;/h2>
&lt;p>En una sala de control de central nuclear, el operador de turno &lt;strong>nunca decide qué hacer al ver una alarma&lt;/strong>. La decisión está pre-tomada y codificada en un procedimiento escrito (SOP) que cubre cada alarma del panel: si suena la X, abrir libro X, leer los pasos 1-N, ejecutar exactamente, llamar al supervisor en el paso M, escalar al director de planta en el paso N+3. La razón es estricta: las alarmas críticas son raras pero catastróficas si se gestionan mal; un operador improvisando en una emergencia toma decisiones peores que uno aplicando un procedimiento revisado por expertos y validado por simulación.&lt;/p>
&lt;p>El reactor no espera que el operador sea genio. Espera que conozca los procedimientos al pie de la letra y que el sistema de gestión de operaciones le entregue el procedimiento correcto al momento. Si los procedimientos no están escritos, no están versionados, o no están integrados con las alarmas que disparan, la sala de control opera por intuición. La diferencia entre ambas operaciones —procedimentada vs intuitiva— es la diferencia entre una central que opera 30 años sin incidentes y otra que entra en lista negra.&lt;/p>
&lt;p>El incident response de un cluster de inferencia LLM funciona idéntico. Las alertas DCGM y vLLM que los posts anteriores listaron son las alarmas del panel. Cada una necesita su SOP escrito, versionado, integrado con la alerta que la dispara y revisado tras cada incidente. Sin esa codificación, el operador de turno improvisa en mitad de un fallo de ECC DBE a las 4 de la mañana; con ella, ejecuta los nueve pasos del runbook 12 y el incidente se cierra en 20 minutos.&lt;/p>
&lt;h2 id="la-arquitectura-del-incident-pipeline">La arquitectura del incident pipeline&lt;/h2>
&lt;div class="diagram" style="max-width:840px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 840 320" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="pipeline de incident response">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.src{fill:#dfe9f5;stroke:#356}.am{fill:#eef0d0;stroke:#7a3}.k{fill:#f4e3cf;stroke:#a63}.kp{fill:#ead8f5;stroke:#634}.ex{fill:#d8eecf;stroke:#373}.au{fill:#f6e2e2;stroke:#a33}.title{font:600 13px sans-serif;fill:#222}.h{font:700 12px sans-serif;fill:#222}.l{font:11px sans-serif;fill:#222}.n{font:italic 10px sans-serif;fill:#444}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#pim)}.dbl{stroke:#666;stroke-width:1.4;fill:none;stroke-dasharray:4 2;marker-end:url(#pim)}&lt;/style>
&lt;defs>&lt;marker id="pim" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="420" y="20" text-anchor="middle" class="title">Pipeline: Prometheus → Alertmanager → Kafka → Keep → Ejecutores · audit WORM en paralelo&lt;/text>
&lt;rect x="20" y="45" width="140" height="60" class="b src"/>&lt;text x="90" y="65" text-anchor="middle" class="h">Prometheus&lt;/text>&lt;text x="90" y="82" text-anchor="middle" class="l">DCGM + vLLM&lt;/text>&lt;text x="90" y="98" text-anchor="middle" class="n">scrape 15s&lt;/text>
&lt;rect x="190" y="45" width="140" height="60" class="b am"/>&lt;text x="260" y="65" text-anchor="middle" class="h">Alertmanager&lt;/text>&lt;text x="260" y="82" text-anchor="middle" class="l">PrometheusRule&lt;/text>&lt;text x="260" y="98" text-anchor="middle" class="n">webhook → kafka&lt;/text>
&lt;rect x="360" y="45" width="160" height="60" class="b k"/>&lt;text x="440" y="65" text-anchor="middle" class="h">Kafka&lt;/text>&lt;text x="440" y="82" text-anchor="middle" class="l">gpu.alerts.enriched&lt;/text>&lt;text x="440" y="98" text-anchor="middle" class="n">incidents.lifecycle&lt;/text>
&lt;rect x="550" y="45" width="140" height="60" class="b kp"/>&lt;text x="620" y="65" text-anchor="middle" class="h">Keep&lt;/text>&lt;text x="620" y="82" text-anchor="middle" class="l">workflows YAML&lt;/text>&lt;text x="620" y="98" text-anchor="middle" class="n">git-versioned&lt;/text>
&lt;rect x="720" y="45" width="100" height="60" class="b ex"/>&lt;text x="770" y="65" text-anchor="middle" class="h">Ejecutores&lt;/text>&lt;text x="770" y="82" text-anchor="middle" class="l">kubectl · API&lt;/text>&lt;text x="770" y="98" text-anchor="middle" class="n">ChatOps&lt;/text>
&lt;path class="arr" d="M160,75 L190,75"/>
&lt;path class="arr" d="M330,75 L360,75"/>
&lt;path class="arr" d="M520,75 L550,75"/>
&lt;path class="arr" d="M690,75 L720,75"/>
&lt;rect x="360" y="160" width="160" height="60" class="b au"/>&lt;text x="440" y="180" text-anchor="middle" class="h">audit.actions&lt;/text>&lt;text x="440" y="197" text-anchor="middle" class="l">topic WORM&lt;/text>&lt;text x="440" y="213" text-anchor="middle" class="n">retención 6 meses+&lt;/text>
&lt;path class="dbl" d="M620,105 L520,160"/>
&lt;path class="dbl" d="M770,105 L520,168"/>
&lt;text x="420" y="252" text-anchor="middle" class="n">Cada acción de Keep o humano se publica en audit.actions: WORM exigido por ENS op.exp.8 + EU AI Act art. 12.&lt;/text>
&lt;rect x="20" y="240" width="220" height="60" class="b kp"/>&lt;text x="130" y="260" text-anchor="middle" class="h">Compliance consumers&lt;/text>&lt;text x="130" y="277" text-anchor="middle" class="l">DPO · auditoría ENS · NIS2 reporting&lt;/text>&lt;text x="130" y="293" text-anchor="middle" class="n">consumen audit.actions read-only&lt;/text>
&lt;path class="arr" d="M360,180 L240,260"/>
&lt;rect x="600" y="240" width="220" height="60" class="b ex"/>&lt;text x="710" y="260" text-anchor="middle" class="h">Postmortem tooling&lt;/text>&lt;text x="710" y="277" text-anchor="middle" class="l">Jira · MLflow · Langfuse&lt;/text>&lt;text x="710" y="293" text-anchor="middle" class="n">enriquecidos con timeline&lt;/text>
&lt;path class="arr" d="M520,180 L600,260"/>
&lt;/svg>
&lt;/div>
&lt;p>&lt;strong>Prometheus + DCGM.&lt;/strong> Recolecta las métricas descritas en los dos posts anteriores. PrometheusRules definen las seis alertas críticas con &lt;code>for: &amp;lt;duración&amp;gt;&lt;/code> para evitar ruido.&lt;/p>
&lt;p>&lt;strong>Alertmanager.&lt;/strong> Recibe alertas crudas; deduplica, agrupa por labels (&lt;code>{cluster, node, gpu, model}&lt;/code>), enruta. En vez de enviar directamente a PagerDuty o Slack, &lt;strong>envía a Kafka&lt;/strong> vía webhook receiver — esto convierte la alerta en un evento del bus que múltiples consumidores procesan (Keep para acción, audit topic para compliance, dashboards para visualización).&lt;/p>
&lt;p>&lt;strong>Kafka como event bus.&lt;/strong> Tres topics canónicos:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>gpu.alerts.enriched&lt;/code>&lt;/strong> — alertas con contexto añadido (tenant, modelo, versión, owner del namespace, severity efectiva). Retención: 7 días, replication factor 3.&lt;/li>
&lt;li>&lt;strong>&lt;code>incidents.lifecycle&lt;/code>&lt;/strong> — eventos del ciclo del incidente: &lt;code>incident.opened&lt;/code>, &lt;code>incident.acknowledged&lt;/code>, &lt;code>action.proposed&lt;/code>, &lt;code>action.executed&lt;/code>, &lt;code>incident.escalated&lt;/code>, &lt;code>incident.resolved&lt;/code>, &lt;code>postmortem.attached&lt;/code>. Retención: 90 días.&lt;/li>
&lt;li>&lt;strong>&lt;code>audit.actions&lt;/code>&lt;/strong> — registro inmutable de cada acción ejecutada (por Keep automáticamente o por humano confirmando). Retención: &lt;strong>6 meses mínimo con compaction off + tiered storage&lt;/strong>, almacenamiento WORM. Es el topic que ENS &lt;code>op.exp.8&lt;/code>, EU AI Act art. 12 y NIS2 obligan a conservar.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Keep como workflow engine.&lt;/strong> Consume de &lt;code>gpu.alerts.enriched&lt;/code>, dispara workflows YAML versionados en git, ejecuta acciones (llamadas HTTP, kubectl jobs, mensajes Slack, tickets Jira) y publica el resultado en &lt;code>incidents.lifecycle&lt;/code> + &lt;code>audit.actions&lt;/code>. La elección de Keep sobre Alertmanager solo (o sobre PagerDuty solo) es deliberada: Keep separa &lt;strong>declaración del runbook&lt;/strong> (YAML legible y revisable) de &lt;strong>distribución de notificación&lt;/strong> (PagerDuty). El runbook es código versionado; las notificaciones son detalles operativos.&lt;/p>
&lt;p>&lt;strong>Ejecutores.&lt;/strong> Lo que de verdad mueve el cluster:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Kubernetes jobs&lt;/strong>: &lt;code>kubectl drain&lt;/code>, &lt;code>kubectl cordon&lt;/code>, &lt;code>kubectl rollout undo&lt;/code>.&lt;/li>
&lt;li>&lt;strong>NVIDIA API&lt;/strong>: &lt;code>nvidia-smi --gpu-reset&lt;/code>, &lt;code>dcgmi diag -r &amp;lt;level&amp;gt;&lt;/code>.&lt;/li>
&lt;li>&lt;strong>ChatOps&lt;/strong>: confirmaciones humanas a través de Slack interactive messages antes de ejecutar acción destructiva.&lt;/li>
&lt;li>&lt;strong>Tooling externo&lt;/strong>: ticket Jira, notificación PagerDuty, llamada a CMDB.&lt;/li>
&lt;/ul>
&lt;h2 id="las-seis-alertas-críticas-y-sus-runbooks">Las seis alertas críticas y sus runbooks&lt;/h2>
&lt;p>Para cada alerta: severity, mitigación inmediata (segundos), evidencia que capturar &lt;strong>antes de remediar&lt;/strong>, acción de resolución, criterios de cierre, trigger de postmortem.&lt;/p>
&lt;h3 id="rb-01--gpuhbmnearoom--hbm--92--sostenido">RB-01 · &lt;code>GpuHbmNearOom&lt;/code> — HBM &amp;gt; 92 % sostenido&lt;/h3>
&lt;p>&lt;strong>Severity&lt;/strong>: WARNING. Riesgo OOM en la siguiente asignación de PagedAttention.&lt;/p>
&lt;p>&lt;strong>Mitigación inmediata.&lt;/strong> Reducir admission temporalmente bajando &lt;code>max_num_seqs&lt;/code> del motor afectado vía hot reload (si el motor lo soporta) o restart escalonado de réplicas. Disparar scale-out adicional vía KEDA si hay nodos GPU libres. No es necesario drenar el nodo.&lt;/p>
&lt;p>&lt;strong>Evidencia a capturar.&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">nvidia-smi --query-gpu&lt;span class="o">=&lt;/span>index,memory.used,memory.free,memory.total --format&lt;span class="o">=&lt;/span>csv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvidia-smi -q -d ROW_REMAPPER &lt;span class="p">|&lt;/span> grep -i pending
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl http://vllm-pod:8000/metrics &lt;span class="p">|&lt;/span> grep -E &lt;span class="s2">&amp;#34;gpu_cache_usage|num_requests&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl logs &amp;lt;pod&amp;gt; --tail&lt;span class="o">=&lt;/span>&lt;span class="m">200&lt;/span> &lt;span class="p">|&lt;/span> grep -i &lt;span class="s2">&amp;#34;preempt\|swap&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Guardar snapshot en &lt;code>audit.actions&lt;/code> con timestamp y &lt;code>incident_id&lt;/code>.&lt;/p>
&lt;p>&lt;strong>Resolución.&lt;/strong> Si la causa es pico de tráfico: dejar al autoscaler escalar a régimen estable, monitorizar 30 min. Si la causa es regresión de modelo (canary v2 consume más KV cache que v1): rollback del canary (ver RB-06). Si es leak (la métrica crece sin que el tráfico crezca): restart del pod con captura de heap dump.&lt;/p>
&lt;p>&lt;strong>Cierre.&lt;/strong> &lt;code>gpu_cache_usage_perc &amp;lt; 80 %&lt;/code> sostenido durante 15 min Y &lt;code>num_requests_waiting == 0&lt;/code>.&lt;/p>
&lt;p>&lt;strong>Postmortem.&lt;/strong> No obligatorio salvo si el incidente duró &amp;gt; 30 min o tuvo impacto en SLO.&lt;/p>
&lt;h3 id="rb-02--gputhermalorpowerthrottle--bit--0-ni-idle-en-clock_throttle_reasons">RB-02 · &lt;code>GpuThermalOrPowerThrottle&lt;/code> — bit ≠ 0 ni Idle en CLOCK_THROTTLE_REASONS&lt;/h3>
&lt;p>&lt;strong>Severity&lt;/strong>: WARNING (térmico) o CRITICAL (HW Power Brake sostenido, riesgo PDU).&lt;/p>
&lt;p>&lt;strong>Mitigación inmediata.&lt;/strong> Identificar el bit (decodificar bitmap). Si es &lt;strong>&lt;code>0x40 HW_THERMAL&lt;/code>&lt;/strong> o &lt;strong>&lt;code>0x20 SW_THERMAL&lt;/code>&lt;/strong>: drenar workload del nodo a otras réplicas si la temperatura no baja en 2 min, evitar nuevos pods en ese nodo (&lt;code>kubectl cordon&lt;/code>). Si es &lt;strong>&lt;code>0x80 HW_POWER_BRAKE&lt;/code>&lt;/strong>: alerta a infraestructura de DC inmediatamente (probable PDU sobrecomprometida — caso Dell KB 000220508 / Lenovo HT514380), reducir TDP de las GPUs del rack vía &lt;code>nvidia-smi -pl&lt;/code> a un valor menor para liberar carga sobre el breaker.&lt;/p>
&lt;p>&lt;strong>Evidencia.&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">nvidia-smi --query-gpu&lt;span class="o">=&lt;/span>index,temperature.gpu,temperature.memory,power.draw,clocks_throttle_reasons.active --format&lt;span class="o">=&lt;/span>csv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ipmitool sdr &lt;span class="p">|&lt;/span> grep -i &lt;span class="s2">&amp;#34;fan\|temp\|inlet&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Datos de PDU si están instrumentadas (modbus / SNMP)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Resolución.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Térmico&lt;/strong>: revisar flujo de aire del rack, verificar rear-door HX, T_inlet, ventiladores DGX. Issue de infra, no de motor.&lt;/li>
&lt;li>&lt;strong>Power Brake&lt;/strong>: revisar dimensionado de PDU rama, breaker, distribución 415 VAC. Probable redistribución de carga a otra rama o limitación temporal de TDP.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cierre.&lt;/strong> &lt;code>CLOCK_THROTTLE_REASONS == 0x1&lt;/code> (solo Idle) o &lt;code>0x0&lt;/code> durante 30 min con carga normal.&lt;/p>
&lt;p>&lt;strong>Postmortem.&lt;/strong> Obligatorio si fue HW Power Brake — implica infraestructura eléctrica del DC.&lt;/p>
&lt;h3 id="rb-03--gpuxiderrordetected--increasedcgm_fi_dev_xid_errors5m--0">RB-03 · &lt;code>GpuXidErrorDetected&lt;/code> — &lt;code>increase(DCGM_FI_DEV_XID_ERRORS[5m]) &amp;gt; 0&lt;/code>&lt;/h3>
&lt;p>&lt;strong>Severity&lt;/strong>: CRITICAL.&lt;/p>
&lt;p>&lt;strong>Mitigación inmediata.&lt;/strong> &lt;code>kubectl cordon&lt;/code> del nodo (sin más nuevos pods). Si el XID es 31/48/79/94/95 (hardware o cascada): drenar los pods existentes del nodo. Si el XID es 13/43 (posible software): mantener pods pero bloquear nuevos, capturar trace y workload activo.&lt;/p>
&lt;p>&lt;strong>Evidencia.&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># El XID concreto del dmesg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dmesg &lt;span class="p">|&lt;/span> grep -i xid &lt;span class="p">|&lt;/span> tail -30
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvidia-smi -q -d ERROR
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvidia-smi -q -d PCIE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Estado de las páginas retiradas&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvidia-smi -q -d ROW_REMAPPER
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Workload que estaba ejecutándose&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl get pods -o wide &lt;span class="p">|&lt;/span> grep &amp;lt;node&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl logs &amp;lt;pod&amp;gt; --previous --tail&lt;span class="o">=&lt;/span>&lt;span class="m">500&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Resolución.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>XID 13/43&lt;/strong> (software exception / channel verif): si recurre solo con un modelo concreto, es bug del workload — issue al equipo de modelos. Si es transitorio, reiniciar el pod basta.&lt;/li>
&lt;li>&lt;strong>XID 31&lt;/strong> (MMU fault): suele ser cascada de un XID 48 previo. Reset de la GPU (&lt;code>nvidia-smi --gpu-reset -i &amp;lt;index&amp;gt;&lt;/code>) o reboot del nodo si reset no resuelve.&lt;/li>
&lt;li>&lt;strong>XID 48 / 95&lt;/strong> (DBE / uncontained ECC): ver RB-04. El nodo entra en cuarentena.&lt;/li>
&lt;li>&lt;strong>XID 79&lt;/strong> (fallen off the bus): reboot del nodo. Si recurre tras reboot, abrir RMA de la GPU. ByteDance reporta 43 % de coocurrencia con errores PCIe — verificar también el slot y el cable.&lt;/li>
&lt;li>&lt;strong>XID 94 / 145 / 149&lt;/strong>: catalogados en el Xid Catalog de NVIDIA con procedimiento específico.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cierre.&lt;/strong> Smoke test del nodo pasado (&lt;code>dcgmi diag -r 3&lt;/code>), 24 h sin nuevos XIDs, vuelta al pool.&lt;/p>
&lt;p>&lt;strong>Postmortem.&lt;/strong> &lt;strong>Obligatorio&lt;/strong>. Incluir XID concreto, distribución de XIDs en el cluster, MTBE actualizado.&lt;/p>
&lt;h3 id="rb-04--gpueccdoublebit--dcgm_fi_dev_ecc_dbe_vol_total--0">RB-04 · &lt;code>GpuEccDoubleBit&lt;/code> — &lt;code>DCGM_FI_DEV_ECC_DBE_VOL_TOTAL &amp;gt; 0&lt;/code>&lt;/h3>
&lt;p>&lt;strong>Severity&lt;/strong>: CRITICAL — corrupción de datos en curso.&lt;/p>
&lt;p>&lt;strong>Mitigación inmediata.&lt;/strong> &lt;strong>Drenar el nodo inmediatamente sin esperar evidencia adicional&lt;/strong>. Páginas guardia (PagerDuty / OpsGenie) ON-CALL primario. Marcar el nodo &lt;code>unschedulable&lt;/code> y &lt;code>failed&lt;/code>. El XID 48 tiene &lt;strong>100 % probabilidad de matar el job en curso&lt;/strong> según el dataset de &lt;em>Story of Two GPUs&lt;/em>; cualquier inferencia ya está comprometida.&lt;/p>
&lt;p>&lt;strong>Evidencia (en paralelo a la mitigación).&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">nvidia-smi -q -d ECC
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvidia-smi -q -d ROW_REMAPPER &lt;span class="c1"># Pending: Yes esperado&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dmesg &lt;span class="p">|&lt;/span> grep -E &lt;span class="s2">&amp;#34;Xid.*48|DBE|double-bit&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> tail -50
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Captura completa del estado de la GPU&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dcgmi diag -r &lt;span class="m">4&lt;/span> -i &amp;lt;gpu_index&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Resolución.&lt;/strong> Reset completo de la GPU (&lt;code>nvidia-smi --gpu-reset&lt;/code>) o reboot del nodo si reset no completa. El reset activa el row remap. Tras el reboot:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">nvidia-smi -q -d ROW_REMAPPER &lt;span class="c1"># Pending: No esperado&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvidia-smi -q -d ECC &lt;span class="c1"># contadores volátiles a 0&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Si &lt;code>RETIRED_DBE &amp;gt; 8&lt;/code> páginas tras el remap: planificar &lt;strong>reemplazo de GPU&lt;/strong> en próxima ventana — la degradación del silicio es progresiva. Documentado &lt;em>~19 horas de downtime&lt;/em> típico en el caso real publicado.&lt;/p>
&lt;p>&lt;strong>Cierre.&lt;/strong> Nodo en pool tras 48 h sin nuevos DBE.&lt;/p>
&lt;p>&lt;strong>Postmortem.&lt;/strong> &lt;strong>Obligatorio&lt;/strong>. Si el incidente afectó a una request con datos personales / clasificados, evaluar notificación a DPO bajo GDPR art. 33 (no es necesariamente brecha, pero hay que evaluarlo).&lt;/p>
&lt;h3 id="rb-05--vllmkvcachepoolnearfull--gpu_cache_usage_perc--95--sostenido-3-min">RB-05 · &lt;code>VllmKvCachePoolNearFull&lt;/code> — &lt;code>gpu_cache_usage_perc &amp;gt; 95 %&lt;/code> sostenido 3 min&lt;/h3>
&lt;p>&lt;strong>Severity&lt;/strong>: WARNING (riesgo de preempt-on-OOM, no de OOM real).&lt;/p>
&lt;p>&lt;strong>Mitigación inmediata.&lt;/strong> Activar scale-out del autoscaler bajando el umbral de KEDA temporalmente (de 0.85 a 0.75) durante 30 min. Si está en modo &lt;code>recompute&lt;/code>, los preempts elevan TTFT pero no rompen requests; aceptable a corto plazo. Si está en modo &lt;code>swap&lt;/code>, latencia se va al techo — preferible cortar tráfico nuevo (devolver 503 desde el &lt;a href="https://blog.lo0.es/posts/router-inferencia-llm-gateway-l7/">router&lt;/a>) durante 5 min.&lt;/p>
&lt;p>&lt;strong>Evidencia.&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl http://vllm-pod:8000/metrics &lt;span class="p">|&lt;/span> grep -E &lt;span class="s2">&amp;#34;gpu_cache|num_requests|num_preemptions&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl get hpa vllm-llama70b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl logs &amp;lt;pod&amp;gt; --tail&lt;span class="o">=&lt;/span>&lt;span class="m">200&lt;/span> &lt;span class="p">|&lt;/span> grep -i preempt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Resolución.&lt;/strong> Si recurre regularmente: capacity planning revisado, posiblemente reducir &lt;code>max_num_seqs&lt;/code> o subir réplicas estables. Ver &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Cierre.&lt;/strong> Pool &amp;lt; 85 % sostenido 30 min, sin preempts en último 15 min.&lt;/p>
&lt;p>&lt;strong>Postmortem.&lt;/strong> No obligatorio salvo recurrencia &amp;gt; 3 veces / semana.&lt;/p>
&lt;h3 id="rb-06--vllmttftp95outofslo--ttft-p95--15-s-durante-5-min">RB-06 · &lt;code>VllmTtftP95OutOfSlo&lt;/code> — TTFT P95 &amp;gt; 1.5 s durante 5 min&lt;/h3>
&lt;p>&lt;strong>Severity&lt;/strong>: CRITICAL (violación de SLO contractual).&lt;/p>
&lt;p>&lt;strong>Mitigación inmediata.&lt;/strong> Diagnóstico rápido del régimen (en orden de probabilidad):&lt;/p>
&lt;ol>
&lt;li>Si hay canary v2 activo y el ratio &lt;code>ttft_p95(v2)/ttft_p95(v1) &amp;gt; 1.30&lt;/code>: &lt;strong>rollback automático&lt;/strong> del canary vía Argo Rollouts (&lt;code>argo rollouts abort vllm-llama70b&lt;/code>).&lt;/li>
&lt;li>Si &lt;code>num_requests_waiting &amp;gt; 5&lt;/code>: scale-out vía KEDA.&lt;/li>
&lt;li>Si &lt;code>DRAM_ACTIVE &amp;gt; 90 %&lt;/code> + &lt;code>gpu_cache_usage_perc &amp;gt; 90 %&lt;/code>: cuello en HBM, palanca de quantization o reducción de contexto.&lt;/li>
&lt;li>Si &lt;code>CLOCK_THROTTLE_REASONS != 0&lt;/code>: ver RB-02.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Evidencia.&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Snapshot del histograma&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl http://vllm-pod:8000/metrics &lt;span class="p">|&lt;/span> grep time_to_first_token
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Distribución por versión si hay canary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Estado DCGM del momento&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl http://dcgm-exporter:9400/metrics &lt;span class="p">|&lt;/span> grep -E &lt;span class="s2">&amp;#34;PIPE_TENSOR|DRAM_ACTIVE|THROTTLE&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Tráfico activo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl top pods -n inference
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Resolución.&lt;/strong> Depende del diagnóstico. Casos típicos:&lt;/p>
&lt;ul>
&lt;li>Canary regresión → rollback completo (ver &lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">Canary&lt;/a>).&lt;/li>
&lt;li>Saturación de capacidad → escalar réplicas o aceptar 503 temporal con &lt;code>Retry-After&lt;/code>.&lt;/li>
&lt;li>Prefill bound → activar/calibrar chunked prefill o disaggregated serving (ver &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">Disaggregated serving&lt;/a>).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cierre.&lt;/strong> TTFT P95 dentro de SLO sostenido 30 min.&lt;/p>
&lt;p>&lt;strong>Postmortem.&lt;/strong> &lt;strong>Obligatorio&lt;/strong>. Documentar causa raíz y palanca aplicada; actualizar runbook.&lt;/p>
&lt;h2 id="workflows-keep-yaml--tres-ejemplos-completos">Workflows Keep YAML — tres ejemplos completos&lt;/h2>
&lt;p>Los runbooks son útiles solo si están &lt;strong>codificados&lt;/strong> en el workflow engine. Keep permite declararlos en YAML versionados en git.&lt;/p>
&lt;h3 id="workflow-1--xid-detectedyaml">Workflow 1 — &lt;code>xid-detected.yaml&lt;/code>&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">workflow&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">xid-detected-drain&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;XID error detected — cordon node and capture evidence&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;RB-03 implementation&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">triggers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">alert&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">filters&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">alertname&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GpuXidErrorDetected&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">capture-evidence&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bash&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> set -e
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> NODE=&amp;#34;{{ alert.labels.node }}&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> GPU=&amp;#34;{{ alert.labels.gpu }}&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> INC_ID=&amp;#34;{{ alert.fingerprint }}&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> mkdir -p /var/evidence/$INC_ID
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> kubectl debug node/$NODE -it --image=nvcr.io/nvidia/cuda:12.4.0-base-ubuntu22.04 -- \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> bash -c &amp;#34;nvidia-smi -q -d ERROR,PCIE,ROW_REMAPPER &amp;gt; /host/var/evidence/$INC_ID/smi.txt&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> kubectl describe node $NODE &amp;gt; /var/evidence/$INC_ID/node.txt&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cordon-node&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kubernetes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cordon&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ alert.labels.node }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ alert.labels.severity == &amp;#39;critical&amp;#39; }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">actions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">open-jira-ticket&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">jira&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">config&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ providers.jira-prod }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">project&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GPUOPS&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">issuetype&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Incident&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">summary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;RB-03: XID {{ alert.annotations.xid_code }} on {{ alert.labels.node }}/{{ alert.labels.gpu }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> Severity: {{ alert.labels.severity }}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> XID: {{ alert.annotations.xid_code }}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> Evidence: /var/evidence/{{ alert.fingerprint }}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> Runbook: https://runbooks.example.local/RB-03&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">notify-slack&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">slack&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">config&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ providers.slack-gpu-incidents }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">message&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> :warning: *RB-03 triggered*
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> Node: `{{ alert.labels.node }}` GPU: `{{ alert.labels.gpu }}`
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> XID: `{{ alert.annotations.xid_code }}`
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> &amp;lt;{{ jira.url }}|Jira ticket&amp;gt;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">emit-audit&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kafka&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">config&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ providers.kafka-audit }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">topic&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">audit.actions&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">message&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">incident_id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ alert.fingerprint }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;cordon_node&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">actor&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;keep-workflow&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">workflow_id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;xid-detected-drain&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">target&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ alert.labels.node }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timestamp&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ now }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="workflow-2--ecc-dbeyaml--paginación-inmediata">Workflow 2 — &lt;code>ecc-dbe.yaml&lt;/code> — paginación inmediata&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">workflow&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ecc-dbe-critical&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;ECC double-bit — page on-call and quarantine node&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">triggers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">alert&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">filters&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">alertname&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GpuEccDoubleBit&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cordon-immediately&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kubernetes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cordon&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ alert.labels.node }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">drain-workload&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kubernetes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">drain&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ alert.labels.node }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">options&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ignore-daemonsets&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">delete-emptydir-data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">grace-period&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">120&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">page-oncall&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pagerduty&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">config&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ providers.pagerduty-critical }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">service_key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ env.PD_SERVICE_KEY }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">severity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">critical&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">summary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;RB-04 ECC DBE on {{ alert.labels.node }}/{{ alert.labels.gpu }} — node drained&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">emit-lifecycle&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kafka&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">config&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ providers.kafka-incidents }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">topic&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">incidents.lifecycle&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">message&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">incident_id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ alert.fingerprint }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">event&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">incident.opened&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">severity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">critical&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runbook&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">RB-04&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">requires_postmortem&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">notify-dpo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">email&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">to&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dpo@example.local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">subject&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;ECC DBE en GPU productiva — evaluación necesaria&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> Incidente RB-04 ECC DBE detectado en {{ alert.labels.node }}.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> Modelo afectado: {{ alert.labels.model }}.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> Por favor evaluar si hubo procesamiento de datos personales/clasificados
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> durante la ventana de error y necesidad de notificación GDPR art. 33.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="workflow-3--canary-rollbackyaml--ttft-p95-fuera-de-slo">Workflow 3 — &lt;code>canary-rollback.yaml&lt;/code> — TTFT P95 fuera de SLO&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">workflow&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">canary-rollback-ttft&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Rollback canary when TTFT P95 ratio v2/v1 &amp;gt; 1.30&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">triggers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">alert&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">filters&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">alertname&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">VllmTtftP95OutOfSlo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">canary_active&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;true&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">check-ratio&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">prometheus&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">config&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ providers.prom-prod }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">query&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket{version=&amp;#34;v2&amp;#34;}[5m])))
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> /
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket{version=&amp;#34;v1&amp;#34;}[5m])))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">result &amp;gt; 1.30&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">actions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">argo-rollback&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kubernetes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">exec&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">kubectl&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">argo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">rollouts&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">abort&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;{{ alert.labels.rollout }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- -&lt;span class="kc">n&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;{{ alert.labels.namespace }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">notify-and-audit&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kafka&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">config&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ providers.kafka-audit }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">topic&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">audit.actions&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">message&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">incident_id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ alert.fingerprint }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">canary_rollback&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ratio&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ steps.check-ratio.result }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">actor&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">keep-workflow&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timestamp&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;{{ now }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Cada workflow se guarda en &lt;code>repos/keep-workflows/&lt;/code> versionado en git, revisado por pull request, validado por CI (&lt;code>keep workflow validate&lt;/code>). El runbook escrito vive como &lt;code>docs/runbooks/RB-XX.md&lt;/code> enlazado desde el workflow YAML — los dos siempre evolucionan juntos.&lt;/p>
&lt;h2 id="el-schema-canónico-de-eventos-kafka">El schema canónico de eventos Kafka&lt;/h2>
&lt;p>Para que los topics sean consumibles por compliance, postmortem tooling y dashboards sin que cada consumer tenga que adivinar el shape, se fija schema con Avro / Protobuf.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;IncidentLifecycleEvent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;record&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fields&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;incident_id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;event&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;enum&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;symbols&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;incident.opened&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;incident.acknowledged&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;action.proposed&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;action.executed&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;action.failed&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;incident.escalated&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;incident.resolved&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;postmortem.attached&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]}},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;timestamp&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;logicalType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;timestamp-millis&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;actor&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;severity&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;enum&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;symbols&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;low&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;warning&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;critical&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;runbook&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;null&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nt">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;alert_name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;labels&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;map&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;values&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;annotations&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;map&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;values&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;evidence_uri&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;null&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nt">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;requires_postmortem&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;boolean&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Para &lt;code>audit.actions&lt;/code> (WORM), un schema separado más exigente con campos no-modificables:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;AuditAction&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;record&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fields&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;incident_id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;actor&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;actor_type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;enum&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;symbols&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;human&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;workflow&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;scheduler&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;workflow_id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;null&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nt">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;command&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;null&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nt">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;result&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;enum&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;symbols&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;success&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;failure&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;partial&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;timestamp&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;logicalType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;timestamp-millis&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;evidence_uri&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;null&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nt">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;approver&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;null&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nt">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El topic se configura con &lt;code>cleanup.policy=delete&lt;/code>, &lt;code>retention.ms=15552000000&lt;/code> (6 meses) y &lt;code>min.insync.replicas=2&lt;/code> con &lt;code>acks=all&lt;/code> para garantizar durabilidad. Para retención más larga sin coste de Kafka, &lt;strong>tiered storage&lt;/strong> a Ceph RGW o S3-compatible — el log nuevo en hot tier, el viejo en cold tier transparente al consumer.&lt;/p>
&lt;h2 id="encaje-formal-en-gestión-de-incidentes">Encaje formal en gestión de incidentes&lt;/h2>
&lt;p>Los runbooks no son una práctica de SRE aislada — encajan en cuatro marcos normativos que las plataformas LLM productivas tocan a diario.&lt;/p>
&lt;h3 id="isoiec-27035--gestión-de-incidentes-de-seguridad-de-la-información">ISO/IEC 27035 — gestión de incidentes de seguridad de la información&lt;/h3>
&lt;p>Define el ciclo formal en cinco fases: &lt;strong>plan &amp;amp; prepare&lt;/strong> → &lt;strong>detect &amp;amp; report&lt;/strong> → &lt;strong>assess &amp;amp; decide&lt;/strong> → &lt;strong>respond&lt;/strong> → &lt;strong>lessons learned&lt;/strong>. Cada fase tiene salidas exigibles documentalmente. La traducción al stack:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Plan &amp;amp; prepare&lt;/strong>: los runbooks RB-01 a RB-06 + los workflows Keep son parte del &lt;em>Information Security Incident Management Plan&lt;/em>. Versionados en git, revisados anualmente.&lt;/li>
&lt;li>&lt;strong>Detect &amp;amp; report&lt;/strong>: las alertas Prometheus que entran a Kafka son la materialización.&lt;/li>
&lt;li>&lt;strong>Assess &amp;amp; decide&lt;/strong>: la severity en &lt;code>gpu.alerts.enriched&lt;/code> + la lógica del workflow Keep.&lt;/li>
&lt;li>&lt;strong>Respond&lt;/strong>: ejecución de los &lt;code>steps&lt;/code> + &lt;code>actions&lt;/code> del workflow.&lt;/li>
&lt;li>&lt;strong>Lessons learned&lt;/strong>: postmortem obligatorio para los runbooks que lo marcan; salida documentada en el repo de postmortems + actualización del runbook.&lt;/li>
&lt;/ul>
&lt;h3 id="ens-esquema-nacional-de-seguridad--controles-opexp">ENS (Esquema Nacional de Seguridad) — controles op.exp&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>&lt;code>op.exp.7&lt;/code> Gestión de incidentes&lt;/strong>: el catálogo de runbooks + el pipeline Keep / Kafka materializan la &amp;ldquo;respuesta organizada y procedimentada&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>&lt;code>op.exp.8&lt;/code> Registro de actividad&lt;/strong>: el topic &lt;code>audit.actions&lt;/code> con retención WORM 6 meses (mínimo nivel ALTO).&lt;/li>
&lt;li>&lt;strong>&lt;code>op.exp.9&lt;/code> Registro de la gestión de incidentes&lt;/strong>: el topic &lt;code>incidents.lifecycle&lt;/code> con el ciclo completo de cada incidente.&lt;/li>
&lt;li>&lt;strong>&lt;code>op.exp.10&lt;/code> Protección de los registros de actividad&lt;/strong>: WORM + cifrado en reposo + control de acceso (consumers compliance solo-lectura).&lt;/li>
&lt;/ul>
&lt;h3 id="nis2--notificación-a-autoridad-competente">NIS2 — notificación a autoridad competente&lt;/h3>
&lt;p>Para entidades esenciales / importantes, el art. 23 fija tres plazos a partir del &lt;em>significant impact&lt;/em> detectado:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>24 horas&lt;/strong>: notificación temprana (&amp;ldquo;early warning&amp;rdquo;) al CSIRT nacional (INCIBE-CERT en España).&lt;/li>
&lt;li>&lt;strong>72 horas&lt;/strong>: notificación formal con assessment inicial.&lt;/li>
&lt;li>&lt;strong>1 mes&lt;/strong>: informe final con causa raíz, impacto, medidas correctivas.&lt;/li>
&lt;/ul>
&lt;p>Los datos para esos informes salen directamente de &lt;code>incidents.lifecycle&lt;/code> + &lt;code>audit.actions&lt;/code> con un consumer que genera el dossier en el formato requerido. Sin el pipeline auditable, los plazos NIS2 son inalcanzables.&lt;/p>
&lt;h3 id="eu-ai-act--art-73-serious-incident-reporting">EU AI Act — art. 73 (serious incident reporting)&lt;/h3>
&lt;p>Aplicable a sistemas de alto riesgo. Plazos:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>2 días&lt;/strong>: para incidentes que provoquen fallecimiento o daño irreversible a personas o infraestructuras críticas.&lt;/li>
&lt;li>&lt;strong>10 días&lt;/strong>: para incidentes que produzcan disrupción seria de infraestructura crítica.&lt;/li>
&lt;li>&lt;strong>15 días&lt;/strong>: para el resto de &amp;ldquo;serious incidents&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;p>La definición de &amp;ldquo;serious incident&amp;rdquo; incluye fallos sistemáticos del modelo, brecha de fundamental rights, daño material o medioambiental. Los runbooks deben marcar qué alertas pueden derivar en serious incident (típicamente cualquier cosa que afecte la salida del modelo en un contexto de alto riesgo) y disparar un sub-workflow específico de evaluación legal.&lt;/p>
&lt;h3 id="isoiec-42001--aims-cláusula-10-mejora-continua">ISO/IEC 42001 — AIMS cláusula 10 mejora continua&lt;/h3>
&lt;p>El postmortem obligatorio post-incidente alimenta la cláusula 10. La actualización del runbook tras cada incidente que descubre un patrón nuevo es la &amp;ldquo;acción correctiva con verificación de eficacia&amp;rdquo; que la norma exige. Ver &lt;a href="https://blog.lo0.es/posts/iso-42001-aims-llm-on-premise/">ISO 42001 AIMS&lt;/a>.&lt;/p>
&lt;h2 id="cuatro-anti-patrones">Cuatro anti-patrones&lt;/h2>
&lt;p>&lt;strong>Anti-patrón 1 — alertas sin runbook.&lt;/strong> La alerta dispara, el operador junior de guardia mira el dashboard, busca en Confluence, no encuentra nada actualizado, llama al senior por Slack, espera 20 minutos. En ese tiempo el incidente ha crecido. Regla: &lt;strong>ninguna alerta entra a producción sin runbook publicado y workflow Keep aprobado&lt;/strong>. CI valida que cada &lt;code>PrometheusRule&lt;/code> con severity ≥ warning tiene su &lt;code>keep workflow&lt;/code> correspondiente.&lt;/p>
&lt;p>&lt;strong>Anti-patrón 2 — runbook sin captura de evidencia previa.&lt;/strong> El workflow ejecuta &lt;code>nvidia-smi --gpu-reset&lt;/code> en cuanto llega el XID, perdiendo el estado que habría diagnosticado la causa raíz. El siguiente XID idéntico exige rehacer el diagnóstico desde cero. Regla: &lt;strong>&lt;code>steps&lt;/code> antes de &lt;code>actions&lt;/code>&lt;/strong>; toda evidencia se captura primero, las acciones destructivas después.&lt;/p>
&lt;p>&lt;strong>Anti-patrón 3 — escalada por antigüedad en vez de severity.&lt;/strong> El operador junior de guardia gestiona un ECC DBE porque &amp;ldquo;le toca&amp;rdquo;. Le falta contexto para entender row remap, retired pages o el riesgo de corrupción de datos. Regla: &lt;strong>paginación por severity, no por rotación&lt;/strong>: RB-04 y RB-03 dispararon ON-CALL primario senior con escalada automática a infra/hardware si no acuse en 10 min.&lt;/p>
&lt;p>&lt;strong>Anti-patrón 4 — ausencia de gate humano para acciones destructivas.&lt;/strong> El workflow ejecuta &lt;code>kubectl drain&lt;/code> automáticamente sobre cualquier alerta marcada como CRITICAL. En la primera falsa alarma (un transitorio que se autoresolvió en 30 s), Keep drenó un nodo productivo durante hora pico. Regla: &lt;strong>acciones destructivas (drain, reset, RMA, rollback completo) exigen confirmación humana&lt;/strong> vía Slack interactive message, con timeout configurable. Excepción justificada: ECC DBE confirmado por &amp;gt; 1 medición — el riesgo de corrupción supera el de falsa alarma.&lt;/p>
&lt;h2 id="aplicado-a-hardware-on-premise-típico">Aplicado a hardware on-premise típico&lt;/h2>
&lt;p>Para un cluster genérico de &lt;strong>4 nodos × 4×H100 SXM 80 GB&lt;/strong> con &lt;strong>Kafka y Keep ya desplegados&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Kafka&lt;/strong>: cluster de 3 brokers en nodos no-GPU del cluster K8s; topics &lt;code>gpu.alerts.enriched&lt;/code>, &lt;code>incidents.lifecycle&lt;/code>, &lt;code>audit.actions&lt;/code> configurados con replication factor 3, min.insync.replicas 2. Audit con tiered storage a Ceph RGW para retención &amp;gt; 6 meses sin coste brutal.&lt;/li>
&lt;li>&lt;strong>Keep&lt;/strong>: 2 réplicas del operator + 1 réplica del worker en un namespace &lt;code>keep&lt;/code>; conectado a Prometheus (provider read), Kafka (provider read + write), Slack, PagerDuty, Jira, Kubernetes (provider con SA específico con permisos &lt;code>get/list/patch nodes&lt;/code>, &lt;code>create jobs&lt;/code>).&lt;/li>
&lt;li>&lt;strong>Workflows&lt;/strong>: ~25-40 YAML en el repo &lt;code>infra/keep-workflows/&lt;/code>, sincronizado con el cluster vía Flux o Argo CD. Validados por CI (&lt;code>keep workflow validate&lt;/code>) en cada PR.&lt;/li>
&lt;li>&lt;strong>Volumen de eventos&lt;/strong>: para 16 GPUs en operación normal con alertas debounced, ~50-200 eventos/día en &lt;code>gpu.alerts.enriched&lt;/code>. En incidente típico, picos de 500-2000 eventos/día.&lt;/li>
&lt;li>&lt;strong>Compliance consumers&lt;/strong>: un consumer python en namespace &lt;code>compliance&lt;/code> que genera reportes NIS2 / ENS / EU AI Act semanalmente, leyendo &lt;code>audit.actions&lt;/code> y &lt;code>incidents.lifecycle&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-posts">Lo que no hemos cubierto (próximos posts)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Playbooks de postmortem&lt;/strong> — la mecánica de RCA con 5-whys, Ishikawa adaptado a LLM, integración con MLflow tracking de re-training si el postmortem produce dataset enriquecido.&lt;/li>
&lt;li>&lt;strong>Chaos engineering para LLM&lt;/strong> — inyección controlada de XID errors, ECC simulados, latencia HBM artificial para validar runbooks &lt;strong>antes&lt;/strong> del incidente real.&lt;/li>
&lt;li>&lt;strong>Multi-cluster incident coordination&lt;/strong> — cómo coordinar Keep entre clusters geográficos cuando un incidente afecta a múltiples regiones.&lt;/li>
&lt;li>&lt;strong>Integración con CMDB y procurement&lt;/strong> — el ciclo &lt;code>RMA → ticket → ServiceNow → reposición de hardware&lt;/code> automatizado vía workflow.&lt;/li>
&lt;li>&lt;strong>Forense LLM&lt;/strong> — extracción de la traza OTel completa de una request afectada por un incidente, redacted PII, conservación en evidence vault.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/anatomia-metricas-dcgm-vllm-anomalias/">Anatomía de las doce métricas DCGM y cinco vLLM&lt;/a> — la anomalía documentada por métrica que estos runbooks resuelven.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a> — la lista compacta y las seis alertas críticas.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a> — la traza OTel que se captura como evidencia.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">Canary, blue-green y shadow&lt;/a> — el mecanismo de rollback que RB-06 invoca.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a> — la palanca de escalado que RB-01 y RB-05 invocan.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning&lt;/a> — el head-room presupuestado para absorber incidentes sin SLO break.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/iso-42001-aims-llm-on-premise/">ISO/IEC 42001 AIMS para LLM on-premise&lt;/a> — la cláusula 10 que estos postmortems materializan.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/controles-tecnicos-ens-42001-eu-ai-act/">Controles técnicos ENS × 42001 × EU AI Act&lt;/a> — el mapeo de controles que estos runbooks satisfacen.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/eu-ai-act-mapeo-arquitectura-llm-on-premise/">EU AI Act: mapeo a arquitectura LLM&lt;/a> — el art. 73 de incidentes graves que activa el sub-workflow legal.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">Cinco niveles de madurez&lt;/a> — los runbooks codificados son requisito del nivel 3-4.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>ISO/IEC 27035-1:2023 — &lt;em>Information security incident management — Principles and process&lt;/em>.&lt;/li>
&lt;li>ISO/IEC 27035-2:2023 — &lt;em>Information security incident management — Guidelines to plan and prepare for incident response&lt;/em>.&lt;/li>
&lt;li>ENS — &lt;em>Real Decreto 311/2022&lt;/em>, Anexo II controles &lt;code>op.exp.7&lt;/code> a &lt;code>op.exp.10&lt;/code>.&lt;/li>
&lt;li>Directiva NIS2 (UE 2022/2555) — art. 23 (notificación de incidentes significativos).&lt;/li>
&lt;li>Reglamento EU AI Act (UE 2024/1689) — art. 73 (reporting of serious incidents).&lt;/li>
&lt;li>ISO/IEC 42001:2023 — &lt;em>AI management system — cláusula 10 (mejora continua)&lt;/em>.&lt;/li>
&lt;li>Keep project — &lt;code>keephq.dev&lt;/code> y &lt;code>github.com/keephq/keep&lt;/code> (documentación de workflows YAML, providers).&lt;/li>
&lt;li>Apache Kafka — &lt;em>Tiered Storage&lt;/em> y &lt;code>cleanup.policy&lt;/code> (docs.confluent.io / kafka.apache.org).&lt;/li>
&lt;li>Confluent — &lt;em>Schema Registry&lt;/em> y best practices para eventos lifecycle.&lt;/li>
&lt;li>NVIDIA — &lt;em>Xid Errors Documentation&lt;/em> y procedimientos de remediación.&lt;/li>
&lt;li>Google SRE Book — &lt;em>Effective Troubleshooting&lt;/em> y &lt;em>Postmortem Culture&lt;/em>.&lt;/li>
&lt;li>Atlassian — &lt;em>Incident Management Handbook&lt;/em> (referencia para severity matrices).&lt;/li>
&lt;/ul></description></item><item><title>Anatomía de las doce métricas DCGM y cinco vLLM: analogías, anomalías documentadas y casos reales 2024-2026</title><link>https://blog.lo0.es/posts/anatomia-metricas-dcgm-vllm-anomalias/</link><pubDate>Tue, 02 Jun 2026 04:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/anatomia-metricas-dcgm-vllm-anomalias/</guid><description>&lt;blockquote>
&lt;p>Este post profundiza la lista de métricas presentada en &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a>. Allí cada métrica recibió su umbral V/Á/R y query PromQL; aquí cada una recibe su analogía explicativa y la anomalía documentada en producción con caso público referenciado. Es el post que conviene tener abierto cuando una alerta dispara y todavía no se sabe qué hacer con ella; el &lt;a href="https://blog.lo0.es/posts/runbooks-incident-response-llm-keep-kafka/">siguiente post sobre runbooks&lt;/a> traduce cada anomalía a acción concreta.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Las doce métricas DCGM (compute, memoria, térmico-energético, salud) y las cinco del motor vLLM (concurrencia, KV pool, latencias del SLO) cubiertas en el post anterior pintan la cabina del cluster, pero la lista sin contexto no enseña a diagnosticar. Cada métrica tiene un &lt;strong>patrón anómalo recurrente&lt;/strong> documentado en literatura pública —papers académicos, issues GitHub, KBs de OEMs, blogs de operadores— que el operador veterano reconoce al instante y el junior no. Este post desarrolla cada métrica con una &lt;strong>analogía propia&lt;/strong> que fija qué pregunta responde y con la &lt;strong>anomalía estadísticamente relevante&lt;/strong> con cifras de incidentes documentados. Tres ejemplos del calibre: &lt;strong>Meta&lt;/strong> publicó que durante el entrenamiento de Llama 3 405B sobre 16.384 H100 hubo &lt;strong>419 fallos no planificados en 54 días&lt;/strong> —uno cada 3 horas—, con GPU + HBM3 acumulando el 47 % del total; el paper &lt;em>Story of Two GPUs&lt;/em> (arXiv 2503.11901) cuantifica que &lt;strong>H100 tiene 3.2× peor MTBE por ECC uncorrectable que A100&lt;/strong> atribuible a la densidad superior de HBM3; el issue &lt;strong>vllm#16300&lt;/strong> documenta que en un cluster de 8×A100 80 GB &lt;strong>TP=8 entrega peor throughput que TP=4&lt;/strong> porque la saturación de NVLink mata el speedup de partition. Las KBs &lt;strong>Dell 000220508&lt;/strong> y &lt;strong>Lenovo HT514380&lt;/strong> formalizan el caso recurrente de &lt;em>HW Power Brake&lt;/em> en racks H100 sobrecomprometidos a nivel de PDU. El issue &lt;strong>vllm#25677&lt;/strong> mostró &lt;em>chunked prefill&lt;/em> 10× más lento que sin él en Qwen3-30B-A3B (mala calibración de &lt;code>max_num_batched_tokens&lt;/code>). El issue &lt;strong>vllm#11912&lt;/strong> documenta regresión de TPOT de 15.7 ms a 25.7 ms cruzando versión 0.6.4. Cada caso incluye URL verificable. La regla operativa: cuando llega una alerta, mira primero el patrón anómalo asociado a la métrica que disparó, &lt;strong>antes&lt;/strong> de abrir la traza de la request; el 80 % de las degradaciones casan con uno de los patrones documentados.&lt;/p>
&lt;h2 id="estás-aquí-observe--la-capa-de-diagnóstico">Estás aquí: OBSERVE — la capa de diagnóstico&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="estás aquí: Observe, capa de diagnóstico">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#c9a8e9;stroke-width:3}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#anm)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#anm)}&lt;/style>
&lt;defs>&lt;marker id="anm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">Estás aquí: OBSERVE · cada métrica es una pregunta con una anomalía típica asociada&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box idle"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box active"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box idle"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="familia-1--compute">Familia 1 — Compute&lt;/h2>
&lt;h3 id="dcgm_fi_prof_sm_occupancy--hay-trabajo-paralelo-en-los-motores">&lt;code>DCGM_FI_PROF_SM_OCCUPANCY&lt;/code> — ¿hay trabajo paralelo en los motores?&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> Una cocina industrial con 32 fogones y un único chef. La métrica responde &lt;em>&amp;quot;¿cuántos fogones tienen una sartén encima ahora mismo?&amp;quot;&lt;/em>. Si la mitad están vacíos, la cocina está infrautilizada — los pedidos van uno detrás de otro porque el chef no abre paralelo. Si todos están ocupados pero el chef está sin moverse mirando un cronómetro, los fogones están encendidos pero no se cocina (un kernel patológico saturando SMs sin hacer trabajo útil).&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> La trampa más conocida: &lt;strong>SM occupancy alto no implica throughput real&lt;/strong>. El artículo &lt;em>&amp;ldquo;GPU Utilization Is a Counter, Not a Cause&amp;rdquo;&lt;/em> (Ingero, mayo 2026) lo formuló con una frase exacta: &lt;em>&amp;ldquo;un kernel que corre al 5 % del pico de FLOPS durante 100 ms todavía marca 100 % en SM_ACTIVE&amp;rdquo;&lt;/em>. En workloads MoE, el efecto se vuelve patológico: los expertos sobrecargados producen el &lt;strong>Straggler Effect&lt;/strong> (paper arXiv 2503.05066) — los SMs aparecen ocupados esperando al experto saturado, y el dashboard de utilización pinta verde mientras la latencia se va al techo.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> No fiar el sizing ni el autoscaling solo a SM occupancy. Combinar siempre con &lt;code>PIPE_TENSOR_ACTIVE&lt;/code> (¿hay compute útil?) y &lt;code>DRAM_ACTIVE&lt;/code> (¿la memoria es el cuello?). El régimen normal LLM en decode es 30–55 %, no 99 %; ver 99 % sostenido con TPOT alto es síntoma de bug del kernel o de straggler MoE.&lt;/p>
&lt;h3 id="dcgm_fi_prof_pipe_tensor_active--los-tensor-cores-producen">&lt;code>DCGM_FI_PROF_PIPE_TENSOR_ACTIVE&lt;/code> — ¿los tensor cores producen?&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> Una fábrica con dos líneas: la artesanal (CUDA cores) y la automatizada (tensor cores). La métrica responde &lt;em>&amp;quot;¿qué porcentaje del tiempo está activa la línea automatizada?&amp;quot;&lt;/em>. Si compras una H100 por sus tensor cores y la línea automatizada está al 5 %, has pagado un Ferrari para llevar mensajería en bicicleta.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> El issue &lt;strong>vllm#20783&lt;/strong> (julio 2025) tituló literalmente &lt;em>&amp;ldquo;Performance Anomaly: compressed-tensors no muestra speedup sobre BF16 en H100&amp;rdquo;&lt;/em>. El operador esperaba 1.5–2× con cuantización FP8 y obtuvo paridad con BF16; la métrica &lt;code>PIPE_TENSOR_ACTIVE&lt;/code> reveló que el path FP8 no estaba ejecutándose en los HMMA (la unidad tensor de FP16/BF16/FP8) y caía a CUDA cores. El issue &lt;strong>vllm#31475&lt;/strong> documentó el caso paralelo en MI300X: FP8 más lento que BF16 por regresión en el path ROCm. DCGM expone counters separados por unidad (&lt;code>HMMA&lt;/code> para FP16/BF16/FP8, &lt;code>IMMA&lt;/code> para INT8, &lt;code>DMMA&lt;/code> para TF32/FP32); si &lt;code>HMMA&lt;/code> está bajo aunque el modelo es BF16, el engine no usa tensor cores.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Verificar &lt;code>PIPE_TENSOR_ACTIVE&lt;/code> después de cada cambio de quantization o versión del motor; un cambio supuestamente neutro puede haber desactivado el path optimizado. Para prefill esperar 50–80 %; para decode 15–30 % es normal (decode es memory-bound, no compute-bound). Cifra &amp;lt; 5 % en prefill = el motor no está usando tensor cores.&lt;/p>
&lt;h3 id="dcgm_fi_prof_dram_active--está-la-hbm-saturada">&lt;code>DCGM_FI_PROF_DRAM_ACTIVE&lt;/code> — ¿está la HBM saturada?&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> Una autopista con N carriles. La métrica responde &lt;em>&amp;quot;¿qué porcentaje del tiempo están todos los carriles ocupados moviendo coches?&amp;quot;&lt;/em>. Cuando los tensor cores piden datos más rápido de lo que la HBM los entrega, la autopista está al 95 % y los motores esperan. En decode, este es el régimen normal — paseas los pesos del modelo y el KV cache por cada token.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> El paper &lt;em>&amp;ldquo;Mind the Memory Gap: Unveiling GPU Bottlenecks in Large-Batch LLM Inference&amp;rdquo;&lt;/em> (arXiv 2503.08311) cuantifica que a contextos ≥ 128k, la lectura del KV cache &lt;strong>domina el tiempo total de decode&lt;/strong> y satura la HBM3 (3.35 TB/s en H100). Patrón distintivo: &lt;code>DRAM_ACTIVE&lt;/code> &amp;gt; 80 % con &lt;code>PIPE_TENSOR_ACTIVE&lt;/code> ~10–20 %. Subir el batch ya no ayuda — el cuello no son FLOPS, es bandwidth. La palanca útil es comprimir KV: ver &lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">Quantization&lt;/a> para &lt;code>--kv-cache-dtype=fp8&lt;/code> que recorta el footprint de KV ~50 %.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Si &lt;code>DRAM_ACTIVE &amp;gt; 95 %&lt;/code> sostenido y &lt;code>gpu_cache_usage_perc &amp;lt; 70 %&lt;/code>, &lt;strong>algo está pidiendo HBM que no es tu motor&lt;/strong> (leak en una librería, otro proceso compartiendo GPU sin MIG). Investigar inmediatamente con &lt;code>nvidia-smi&lt;/code> y &lt;code>fuser /dev/nvidia*&lt;/code>.&lt;/p>
&lt;h2 id="familia-2--memoria">Familia 2 — Memoria&lt;/h2>
&lt;h3 id="dcgm_fi_dev_fb_used--cuánta-vram-lleva-consumida">&lt;code>DCGM_FI_DEV_FB_USED&lt;/code> — ¿cuánta VRAM lleva consumida?&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> El nivel de combustible del depósito de un avión en vuelo: el piloto necesita saber cuánto queda &lt;strong>y a qué ritmo se consume&lt;/strong>, no solo la cifra puntual. Una H100 al 88 % de FB used &lt;strong>estable&lt;/strong> puede operar tranquila; la misma cifra &lt;strong>subiendo 2 %/min&lt;/strong> anuncia OOM en 7 minutos.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> El issue &lt;strong>dcgm-exporter#512&lt;/strong> documenta una sorpresa relevante para clusters MIG: &lt;strong>&lt;code>DCGM_FI_DEV_FB_USED&lt;/code> y &lt;code>DCGM_FI_DEV_FB_FREE&lt;/code> están ausentes en GPU instances H100 con MIG activado&lt;/strong> — sí presentes en A100 y B200, pero un bug del exporter los esconde en H100-MIG. Operadores que asumen el dashboard cubre todo descubren la ceguera el día del primer OOM. Issue &lt;strong>dcgm-exporter#271&lt;/strong> documenta otro detalle: &lt;code>FB_USED + FB_FREE&lt;/code> &lt;strong>no siempre suma constante&lt;/strong> porque hay overhead reservado por el driver que aparece en el delta. El paper original de &lt;strong>PagedAttention/vLLM&lt;/strong> estimaba que serving frameworks pre-PagedAttention desperdiciaban &lt;strong>60–80 %&lt;/strong> del KV cache por fragmentación; PagedAttention lo bajó a &amp;lt; 4 %.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> En clusters MIG H100, verificar que &lt;code>DCGM_FI_DEV_FB_USED&lt;/code> aparece por instance antes de confiar en alertas; si está ausente, monitorizar vía &lt;code>nvidia-smi --query-gpu=memory.used&lt;/code> directamente. Regla operativa: alertar sobre &lt;strong>delta&lt;/strong> (subida sostenida), no solo umbral absoluto.&lt;/p>
&lt;h3 id="dcgm_fi_dev_fb_free--el-complemento-absoluto">&lt;code>DCGM_FI_DEV_FB_FREE&lt;/code> — el complemento absoluto&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> El indicador &amp;ldquo;kilómetros restantes&amp;rdquo; del coche moderno: complementa al porcentaje con una cifra absoluta directamente accionable.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> Cuando un PagedAttention pool agresivo deja &lt;code>FB_FREE&lt;/code> en valores absolutos pequeños (&amp;lt; 2 GiB), cualquier asignación normal de buffers transitorios (activaciones de un prefill grande) puede empujar al OOM. El patrón clásico: porcentaje &amp;ldquo;verde&amp;rdquo; (87 %) pero absoluto &amp;ldquo;rojo&amp;rdquo; (&amp;lt; 4 GiB libres en una H100 de 80 GB).&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Alerta complementaria con umbral absoluto: &lt;code>DCGM_FI_DEV_FB_FREE &amp;lt; 4096&lt;/code> (MiB). Es la red de seguridad para los casos donde el porcentaje engaña porque el motor está configurado con &lt;code>gpu_memory_utilization&lt;/code> muy alto.&lt;/p>
&lt;h3 id="dcgm_fi_dev_nvlink_bandwidth_total--el-bus-interno-aguanta">&lt;code>DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL&lt;/code> — ¿el bus interno aguanta?&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> Una autopista interestatal entre cuatro ciudades. Cada coche que cruza para hacer un all-reduce de tensor parallel paga peaje y consume ancho. Cuando hay más coches que la autopista soporta, la latencia para llegar a destino se dispara — aunque cada coche individual sea rápido.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> El issue &lt;strong>vllm#16300&lt;/strong> (abril 2025) tituló &lt;em>&amp;ldquo;Performance degradation with tp=8 compared to tp=4 on 8×A100(80G)&amp;rdquo;&lt;/em> y documentó &lt;strong>TP=8 entregando peor throughput que TP=4&lt;/strong> en el mismo cluster, mismo modelo, misma quantization. Causa raíz: el tensor parallelism requiere all-reduce tras cada bloque de atención y MLP; a TP=8, el coste de comunicación entre 8 GPUs (incluso vía NVSwitch) crece más rápido que el speedup del partition compute. La regla práctica que emerge: &lt;strong>TP=4 + 2 réplicas&lt;/strong> suele entregar mejor latencia/throughput que &lt;strong>TP=8 + 1 réplica&lt;/strong> salvo para contextos extremadamente largos (≥128k) donde necesitas la VRAM agregada. Capacidad teórica NVLink 4.0 en H100 SXM: ~450 GB/s por GPU; régimen TP=4 sostenido típico: 50–150 GB/s.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Si &lt;code>NVLINK_BANDWIDTH_TOTAL &amp;gt; 90 %&lt;/code> capacidad sostenido, no es problema &lt;em>resoluble subiendo paralelismo&lt;/em> — al revés, &lt;strong>bajar TP&lt;/strong>. La métrica es ortogonal al sizing del &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">capacity planning&lt;/a>: el techo no es solo VRAM/tiempo, también el bus.&lt;/p>
&lt;h2 id="familia-3--térmico-y-energético">Familia 3 — Térmico y energético&lt;/h2>
&lt;h3 id="dcgm_fi_dev_gpu_temp--la-gpu-respira">&lt;code>DCGM_FI_DEV_GPU_TEMP&lt;/code> — ¿la GPU respira?&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> La temperatura corporal de un atleta de élite en pleno esfuerzo. 36–37 °C es normal; 38 °C es estrés sostenible; por encima de 39 °C el cuerpo activa mecanismos de protección (sudoración, ralentización) que &lt;strong>degradan el rendimiento&lt;/strong>. La GPU hace lo mismo: por encima de un umbral térmico, reduce su clock automáticamente. Si no lo hiciera, se rompería.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> El H100 SXM5 con TDP 700 W tiene thresholds térmicos no enteramente públicos (NVIDIA no los publica exhaustivamente en datasheet), pero el comportamiento es bien conocido: por encima de ~85 °C edge o ~95 °C HBM aparece el bit &lt;code>0x40 HW_THERMAL&lt;/code> en clock throttle reasons. Operadores en el foro NVIDIA developer reportan que con &lt;strong>temperatura de entrada al rack &amp;gt; 27 °C&lt;/strong>, el throttle es habitual. El paper de NVIDIA HGX Platform indica que el flujo de aire mínimo recomendado es &lt;strong>&amp;gt; 1000 CFM/kW&lt;/strong>; densidades &amp;gt; 30 kW/rack a 700 W TDP exigen liquid cooling obligatorio porque el aire forzado no llega.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Si &lt;code>GPU_TEMP &amp;gt; 83 °C&lt;/code> sostenido, mirar primero &lt;code>CLOCK_THROTTLE_REASONS&lt;/code> (bit 0x40) y temperatura de entrada al rack — no es problema del motor, es del flujo de aire. Para racks legacy aire-cooled, plantear redistribuir carga térmica o instalar rear-door HX.&lt;/p>
&lt;h3 id="dcgm_fi_dev_power_usage--cuánto-pide-al-enchufe">&lt;code>DCGM_FI_DEV_POWER_USAGE&lt;/code> — ¿cuánto pide al enchufe?&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> El consumo instantáneo de un electrodoméstico industrial conectado a una toma trifásica con un breaker dimensionado. Si la lavadora arranca a 9 kW y el breaker es de 10 kW, vives al filo; si la lavadora se &amp;ldquo;lleva bien&amp;rdquo; con el breaker es porque alguien dimensionó conscientemente.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> Medición real publicada: una H100 SXM5 con vLLM corriendo Llama 3.1 405B batch=4 consume &lt;strong>~697 W at-wall&lt;/strong> sostenido (NVIDIA TDP 700 W). Ahora la palanca operativa interesante: &lt;strong>bajar &lt;code>nvidia-smi -pl&lt;/code> de 700 W a 500 W&lt;/strong> entrega ~30 % de ahorro energético con solo ~20 % de pérdida de throughput. Cluster de 4 nodos × 8 H100 a 700 W = ~22 kW solo de GPU; a 500 W = ~16 kW. La diferencia paga la factura eléctrica entera de un trimestre en clusters operados ininterrumpidamente. Una rama PDU 415 VAC trifásica 60–80 A soporta ~32 kW, ~4 DGX H100. Legacy 208 V no soporta densidad H100 — referencia: NVIDIA DGX SuperPOD Electrical Specifications.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Métrica útil para tres cosas: (1) detectar workloads anómalamente bajos (idle inesperado), (2) calcular showback de coste energético real por tenant (no estimaciones), (3) alertar si el draw se acerca al límite de PDU rama. Tener mapeado &lt;strong>GPU → PDU rama → breaker&lt;/strong> en CMDB.&lt;/p>
&lt;h3 id="dcgm_fi_dev_clock_throttle_reasons--quién-pisa-el-freno">&lt;code>DCGM_FI_DEV_CLOCK_THROTTLE_REASONS&lt;/code> — ¿quién pisa el freno?&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> El testigo de &amp;ldquo;modo limitado&amp;rdquo; en el salpicadero de un coche moderno. Cuando se enciende, el coche reduce su rendimiento automáticamente, pero &lt;strong>no te dice por qué&lt;/strong> salvo que sepas leer la combinación de letras. Los bits del bitmap son esas letras.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> Caso público formalmente reconocido por dos OEMs distintos: &lt;strong>Dell KB 000220508&lt;/strong> y &lt;strong>Lenovo HT514380&lt;/strong> abordan el mismo fenómeno: &lt;em>HW Power Brake Slowdown active&lt;/em> (bit &lt;code>0x80&lt;/code>) en H100 SXM. La causa no es la GPU — es la PDU del chasis enviando una señal eléctrica de power-brake porque la rama del rack está cerca del límite del breaker. El operador ve throughput caído 30–50 % sin XID ni ECC, y el motor de inferencia &amp;ldquo;está sano&amp;rdquo;; el problema está en electricidad. Foro NVIDIA developer en &lt;em>&amp;ldquo;HW Power Brake Slowdown&amp;rdquo;&lt;/em> corrobora el patrón. El bit &lt;code>0x40 HW_THERMAL&lt;/code> aparece en racks mal ventilados; el bit &lt;code>0x04 SW_POWER_CAP&lt;/code> aparece si alguien dejó &lt;code>nvidia-smi -pl 500&lt;/code> y nadie revertirá.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Cualquier bit ≠ 0 ni &lt;code>Idle&lt;/code> (bit 0x01) sostenido &lt;strong>es alerta inmediata&lt;/strong>. La descodificación recomendada: registrar el valor bitmap completo en el log + atributo &lt;code>throttle.reasons.decoded=[&amp;quot;HW_THERMAL&amp;quot;, &amp;quot;HW_POWER_BRAKE&amp;quot;]&lt;/code> en el span OTel. Sin esto, el incident response no sabe qué hacer.&lt;/p>
&lt;h2 id="familia-4--salud-los-reportes-catastróficos">Familia 4 — Salud (los reportes catastróficos)&lt;/h2>
&lt;h3 id="dcgm_fi_dev_xid_errors--los-códigos-rojos-del-driver">&lt;code>DCGM_FI_DEV_XID_ERRORS&lt;/code> — los códigos rojos del driver&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> Las luces de alarma críticas en una sala de control nuclear. No suben gradualmente; aparecen o no aparecen. Cada XID es un código predefinido (XID 13 = excepción del motor de gráficos; XID 31 = fault de MMU; XID 43 = stopped channel; XID 79 = GPU fallen off the bus; XID 95 = uncontained ECC), y cada uno tiene su procedimiento documentado.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> El caso público más estudiado: &lt;strong>Meta&lt;/strong> publicó que durante el entrenamiento de Llama 3 405B sobre &lt;strong>16.384 H100 en 54 días&lt;/strong> hubo &lt;strong>419 fallos no planificados&lt;/strong>, uno cada 3 horas a escala de cluster. GPU acumuló 148 (35 %) + HBM3 72 (17 %) = casi la mitad de todos los fallos. El paper &lt;em>&amp;ldquo;Story of Two GPUs: Characterizing the Resilience of Hopper H100 and Ampere A100&amp;rdquo;&lt;/em> (arXiv 2503.11901) cuantifica con un dataset distinto (2.1M GPU-horas) que H100 tiene &lt;strong>3.2× peor MTBE para ECC uncorrectable que A100&lt;/strong>. El paper de ByteDance MegaScale reporta que XID 79 (&amp;ldquo;GPU fallen off the bus&amp;rdquo;) coocurre con errores PCIe en el &lt;strong>43 % de los casos&lt;/strong>. El foro NVIDIA developer documenta casos persistentes de XID 31 (MMU fault) que &lt;strong>siguen a la GPU al cambiar de slot PCIe&lt;/strong> — bug hardware del módulo, no del backplane.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> &lt;strong>Cualquier incremento del contador es alerta inmediata&lt;/strong>: muchos XID exigen reset del nodo o RMA de la GPU. La distinción XID-por-XID importa: XID 13/43 suele ser bug de software si coincide con cambio reciente; XID 31/48/79/94/95 suele ser hardware. Mantener tabla canónica &lt;code>xid → procedimiento&lt;/code>. Ver &lt;a href="https://blog.lo0.es/posts/runbooks-incident-response-llm-keep-kafka/">los runbooks&lt;/a> para la traducción a acción concreta.&lt;/p>
&lt;h3 id="dcgm_fi_dev_ecc_dbe_vol_total--los-errores-que-corrompen-datos">&lt;code>DCGM_FI_DEV_ECC_DBE_VOL_TOTAL&lt;/code> — los errores que corrompen datos&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> Un libro de contabilidad donde a veces alguien borra una entrada y la rescribe (ECC single-bit corregido — anota un cambio en el margen y sigue) y a veces alguien quema dos páginas a la vez (double-bit — la información se perdió, hay que parar la auditoría).&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> El paper &lt;em>&amp;ldquo;Characterizing GPU Resilience&amp;rdquo;&lt;/em> cuantifica para H100: cuando XID 48 (DBE) aparece, &lt;strong>el job en curso muere con 100 % de probabilidad&lt;/strong> (5/5 en el dataset estudiado). La recuperación documentada: drenar el nodo + reset + completar row remap = &lt;strong>~19 horas de downtime de nodo&lt;/strong>. La densidad HBM3 explica el peor MTBE vs HBM2e: hay más celdas por unidad de área, mayor probabilidad estadística de degradación. En Llama 3, HBM3 causó 72 de 419 interrupciones (17 %).&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Cualquier valor &amp;gt; 0 = alerta crítica. La GPU debe ser &lt;strong>drenada inmediatamente&lt;/strong>, retirada del scheduler, reset completo, validación de row remap con &lt;code>nvidia-smi -q -d ROW_REMAPPER&lt;/code> (&lt;code>Pending: No&lt;/code>), y antes de volver al pool, smoke test extenso. Si el row remap usa &amp;gt; 4–8 páginas de spare en una GPU, planificar reemplazo en próxima ventana — la degradación es progresiva.&lt;/p>
&lt;h3 id="dcgm_fi_dev_retired_dbe--las-páginas-marcadas-para-retirar">&lt;code>DCGM_FI_DEV_RETIRED_DBE&lt;/code> — las páginas marcadas para retirar&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> Las baldosas que el restaurador del museo marca con cinta amarilla porque están dañadas. No suponen peligro inmediato (la sala sigue abierta), pero la acumulación dice que el suelo se está degradando estructuralmente y el reemplazo entero hay que planificarlo.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> NVIDIA documenta hasta &lt;strong>512 páginas de spare por banco HBM&lt;/strong> en H100; el contador &lt;code>RETIRED_DBE&lt;/code> indica cuántas se han usado. Operadores en foros NVIDIA reportan que por encima de &lt;strong>4–8 páginas retiradas en una GPU concreta&lt;/strong>, la frecuencia de XID 48 sube. Patrón: GPU con 6 páginas retiradas hoy → 12 en un mes → primer XID 48 dos meses después → drain forzoso.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Métrica de tendencia, no de alerta inmediata. Documentar valor por GPU y revisar mensualmente; las GPUs con valores crecientes entran al plan de reemplazo proactivo antes del fallo catastrófico.&lt;/p>
&lt;h2 id="las-cinco-métricas-del-motor-vllm">Las cinco métricas del motor vLLM&lt;/h2>
&lt;h3 id="vllmnum_requests_running--cuántas-requests-caben-en-el-batch">&lt;code>vllm:num_requests_running&lt;/code> — ¿cuántas requests caben en el batch?&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> El número de coches que un peaje deja pasar simultáneamente. Si la barrera abre N a la vez, las N+1 esperan en cola. La saturación se nota porque la fila no se acorta.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> Llegar al &lt;code>--max-num-seqs&lt;/code> configurado y mantenerse ahí es síntoma típico de &lt;strong>cluster por debajo del sizing&lt;/strong>; el motor admite hasta el techo y no más. La query &lt;code>vllm:num_requests_running == max_num_seqs&lt;/code> durante &amp;gt; 5 minutos indica saturación firme.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Combinar con &lt;code>num_requests_waiting&lt;/code>: si running está al techo Y waiting &amp;gt; 0, hay que escalar. Si running está al techo y waiting es 0, estás en el régimen óptimo (cluster usado al máximo sin cola).&lt;/p>
&lt;h3 id="vllmnum_requests_waiting--el-indicador-primario-de-saturación">&lt;code>vllm:num_requests_waiting&lt;/code> — el indicador primario de saturación&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> La cola visible delante del peaje. Mientras esté vacía, el sistema fluye; en cuanto se forma cola sostenida, los conductores empiezan a llegar tarde a destino — el TTFT se va al techo.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> Caso público en &lt;em>&amp;ldquo;11-Second Time to First Token on a Healthy vLLM Server&amp;rdquo;&lt;/em> (Medium, Ingero, 2026): servidor sin XIDs, sin preemption, métricas DCGM en verde, pero &lt;code>num_requests_waiting&lt;/code> sostenido &amp;gt; 0 y TTFT de &lt;strong>11 segundos&lt;/strong>. El issue &lt;strong>vllm#16985&lt;/strong> documenta degradación progresiva en sesiones largas: la queue crece lentamente durante horas sin que ningún otro indicador se mueva. La causa raíz no es de hardware — es de &lt;strong>admission control&lt;/strong>: la tasa de entrada supera la de finalización y el sistema no encola más, deja en &lt;code>WAITING&lt;/code>. Red Hat la designa como &lt;strong>la métrica primaria de saturación&lt;/strong> en su tutorial &lt;em>&amp;ldquo;5 steps to triage vLLM performance&amp;rdquo;&lt;/em>.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Métrica primaria del HPA en KEDA —ver &lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a>—. Umbral típico: alertar si &lt;code>avg_over_time(vllm:num_requests_waiting[5m]) &amp;gt; 5&lt;/code>. Para canary: si la cola se forma solo en el pool v2, es regresión del nuevo modelo, no carga del cluster.&lt;/p>
&lt;h3 id="vllmgpu_cache_usage_perc--el-pool-de-kv-cache">&lt;code>vllm:gpu_cache_usage_perc&lt;/code> — el pool de KV cache&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> La capacidad de una sala de eventos donde cada invitado ocupa un espacio variable. El maître admite hasta el aforo; cuando llega un invitado nuevo y no hay sitio, &lt;strong>echa al invitado que lleva más tiempo&lt;/strong> para hacerle hueco al recién llegado. Eso es el preempt-on-OOM de vLLM.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> El issue &lt;strong>vllm#5051&lt;/strong> &lt;em>&amp;ldquo;Add num_requests_preempted metric&amp;rdquo;&lt;/em> nació exactamente de operadores observando degradación pero sin métrica directa que les dijese cuántas requests se estaban echando. Documentación oficial vLLM confirma: &lt;em>&amp;ldquo;sustained &lt;code>gpu_cache_usage_perc&lt;/code> above 90 % indicates the server is approaching its KV cache limit and will begin preempting sequences&amp;rdquo;&lt;/em> (oldest-first). El patrón visual distintivo: &lt;strong>sierra (sawtooth) cerca del 100 %&lt;/strong> con picos de preemption. En modo &lt;code>swap&lt;/code>, la latencia de la request preempted explota porque hay copia PCIe host↔device; en modo &lt;code>recompute&lt;/code> (default en V1), la request preempted rehace su prefill desde cero, lo que dispara su TTFT al doble o triple.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Si &lt;code>gpu_cache_usage_perc &amp;gt; 92 %&lt;/code> sostenido, &lt;strong>dos palancas&lt;/strong>: bajar &lt;code>max_num_seqs&lt;/code> (admite menos concurrencia pero ninguna se preempta) o subir &lt;code>gpu_memory_utilization&lt;/code> (más pool, menos VRAM para activations transitorias — riesgo distinto). La elección depende del workload. La métrica que falta directamente —contador de preempted— se exporta a partir de vLLM v1.0 en &lt;code>vllm:num_preemptions_total&lt;/code> (ver issue #5051).&lt;/p>
&lt;h3 id="vllmtime_to_first_token_seconds--la-latencia-visible-al-cliente">&lt;code>vllm:time_to_first_token_seconds&lt;/code> — la latencia visible al cliente&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> El tiempo desde que un cliente entra a un restaurante hasta que recibe el primer trozo de pan en la mesa. Demasiado largo y el cliente piensa que se han olvidado de él, aunque la comida principal vaya a llegar perfecta.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> Tres patrones documentados de spike de TTFT recurrentes:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Chunked prefill mal calibrado.&lt;/strong> Issue &lt;strong>vllm#25677&lt;/strong> (Qwen3-30B-A3B) reportó &lt;strong>prefill 10–11× más lento con chunked prefill activado&lt;/strong> que sin él. Causa: &lt;code>max_num_batched_tokens&lt;/code> muy bajo fuerza chunks pequeños que no llenan los kernels. Issue &lt;strong>vllm#7604&lt;/strong> documenta regresión equivalente en Llama-3-70B v0.5.4. La palanca: subir &lt;code>max_num_batched_tokens&lt;/code> a 4096–8192 para prompts típicos &amp;gt; 2k.&lt;/li>
&lt;li>&lt;strong>Regresión entre versiones del motor.&lt;/strong> Issue &lt;strong>vllm#8819&lt;/strong> documenta regresión de &lt;code>vllm:time_to_first_token_seconds_sum&lt;/code> entre versiones minor. Issue &lt;strong>vllm#11912&lt;/strong> reporta que con prompt ~8000 tokens, TPOT subió de &lt;strong>15.7 ms → 25.7 ms&lt;/strong> desde v0.6.4.post1 sin cambio de config — regresión confirmada y trackable solo con la métrica.&lt;/li>
&lt;li>&lt;strong>Long-context prefill bloqueando decodes.&lt;/strong> El caso &lt;em>&amp;ldquo;11s TTFT on healthy server&amp;rdquo;&lt;/em> citado arriba: un prefill de 30k tokens monopoliza la GPU durante varios segundos y los decodes activos congelan. Solución: chunked prefill bien calibrado, o disaggregated serving (ver &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">Disaggregated serving&lt;/a>).&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> No alertar solo sobre P95 absoluto; alertar también sobre &lt;strong>ratio v2/v1&lt;/strong> cuando hay canary (&lt;code>histogram_quantile(0.95, ..., version=&amp;quot;v2&amp;quot;) / histogram_quantile(0.95, ..., version=&amp;quot;v1&amp;quot;) &amp;gt; 1.10&lt;/code>). Si TTFT crece y la queue está estable, el bottleneck es prefill — no resoluble subiendo réplicas, sí palanca de quantization o chunked prefill.&lt;/p>
&lt;h3 id="vllmtime_per_output_token_seconds--la-fluidez-del-streaming">&lt;code>vllm:time_per_output_token_seconds&lt;/code> — la fluidez del streaming&lt;/h3>
&lt;p>&lt;strong>La analogía.&lt;/strong> La velocidad a la que el camarero trae los platos uno detrás de otro después del primero. Si tarda en venir el siguiente, el comensal nota que algo no va bien aunque el primer plato haya llegado a tiempo.&lt;/p>
&lt;p>&lt;strong>La anomalía documentada.&lt;/strong> El patrón distintivo es el &lt;strong>escalón abrupto cuando &lt;code>gpu_cache_usage_perc&lt;/code> cruza ~85 %&lt;/strong>: el TPOT pasa de 35 ms a 80 ms en pocos segundos porque el motor empieza a competir por la HBM con sus propias evicciones. Issue &lt;strong>vllm#35387&lt;/strong> documenta otro caso anómalo: &lt;strong>MTP (speculative decoding) causando 76 % de regresión de latencia&lt;/strong> en Qwen3-Next-80B-A3B-Instruct-FP8 — la métrica TPOT lo capturó antes de que se reportasen quejas de clientes.&lt;/p>
&lt;p>&lt;strong>Implicación operacional.&lt;/strong> Diferencia con TTFT: si TTFT crece y Queue Time estable → prefill bound; si TPOT crece a tasa estable → presión sobre HBM (KV cache pool o swap activado). Alerta secundaria sobre el SLO de TPOT, pero también vigilar la &lt;strong>derivada&lt;/strong>: TPOT subiendo 1 ms cada 10 minutos es regresión latente que aún no rompe SLO pero lo hará.&lt;/p>
&lt;h2 id="la-regla-operativa-leer-las-métricas-por-familia-no-aisladas">La regla operativa: leer las métricas por familia, no aisladas&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 280" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="lectura combinada de métricas por familia">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.c{fill:#dfe9f5;stroke:#356}.m{fill:#eef0d0;stroke:#7a3}.t{fill:#f4e3cf;stroke:#a63}.s{fill:#f6e2e2;stroke:#a33}.title{font:600 13px sans-serif;fill:#222}.h{font:700 12px sans-serif;fill:#222}.l{font:11px sans-serif;fill:#222}.n{font:italic 10px sans-serif;fill:#444}&lt;/style>
&lt;text x="410" y="20" text-anchor="middle" class="title">Combinaciones que diagnostican (cada familia por sí sola engaña)&lt;/text>
&lt;rect x="20" y="40" width="380" height="100" class="b c"/>
&lt;text x="30" y="62" class="h">COMPUTE saturada PERO memoria libre&lt;/text>
&lt;text x="30" y="80" class="l">SM_OCCUPANCY 95% + TENSOR_ACTIVE 75% + DRAM_ACTIVE 50%&lt;/text>
&lt;text x="30" y="98" class="l">+ FB_USED 60%&lt;/text>
&lt;text x="30" y="120" class="n">→ Prefill bound. Palanca: speculative decoding,&lt;/text>
&lt;text x="30" y="134" class="n"> chunked prefill, disaggregated serving.&lt;/text>
&lt;rect x="420" y="40" width="380" height="100" class="b m"/>
&lt;text x="430" y="62" class="h">MEMORIA saturada PERO compute holgado&lt;/text>
&lt;text x="430" y="80" class="l">SM_OCCUPANCY 35% + TENSOR_ACTIVE 18% + DRAM_ACTIVE 92%&lt;/text>
&lt;text x="430" y="98" class="l">+ gpu_cache_usage_perc 88%&lt;/text>
&lt;text x="430" y="120" class="n">→ Decode bound + KV cache presionado.&lt;/text>
&lt;text x="430" y="134" class="n"> Palanca: KV cache FP8, contexto más corto.&lt;/text>
&lt;rect x="20" y="150" width="380" height="120" class="b t"/>
&lt;text x="30" y="172" class="h">TPOT alto SIN saturar compute ni memoria&lt;/text>
&lt;text x="30" y="190" class="l">DRAM_ACTIVE 65% + FB_USED 70% + temp 78°C&lt;/text>
&lt;text x="30" y="208" class="l">+ THROTTLE_REASONS = 0x40 (HW_THERMAL)&lt;/text>
&lt;text x="30" y="230" class="n">→ Throttle térmico silencioso.&lt;/text>
&lt;text x="30" y="244" class="n"> Palanca: revisar ventilación rack, no motor.&lt;/text>
&lt;text x="30" y="258" class="n"> Caso clásico Dell/Lenovo KB.&lt;/text>
&lt;rect x="420" y="150" width="380" height="120" class="b s"/>
&lt;text x="430" y="172" class="h">TTFT P95 alto SIN throttle ni cola&lt;/text>
&lt;text x="430" y="190" class="l">num_requests_waiting 0 + throttle 0 + DRAM_ACTIVE 70%&lt;/text>
&lt;text x="430" y="208" class="l">+ ratio v2/v1 = 1.4 (canary activo)&lt;/text>
&lt;text x="430" y="230" class="n">→ Regresión del modelo v2 en prefill.&lt;/text>
&lt;text x="430" y="244" class="n"> Palanca: rollback del canary,&lt;/text>
&lt;text x="430" y="258" class="n"> revisar config del motor v2.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h2 id="tres-anti-patterns-del-operador-novato">Tres anti-patterns del operador novato&lt;/h2>
&lt;p>&lt;strong>Anti-pattern 1 — alertar solo sobre umbrales absolutos.&lt;/strong> Una H100 al 87 % de FB no es necesariamente alarma; la H100 con 87 % subiendo 2 %/min sí lo es. Las alertas que disparan por umbral fijo sin mirar derivada producen el doble de ruido y la mitad de la utilidad. Regla: para métricas con dinámica conocida (KV cache, FB, queue), alertar sobre &lt;strong>delta sostenido&lt;/strong>, no solo nivel.&lt;/p>
&lt;p>&lt;strong>Anti-pattern 2 — confundir SBE con DBE.&lt;/strong> El contador &lt;code>DCGM_FI_DEV_ECC_SBE_VOL_TOTAL&lt;/code> (single-bit, corregibles) crece &lt;strong>continuamente&lt;/strong> en cualquier HBM bajo carga; no es alarma, es física. El que importa es &lt;code>DCGM_FI_DEV_ECC_DBE_VOL_TOTAL&lt;/code> (double-bit, no corregibles). Confundirlos = falsos negativos (no alertar sobre DBE real) o falsos positivos (alertar sobre SBE inofensivo).&lt;/p>
&lt;p>&lt;strong>Anti-pattern 3 — tratar SM_OCCUPANCY 99 % como &amp;ldquo;saturada&amp;rdquo;.&lt;/strong> El régimen LLM en decode es memory-bound, no compute-bound; SM occupancy alto con TENSOR_ACTIVE bajo y DRAM_ACTIVE alto &lt;strong>es lo normal&lt;/strong>. Dimensionar para &amp;ldquo;GPU al 60 %&amp;rdquo; pidiendo más hardware cuando el cluster está saturado en HBM (no en SM) es comprar el doble de GPU sin ganar throughput. Regla: leer SM_OCCUPANCY siempre con TENSOR_ACTIVE y DRAM_ACTIVE; aislada no significa nada.&lt;/p>
&lt;h2 id="aplicado-a-hardware-on-premise-típico">Aplicado a hardware on-premise típico&lt;/h2>
&lt;p>Para un cluster genérico de &lt;strong>4 nodos × 4×H100 SXM 80 GB con NVLink intra-nodo&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>DCGM Exporter&lt;/strong> por nodo (DaemonSet del GPU Operator) emitiendo cada 15 s; cardinalidad por GPU = ~80 series. Cluster 16 GPUs ≈ 1.3k series base, ~85k samples/min con scrape de 15 s.&lt;/li>
&lt;li>&lt;strong>vLLM /metrics&lt;/strong> por pod inferencia; cada réplica emite ~50 series base. Para 16 réplicas, ~800 series adicionales, ~3k samples/min.&lt;/li>
&lt;li>&lt;strong>Prometheus retention&lt;/strong>: 30 días alta resolución + 1 año downsampled vía Thanos sidecar o Mimir. Volumen estimado: 25–35 GB/día.&lt;/li>
&lt;li>&lt;strong>Alertmanager&lt;/strong>: las 6 alertas críticas del &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">post anterior&lt;/a> + alertas derivadas (delta, ratio v2/v1, throttle bitmap decodificado).&lt;/li>
&lt;/ul>
&lt;p>Cada métrica conviene exponer también como &lt;strong>atributo OTel&lt;/strong> en los spans del &lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">tracing GenAI&lt;/a>: &lt;code>gpu.fb_used_pct&lt;/code>, &lt;code>gpu.dram_active&lt;/code>, &lt;code>gpu.throttle_reasons.decoded&lt;/code>. Eso permite correlacionar una request lenta con el estado de la GPU en ese instante, sin saltar entre dashboards.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-posts">Lo que no hemos cubierto (próximos posts)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Runbooks por alerta&lt;/strong> — la traducción de cada métrica anómala a acción concreta (drain, reset, RMA, escalado, rollback) en el siguiente post: &lt;a href="https://blog.lo0.es/posts/runbooks-incident-response-llm-keep-kafka/">Runbooks de incident response&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Tail-sampling para correlación métrica ↔ traza&lt;/strong> — qué se preserva cuando una alerta dispara para investigación post-mortem.&lt;/li>
&lt;li>&lt;strong>Showback por tenant&lt;/strong> combinando &lt;code>vllm:request_success_total&lt;/code> × &lt;code>gen_ai.usage.*&lt;/code> × &lt;code>DCGM_FI_DEV_POWER_USAGE&lt;/code> para facturar coste energético real.&lt;/li>
&lt;li>&lt;strong>Métricas de fairness multi-tenant&lt;/strong> — cuándo un tenant acapara el KV cache pool y cómo detectarlo.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a> — la lista compacta que este post profundiza.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/runbooks-incident-response-llm-keep-kafka/">Runbooks de incident response para LLM con Keep + Kafka&lt;/a> — la traducción de cada anomalía a acción.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a> — la otra mitad de la observabilidad.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a> — &lt;code>num_requests_waiting&lt;/code> y &lt;code>gpu_cache_usage_perc&lt;/code> como métricas primarias de HPA.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning para inferencia LLM on-premise&lt;/a> — cómo se relacionan los umbrales con el sizing.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">Canary, blue-green y shadow&lt;/a> — el ratio TTFT v2/v1 como gate.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">Continuous batching&lt;/a> — explica el preempt-on-OOM y la sierra del KV pool.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache&lt;/a> — fundamenta el cálculo de &lt;code>gpu_cache_usage_perc&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>Meta — &lt;em>Faulty Nvidia H100 GPUs and HBM3 memory caused half of failures during Llama 3 training&lt;/em> (Tom&amp;rsquo;s Hardware, 2024). &lt;a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/faulty-nvidia-h100-gpus-and-hbm3-memory-caused-half-of-the-failures-during-llama-3-training-one-failure-every-three-hours-for-metas-16384-gpu-training-cluster">tomshardware.com&lt;/a>&lt;/li>
&lt;li>&lt;em>Story of Two GPUs: Characterizing the Resilience of Hopper H100 and Ampere A100&lt;/em>. arXiv 2503.11901. &lt;a href="https://arxiv.org/html/2503.11901v3">https://arxiv.org/html/2503.11901v3&lt;/a>&lt;/li>
&lt;li>ByteDance — &lt;em>Robust LLM Training Infrastructure at ByteDance&lt;/em>. arXiv 2509.16293. &lt;a href="https://arxiv.org/pdf/2509.16293">https://arxiv.org/pdf/2509.16293&lt;/a>&lt;/li>
&lt;li>&lt;em>Mind the Memory Gap: Unveiling GPU Bottlenecks in Large-Batch LLM Inference&lt;/em>. arXiv 2503.08311.&lt;/li>
&lt;li>&lt;em>Capacity-Aware Inference: Mitigating the Straggler Effect in Mixture of Experts&lt;/em>. arXiv 2503.05066.&lt;/li>
&lt;li>NVIDIA — &lt;em>Analyzing Xid Errors with the Xid Catalog&lt;/em> y &lt;em>Memory Error Management&lt;/em> (docs.nvidia.com/deploy).&lt;/li>
&lt;li>Dell — &lt;em>PowerEdge XE8640 with H100 - GPU Performance Issue HW Power Brake Slowdown - Active&lt;/em> (KB 000220508).&lt;/li>
&lt;li>Lenovo — &lt;em>Power brake reporting on H100 GPU&lt;/em> (HT514380).&lt;/li>
&lt;li>vLLM project — issues #5051 (preempted metric), #7604 y #25677 (chunked prefill regression), #11912 (long-prompt regression), #16300 (TP=8 worse than TP=4), #16985 (long-running degradation), #20783 (compressed-tensors no speedup), #35387 (MTP regression).&lt;/li>
&lt;li>Red Hat — &lt;em>5 steps to triage vLLM performance&lt;/em>. &lt;a href="https://developers.redhat.com/articles/2026/03/09/5-steps-triage-vllm-performance">https://developers.redhat.com/articles/2026/03/09/5-steps-triage-vllm-performance&lt;/a>&lt;/li>
&lt;li>AI21 — &lt;em>Go big or go OOM: the art of scaling vLLM&lt;/em>. &lt;a href="https://www.ai21.com/blog/scaling-vllm-without-oom/">https://www.ai21.com/blog/scaling-vllm-without-oom/&lt;/a>&lt;/li>
&lt;li>&lt;em>11-Second Time to First Token on a Healthy vLLM Server&lt;/em> (Medium, Ingero, 2026).&lt;/li>
&lt;li>NVIDIA — &lt;em>DGX SuperPOD Electrical Specifications&lt;/em> (docs.nvidia.com/dgx-superpod).&lt;/li>
&lt;/ul>
&lt;p>Sources: las URLs completas están enlazadas en línea sobre cada referencia.&lt;/p></description></item><item><title>El router de inferencia LLM: la centralita L7 que en el post de canary llamábamos LoadBalancer</title><link>https://blog.lo0.es/posts/router-inferencia-llm-gateway-l7/</link><pubDate>Tue, 02 Jun 2026 03:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/router-inferencia-llm-gateway-l7/</guid><description>&lt;blockquote>
&lt;p>Este post es la continuación natural de &lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">Canary, blue-green y shadow para modelos LLM&lt;/a>. Allí la mecánica de promoción depositó toda la complejidad de reparto de tráfico en una caja a la que llamamos &amp;ldquo;LoadBalancer&amp;rdquo;. La descripción era operacional —servía para entender la coreografía— pero estructuralmente vaga: lo que de verdad hace ese reparto es un router de inferencia L7 con awareness LLM, una pieza de pleno derecho del stack (&lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">capa 1 de las siete capas&lt;/a>) que merece su propio post.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>En el post anterior sobre &lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">canary&lt;/a> llamamos &lt;strong>LoadBalancer&lt;/strong> a la pieza que reparte tráfico entre los pools v1 estable y v2 candidato. La descripción servía para entender el flujo, pero técnicamente era borrosa: ni un LoadBalancer L4 (kube-proxy, MetalLB, IPVS) ni un LoadBalancer L7 HTTP genérico (NGINX o HAProxy sin extensión) saben qué es un modelo, qué es una versión, cuántos tokens cuesta una request, qué prefijo tiene el prompt o qué KV cache tiene caliente cada réplica. La pieza correcta es un &lt;strong>router de inferencia LLM&lt;/strong>: un proxy L7 con conocimiento explícito del dominio. Combina cuatro funciones: &lt;strong>catálogo de modelos&lt;/strong> (resolver &lt;code>model=llama-70b@v2&lt;/code> → &lt;code>service.namespace:port&lt;/code>), &lt;strong>traffic splitting&lt;/strong> (aplicar el weight de canary con hash determinista o sticky deliberado para A/B), &lt;strong>política transversal&lt;/strong> (auth OIDC, rate limit y quota por tenant, redact PII pre-prompt, guardrails ligeros inline, propagación de tracing &lt;code>gen_ai.*&lt;/code>) y &lt;strong>failover/degradación&lt;/strong> (si v2 cae, redirigir a v1; si todo el cluster está saturado, devolver 503 con &lt;code>Retry-After&lt;/code> en vez de encolar para siempre). La pieza &lt;strong>no obvia&lt;/strong> que justifica su existencia técnica más allá de la operacional es el &lt;strong>prefix-aware routing&lt;/strong>: el router decide a qué réplica de la flota va cada request en función del prefijo del prompt, para que un sistema RAG con el mismo system prompt + el mismo bloque de documentos recuperados acierte sistemáticamente en el prefix cache (RadixAttention en SGLang, PrefixCaching en vLLM, KV reuse en TensorRT-LLM) de la &lt;strong>misma&lt;/strong> réplica, multiplicando el hit rate del &lt;strong>5–15 %&lt;/strong> (round-robin ciego) al &lt;strong>60–85 %&lt;/strong> (afinidad por prefix). Las piezas concretas en mayo 2026 son &lt;strong>LiteLLM Proxy&lt;/strong> (la opción más simple, OpenAI-compatible, catálogo declarativo YAML), &lt;strong>vLLM Production Stack router&lt;/strong> (específico para flotas vLLM, aware del KV cache y del prefix), &lt;strong>Envoy AI Gateway&lt;/strong> (filtros Envoy LLM-aware, integrable con Istio), &lt;strong>Kong AI Gateway&lt;/strong> (alternativa empresarial con plugin ecosystem), &lt;strong>KGateway&lt;/strong> (CNCF en gestación) y &lt;strong>NVIDIA Dynamo router&lt;/strong> (production-grade, aware de &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">disaggregated serving prefill/decode&lt;/a>). En el stack de &lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">siete capas&lt;/a> vive en la &lt;strong>capa 1&lt;/strong> (gateway); en el de &lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">cinco niveles de madurez&lt;/a> aparece a partir del &lt;strong>nivel 3&lt;/strong>; en el ciclo de &lt;a href="https://blog.lo0.es/posts/siete-fases-despliegue-plataforma-llm-on-premise/">siete fases de despliegue&lt;/a> es la última pieza que &lt;strong>F6&lt;/strong> cierra. Este post incluye un manifest mínimo aplicable a un cluster genérico de 4×H100 SXM.&lt;/p>
&lt;h2 id="estás-aquí-deploy-capa-1-del-stack">Estás aquí: DEPLOY (capa 1 del stack)&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="estás aquí: Deploy, capa 1 del stack">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#7ad88f;stroke-width:3}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#rim)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#rim)}&lt;/style>
&lt;defs>&lt;marker id="rim" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">Estás aquí: DEPLOY · capa 1 del stack (gateway / router de inferencia)&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box active"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box idle"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box idle"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="el-antecedente-lo-que-el-post-de-canary-llamaba-loadbalancer">El antecedente: lo que el post de canary llamaba &amp;ldquo;LoadBalancer&amp;rdquo;&lt;/h2>
&lt;p>En &lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">Canary, blue-green y shadow para modelos LLM&lt;/a> describimos el flujo así: &lt;em>&amp;ldquo;el LoadBalancer reparte progresivamente el tráfico siguiendo un cronograma: 1 % → 5 % → 25 % → 100 %&amp;rdquo;&lt;/em>. Era una descripción &lt;strong>operacional&lt;/strong> correcta — el lector entendía la coreografía sin necesitar más. Pero &lt;strong>técnicamente&lt;/strong> dejaba sin nombre a una pieza que merece tratamiento explícito, porque ninguno de los dos sentidos habituales de &amp;ldquo;LoadBalancer&amp;rdquo; hace lo que ese párrafo asumía:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Un LoadBalancer L4&lt;/strong> —kube-proxy con iptables/IPVS, MetalLB, F5 BIG-IP en modo TCP— reparte paquetes IP sin mirar dentro del payload. No sabe qué modelo se pide, ni qué versión, ni cuántos tokens lleva, ni si el cliente tiene quota. No puede aplicar el weight del canary &amp;ldquo;para el modelo X versión 2&amp;rdquo;: para él todos los paquetes hacia el VIP &lt;code>vllm-llama70b&lt;/code> son indistinguibles.&lt;/li>
&lt;li>&lt;strong>Un LoadBalancer L7 HTTP genérico&lt;/strong> —NGINX o HAProxy en modo HTTP sin extensión, una Service de tipo &lt;code>ClusterIP&lt;/code> con backend múltiple— sí reparte por URL y puede hacer routing por header, pero &lt;strong>no entiende el cuerpo OpenAI-compatible&lt;/strong> de la request. No sabe que &lt;code>{&amp;quot;model&amp;quot;: &amp;quot;llama-70b&amp;quot;, &amp;quot;messages&amp;quot;: [...]}&lt;/code> lleva en el campo &lt;code>model&lt;/code> la clave de routing; no cuenta tokens; no aplica políticas sobre estructuras LLM; no hace prefix-aware routing porque eso exige parsear el &lt;code>messages&lt;/code> y hashear el prefijo común.&lt;/li>
&lt;/ul>
&lt;p>La pieza que el post de canary asumía haciendo este trabajo es un &lt;strong>router de inferencia L7 con awareness LLM&lt;/strong>. Una capa de pleno derecho, con su propia configuración, su propio CI/CD, sus propias métricas y sus propios pitfalls. Este post la nombra y la desmonta.&lt;/p>
&lt;h2 id="la-analogía-la-centralita-y-triage-de-un-hospital-con-múltiples-especialidades">La analogía: la centralita y triage de un hospital con múltiples especialidades&lt;/h2>
&lt;p>Un hospital grande recibe pacientes que llegan a urgencias por puertas distintas y que necesitan especialidades distintas: traumatología, cardiología, pediatría, oncología. Hay tres modelos posibles de &amp;ldquo;puerta de entrada&amp;rdquo;.&lt;/p>
&lt;p>&lt;strong>Puerta única sin triage.&lt;/strong> Todos los pacientes esperan en la misma sala y los van pasando por orden de llegada al primer médico libre, sea su especialidad la que sea. Funciona en un consultorio de aldea con un único médico generalista. Cuando hay 200 pacientes al día y 12 especialidades, cae rápido en disfunción: el cardiólogo atiende esguinces, el pediatra atiende infartos, los recursos especializados se desperdician. Es el equivalente del LoadBalancer L4 — reparte cuerpos sin entender qué traen.&lt;/p>
&lt;p>&lt;strong>Puerta con receptionist que pregunta el síntoma.&lt;/strong> Una persona en mesa de entrada pregunta &amp;ldquo;¿qué le pasa?&amp;rdquo; y dirige al paciente al pasillo correcto. El cardiólogo ve solo cardiología, el pediatra solo niños. Mejor, pero el receptionist es lento, no calibra urgencias y no conoce el estado de las salas: puede mandar al cardiólogo del pasillo A cuando el del B está libre. Es el equivalente de un L7 HTTP genérico con &lt;code>path-based routing&lt;/code> — reparte por categoría pero sin información del estado interno.&lt;/p>
&lt;p>&lt;strong>Triage profesional con awareness completo.&lt;/strong> Una enfermera de triage formada que conoce el catálogo de especialidades, sabe qué box está ocupado y cuál libre, recuerda al paciente recurrente cuyo expediente ya está abierto en el sistema (manda al mismo médico para continuidad), aplica política transversal (verifica cobertura del seguro, registra alérgenos, redirige a urgencias pediátricas si el paciente es menor) y, si la sala de cardiología cae por una avería del electrocardiograma, redirige al hospital del otro lado de la ciudad. Esta es la pieza que un hospital grande necesita. En LLM se llama &lt;strong>router de inferencia&lt;/strong>.&lt;/p>
&lt;p>La analogía sostiene hasta el último detalle, incluido el del &amp;ldquo;expediente ya abierto&amp;rdquo;: el paciente que vuelve al mismo médico es exactamente el cliente cuyo prompt comparte prefijo con el de hace 5 minutos. Si el router lo manda a la &lt;strong>misma réplica&lt;/strong>, esa réplica todavía tiene el KV cache caliente y la request acierta el prefix cache. Si lo manda a una réplica distinta porque iba &amp;ldquo;la siguiente en round-robin&amp;rdquo;, el KV cache hay que reconstruirlo desde cero y la TTFT se va al doble. La enfermera de triage sabe esto. El LoadBalancer ciego no.&lt;/p>
&lt;h2 id="las-cuatro-funciones-del-router-de-inferencia">Las cuatro funciones del router de inferencia&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 320" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="cuatro funciones del router de inferencia LLM">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.c{fill:#dfe9f5;stroke:#356}.t{fill:#eef0d0;stroke:#7a3}.p{fill:#f4e3cf;stroke:#a63}.f{fill:#ead8f5;stroke:#634}.title{font:600 13px sans-serif;fill:#222}.h{font:700 12px sans-serif;fill:#222}.l{font:11px sans-serif;fill:#222}.n{font:italic 10px sans-serif;fill:#444}&lt;/style>
&lt;text x="410" y="20" text-anchor="middle" class="title">Cuatro funciones que el router de inferencia combina&lt;/text>
&lt;rect x="20" y="40" width="380" height="120" class="b c"/>
&lt;text x="30" y="62" class="h">1 · CATÁLOGO DE MODELOS&lt;/text>
&lt;text x="30" y="82" class="l">Resolver `model=llama-70b@v2` → service:port&lt;/text>
&lt;text x="30" y="102" class="l">Versionado, aliases, lifecycle (preview/stable/deprecated)&lt;/text>
&lt;text x="30" y="125" class="n">Lo que evita que el cliente conozca topología.&lt;/text>
&lt;text x="30" y="145" class="n">Sin esto, cada cliente sabe IPs/puertos internos.&lt;/text>
&lt;rect x="420" y="40" width="380" height="120" class="b t"/>
&lt;text x="430" y="62" class="h">2 · TRAFFIC SPLITTING&lt;/text>
&lt;text x="430" y="82" class="l">Weight de canary / blue-green / shadow&lt;/text>
&lt;text x="430" y="102" class="l">Hash determinista por request o sticky deliberado&lt;/text>
&lt;text x="430" y="125" class="n">Las particiones del post de canary se aplican aquí,&lt;/text>
&lt;text x="430" y="145" class="n">no en el motor de inferencia.&lt;/text>
&lt;rect x="20" y="170" width="380" height="120" class="b p"/>
&lt;text x="30" y="192" class="h">3 · POLÍTICA TRANSVERSAL&lt;/text>
&lt;text x="30" y="212" class="l">Auth OIDC · rate limit · quota por tenant&lt;/text>
&lt;text x="30" y="232" class="l">Redact PII pre-prompt · guardrails ligeros inline&lt;/text>
&lt;text x="30" y="252" class="l">Tracing gen_ai.* propagado · semantic cache&lt;/text>
&lt;text x="30" y="275" class="n">Lo que se aplica una vez por todos los modelos.&lt;/text>
&lt;rect x="420" y="170" width="380" height="120" class="b f"/>
&lt;text x="430" y="192" class="h">4 · FAILOVER · DEGRADACIÓN&lt;/text>
&lt;text x="430" y="212" class="l">Si v2 cae → redirige a v1&lt;/text>
&lt;text x="430" y="232" class="l">Si todo saturado → 503 con Retry-After&lt;/text>
&lt;text x="430" y="252" class="l">Circuit breaker · health probes activos&lt;/text>
&lt;text x="430" y="275" class="n">Lo que evita encolar para siempre.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h3 id="función-1--catálogo-de-modelos">Función 1 — Catálogo de modelos&lt;/h3>
&lt;p>El router mantiene un catálogo declarativo que mapea identidad de modelo a deployment concreto:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">models&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;llama-70b&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># alias estable&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;v2&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># versión canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">weight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 5% del tráfico&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;vllm-llama70b-v2.inference.svc.cluster.local:8000&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">chat, tool_use]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">lifecycle&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;llama-70b&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;v1&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">weight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">95&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;vllm-llama70b-v1.inference.svc.cluster.local:8000&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">chat, tool_use]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">lifecycle&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">stable&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;embedding-multilingual&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;v1&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">weight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">100&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">endpoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;tei-bge-m3.inference.svc.cluster.local:8080&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">embeddings]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">lifecycle&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">stable&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El cliente envía &lt;code>{&amp;quot;model&amp;quot;: &amp;quot;llama-70b&amp;quot;, &amp;quot;messages&amp;quot;: [...]}&lt;/code> sin saber que detrás hay dos pools de réplicas. El router resuelve. Si mañana migras de vLLM a SGLang para una versión concreta, el cliente no se entera; cambias el &lt;code>endpoint&lt;/code> en el catálogo y listo.&lt;/p>
&lt;p>Lo que se gana con este desacoplamiento es la libertad de mover topología sin romper clientes. Lo que cuesta es mantener disciplinada la convención de nombres (&lt;code>llama-70b&lt;/code> siempre es el alias estable; &lt;code>llama-70b@v2&lt;/code> es la versión específica para canary). Sin esa disciplina, los aliases se ensucian con &lt;code>llama-70b-prod-fixed-real-final-v3&lt;/code> y el catálogo deja de ser navegable a las pocas semanas.&lt;/p>
&lt;h3 id="función-2--traffic-splitting">Función 2 — Traffic splitting&lt;/h3>
&lt;p>Las particiones del &lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">post de canary&lt;/a> (1 % → 5 % → 25 % → 100 %) se materializan &lt;strong>aquí&lt;/strong>, no en el motor de inferencia. El router calcula un hash determinista del &lt;code>request_id&lt;/code> (o del &lt;code>user_id&lt;/code>, si se quiere sticky) y lo mapea al rango de weights del catálogo. Para un weight &lt;code>[v1: 95, v2: 5]&lt;/code>, el 5 % del espacio hash cae en v2 y el 95 % en v1.&lt;/p>
&lt;p>Tres decisiones de diseño que importan:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Hash por &lt;code>request_id&lt;/code> aleatorio&lt;/strong> = muestreo independiente. Cada request es una observación independiente de la distribución v1 vs v2. Es el setting correcto para canary estadísticamente comparables.&lt;/li>
&lt;li>&lt;strong>Hash por &lt;code>user_id&lt;/code>&lt;/strong> = sticky por usuario. El mismo cliente ve siempre el mismo pool. Útil para A/B testing con memoria conversacional persistida, pero &lt;strong>rompe la comparabilidad estadística del canary&lt;/strong> porque las poblaciones de usuarios no son simétricas — pitfall explicado en el &lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">post anterior&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Hash por &lt;code>tenant_id&lt;/code>&lt;/strong> = particionado fuerte. Tenant A va a v1, tenant B a v2. Es el patrón para clientes con SLA distintos o para validar v2 en un tenant interno antes de exponerlo a clientes externos.&lt;/li>
&lt;/ul>
&lt;h3 id="función-3--política-transversal">Función 3 — Política transversal&lt;/h3>
&lt;p>Una vez por encima de todos los modelos, el router aplica:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Auth&lt;/strong>: OIDC con tokens JWT validados contra Keycloak / Authentik. Headers &lt;code>Authorization: Bearer ...&lt;/code> traducidos a &lt;code>tenant_id&lt;/code> y &lt;code>roles&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Rate limit&lt;/strong>: token bucket por tenant (&lt;code>X req/min&lt;/code>) o por modelo (&lt;code>Y req/min&lt;/code> para llama-70b porque es caro).&lt;/li>
&lt;li>&lt;strong>Quota&lt;/strong>: cuota mensual de tokens consumidos por tenant. El router cuenta &lt;code>gen_ai.usage.input_tokens&lt;/code> + &lt;code>gen_ai.usage.output_tokens&lt;/code> y rechaza con &lt;code>429 Quota exceeded&lt;/code> cuando se agota.&lt;/li>
&lt;li>&lt;strong>Redact PII pre-prompt&lt;/strong>: Presidio o Llama Guard en línea antes de que el prompt toque el modelo. Lo que el modelo no ve, no se entrena con ello, no se loguea, no se filtra.&lt;/li>
&lt;li>&lt;strong>Guardrails ligeros inline&lt;/strong>: PromptGuard 2, Llama Guard 4, Granite Guardian — los que aparecen en &lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">Guardrails y safety en LLMs&lt;/a>— se ejecutan en el router porque su latencia (30–150 ms) cabe en el presupuesto de TTFT.&lt;/li>
&lt;li>&lt;strong>Propagación de tracing &lt;code>gen_ai.*&lt;/code>&lt;/strong>: el router inicia el span padre, propaga &lt;code>traceparent&lt;/code> al motor y emite los atributos &lt;code>gen_ai.system&lt;/code>, &lt;code>gen_ai.request.model&lt;/code>, &lt;code>gen_ai.request.version&lt;/code> que el &lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">tracing OTel GenAI&lt;/a> consume.&lt;/li>
&lt;li>&lt;strong>Semantic cache&lt;/strong>: para prompts repetidos exactos o con similitud semántica alta (embedding cosine &amp;gt; 0.97 contra cache previa), devuelve la respuesta cacheada sin tocar el motor. Ahorro típico en RAG con preguntas frecuentes: 20–40 % de las requests.&lt;/li>
&lt;/ul>
&lt;h3 id="función-4--failover-y-degradación">Función 4 — Failover y degradación&lt;/h3>
&lt;p>El router conoce el estado de salud de cada endpoint (health probes activos cada 5–15 s, latencia de TTFT recientes) y decide:&lt;/p>
&lt;ul>
&lt;li>Si v2 devuelve 5xx persistente o no responde, &lt;strong>circuit breaker&lt;/strong> abierto: el router redirige el tráfico que iba a v2 hacia v1 hasta que las probes vuelvan a verde. Esto es el rollback automático del canary en su forma más simple.&lt;/li>
&lt;li>Si todo el cluster está saturado (todas las réplicas reportan &lt;code>num_requests_waiting &amp;gt; N&lt;/code> durante T segundos), el router devuelve &lt;strong>&lt;code>503 Service Unavailable&lt;/code>&lt;/strong> con &lt;code>Retry-After: 30&lt;/code> en vez de encolar para siempre. Mejor decirle al cliente &amp;ldquo;vuelve en 30 segundos&amp;rdquo; que tenerlo esperando 4 minutos y luego dar timeout.&lt;/li>
&lt;li>Si hay multi-region o multi-cluster, &lt;strong>failover cross-cluster&lt;/strong> vía DNS o L7: la región primaria cae, el router de la secundaria asume.&lt;/li>
&lt;/ul>
&lt;h2 id="la-pieza-no-obvia-prefix-aware-routing">La pieza no obvia: prefix-aware routing&lt;/h2>
&lt;p>Esta es la función que un LoadBalancer convencional no puede hacer y que justifica un router específico de LLM más allá de las cuatro genéricas.&lt;/p>
&lt;p>El KV cache de vLLM, SGLang y TensorRT-LLM puede &lt;strong>reusar prefijos comunes entre requests&lt;/strong> —ver &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache&lt;/a>—. Concretamente:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>vLLM&lt;/strong> con &lt;code>--enable-prefix-caching&lt;/code>: detecta que la request actual comparte un prefijo (longitud múltiplo del block size, default 16 tokens) con una request anterior cuyas páginas todavía están en HBM, y reutiliza esas páginas en vez de reprocesarlas.&lt;/li>
&lt;li>&lt;strong>SGLang&lt;/strong> con &lt;strong>RadixAttention&lt;/strong>: estructura el cache como un árbol radix indexado por tokens; cada request acierta el camino común del árbol y solo computa la cola.&lt;/li>
&lt;li>&lt;strong>TensorRT-LLM&lt;/strong>: feature similar, llamado &lt;em>KV cache reuse&lt;/em>.&lt;/li>
&lt;/ul>
&lt;p>El hit rate del prefix cache es la métrica clave: cada token acertado es un token que &lt;strong>no se procesa en prefill&lt;/strong>, reduciendo TTFT en proporción directa. Para un sistema RAG típico —system prompt de 400 tokens + documentos retrieved de 2 000 tokens + pregunta del usuario de 50 tokens— el prefijo común (&lt;code>system_prompt + docs&lt;/code>) son 2 400 de los 2 450 tokens totales. Si el cache acierta, &lt;strong>el prefill solo procesa 50 tokens en vez de 2 450&lt;/strong>: TTFT cae aproximadamente a la &lt;strong>vigésima parte&lt;/strong>.&lt;/p>
&lt;p>Pero el cache vive &lt;strong>por réplica&lt;/strong>, no globalmente. Si dos requests con el mismo prefix de 2 400 tokens caen en réplicas distintas, ambas hacen el prefill completo: el cache de la primera no sirve a la segunda. La segunda paga el coste íntegro.&lt;/p>
&lt;p>Con &lt;strong>round-robin ciego&lt;/strong> (cualquier LB convencional), las requests se reparten uniformemente entre N réplicas. Para un cluster de 4 réplicas y 1 000 requests con el mismo &lt;code>system_prompt + docs&lt;/code>, &lt;strong>cada réplica recibe ~250 requests&lt;/strong>, pero las 4 hacen su propio &amp;ldquo;primer prefill&amp;rdquo; y los siguientes 249 se benefician dentro de su réplica. El hit rate global es decente pero no óptimo. Para tráfico con muchos sistemas prompts distintos y poca repetición intra-prefix, el hit rate ronda el &lt;strong>5–15 %&lt;/strong>.&lt;/p>
&lt;p>Con &lt;strong>prefix-aware routing&lt;/strong>, el router calcula un hash del prefijo del prompt (los primeros N tokens, o el &lt;code>system_prompt&lt;/code> declarado en &lt;code>messages[0]&lt;/code>) y mantiene una &lt;strong>tabla de afinidad&lt;/strong> &lt;code>hash → réplica&lt;/code>. Todas las requests con el mismo prefijo caen en la &lt;strong>misma&lt;/strong> réplica. La primera paga el prefill completo; las 999 siguientes aciertan el cache. Hit rate global: &lt;strong>60–85 %&lt;/strong>.&lt;/p>
&lt;p>El coste de implementarlo: el router debe parsear el body de la request (no solo el header HTTP), aplicar un tokenizer ligero o un hash basado en bytes, y mantener una tabla LRU/consistent-hash de afinidad que se rebalancea cuando una réplica entra o sale. Es trabajo de servidor, no de proxy genérico. &lt;strong>vLLM Production Stack router&lt;/strong> lo implementa nativamente. &lt;strong>NVIDIA Dynamo&lt;/strong> también. LiteLLM en su versión enterprise tiene un beta. Envoy AI Gateway lo está incorporando como filtro experimental.&lt;/p>
&lt;p>La diferencia operativa para un RAG productivo: con prefix-aware routing, el mismo cluster sirve &lt;strong>2–4× más requests&lt;/strong> sin añadir GPUs, simplemente porque el prefill desaparece en la mayoría de los casos.&lt;/p>
&lt;h2 id="token-aware-load-balancing">Token-aware load balancing&lt;/h2>
&lt;p>La segunda pieza no obvia. El round-robin clásico reparte por número de requests; pero un prompt de 50 tokens y otro de 8 000 tokens cuestan radicalmente distinto (factor ~160× en prefill). Repartir igualmente por count desequilibra severamente la carga real.&lt;/p>
&lt;p>&lt;strong>Token-aware load balancing&lt;/strong> suma tokens de prefill esperados (longitud del prompt) y decode esperados (max_tokens del cliente) por réplica activa, y manda la nueva request a la réplica con menor carga acumulada. Es lo que tanto vLLM Production Stack como NVIDIA Dynamo implementan como estrategia por defecto cuando se activa.&lt;/p>
&lt;p>La métrica que alimenta el cálculo es —otra vez— &lt;code>vllm:num_requests_running&lt;/code> y &lt;code>vllm:gpu_cache_usage_perc&lt;/code> —ver &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a>—, idealmente complementadas con un estimador de tokens del prompt entrante. Los routers maduros usan &lt;code>tiktoken&lt;/code> o el tokenizer real del modelo para contar tokens del prompt antes de elegir réplica.&lt;/p>
&lt;h2 id="comparativa-de-piezas-concretas-mayo-2026">Comparativa de piezas concretas (mayo 2026)&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Pieza&lt;/th>
&lt;th>Awareness LLM&lt;/th>
&lt;th>Prefix-aware&lt;/th>
&lt;th>Token-aware LB&lt;/th>
&lt;th>Multi-modelo&lt;/th>
&lt;th>Semantic cache&lt;/th>
&lt;th>Plug &amp;amp; play&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>LiteLLM Proxy&lt;/strong>&lt;/td>
&lt;td>Alta&lt;/td>
&lt;td>Beta (enterprise)&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>Excelente&lt;/td>
&lt;td>Sí (Redis)&lt;/td>
&lt;td>Muy alto&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>vLLM Production Stack router&lt;/strong>&lt;/td>
&lt;td>Específico vLLM&lt;/td>
&lt;td>Sí, nativo&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>Solo vLLM&lt;/td>
&lt;td>No (externa)&lt;/td>
&lt;td>Medio&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>NVIDIA Dynamo router&lt;/strong>&lt;/td>
&lt;td>Alta + disagg-aware&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>vLLM/SGLang/TRT-LLM&lt;/td>
&lt;td>No (externa)&lt;/td>
&lt;td>Bajo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Envoy AI Gateway&lt;/strong>&lt;/td>
&lt;td>Media (filtros)&lt;/td>
&lt;td>Experimental&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>Vía filtro&lt;/td>
&lt;td>Medio&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Kong AI Gateway&lt;/strong>&lt;/td>
&lt;td>Media (plugins)&lt;/td>
&lt;td>No&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>Sí (plugin)&lt;/td>
&lt;td>Medio&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>KGateway&lt;/strong>&lt;/td>
&lt;td>Media&lt;/td>
&lt;td>Roadmap&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>Roadmap&lt;/td>
&lt;td>Bajo (CNCF gestación)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>NGINX + custom Lua&lt;/strong>&lt;/td>
&lt;td>Manual&lt;/td>
&lt;td>No&lt;/td>
&lt;td>Manual&lt;/td>
&lt;td>Manual&lt;/td>
&lt;td>No&lt;/td>
&lt;td>Bajo (build it yourself)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>LiteLLM Proxy&lt;/strong> es la opción por defecto para empezar. OpenAI-compatible, YAML simple, soporta los providers comerciales + cualquier OpenAI-compatible self-hosted. La versión OSS cubre las cuatro funciones básicas y semantic cache; el prefix-aware y la versión enterprise añaden multi-tenancy avanzado.&lt;/p>
&lt;p>&lt;strong>vLLM Production Stack router&lt;/strong> es la opción correcta si la flota es 100 % vLLM. Aware del KV cache, del prefix, del LoRA loaded por réplica. Integra mejor con métricas vLLM nativas.&lt;/p>
&lt;p>&lt;strong>NVIDIA Dynamo router&lt;/strong> es la opción production-grade más completa, especialmente si se opera &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">disaggregated serving&lt;/a> (prefill workers vs decode workers separados). Requiere stack NVIDIA-aligned.&lt;/p>
&lt;p>&lt;strong>Envoy AI Gateway&lt;/strong> y &lt;strong>Kong AI Gateway&lt;/strong> son las opciones si la organización ya tiene Envoy/Kong como gateway corporativo y quiere extenderlo con LLM-awareness sin introducir otra pieza nueva.&lt;/p>
&lt;h2 id="manifest-mínimo-litellm-proxy-sobre-cluster-genérico">Manifest mínimo: LiteLLM Proxy sobre cluster genérico&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ConfigMap&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: litellm-config, namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">inference }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">config.yaml&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> model_list:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> - model_name: llama-70b
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> litellm_params:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> model: openai/llama-70b
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> api_base: http://vllm-llama70b-v1.inference.svc:8000/v1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> weight: 95
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> model_info:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> version: v1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> lifecycle: stable
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> - model_name: llama-70b
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> litellm_params:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> model: openai/llama-70b
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> api_base: http://vllm-llama70b-v2.inference.svc:8000/v1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> weight: 5
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> model_info:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> version: v2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> lifecycle: canary
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> - model_name: embedding-multilingual
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> litellm_params:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> model: openai/bge-m3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> api_base: http://tei-bge-m3.inference.svc:8080
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> router_settings:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> routing_strategy: least-busy # token-aware basic
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> num_retries: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> timeout: 60
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> general_settings:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> master_key: &amp;#34;os.environ/LITELLM_MASTER_KEY&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> database_url: &amp;#34;os.environ/DATABASE_URL&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> litellm_settings:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> cache: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> cache_params:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> type: redis
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> host: redis.inference.svc
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> port: 6379
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> similarity_threshold: 0.97
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> success_callback: [&amp;#34;langfuse&amp;#34;]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> failure_callback: [&amp;#34;langfuse&amp;#34;]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: litellm-router, namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">inference }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">replicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">litellm } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">litellm } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">litellm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ghcr.io/berriai/litellm:v1.55.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;--config=/config/config.yaml&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--port=4000&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--num_workers=4&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">containerPort: 4000, name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http }, { containerPort: 4000, name: metrics }]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">name: LITELLM_MASTER_KEY, valueFrom&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">secretKeyRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: litellm-secret, key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">master_key } } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">name: DATABASE_URL, valueFrom&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">secretKeyRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: litellm-secret, key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">db_url } } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">name: LANGFUSE_PUBLIC_KEY, valueFrom&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">secretKeyRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: langfuse-keys, key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">public } } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">name: LANGFUSE_SECRET_KEY, valueFrom&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">secretKeyRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: langfuse-keys, key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">secret } } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeMounts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: config, mountPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/config }]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readinessProbe&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">httpGet&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">path: /health, port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">4000&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: config, configMap&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">litellm-config } }]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Service&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: litellm-router, namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">inference }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">litellm }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: http, port: 80, targetPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">4000&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">monitoring.coreos.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PodMonitor&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: litellm-metrics, namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">inference }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">litellm } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">podMetricsEndpoints&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metrics&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">path&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/metrics&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">15s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El cliente final apunta a &lt;code>litellm-router.inference.svc:80/v1/chat/completions&lt;/code>, pone &lt;code>model=llama-70b&lt;/code>, y el router decide en cada request si va a v1 (95 %) o v2 (5 %), aplica el rate limit, busca en semantic cache, propaga tracing a Langfuse, y traduce de OpenAI-compatible a OpenAI-compatible del vLLM de destino. Tres réplicas del router para HA y para que el propio gateway escale horizontalmente con KEDA si hace falta —ver &lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a>—.&lt;/p>
&lt;h2 id="cuatro-pitfalls-operacionales">Cuatro pitfalls operacionales&lt;/h2>
&lt;p>&lt;strong>Pitfall 1 — el router se convierte en SPoF si no se replica.&lt;/strong> Tres o más réplicas del propio router, detrás de un Service &lt;code>LoadBalancer&lt;/code> (este sí, L4) con healthchecks. Una sola réplica del router significa que cada deploy de la configuración cierra el servicio entero unos segundos.&lt;/p>
&lt;p>&lt;strong>Pitfall 2 — la latencia del router se suma a la del modelo.&lt;/strong> Cada función añade milisegundos: parsing del body (5–10 ms), auth JWT (2–5 ms), rate limit (1–2 ms), redact PII con Presidio (20–80 ms), guardrails con Llama Guard inline (50–150 ms), prefix hash (5–10 ms), token counting con tokenizer (10–30 ms). En total &lt;strong>100–300 ms&lt;/strong> de overhead antes de tocar el motor. Si el TTFT del modelo es 400 ms y el del router 200 ms, el cliente ve &lt;strong>600 ms&lt;/strong> — vale la pena medir cuánto cuesta cada función y desactivar las no críticas en el path de baja latencia.&lt;/p>
&lt;p>&lt;strong>Pitfall 3 — el catálogo deriva del estado real del cluster.&lt;/strong> El router cree que &lt;code>vllm-llama70b-v2&lt;/code> existe porque está en su YAML, pero el deployment fue retirado hace tres días y nadie actualizó el config. El router devuelve 502 en el 5 % del tráfico. Solución: validar el catálogo contra &lt;code>kubectl get svc&lt;/code> en CI; ningún &lt;code>endpoint&lt;/code> del catálogo puede apuntar a un Service inexistente. O mejor: el router descubre dinámicamente los endpoints disponibles vía label selector (&lt;code>app=vllm,model=llama-70b&lt;/code>) y aplica weights del catálogo sobre los que están vivos.&lt;/p>
&lt;p>&lt;strong>Pitfall 4 — semantic cache con embedding outdated.&lt;/strong> El semantic cache compara embedding del prompt nuevo contra embeddings de prompts cacheados. Si actualizas el modelo de embeddings (ver &lt;a href="https://blog.lo0.es/posts/rag-corpus-curation-fundamentos/">RAG corpus curation&lt;/a>), las distancias se calculan en un espacio distinto y el cache deja de funcionar correctamente (falsos hits o falsos misses). Política: el cache se &lt;strong>invalida&lt;/strong> al cambiar el modelo de embeddings; nunca se mezclan generaciones.&lt;/p>
&lt;h2 id="encaje-en-el-stack-y-la-madurez">Encaje en el stack y la madurez&lt;/h2>
&lt;p>En el &lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">stack de siete capas&lt;/a>, el router es la &lt;strong>capa 1&lt;/strong>: la puerta de entrada que precede al motor de inferencia (capa 2), al KV cache + PagedAttention (capa 3) y al resto. Es la única pieza que ve &lt;strong>todo el tráfico&lt;/strong> desde fuera; cualquier política que no se aplique aquí, se duplica N veces en los motores.&lt;/p>
&lt;p>En los &lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">cinco niveles de madurez&lt;/a>, el router aparece a partir del &lt;strong>nivel 3&lt;/strong> (GESTIONADO): sin OIDC + RBAC + cert-manager + NetworkPolicy default deny, el router no tiene a quien autenticar ni a quien aplicar quotas; antes del nivel 3 lo que toca es montar un proxy mínimo sin pretensión de catálogo. Plataformas que intentan tener router pulido en nivel 1 acaban con un yaml grande que nadie mantiene.&lt;/p>
&lt;p>En las &lt;a href="https://blog.lo0.es/posts/siete-fases-despliegue-plataforma-llm-on-premise/">siete fases de despliegue&lt;/a>, el router es lo que cierra &lt;strong>F6&lt;/strong>: el último paso atómico que pone al cluster en producción. Sin router, F6 no termina — el catálogo, las quotas, los canaries y los failovers son condición necesaria para abrir tráfico productivo.&lt;/p>
&lt;h2 id="aplicado-a-hardware-on-premise-típico">Aplicado a hardware on-premise típico&lt;/h2>
&lt;p>Para un cluster genérico de &lt;strong>4 nodos × 4×H100 SXM 80 GB&lt;/strong>, el router de inferencia consume &lt;strong>recursos modestos&lt;/strong>: 3 réplicas del router-pod (CPU 2 cores, memoria 4 GiB cada una) bastan para soportar miles de RPS porque su trabajo es ligero (parsing, hashing, routing, no inferencia). El router vive en nodos &lt;strong>no-GPU&lt;/strong> del cluster (nodos de control plane o de workload general), nunca consume &lt;code>nvidia.com/gpu&lt;/code>.&lt;/p>
&lt;p>Volumen de tráfico que un LiteLLM con 3 réplicas y 4 workers cada una sostiene: &lt;strong>2 000–5 000 RPS&lt;/strong> routing a backend vLLM, con overhead de &lt;strong>80–150 ms&lt;/strong> en path completo (auth + rate limit + cache check + propagación). Si se necesita más, escalar el router con KEDA sobre &lt;code>litellm:requests_per_second&lt;/code> es trivial.&lt;/p>
&lt;p>Para clusters más grandes (16+ nodos GPU), considerar &lt;strong>vLLM Production Stack router&lt;/strong> o &lt;strong>NVIDIA Dynamo router&lt;/strong> que son más complejos pero exprimen el prefix-aware routing y el token-aware LB que LiteLLM OSS no cubre. Para clusters multi-region, &lt;strong>Envoy AI Gateway&lt;/strong> con Istio Service Mesh es la elección estándar.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-artículos">Lo que no hemos cubierto (próximos artículos)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Comparativa profunda LiteLLM vs vLLM PStack vs Dynamo&lt;/strong> con benchmarks de prefix-aware sobre cluster on-premise real.&lt;/li>
&lt;li>&lt;strong>Semantic cache con Redis Stack + RedisVL&lt;/strong>: hit rate, falsos positivos, política de TTL.&lt;/li>
&lt;li>&lt;strong>Multi-region routing&lt;/strong>: cómo el router decide entre clúster DC1 y DC2 según latencia, salud y carga.&lt;/li>
&lt;li>&lt;strong>AI Gateway specific features&lt;/strong>: token-bucket cost-based rate limiting (penaliza prompts largos), guardrails policy engine en el router.&lt;/li>
&lt;li>&lt;strong>Migration path&lt;/strong>: cómo introducir un router en un cluster que ya tiene clientes apuntando directo al servicio vLLM, sin downtime.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">Canary, blue-green y shadow para modelos LLM&lt;/a> — el post anterior donde llamamos &amp;ldquo;LoadBalancer&amp;rdquo; a esta pieza; este post la nombra y la desmonta.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">Siete capas del stack de inferencia LLM on-premise&lt;/a> — el router es la capa 1 del stack.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">Cinco niveles de madurez&lt;/a> — el router aparece a partir del nivel 3.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/siete-fases-despliegue-plataforma-llm-on-premise/">Siete fases de despliegue&lt;/a> — el router es lo que cierra F6.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a> — el router puede escalar con KEDA sobre sus propias métricas; convive con el autoscaling de los motores.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a> — el token-aware LB consume &lt;code>vllm:num_requests_running&lt;/code> y &lt;code>vllm:gpu_cache_usage_perc&lt;/code> para decidir réplica.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache&lt;/a> — qué cachea el prefix-aware routing y por qué multiplica el hit rate.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">Disaggregated serving prefill/decode&lt;/a> — los routers production-grade (Dynamo) son aware de la disaggregation y rutean prefill y decode a pools distintos.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a> — el router emite los spans padre &lt;code>gen_ai.*&lt;/code> y propaga &lt;code>traceparent&lt;/code> a los motores.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">Guardrails y safety en LLMs&lt;/a> — los guardrails ligeros inline se ejecutan típicamente en el router.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/entornos-mixtos-nvidia-intel-servidores-nucs/">Entornos mixtos NVIDIA + Intel para inferencia LLM&lt;/a> — el router por capability cobra todo su sentido cuando hay backends heterogéneos (NVIDIA para LLM grande, Intel para embeddings/reranker, NUC para edge); el catálogo se extiende con &lt;code>backend&lt;/code> y &lt;code>region&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>LiteLLM project — &lt;code>litellm.ai&lt;/code> (documentación de Proxy, routing strategies, semantic cache).&lt;/li>
&lt;li>vLLM Production Stack — &lt;code>github.com/vllm-project/production-stack&lt;/code> (router con prefix-aware nativo).&lt;/li>
&lt;li>NVIDIA Dynamo — &lt;code>developer.nvidia.com/blog/nvidia-dynamo-1-production-ready/&lt;/code> (router production-grade con disaggregated-aware).&lt;/li>
&lt;li>Envoy AI Gateway — &lt;code>gateway.envoyproxy.io/docs/tasks/ai-gateway/&lt;/code> (proyecto en gestación dentro de Envoy).&lt;/li>
&lt;li>Kong AI Gateway — &lt;code>konghq.com/products/kong-ai-gateway&lt;/code> (proxy enterprise con plugin LLM).&lt;/li>
&lt;li>KGateway — &lt;code>kgateway.dev&lt;/code> (alternativa CNCF en gestación).&lt;/li>
&lt;li>Zheng et al. — &lt;em>SGLang: Efficient Execution of Structured Language Model Programs&lt;/em> (NeurIPS 2024) — RadixAttention y prefix caching.&lt;/li>
&lt;li>vLLM project — &lt;em>Automatic Prefix Caching&lt;/em> (&lt;code>docs.vllm.ai/en/latest/features/automatic_prefix_caching.html&lt;/code>).&lt;/li>
&lt;li>Patel et al. — &lt;em>SplitWise: Efficient Generative LLM Inference Using Phase Splitting&lt;/em> (ISCA 2024) — base teórica del routing prefill/decode aware.&lt;/li>
&lt;/ul></description></item><item><title>Canary, blue-green y shadow para modelos LLM: cómo desplegar una versión nueva sin tirar el SLO</title><link>https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/</link><pubDate>Mon, 01 Jun 2026 16:30:00 +0200</pubDate><guid>https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/</guid><description>&lt;blockquote>
&lt;p>Este post complementa los de &lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a> (el autoscaler convive con el rollout y debe respetarlo), &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a> (las métricas que actúan como gate vienen de ahí), &lt;a href="https://blog.lo0.es/posts/evals-llm-la-capa-despues-de-tracing/">Evals para LLMs&lt;/a> (la eval que decide si el nuevo modelo está listo), &lt;a href="https://blog.lo0.es/posts/llm-as-judge-fundamentos/">LLM-as-judge&lt;/a> (la técnica que pone el &amp;ldquo;quality&amp;rdquo; en el gate de canary) y &lt;a href="https://blog.lo0.es/posts/retrain-cerrar-el-bucle-feedback-dataset-adapter/">Retrain: cerrar el bucle&lt;/a> (el step previo del que sale el modelo nuevo).&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Promocionar una versión nueva de un modelo LLM al cluster productivo sin cortar tráfico ni romper SLO exige despliegue progresivo. Las tres estrategias canónicas —&lt;strong>blue-green&lt;/strong>, &lt;strong>canary&lt;/strong>, &lt;strong>shadow&lt;/strong>— responden a preguntas distintas y tienen costes distintos. &lt;strong>Blue-green&lt;/strong>: pool completo nuevo levantado en paralelo, conmutación atómica del load balancer. Rollback instantáneo (volver a apuntar al pool viejo); exige el doble de GPUs durante la ventana. &lt;strong>Canary&lt;/strong>: el tráfico se reparte progresivamente entre la versión vieja y la nueva (1 % → 5 % → 25 % → 100 %), midiendo en cada salto gates de regresión; consume incrementalmente menos hardware pero expone usuarios reales al modelo nuevo desde el primer porcentaje. &lt;strong>Shadow / mirror&lt;/strong>: el viejo modelo sirve el 100 % del tráfico real al cliente y, en paralelo, una copia de cada request va al nuevo modelo sin devolver su respuesta al usuario; aísla del riesgo de calidad pero gasta GPU del nuevo en respuestas que nadie consume, y no funciona bien con streaming SSE largo. La elección depende de tres factores: presupuesto GPU disponible, criticidad del servicio y disponibilidad de eval automática rápida. Las &lt;strong>cinco métricas de regresión&lt;/strong> que cualquier canary LLM gatear son: TTFT P95, error rate (HTTP 5xx + &lt;code>finish_reason=&amp;quot;length&amp;quot;&lt;/code> prematuro), quality score con LLM-as-judge sobre golden set, drift estadístico de embeddings de output (Wasserstein o KL contra distribución del baseline) y coste por request (tokens/s y kW/request). En Kubernetes, &lt;strong>Argo Rollouts&lt;/strong> gestiona el tráfico y los &lt;code>AnalysisTemplate&lt;/code> como gates automáticos; &lt;strong>Flagger&lt;/strong> es la alternativa más opinionada. vLLM v1 &lt;strong>no soporta hot model swap&lt;/strong> robusto a mayo 2026, así que la unidad de rollout es la &lt;strong>réplica entera&lt;/strong> (deployment v2 al lado de deployment v1). Los tres pitfalls específicos: sticky sessions del LB rompen la comparabilidad estadística del canary (un cliente A siempre cae al nuevo, B al viejo — las poblaciones no son equivalentes); eval semántica con LLM-as-judge tarda 2–8 segundos por sample y no sirve como gate en tiempo real (se usa en post-análisis o offline pre-promoción); el streaming SSE complica el shadow porque hay que descartar la respuesta del nuevo modelo sin afectar a la del viejo. Este post incluye un manifest Argo Rollouts mínimo aplicable a un cluster genérico con NVIDIA GPU Operator.&lt;/p>
&lt;h2 id="estás-aquí-deploy-y-la-transición-a-retrain">Estás aquí: DEPLOY (y la transición a RETRAIN)&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="estás aquí: Deploy con transición a Retrain">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#7ad88f;stroke-width:3}.semiactive{fill:#cfead0;stroke-width:2}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#crm)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#crm)}&lt;/style>
&lt;defs>&lt;marker id="crm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">Estás aquí: DEPLOY · canary cierra el círculo iniciado en RETRAIN&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box active"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box idle"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box semiactive"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;/svg>
&lt;/div>
&lt;p>Un modelo nuevo no aparece por arte de magia en el cluster: viene del bucle de &lt;a href="https://blog.lo0.es/posts/retrain-cerrar-el-bucle-feedback-dataset-adapter/">retrain&lt;/a> o de una actualización del proveedor de pesos. El paso entre &amp;ldquo;tengo un artefacto que pasó eval offline&amp;rdquo; y &amp;ldquo;está sirviendo el 100 % del tráfico&amp;rdquo; es exactamente este post.&lt;/p>
&lt;h2 id="la-analogía-el-estreno-de-una-obra-en-teatro">La analogía: el estreno de una obra en teatro&lt;/h2>
&lt;p>Una compañía de teatro va a estrenar una nueva versión de una obra que lleva un año en cartel con éxito. La compañía sabe varias cosas duras: el público actual paga por una experiencia consistente; un mal estreno daña el negocio durante meses; pero no estrenar nada deja a la compañía obsoleta frente a la competencia.&lt;/p>
&lt;p>Las tres rutas de estreno que la dirección puede elegir son las mismas tres del rollout LLM.&lt;/p>
&lt;p>&lt;strong>Ensayo general a puerta cerrada (shadow / mirror).&lt;/strong> Los actores nuevos representan la obra entera ante un teatro vacío. No hay público; nadie compra entrada. Tres pases enteros sirven para comprobar continuidad, tiempos y química del reparto. Es &lt;strong>caro&lt;/strong> porque hay sueldos y alquiler del teatro, pero &lt;strong>no expone al público al riesgo&lt;/strong>. Útil cuando el reparto nuevo está sin probar y el director quiere ver cómo aguanta una función completa antes de venderla. En LLM: el modelo nuevo procesa cada request real en paralelo al viejo pero sus respuestas se descartan; gastas GPU del nuevo en respuestas que nadie ve.&lt;/p>
&lt;p>&lt;strong>Reparto por funciones, alternando (canary).&lt;/strong> En lugar de cambiar todo el reparto de golpe, las funciones de jueves son del reparto nuevo, las del viernes del viejo, las de sábado mitad y mitad. La dirección lee los comentarios del libro de visitas y la afluencia de público función a función, decidiendo al cabo de dos semanas si promociona el reparto nuevo a titular o lo retira. &lt;strong>Más barato&lt;/strong> que el ensayo general porque las funciones venden entrada igual, pero &lt;strong>expone público real al riesgo&lt;/strong> desde el primer jueves. En LLM: el tráfico se reparte progresivamente entre la versión vieja y la nueva, midiendo gates en cada salto.&lt;/p>
&lt;p>&lt;strong>Doble compañía con cambio atómico (blue-green).&lt;/strong> La compañía contrata el reparto nuevo, lo prepara durante un mes a puerta cerrada, y un sábado anuncia: &amp;ldquo;a partir del próximo estreno todas las funciones son con el reparto nuevo&amp;rdquo;. Si la primera función va mal, se vuelve al reparto viejo en 24 horas — pero durante ese mes de preparación se paga &lt;strong>doble sueldo&lt;/strong> a las dos compañías. En LLM: dos pools completos del mismo tamaño, conmutación instantánea del LB de uno a otro, rollback en segundos si las métricas se rompen.&lt;/p>
&lt;p>La analogía sostiene también la decisión: la elección depende de &lt;strong>cuán crítica&lt;/strong> sea la obra para el negocio (criticidad del servicio LLM), &lt;strong>cuánto presupuesto&lt;/strong> hay para sostener dos repartos a la vez (presupuesto GPU), y &lt;strong>cuánta confianza&lt;/strong> se tiene en el nuevo reparto a partir de los ensayos de cámara (eval offline previa al canary).&lt;/p>
&lt;h2 id="las-tres-estrategias-en-detalle">Las tres estrategias en detalle&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 340" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="tres estrategias de rollout LLM comparadas">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.bg{fill:#d8eecf;stroke:#373}.cn{fill:#dfe9f5;stroke:#356}.sh{fill:#ead8f5;stroke:#634}.title{font:600 13px sans-serif;fill:#222}.h{font:700 12px sans-serif;fill:#222}.l{font:11px sans-serif;fill:#222}.n{font:italic 10px sans-serif;fill:#444}&lt;/style>
&lt;text x="410" y="20" text-anchor="middle" class="title">Tres estrategias de rollout LLM con sus tradeoffs&lt;/text>
&lt;rect x="20" y="40" width="250" height="280" class="b bg"/>
&lt;text x="145" y="62" text-anchor="middle" class="h">BLUE-GREEN&lt;/text>
&lt;text x="30" y="90" class="l">Pool v1 (azul) sirve 100%&lt;/text>
&lt;text x="30" y="106" class="l">Pool v2 (verde) levantado en idle&lt;/text>
&lt;text x="30" y="122" class="l">Conmutación LB: v1 → v2 instantánea&lt;/text>
&lt;text x="30" y="138" class="l">Rollback: re-conmutar a v1&lt;/text>
&lt;text x="30" y="170" class="h">+ Rollback instantáneo&lt;/text>
&lt;text x="30" y="190" class="h">+ Test E2E en pool real&lt;/text>
&lt;text x="30" y="216" class="h">− Doble GPU durante ventana&lt;/text>
&lt;text x="30" y="236" class="h">− Switch grande = riesgo total&lt;/text>
&lt;text x="30" y="270" class="n">Caso típico: actualización menor&lt;/text>
&lt;text x="30" y="284" class="n">de proveedor (FP8 → FP4 nueva&lt;/text>
&lt;text x="30" y="298" class="n">versión del mismo modelo).&lt;/text>
&lt;rect x="285" y="40" width="250" height="280" class="b cn"/>
&lt;text x="410" y="62" text-anchor="middle" class="h">CANARY&lt;/text>
&lt;text x="295" y="90" class="l">v1 sirve mayoría · v2 fracción&lt;/text>
&lt;text x="295" y="106" class="l">Reparto progresivo: 1→5→25→100%&lt;/text>
&lt;text x="295" y="122" class="l">Gate de regresión entre saltos&lt;/text>
&lt;text x="295" y="138" class="l">Auto-rollback si gate falla&lt;/text>
&lt;text x="295" y="170" class="h">+ Exposición controlada&lt;/text>
&lt;text x="295" y="190" class="h">+ GPU incremental, no doble&lt;/text>
&lt;text x="295" y="216" class="h">− Usuarios reales en muestra&lt;/text>
&lt;text x="295" y="236" class="h">− Sticky sessions rompen muestreo&lt;/text>
&lt;text x="295" y="270" class="n">Caso típico: cambio de modelo&lt;/text>
&lt;text x="295" y="284" class="n">(Llama 70B → Llama 3.3 70B&lt;/text>
&lt;text x="295" y="298" class="n">fine-tuned por dominio).&lt;/text>
&lt;rect x="550" y="40" width="250" height="280" class="b sh"/>
&lt;text x="675" y="62" text-anchor="middle" class="h">SHADOW · MIRROR&lt;/text>
&lt;text x="560" y="90" class="l">v1 sirve 100% al usuario&lt;/text>
&lt;text x="560" y="106" class="l">v2 recibe copia de cada request&lt;/text>
&lt;text x="560" y="122" class="l">Respuesta v2 se descarta&lt;/text>
&lt;text x="560" y="138" class="l">Comparación offline v1 vs v2&lt;/text>
&lt;text x="560" y="170" class="h">+ Cero exposición al riesgo&lt;/text>
&lt;text x="560" y="190" class="h">+ Tráfico real en v2 sin daño&lt;/text>
&lt;text x="560" y="216" class="h">− GPU del v2 100% sin valor user&lt;/text>
&lt;text x="560" y="236" class="h">− Mal fit con streaming SSE largo&lt;/text>
&lt;text x="560" y="270" class="n">Caso típico: validación pre-canary&lt;/text>
&lt;text x="560" y="284" class="n">de modelo de arquitectura distinta&lt;/text>
&lt;text x="560" y="298" class="n">(denso → MoE).&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h3 id="blue-green">Blue-green&lt;/h3>
&lt;p>El operador mantiene dos pools de réplicas idénticos en tamaño: el &lt;strong>azul&lt;/strong> (versión productiva v1) y el &lt;strong>verde&lt;/strong> (versión candidata v2). Cuando v2 está validado offline (eval pasada, smoke tests), el switch del LoadBalancer redirige el 100 % del tráfico de azul a verde &lt;strong>en un solo paso&lt;/strong>. Si las métricas del SLO se rompen, el switch vuelve atrás en segundos.&lt;/p>
&lt;p>Coste: &lt;strong>2× GPUs&lt;/strong> durante toda la ventana (preparación de v2 + ventana de observación post-switch). Para un cluster de 16 GPUs sirviendo Llama 70B con TP=4 (4 réplicas), preparar el blue-green requiere 16 GPUs adicionales durante 1–3 días.&lt;/p>
&lt;p>Riesgo: el switch es &lt;strong>atómico&lt;/strong> — si v2 tiene un problema que no apareció en eval offline pero sí aparece a escala (por ejemplo, edge cases que solo se ven a 200 RPS), &lt;strong>el 100 % de usuarios lo nota a la vez&lt;/strong>. El rollback es instantáneo, pero las requests del primer minuto post-switch ya se vieron afectadas. Por tanto blue-green es preferible cuando se tiene &lt;strong>alta confianza&lt;/strong> en v2 (cambio menor: misma arquitectura, mismo formato, solo nueva versión de pesos) y se prioriza &lt;strong>rollback inmediato&lt;/strong> sobre exposición gradual.&lt;/p>
&lt;h3 id="canary">Canary&lt;/h3>
&lt;p>El operador despliega v2 con un número pequeño de réplicas (típicamente 1) junto al pool de v1. El LoadBalancer reparte progresivamente el tráfico siguiendo un cronograma: 1 % durante 30 minutos → 5 % durante 1 hora → 25 % durante 2 horas → 50 % durante 4 horas → 100 %. Entre cada salto, un &lt;strong>gate de análisis&lt;/strong> evalúa métricas de regresión sobre el tráfico que ya está cayendo en v2. Si el gate falla, el rollback retira el tráfico de v2 automáticamente y deja v1 sirviendo todo.&lt;/p>
&lt;p>Coste: &lt;strong>incremental&lt;/strong>. Al inicio (1 % de tráfico) basta una réplica v2; al 50 % se necesita la mitad de réplicas v2 que el total de v1. Pico de GPU adicional durante el canary: ~30–50 % por encima del baseline.&lt;/p>
&lt;p>Riesgo: usuarios reales &lt;strong>están viendo v2&lt;/strong> desde el primer 1 %. Si v2 produce respuestas con calidad degradada pero TTFT y error rate normales, los usuarios afectados perciben la degradación sin que el gate la detecte (a menos que el gate incluya quality drift, que tarda). Por tanto canary es preferible cuando se tiene &lt;strong>confianza media&lt;/strong> en v2 (cambio significativo: arquitectura o entrenamiento distinto) y se acepta que un % bajo de usuarios sea conejillo.&lt;/p>
&lt;h3 id="shadow--mirror">Shadow / mirror&lt;/h3>
&lt;p>El LoadBalancer envía el 100 % del tráfico real a v1 (que responde al cliente) &lt;strong>y duplica cada request&lt;/strong> hacia v2 (cuya respuesta se descarta o se guarda para análisis offline). El cliente nunca ve v2; nunca está expuesto al riesgo.&lt;/p>
&lt;p>Coste: &lt;strong>100 % adicional del compute de v2&lt;/strong> sin valor de usuario directo durante toda la ventana de shadow. Para un cluster de 16 GPUs sirviendo Llama 70B con TP=4 (4 réplicas), un shadow del mismo tamaño consume 16 GPUs adicionales &lt;strong>a tiempo completo&lt;/strong>.&lt;/p>
&lt;p>Riesgo: el shadow es &lt;strong>el más seguro&lt;/strong> para el usuario. Pero tiene dos limitaciones serias: (a) si v2 tiene un cuello de botella que causa que la copia de request al shadow tarde mucho, el proxy de shadowing puede consumir conexiones del LB; debe estar &lt;strong>out-of-band&lt;/strong> (asíncrono); (b) el &lt;strong>streaming SSE largo&lt;/strong> complica la mirroring porque hay que mantener dos streams paralelos y descartar uno mientras el otro fluye al cliente. Patrón habitual: shadow solo de requests no-streaming (completiones cortas, classification), eval offline manual de las requests con streaming.&lt;/p>
&lt;h2 id="las-cinco-métricas-de-regresión-que-actúan-como-gate">Las cinco métricas de regresión que actúan como gate&lt;/h2>
&lt;p>Sin gates automáticos, el &amp;ldquo;canary&amp;rdquo; es solo un nombre bonito para &amp;ldquo;rollout manual con un porcentaje variable&amp;rdquo;. Los gates son la pieza que convierte el canary en una operación defendible.&lt;/p>
&lt;p>&lt;strong>Métrica 1 — TTFT P95.&lt;/strong> Comparación P95 del nuevo modelo contra P95 del baseline (v1) en ventanas de 5 minutos. Gate: &lt;code>ttft_p95(v2) / ttft_p95(v1) &amp;lt; 1.10&lt;/code>. Detecta regresiones de latencia de prefill (modelo nuevo más lento) o problemas de motor (config subóptima). Fuente: &lt;code>vllm:time_to_first_token_seconds_bucket&lt;/code> —ver &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a>—.&lt;/p>
&lt;p>&lt;strong>Métrica 2 — Error rate.&lt;/strong> Suma de HTTP 5xx + 4xx no esperados + tasa de &lt;code>finish_reason=&amp;quot;length&amp;quot;&lt;/code> prematuro (respuestas cortadas porque el modelo nuevo no genera EOS). Gate: &lt;code>error_rate(v2) - error_rate(v1) &amp;lt; 0.01&lt;/code> (1 punto porcentual). Detecta crashes del motor, tokenizer roto, problemas de generación. Fuente: &lt;code>vllm:request_success_total{status=...}&lt;/code>.&lt;/p>
&lt;p>&lt;strong>Métrica 3 — Quality score (LLM-as-judge).&lt;/strong> Sobre un &lt;strong>golden set&lt;/strong> de 200–1 000 prompts representativos, se ejecutan v1 y v2 offline y un modelo juez (típicamente más grande: GPT-4 class, Claude, Llama 405B local) puntúa cada par. Gate típico: &lt;code>mean_score(v2) &amp;gt;= mean_score(v1) - 0.05&lt;/code>. Esta métrica &lt;strong>no se mide en tiempo real durante el canary&lt;/strong> — la inferencia del juez tarda 2–8 segundos por sample y no escala como gate inline. Se usa como &lt;strong>gate offline pre-promoción&lt;/strong> (antes de empezar el canary) y como &lt;strong>post-mortem&lt;/strong> sobre muestra de tráfico real capturado durante el canary. Ver &lt;a href="https://blog.lo0.es/posts/llm-as-judge-fundamentos/">LLM-as-judge&lt;/a> para la mecánica.&lt;/p>
&lt;p>&lt;strong>Métrica 4 — Drift estadístico de output.&lt;/strong> Para cada request que cae en v2 durante el canary, embeber la respuesta con un modelo de embedding ligero (e5, BGE) y comparar la distribución de embeddings de v2 contra la distribución del baseline v1 sobre la misma ventana. Métricas usables: &lt;strong>Wasserstein distance&lt;/strong>, divergencia KL, o más simple, comparar medias y varianzas por dimensión. Gate: distancia normalizada &amp;lt; umbral calibrado (típicamente Wasserstein &amp;lt; 0.15). Detecta cambios sutiles en estilo, longitud, vocabulario que LLM-as-judge no captura sin pasar también por él. Es &lt;strong>rápida&lt;/strong>: el embedding ligero tarda ~50 ms por respuesta.&lt;/p>
&lt;p>&lt;strong>Métrica 5 — Coste por request.&lt;/strong> Tokens out / request y kW / request. Gate: &lt;code>cost_per_request(v2) / cost_per_request(v1) &amp;lt; 1.20&lt;/code>. Detecta modelos nuevos que generan respuestas significativamente más largas o que consumen más energía por la misma carga (degradación de quantization, fallo de optimizations). Sin este gate, una &amp;ldquo;actualización&amp;rdquo; puede duplicar la factura silenciosamente.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Métrica&lt;/th>
&lt;th>Tipo&lt;/th>
&lt;th>Latencia de medida&lt;/th>
&lt;th>Gate típico&lt;/th>
&lt;th>Detección&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>TTFT P95&lt;/td>
&lt;td>Cuantitativa&lt;/td>
&lt;td>5 min&lt;/td>
&lt;td>&lt;code>&amp;lt; 110% baseline&lt;/code>&lt;/td>
&lt;td>Regresión de latencia&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Error rate&lt;/td>
&lt;td>Cuantitativa&lt;/td>
&lt;td>1 min&lt;/td>
&lt;td>&lt;code>&amp;lt; 1pp sobre baseline&lt;/code>&lt;/td>
&lt;td>Crashes, generation broken&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Quality (LLM-judge)&lt;/td>
&lt;td>Semántica offline&lt;/td>
&lt;td>horas, sobre golden&lt;/td>
&lt;td>&lt;code>&amp;gt; baseline − 0.05&lt;/code>&lt;/td>
&lt;td>Calidad funcional&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Drift estadístico&lt;/td>
&lt;td>Estadística&lt;/td>
&lt;td>~5 min&lt;/td>
&lt;td>Wasserstein &amp;lt; 0.15&lt;/td>
&lt;td>Estilo, longitud, vocabulario&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Coste por request&lt;/td>
&lt;td>Cuantitativa&lt;/td>
&lt;td>5 min&lt;/td>
&lt;td>&lt;code>&amp;lt; 120% baseline&lt;/code>&lt;/td>
&lt;td>Eficiencia económica/energética&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="la-mecánica-en-kubernetes-argo-rollouts">La mecánica en Kubernetes: Argo Rollouts&lt;/h2>
&lt;p>Argo Rollouts extiende el &lt;code>Deployment&lt;/code> estándar de Kubernetes con un nuevo recurso &lt;code>Rollout&lt;/code> que orquesta la progresión del tráfico y los análisis automáticos. Se integra con cualquier service mesh (Istio, Linkerd) o controlador de ingress que soporte traffic splitting (NGINX, Traefik, Gateway API).&lt;/p>
&lt;p>Ejemplo mínimo de canary 1 → 5 → 25 → 100 % con gates de TTFT y error rate:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">argoproj.io/v1alpha1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Rollout&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">replicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">strategy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">canary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">canaryService&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b-canary&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stableService&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b-stable&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">trafficRouting&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nginx&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stableIngress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b-ingress&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">setWeight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">pause&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">duration&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">30m }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">analysis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">templates&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">templateName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ttft-error-gate }] }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">setWeight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">pause&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">duration&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">1h }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">analysis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">templates&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">templateName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ttft-error-gate }] }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">setWeight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">25&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">pause&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">duration&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">2h }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">analysis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">templates&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">templateName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ttft-error-gate }, { templateName: drift-gate }] }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">setWeight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">pause&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">duration&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">4h }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">analysis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">templates&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">templateName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ttft-error-gate }, { templateName: drift-gate }] }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">setWeight&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">100&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm/vllm-openai:v0.10.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="w"> &lt;/span>--&lt;span class="l">model=/models/llama-70b-fp8-v2 ] &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># versión nueva&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">argoproj.io/v1alpha1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">AnalysisTemplate&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ttft-error-gate }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metrics&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ttft-p95-ratio&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">1m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">count&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">failureLimit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">successCondition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">result &amp;lt; 1.10&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prometheus&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">address&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://prometheus.observability.svc:9090&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">query&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket{version=&amp;#34;v2&amp;#34;}[5m])))
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> /
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket{version=&amp;#34;v1&amp;#34;}[5m])))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">error-rate-diff&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">1m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">count&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">failureLimit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">successCondition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">result &amp;lt; 0.01&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prometheus&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">address&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://prometheus.observability.svc:9090&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">query&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> sum(rate(vllm:request_total{version=&amp;#34;v2&amp;#34;,status=~&amp;#34;5..&amp;#34;}[5m])) / sum(rate(vllm:request_total{version=&amp;#34;v2&amp;#34;}[5m]))
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> -
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> sum(rate(vllm:request_total{version=&amp;#34;v1&amp;#34;,status=~&amp;#34;5..&amp;#34;}[5m])) / sum(rate(vllm:request_total{version=&amp;#34;v1&amp;#34;}[5m]))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Si cualquiera de los &lt;code>AnalysisTemplate&lt;/code> falla, Argo Rollouts retrocede automáticamente: pone weight=0 en el canary, alerta al operador, mantiene v1 sirviendo el 100 %. La operación humana se reduce a investigar el fallo y decidir si re-lanzar o abortar.&lt;/p>
&lt;p>&lt;strong>Flagger&lt;/strong> ofrece una alternativa más opinionada: la progresión del weight es automática en función del éxito de las métricas en vez de pausa fija; el operador define un objetivo (&lt;code>maxWeight: 100&lt;/code>, &lt;code>stepWeight: 10&lt;/code>, &lt;code>metrics: [...]&lt;/code>) y Flagger sube o baja según comportamiento. Ambas son maduras en mayo 2026; la elección suele venir dictada por qué service mesh ya está en el cluster.&lt;/p>
&lt;h2 id="el-detalle-de-vllm-por-qué-no-se-hace-hot-swap-del-modelo">El detalle de vLLM: por qué no se hace &amp;ldquo;hot swap&amp;rdquo; del modelo&lt;/h2>
&lt;p>A mayo 2026, vLLM v1 &lt;strong>no soporta cambio caliente&lt;/strong> del modelo dentro de la misma réplica sin reiniciar el motor. El comando &lt;code>--model&lt;/code> se evalúa al arranque; cambiarlo requiere re-instanciar el &lt;code>LLMEngine&lt;/code>, lo que reinicia conexiones y descarta el KV cache. Por tanto la &lt;strong>unidad de rollout es la réplica entera&lt;/strong>: no se hace &amp;ldquo;v1 carga el modelo nuevo en una de sus GPUs&amp;rdquo; sino &amp;ldquo;se levanta una réplica v2 al lado de una réplica v1 y se reparte tráfico vía LB&amp;rdquo;.&lt;/p>
&lt;p>TensorRT-LLM con Triton tiene un mecanismo similar: cambiar el modelo exige reload del backend Triton. SGLang tampoco soporta hot swap robusto. La consecuencia operativa: el rollout LLM siempre va a costar GPUs adicionales durante la ventana, y la elección entre blue-green, canary y shadow es exactamente la pregunta de &lt;strong>cuántas adicionales&lt;/strong> y &lt;strong>cuánto tiempo&lt;/strong>.&lt;/p>
&lt;h2 id="los-tres-pitfalls-específicos-del-rollout-llm">Los tres pitfalls específicos del rollout LLM&lt;/h2>
&lt;p>&lt;strong>Pitfall 1 — sticky sessions rompen la comparabilidad del canary.&lt;/strong> Si el LoadBalancer hace session affinity por IP del cliente (común en NGINX, Traefik con &lt;code>loadbalancer.kubernetes.io/session-affinity: ClientIP&lt;/code>), un usuario A siempre cae en v2 mientras B siempre cae en v1. Las distribuciones de carga, perfiles de prompt y comportamiento de cliente &lt;strong>no son aleatorias&lt;/strong> entre los dos pools, lo que invalida estadísticamente cualquier comparación de gates. Solución: para canary, desactivar session affinity (&lt;code>sessionAffinity: None&lt;/code>) o usar affinity por request-id aleatorio. Si la app cliente exige sticky por funcionalidad (memoria conversacional persistida en cache), el canary no es la estrategia adecuada — usar blue-green o shadow.&lt;/p>
&lt;p>&lt;strong>Pitfall 2 — LLM-as-judge no es gate inline en tiempo real.&lt;/strong> La tentación de usar quality score como gate live es alta, pero la latencia del juez (2–8 s por sample) hace inviable evaluar más que un sampling del 1–2 % del tráfico, y los resultados llegan con minutos de retraso. Soluciones operativas: (a) &lt;strong>eval offline pre-canary&lt;/strong> sobre golden set como pre-requisito para arrancar (si falla, ni se inicia el canary); (b) durante el canary, &lt;strong>capturar requests + responses&lt;/strong> de v2 a tiempo real y correr el juez &lt;strong>asíncrono&lt;/strong> en un job batch que termina antes del siguiente salto; (c) usar drift estadístico de embeddings como &lt;strong>proxy rápido&lt;/strong> de calidad inline, y reservar el juez para gates intermedios entre saltos.&lt;/p>
&lt;p>&lt;strong>Pitfall 3 — streaming SSE complica el shadow.&lt;/strong> El mirror de tráfico clásico (NGINX &lt;code>mirror&lt;/code>, Istio &lt;code>MirrorPolicy&lt;/code>) está pensado para HTTP de request/response — copia la request, deja al servidor primario responder al cliente, y duplica la request al secundario descartando la respuesta. Con SSE, la respuesta del secundario es un &lt;strong>stream continuo de varios segundos&lt;/strong>, y mantener dos streams en paralelo carga doblemente al proxy. Soluciones: (a) shadow &lt;strong>solo de requests no-streaming&lt;/strong> (chat sin stream, embeddings, classification, batch eval), (b) shadow del tráfico streaming pero con &lt;strong>timeout corto&lt;/strong> en el secundario (descartar el shadow si tarda más de 30 s), (c) reemplazar el shadow por canary con weight pequeño (1 %) que sí soporta streaming bien.&lt;/p>
&lt;h2 id="aplicado-a-hardware-on-premise-típico">Aplicado a hardware on-premise típico&lt;/h2>
&lt;p>Para un cluster genérico de &lt;strong>4 nodos × 4×H100 SXM 80 GB = 16 GPUs&lt;/strong>, sirviendo Llama 70B FP8 con TP=4 (4 réplicas posibles, una por nodo):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Blue-green&lt;/strong>: imposible mantener dos pools completos de 4 réplicas sin GPUs adicionales. Solución práctica: blue-green con pools reducidos (2 réplicas v1 + 2 réplicas v2) durante la ventana, &lt;strong>degradación de capacidad aceptada&lt;/strong> (mitad del SLO de RPS sostenido), o disponer de un cluster paralelo (otro nodo) reservado para rollouts.&lt;/li>
&lt;li>&lt;strong>Canary&lt;/strong>: factible. Empezar con 3 réplicas v1 + 1 réplica v2 (25 % weight nominal pero también peso variable de tráfico). Avanzar a 2 v1 + 2 v2 al 50 %, luego 1 v1 + 3 v2, finalmente 0 v1 + 4 v2.&lt;/li>
&lt;li>&lt;strong>Shadow&lt;/strong>: complicado por el coste de GPU. Reservar para validación pre-canary de cambios mayores, durante una ventana corta (4–8 horas) con tráfico shadowed limitado a una muestra (10–20 % de requests, no 100 %).&lt;/li>
&lt;/ul>
&lt;p>Para clusters de &lt;strong>8 nodos GPU&lt;/strong>, los tres patrones son sostenibles. La regla operativa: el presupuesto de rollout es típicamente el &lt;strong>25–30 % de la capacidad sostenida&lt;/strong> del cluster — comprar para el pico + ese head-room cuadra los números del &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">capacity planning&lt;/a>.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-artículos">Lo que no hemos cubierto (próximos artículos)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Rollouts multi-region&lt;/strong>: cómo coordinar canary cuando el cluster está distribuido geográficamente.&lt;/li>
&lt;li>&lt;strong>A/B testing de prompts&lt;/strong> (no de modelos): el mismo modelo con dos system prompts distintos, medir conversion.&lt;/li>
&lt;li>&lt;strong>Rollback de embeddings&lt;/strong>: cambiar el modelo de embeddings de un sistema RAG implica re-embedir todo el corpus — la mecánica de canary es distinta. Ver &lt;a href="https://blog.lo0.es/posts/rag-corpus-curation-fundamentos/">RAG corpus curation&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Feature flags para LLM&lt;/strong>: granularidad por tenant o por feature dentro del mismo modelo.&lt;/li>
&lt;li>&lt;strong>Continuous deployment&lt;/strong> end-to-end: integración con el retrain pipeline para que un nuevo adapter se promocione automáticamente tras pasar evals.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a> — el autoscaler convive con el canary y debe respetar las particiones de tráfico.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a> — las métricas que actúan como gate vienen de aquí.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning&lt;/a> — define el head-room necesario para rollouts.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/evals-llm-la-capa-despues-de-tracing/">Evals para LLMs&lt;/a> — la eval offline que valida v2 antes de empezar el canary.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/llm-as-judge-fundamentos/">LLM-as-judge&lt;/a> — la técnica de quality score como gate offline.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/retrain-cerrar-el-bucle-feedback-dataset-adapter/">Retrain: cerrar el bucle&lt;/a> — de donde sale el modelo nuevo que entra al canary.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">Cinco niveles de madurez&lt;/a> — Argo Rollouts es pieza del nivel 4–5.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/router-inferencia-llm-gateway-l7/">El router de inferencia LLM&lt;/a> — la pieza que en este post llamamos &amp;ldquo;LoadBalancer&amp;rdquo; desmontada como capa de pleno derecho: catálogo de modelos, traffic splitting L7, política transversal, failover y prefix-aware routing. El reparto 1 % → 5 % → 25 % → 100 % se materializa allí.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/runbooks-incident-response-llm-keep-kafka/">Runbooks de incident response para LLM con Keep + Kafka&lt;/a> — el rollback automático del canary cuando &lt;code>ttft_p95(v2)/ttft_p95(v1) &amp;gt; 1.30&lt;/code> es el runbook RB-06; allí está el workflow Keep YAML completo y el encaje en compliance.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>Argo Rollouts project — &lt;code>argoproj.io/argo-rollouts&lt;/code> (CRD &lt;code>Rollout&lt;/code> y &lt;code>AnalysisTemplate&lt;/code>).&lt;/li>
&lt;li>Flagger project — &lt;code>fluxcd.io/flagger&lt;/code> (alternativa con progresión automática).&lt;/li>
&lt;li>Istio — &lt;em>Traffic Mirroring&lt;/em> (mirror configurable a nivel &lt;code>VirtualService&lt;/code>).&lt;/li>
&lt;li>NGINX Ingress — &lt;em>Canary annotations&lt;/em> (&lt;code>nginx.ingress.kubernetes.io/canary-*&lt;/code>).&lt;/li>
&lt;li>vLLM project — issue tracker sobre hot model swap (estado a mayo 2026: en diseño, no production-ready).&lt;/li>
&lt;li>Hou et al. — &lt;em>DistServe: Disaggregating Prefill and Decoding for Goodput-optimized LLM Serving&lt;/em> (OSDI 2024) — referencia sobre métricas de goodput aplicables a gates de canary.&lt;/li>
&lt;li>Bürkner et al. — &lt;em>Statistical methods for detecting model drift in production&lt;/em> (artículos varios sobre Wasserstein y KL en monitoring ML).&lt;/li>
&lt;/ul></description></item><item><title>Autoscaling de inferencia LLM en Kubernetes: HPA con custom metrics y KEDA para vLLM</title><link>https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/</link><pubDate>Mon, 01 Jun 2026 16:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/</guid><description>&lt;blockquote>
&lt;p>Este post complementa los de &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a> (de donde vienen las métricas que alimentan al HPA), &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning&lt;/a> (qué techo y qué head-room presupone el autoscaler) y &lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">Continuous batching&lt;/a> (lo que explica por qué &lt;code>num_requests_waiting&lt;/code> es la métrica primaria).&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>El autoscaling clásico de Kubernetes —HPA sobre &lt;code>cpu&lt;/code> o &lt;code>memory&lt;/code>— &lt;strong>no sirve para inferencia LLM&lt;/strong>. Razón: el pod vLLM consume poco CPU (el trabajo lo hace la GPU) y la memoria RSS del proceso es plana; ambas métricas pueden quedarse al 30 % mientras la GPU está saturada y la cola de requests crece sin freno. Las cuatro señales viables que sí responden a la carga real son: &lt;strong>&lt;code>vllm:num_requests_waiting&lt;/code>&lt;/strong> (la cola, la métrica primaria), &lt;strong>&lt;code>vllm:gpu_cache_usage_perc&lt;/code>&lt;/strong> (presión sobre el KV cache pool), &lt;strong>TTFT P95&lt;/strong> vía histogram de &lt;code>vllm:time_to_first_token_seconds_bucket&lt;/code> (la garantía del SLO) y el &lt;strong>batch fill ratio&lt;/strong> &lt;code>num_requests_running / max_num_seqs&lt;/code> (utilización del techo de concurrencia). Para que un HPA pueda consumir métricas Prometheus hace falta un adaptador; en mayo 2026 hay dos opciones maduras: &lt;strong>&lt;code>prometheus-adapter&lt;/code>&lt;/strong> (sigma de cluster, configuración estática, output &lt;code>external.metrics.k8s.io&lt;/code>) y &lt;strong>KEDA&lt;/strong> (&lt;code>ScaledObject&lt;/code> con trigger Prometheus, polling configurable, escalado a cero opcional, integración con cron). KEDA es la opción dominante para LLM en cluster genérico porque resuelve el patrón &amp;ldquo;warm pool + cron + métrica del motor&amp;rdquo; en un solo CRD. El reto operacional dominante no es la lógica de escalado sino el &lt;strong>cold start&lt;/strong>: un pod vLLM con Llama 70B BF16 (140 GB) tarda entre &lt;strong>90 segundos&lt;/strong> (modelo precacheado en PV local) y &lt;strong>6 minutos&lt;/strong> (image pull + descarga del modelo desde object store) hasta servir el primer token. Las cinco palancas que lo recortan son imagen pre-pulled vía DaemonSet, modelo cacheado en PV o tmpfs regional, &lt;strong>warm pool&lt;/strong> con &lt;code>minReplicaCount &amp;gt; 0&lt;/code>, &lt;strong>predictive scaling&lt;/strong> vía KEDA cron cuando el patrón de tráfico es predecible (oficinas 9–18 h), y descarga paralela del modelo. Los tres pitfalls específicos del scale-down LLM: cortar conexiones SSE de streaming a media respuesta (drain elegante con &lt;code>terminationGracePeriodSeconds&lt;/code> ≥ 60 s), oscilación de scale-out/in por stabilization window mal calibrada, y olvidar que el HPA solo escala pods — los &lt;strong>nodos GPU&lt;/strong> se escalan con cluster-autoscaler sobre nodepools etiquetados. Este post incluye los manifests YAML mínimos.&lt;/p>
&lt;h2 id="estás-aquí-deploy">Estás aquí: DEPLOY&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="estás aquí: Deploy">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#7ad88f;stroke-width:3}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#asm)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#asm)}&lt;/style>
&lt;defs>&lt;marker id="asm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">Estás aquí: DEPLOY · autoscaling sobre las métricas que mide OBSERVE&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box active"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box idle"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box idle"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="la-analogía-la-panadería-con-hornos-de-leña">La analogía: la panadería con hornos de leña&lt;/h2>
&lt;p>Una panadería artesanal tiene tres hornos de leña. Cada horno tarda 25 minutos en alcanzar temperatura desde frío. Una vez caliente, hornea pan continuamente con una tirada de 18 minutos por hornada. La encargada quiere maximizar pan vendido por día sin gastar leña inútil, y sabe tres cosas: que hay un pico de demanda a las 7:30 cada mañana, que los lunes no se vende casi nada, y que cuando se acaba el pan en mostrador los clientes se van al supermercado de al lado.&lt;/p>
&lt;p>La estrategia barata —encender hornos cuando hay cola en la tienda— &lt;strong>no funciona&lt;/strong>. Para cuando la cola crece y la encargada enciende el segundo horno, ese horno no estará listo hasta 25 minutos después; los clientes de esa ventana se perdieron. La señal &amp;ldquo;cola en mostrador&amp;rdquo; llega tarde.&lt;/p>
&lt;p>La estrategia inteligente: encender el segundo horno a las 6:55, antes del pico previsible de las 7:30, y dejarlo activo hasta las 10:00 aunque la cola baje a las 8:15. Mantener el tercer horno apagado entre lunes y miércoles porque la demanda no llega; encenderlo proactivamente los jueves a las 12:00 porque históricamente sube. Tener una bolsa de masa cruda pre-fermentada en cámara para que cuando el horno esté listo, el pan entre en 30 segundos y no haya que esperar dos horas de fermentación.&lt;/p>
&lt;p>El autoscaling de un cluster de inferencia LLM funciona igual:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Encender hornos en frío&lt;/strong> = scale-out reactivo cuando la cola crece (lento, pierde clientes).&lt;/li>
&lt;li>&lt;strong>Cron proactivo&lt;/strong> = predictive scaling cuando el patrón es conocido (horario laboral, picos previstos).&lt;/li>
&lt;li>&lt;strong>Masa pre-fermentada&lt;/strong> = warm pool de réplicas con modelo cargado pero a 0 carga.&lt;/li>
&lt;li>&lt;strong>Apagar hornos sin pan en curso&lt;/strong> = scale-down respetando las streamings activas (no se cierra el horno con pan dentro).&lt;/li>
&lt;/ul>
&lt;p>La métrica clave —&amp;ldquo;cuántos clientes hay en cola&amp;rdquo;— se llama &lt;code>num_requests_waiting&lt;/code>. La métrica que dice &amp;ldquo;el horno se va a quedar sin masa para nuevos panes&amp;rdquo; se llama &lt;code>gpu_cache_usage_perc&lt;/code>. Y la métrica de calidad de servicio —&amp;ldquo;cuánto tarda el primer pan en salir cuando un cliente nuevo entra&amp;rdquo;— se llama TTFT.&lt;/p>
&lt;h2 id="por-qué-hpa-sobre-cpu-no-sirve">Por qué HPA sobre CPU no sirve&lt;/h2>
&lt;p>El HPA clásico de Kubernetes mira &lt;code>resource.cpu&lt;/code> del pod. Para un servicio HTTP convencional —Node.js, una API REST— la CPU se mueve linealmente con el tráfico y el HPA escala con razonable acierto. Para un pod vLLM o SGLang sobre GPU, la CPU del pod típicamente vive entre 5 % y 15 % independientemente de si la GPU está al 30 % o al 99 % de carga: el trabajo real lo hace el dispositivo, no el proceso. Resultado: el HPA basado en CPU &lt;strong>nunca&lt;/strong> dispara scale-out aunque la GPU esté reventando, y los clientes acumulan en la cola hasta que TTFT P95 cruza el SLO. El operador descubre el problema por la alerta de TTFT, no por el HPA.&lt;/p>
&lt;p>&lt;code>memory&lt;/code> tampoco sirve: la RSS del proceso vLLM es plana después del arranque (modelo + buffers cargados de una vez); no refleja la presión real sobre la GPU. Lo único que crece y baja con la carga útil de inferencia son métricas que el motor publica explícitamente: cola de requests, KV cache pool, latencias del SLO. Sin un adaptador que las haga visibles al HPA, el autoscaling es ciego.&lt;/p>
&lt;h2 id="las-cuatro-señales-viables">Las cuatro señales viables&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 320" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="cuatro señales de autoscaling LLM">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.p{fill:#dfe9f5;stroke:#356}.s{fill:#eef0d0;stroke:#7a3}.t{fill:#f4e3cf;stroke:#a63}.f{fill:#ead8f5;stroke:#634}.title{font:600 13px sans-serif;fill:#222}.h{font:700 12px sans-serif;fill:#222}.m{font:11px monospace;fill:#222}.n{font:italic 11px sans-serif;fill:#444}&lt;/style>
&lt;text x="410" y="20" text-anchor="middle" class="title">Las cuatro métricas que alimentan el HPA LLM&lt;/text>
&lt;rect x="30" y="40" width="370" height="120" class="b p"/>
&lt;text x="40" y="62" class="h">1 · COLA (PRIMARIA)&lt;/text>
&lt;text x="40" y="82" class="m">vllm:num_requests_waiting&lt;/text>
&lt;text x="40" y="105" class="n">¿Hay requests esperando entrar al batch?&lt;/text>
&lt;text x="40" y="125" class="n">Reacciona al instante. Robusta a cambios de modelo.&lt;/text>
&lt;text x="40" y="145" class="n">Umbral típico HPA: target = 5 (scale-out si &amp;gt; 5 sostenido).&lt;/text>
&lt;rect x="420" y="40" width="370" height="120" class="b s"/>
&lt;text x="430" y="62" class="h">2 · KV CACHE POOL&lt;/text>
&lt;text x="430" y="82" class="m">vllm:gpu_cache_usage_perc&lt;/text>
&lt;text x="430" y="105" class="n">¿Cuánta VRAM de KV cache se usa?&lt;/text>
&lt;text x="430" y="125" class="n">Predictiva: avisa antes de que la cola empiece.&lt;/text>
&lt;text x="430" y="145" class="n">Umbral típico: target = 0.85 (scale-out si &amp;gt; 0.85).&lt;/text>
&lt;rect x="30" y="170" width="370" height="120" class="b t"/>
&lt;text x="40" y="192" class="h">3 · TTFT P95 (SLO)&lt;/text>
&lt;text x="40" y="212" class="m">histogram_quantile(0.95,&lt;/text>
&lt;text x="40" y="226" class="m"> rate(vllm:time_to_first_token_seconds_bucket[5m]))&lt;/text>
&lt;text x="40" y="246" class="n">La garantía contractual al cliente.&lt;/text>
&lt;text x="40" y="266" class="n">Backup de las dos anteriores; reacciona tarde pero defiende SLO.&lt;/text>
&lt;rect x="420" y="170" width="370" height="120" class="b f"/>
&lt;text x="430" y="192" class="h">4 · BATCH FILL RATIO&lt;/text>
&lt;text x="430" y="212" class="m">vllm:num_requests_running&lt;/text>
&lt;text x="430" y="226" class="m"> / max_num_seqs (config)&lt;/text>
&lt;text x="430" y="246" class="n">Utilización del techo de concurrencia del motor.&lt;/text>
&lt;text x="430" y="266" class="n">Útil para scale-down: si ratio &amp;lt; 0.4 sostenido, sobra réplica.&lt;/text>
&lt;text x="410" y="310" text-anchor="middle" class="n">Política recomendada: cola como primaria, KV cache como secundaria, TTFT como guardrail&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>&lt;strong>Señal 1 — &lt;code>vllm:num_requests_waiting&lt;/code> (cola).&lt;/strong> Es la métrica más directa: cuántas requests esperan entrar al batch. Reacciona en el instante en que la concurrencia objetivo se satura. Es robusta frente a cambios de modelo (el número de requests es el mismo concepto sea Llama 7B o 70B). Es la &lt;strong>métrica primaria&lt;/strong> del HPA LLM. Umbral típico: &lt;code>target = 5&lt;/code> requests waiting de media; si la cola crece por encima de 5 sostenido durante 2 minutos, scale-out.&lt;/p>
&lt;p>&lt;strong>Señal 2 — &lt;code>vllm:gpu_cache_usage_perc&lt;/code> (KV pool).&lt;/strong> Se mueve &lt;strong>antes&lt;/strong> que la cola: el KV pool se va llenando mientras los slots del batch aún están libres, hasta que el motor empieza a rechazar nuevas requests por OOM-prevention y se forma la cola. Por tanto es &lt;strong>predictiva&lt;/strong>: dispara scale-out antes de que el cliente note degradación. Umbral típico: &lt;code>target = 0.85&lt;/code> (85 % de pool usado).&lt;/p>
&lt;p>&lt;strong>Señal 3 — TTFT P95.&lt;/strong> La garantía contractual. Si TTFT P95 sale del SLO, scale-out aunque cola y KV pool parezcan razonables (puede haber un pico de prompts largos). Es &lt;strong>reactiva&lt;/strong> —sale del SLO antes de que tu HPA reaccione— pero sirve de guardrail final.&lt;/p>
&lt;p>&lt;strong>Señal 4 — batch fill ratio.&lt;/strong> El cociente &lt;code>num_requests_running / max_num_seqs&lt;/code> (este último es config del motor, no métrica). Útil para &lt;strong>scale-down&lt;/strong>: si el ratio queda por debajo de 0.4 durante 10 minutos, sobra capacidad y se puede reducir réplicas con seguridad.&lt;/p>
&lt;p>La política recomendada combina las cuatro: la cola y el KV pool disparan scale-out (lo que llegue antes), TTFT lo confirma como guardrail, y el batch fill ratio gestiona scale-down. Implementarlo en un único HPA exige métricas externas; KEDA hace esto manejable.&lt;/p>
&lt;h2 id="el-cableado-keda-como-adaptador-prometheus">El cableado: KEDA como adaptador Prometheus&lt;/h2>
&lt;p>KEDA introduce dos CRDs principales: &lt;code>TriggerAuthentication&lt;/code> (cómo autenticarse contra la fuente) y &lt;code>ScaledObject&lt;/code> (qué deployment escalar con qué triggers). Para un deployment vLLM con Prometheus como fuente:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">keda.sh/v1alpha1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ScaledObject&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b-scaler&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">inference&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scaleTargetRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">minReplicaCount&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># warm pool&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">maxReplicaCount&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">20&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pollingInterval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">15&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cooldownPeriod&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">300&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 5 min antes de scale-down&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">advanced&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">horizontalPodAutoscalerConfig&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">behavior&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scaleDown&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stabilizationWindowSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># ventana grande para evitar oscilación&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">policies&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Pods&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">periodSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">120&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scaleUp&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stabilizationWindowSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">30&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">policies&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Pods&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">value&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">periodSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">60&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">triggers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">prometheus&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serverAddress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://prometheus.observability.svc:9090&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metricName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm_queue_depth&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">threshold&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;5&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">query&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> avg(vllm:num_requests_waiting{deployment=&amp;#34;vllm-llama70b&amp;#34;})&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">prometheus&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serverAddress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://prometheus.observability.svc:9090&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metricName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm_kv_cache&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">threshold&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.85&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">query&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> avg(vllm:gpu_cache_usage_perc{deployment=&amp;#34;vllm-llama70b&amp;#34;})&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">prometheus&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serverAddress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://prometheus.observability.svc:9090&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metricName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm_ttft_p95&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">threshold&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;1.5&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">query&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> histogram_quantile(0.95,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> sum by(le)(rate(vllm:time_to_first_token_seconds_bucket{deployment=&amp;#34;vllm-llama70b&amp;#34;}[5m])))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Tres detalles operativos no obvios:&lt;/p>
&lt;p>&lt;strong>&lt;code>minReplicaCount: 2&lt;/code>.&lt;/strong> Es el warm pool. Mantener al menos dos réplicas garantiza disponibilidad ante pérdida de un nodo y absorbe spikes sin esperar al cold start del primer escalado. Bajarlo a 0 ahorra GPU en off-peak pero introduce 90 s–6 min de latencia al primer cliente nuevo.&lt;/p>
&lt;p>&lt;strong>&lt;code>stabilizationWindowSeconds: 600&lt;/code> en scale-down.&lt;/strong> Diez minutos. Los modelos no son &lt;code>nginx&lt;/code>: si una réplica se cierra prematuramente y a los dos minutos hay otro pico, el cold start de un nuevo pod tarda lo que el cliente espera. Mejor mantener réplicas extra el doble de lo que mantendrías para un servicio web normal.&lt;/p>
&lt;p>&lt;strong>&lt;code>scaleUp: stabilizationWindowSeconds: 30&lt;/code>.&lt;/strong> Treinta segundos. El scale-out tiene que ser rápido — el cold start del nuevo pod añade su propio retraso, y si encima el HPA espera otros minutos antes de disparar, el SLO ya está roto.&lt;/p>
&lt;h2 id="el-gran-problema-operativo-cold-start">El gran problema operativo: cold start&lt;/h2>
&lt;p>Un pod vLLM cargando Llama 70B pasa por estas fases antes de servir el primer token:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Fase&lt;/th>
&lt;th>Tiempo típico&lt;/th>
&lt;th>Acelerable con&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Image pull (4–6 GB)&lt;/td>
&lt;td>30–90 s&lt;/td>
&lt;td>DaemonSet pre-pull&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Descarga del modelo (140 GB BF16)&lt;/td>
&lt;td>60–300 s&lt;/td>
&lt;td>PV regional cacheado, S3 + multi-thread&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Carga del modelo a HBM&lt;/td>
&lt;td>30–90 s&lt;/td>
&lt;td>tmpfs o NVMe local&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Capture de CUDA graphs&lt;/td>
&lt;td>20–60 s&lt;/td>
&lt;td>&lt;code>--enforce-eager&lt;/code> (más lento en runtime pero arranque rápido)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Warmup de PagedAttention&lt;/td>
&lt;td>5–15 s&lt;/td>
&lt;td>—&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Health check ready&lt;/td>
&lt;td>10–30 s&lt;/td>
&lt;td>tuning de probe&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Total sin optimización: 4–10 minutos.&lt;/strong> Una réplica nueva tarda eso en absorber tráfico. Con todas las palancas combinadas: &lt;strong>45–90 segundos&lt;/strong>. La diferencia entre los dos números es el principal trabajo de plataforma para autoscaling LLM.&lt;/p>
&lt;h3 id="las-cinco-palancas">Las cinco palancas&lt;/h3>
&lt;p>&lt;strong>Palanca 1 — imagen pre-pulled.&lt;/strong> Un DaemonSet trivial corre &lt;code>ctr image pull&lt;/code> (o &lt;code>crictl pull&lt;/code>) sobre los nodos GPU en cuanto se incorporan al cluster. La imagen del motor de inferencia queda en disco; los nuevos pods saltan los 30–90 s de pull. Coste: ~6 GB de disco por nodo.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">DaemonSet&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-image-warmer }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-warmer } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-warmer } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nodeSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">workload&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gpu }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">initContainers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pull&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm/vllm-openai:v0.10.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;/bin/true&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pause&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">registry.k8s.io/pause:3.10&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Palanca 2 — modelo en PV regional.&lt;/strong> El download del modelo (140 GB BF16 o 35 GB FP8) desde object storage central es el componente dominante del cold start. Cachear el modelo en un &lt;strong>PV de zona/rack&lt;/strong> —Rook-Ceph RBD, o NVMe local provisionado por el operador— recorta 60–300 s a 5–15 s. El antipatrón: descargar el modelo en cada arranque desde S3 externo.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">volumeMounts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">model-cache&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mountPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/models&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readOnly&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">model-cache&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">persistentVolumeClaim&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">claimName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama70b-fp8-pvc &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># RWX shared, llenado offline&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Palanca 3 — warm pool.&lt;/strong> &lt;code>minReplicaCount &amp;gt; 0&lt;/code> mantiene réplicas pre-cargadas en idle. El coste es GPU ociosa; el beneficio es 0 s de cold start para el primer cliente de un pico. Para clusters productivos con tráfico continuo: warm pool de 2–3 réplicas. Para clusters batch nocturnos con tráfico 0: warm pool 0 y aceptar el cold start, o KEDA con cron que pre-encienda 10 minutos antes.&lt;/p>
&lt;p>&lt;strong>Palanca 4 — predictive scaling con cron.&lt;/strong> Cuando el patrón es predecible (oficinas 9–18 h):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">triggers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cron&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timezone&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Europe/Madrid&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">start&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;30 8 * * 1-5&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 8:30 lunes–viernes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">end&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0 19 * * 1-5&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 19:00&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">desiredReplicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;6&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Combinado con triggers reactivos. El HPA escala según el máximo de las señales: si la cron pide 6 y la cola pide 10, el resultado es 10.&lt;/p>
&lt;p>&lt;strong>Palanca 5 — descarga paralela y formato eficiente.&lt;/strong> Para PVs no pre-cargados, herramientas como &lt;code>nvidia-modelmanager&lt;/code>, &lt;code>s5cmd&lt;/code> o &lt;code>aria2c&lt;/code> paralelizan la descarga del modelo. Pasar de descarga serial (~150 MB/s) a paralela 8 threads (~1.2 GB/s) divide entre 8 el tiempo. Y formatos como &lt;strong>safetensors&lt;/strong> se cargan en HBM más rápido que &lt;strong>PyTorch pickle&lt;/strong> original.&lt;/p>
&lt;h2 id="cuándo-escalar-nodos-no-solo-pods">Cuándo escalar nodos, no solo pods&lt;/h2>
&lt;p>El HPA escala &lt;strong>pods&lt;/strong>. Si el cluster no tiene nodos GPU libres, el nuevo pod se queda en &lt;code>Pending&lt;/code> por falta de recursos. Para escalar &lt;strong>nodos&lt;/strong>, hace falta cluster-autoscaler con un nodepool GPU específico, etiquetado:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># nodepool config (Karpenter o cluster-autoscaler equivalent)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">workload&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gpu&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">gpu-model&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">h100-sxm-80gb&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">taints&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nvidia.com/gpu&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">effect&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">NoSchedule&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">min&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nodes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">max&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nodes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Sin esto, el HPA puede pedir 10 réplicas pero el cluster solo entrega las que caben en nodos ya levantados. El cold start de un nodo nuevo (provisioning bare metal o cloud, PXE, OS boot, drivers NVIDIA, join del cluster) es &lt;strong>mucho&lt;/strong> mayor que el cold start de un pod: típicamente 5–15 minutos en bare metal preconfigurado, 30–60 minutos en provisioning real. Para clusters on-premise, el nodepool debe estar &lt;strong>siempre dimensionado al máximo previsto&lt;/strong>, y el &amp;ldquo;scaling&amp;rdquo; es solo del lado de pods. El concepto de scale-out reactivo de nodos solo aplica a clouds; en on-premise hay que comprar para el pico.&lt;/p>
&lt;h2 id="tres-pitfalls-específicos-del-scale-down-llm">Tres pitfalls específicos del scale-down LLM&lt;/h2>
&lt;p>&lt;strong>Pitfall 1 — cortar conexiones SSE de streaming.&lt;/strong> Cuando una réplica entra en &lt;code>Terminating&lt;/code>, Kubernetes envía SIGTERM al pod y, por defecto, lo mata 30 segundos después. Para vLLM eso significa &lt;strong>cortar conexiones SSE de streaming a la mitad de la respuesta&lt;/strong>. El cliente recibe un error 502 con el output parcial perdido. Solución: &lt;code>terminationGracePeriodSeconds: 120&lt;/code> + un preStop hook que avise al motor de no aceptar nuevas requests pero terminar las en curso:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">terminationGracePeriodSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">120&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">lifecycle&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">preStop&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">httpGet&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">path&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/shutdown&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Esto requiere que el motor exponga un endpoint de shutdown elegante; vLLM v1 lo soporta vía &lt;code>--enable-graceful-shutdown&lt;/code>. Sin esto, el scale-down rompe SLO aunque las métricas no lo capturen (las requests cortadas no entran al histograma de TTFT).&lt;/p>
&lt;p>&lt;strong>Pitfall 2 — oscilación scale-up/scale-down.&lt;/strong> Si la &lt;code>stabilizationWindowSeconds&lt;/code> del scale-down es corta (~60 s default), la siguiente bajada de cola dispara scale-down, y dos minutos después el siguiente pico dispara scale-up. El sistema oscila, paga cold starts repetidos, y nunca alcanza un régimen estable. Solución: scale-down con ventana de 10 minutos como mínimo y políticas conservadoras (&lt;code>type: Pods, value: 1, periodSeconds: 120&lt;/code> — máximo una réplica menos cada 2 minutos).&lt;/p>
&lt;p>&lt;strong>Pitfall 3 — &lt;code>vllm:num_requests_waiting&lt;/code> con &lt;code>avg&lt;/code> cuando hay rebalanceo.&lt;/strong> Si dos réplicas están desbalanceadas (una con cola 20, otra con cola 0), &lt;code>avg&lt;/code> da 10 — el HPA dispara scale-out cuando lo correcto sería rebalancear vía el load balancer. Para detectarlo: añadir una alerta sobre &lt;code>stddev(vllm:num_requests_waiting)&lt;/code> por deployment. Si la dispersión es alta, el problema no es de capacidad sino de routing.&lt;/p>
&lt;h2 id="manifest-completo-de-ejemplo">Manifest completo de ejemplo&lt;/h2>
&lt;p>Para un deployment vLLM con Llama 70B FP8 en 4×H100 SXM por réplica, KEDA con warm pool 2:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apps/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">inference&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">replicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># gestionado por KEDA después&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app: vllm-llama70b, deployment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">terminationGracePeriodSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">120&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nodeSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">workload: gpu, gpu-model&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">h100-sxm-80gb }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tolerations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nvidia.com/gpu&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">operator&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Exists&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">effect&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">NoSchedule&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm/vllm-openai:v0.10.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">model=/models/llama-3.3-70b-fp8&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">tensor-parallel-size=4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">max-num-seqs=64&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">enable-prefix-caching&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- --&lt;span class="l">enable-graceful-shutdown&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">name: http, containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8000&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">name: metrics, containerPort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8000&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nvidia.com/gpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;4&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">200Gi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readinessProbe&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">httpGet&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">path: /health, port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8000&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">initialDelaySeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">60&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">periodSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">10&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">failureThreshold&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">30&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># tolera el warmup&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">lifecycle&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">preStop&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">httpGet&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">path: /shutdown, port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8000&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeMounts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">name: model-cache, mountPath: /models, readOnly&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">name: dshm, mountPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/dev/shm }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">model-cache&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">persistentVolumeClaim&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">claimName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama70b-fp8-pvc }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dshm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">emptyDir&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">medium: Memory, sizeLimit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">16Gi }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">monitoring.coreos.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PodMonitor&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: vllm-llama70b-metrics, namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">inference }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b } }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">podMetricsEndpoints&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metrics&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">path&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/metrics&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">15s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">keda.sh/v1alpha1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ScaledObject&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name: vllm-llama70b-scaler, namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">inference }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scaleTargetRef&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-llama70b }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">minReplicaCount&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">maxReplicaCount&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">20&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pollingInterval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">15&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cooldownPeriod&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">300&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">advanced&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">horizontalPodAutoscalerConfig&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">behavior&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scaleDown&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stabilizationWindowSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">600&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">policies&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">type: Pods, value: 1, periodSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">120&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scaleUp&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stabilizationWindowSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">30&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">policies&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">type: Pods, value: 2, periodSeconds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">60&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">triggers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">prometheus&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serverAddress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://prometheus.observability.svc:9090&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metricName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm_queue&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">threshold&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;5&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">query&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">avg(vllm:num_requests_waiting{deployment=&amp;#34;vllm-llama70b&amp;#34;})&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">prometheus&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serverAddress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://prometheus.observability.svc:9090&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metricName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm_kv&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">threshold&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.85&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">query&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">avg(vllm:gpu_cache_usage_perc{deployment=&amp;#34;vllm-llama70b&amp;#34;})&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cron&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timezone&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Europe/Madrid&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">start&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;30 8 * * 1-5&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">end&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0 19 * * 1-5&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">desiredReplicas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;6&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Este conjunto es el mínimo viable para autoscaling LLM en cluster genérico con NVIDIA GPU Operator. Cada equipo lo adapta a su SLO concreto.&lt;/p>
&lt;h2 id="aplicado-a-hardware-on-premise-típico">Aplicado a hardware on-premise típico&lt;/h2>
&lt;p>Para un cluster genérico de &lt;strong>4×H100 SXM 80 GB por nodo, 4 nodos GPU&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>Cada nodo aloja una réplica vLLM TP=4 con Llama 70B FP8 (un modelo por nodo, no se comparten).&lt;/li>
&lt;li>Warm pool de 2 réplicas en off-peak; KEDA cron eleva a 4 en horario laboral.&lt;/li>
&lt;li>Cluster-autoscaler &lt;strong>no aplica&lt;/strong> (4 nodos físicos comprados; el escalado es solo de pods). El número de réplicas concurrentes es como máximo el número de nodos disponibles (si cada réplica usa los 4 GPUs del nodo entero).&lt;/li>
&lt;li>Si el dimensionamiento requiere más réplicas simultáneas que nodos, hay dos vías: (a) bajar el TP de cada réplica para que entren dos por nodo, (b) ampliar el nodepool físico. La decisión la dicta el capacity planning —ver &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning para inferencia LLM on-premise&lt;/a>—.&lt;/li>
&lt;/ul>
&lt;p>Volumen de eventos KEDA: ~5 evaluations/min por ScaledObject. Para 10 modelos servidos en paralelo, 3 000 evaluations/h. Manejable con un KEDA operator por cluster.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-artículos">Lo que no hemos cubierto (próximos artículos)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Cluster-autoscaler para nodos GPU on-premise&lt;/strong>: cómo orquestar provisioning bare metal (Tinkerbell, Metal³) en función de demanda.&lt;/li>
&lt;li>&lt;strong>Multi-cluster autoscaling&lt;/strong>: escalar entre clusters de DCs distintos para resiliencia geográfica.&lt;/li>
&lt;li>&lt;strong>Cost-aware autoscaling&lt;/strong>: priorizar nodos según coste energético horario (en clusters con tarifa indexada).&lt;/li>
&lt;li>&lt;strong>Predictive ML-based scaling&lt;/strong>: en lugar de cron estático, entrenar un modelo que prediga demanda con 30 minutos de antelación.&lt;/li>
&lt;li>&lt;strong>Quotas y fairness multi-tenant&lt;/strong>: KEDA con namespace quotas para que un tenant no acapare el HPA.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a> — fuente de las métricas que alimentan al HPA.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning para inferencia LLM on-premise&lt;/a> — qué techo y qué head-room presupone el autoscaler.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">Continuous batching&lt;/a> — explica &lt;code>num_requests_running&lt;/code>, &lt;code>num_requests_waiting&lt;/code> y &lt;code>gpu_cache_usage_perc&lt;/code>.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache&lt;/a> — domina el KV pool y por tanto los thresholds.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">Cinco niveles de madurez&lt;/a> — KEDA es pieza del nivel 4.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">Canary, blue-green y shadow&lt;/a> — el autoscaler convive con la estrategia de despliegue.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/router-inferencia-llm-gateway-l7/">El router de inferencia LLM&lt;/a> — el router consume &lt;code>vllm:num_requests_running&lt;/code> y &lt;code>vllm:gpu_cache_usage_perc&lt;/code> (mismas métricas que el autoscaler) para decidir réplica con token-aware LB y prefix-aware routing; los dos componentes comparten cabina pero deciden cosas distintas.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/runbooks-incident-response-llm-keep-kafka/">Runbooks de incident response para LLM con Keep + Kafka&lt;/a> — los runbooks RB-01 (&lt;code>GpuHbmNearOom&lt;/code>) y RB-05 (&lt;code>VllmKvCachePoolNearFull&lt;/code>) usan el autoscaler como palanca de mitigación inmediata.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>KEDA project — &lt;code>keda.sh&lt;/code> (documentación oficial de triggers Prometheus y cron).&lt;/li>
&lt;li>Kubernetes — &lt;em>Horizontal Pod Autoscaler walkthrough&lt;/em> (kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale).&lt;/li>
&lt;li>NVIDIA — &lt;em>GPU Operator on Kubernetes&lt;/em> (Helm chart oficial con DaemonSet de drivers y DCGM).&lt;/li>
&lt;li>vLLM project — &lt;code>production_monitoring/&lt;/code> (métricas Prometheus expuestas por el servidor).&lt;/li>
&lt;li>Karpenter — &lt;em>NodePool spec&lt;/em> (etiquetado y taints para nodepools GPU).&lt;/li>
&lt;li>Cluster Autoscaler — &lt;em>Scaling GPU nodes&lt;/em> (caveats de descubrimiento de recursos GPU).&lt;/li>
&lt;li>Kubernetes — &lt;em>Pod lifecycle and termination&lt;/em> (preStop, terminationGracePeriodSeconds).&lt;/li>
&lt;/ul></description></item><item><title>Observabilidad GPU para inferencia LLM: las doce métricas DCGM y vLLM que dictan la salud de tu producción</title><link>https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/</link><pubDate>Mon, 01 Jun 2026 15:30:00 +0200</pubDate><guid>https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/</guid><description>&lt;blockquote>
&lt;p>Este post complementa los de &lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a> (la capa de tracing por encima de las métricas), &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning&lt;/a> (qué se dimensionó y qué se debe vigilar) y &lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">Continuous batching&lt;/a> (el mecanismo que explica varias de las métricas del motor).&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>La observabilidad de un cluster de inferencia LLM se construye sobre &lt;strong>dos fuentes complementarias&lt;/strong>: las métricas del hardware GPU expuestas por &lt;strong>DCGM (Data Center GPU Manager) Exporter&lt;/strong> —parte del NVIDIA GPU Operator— y las métricas del &lt;strong>motor de inferencia&lt;/strong> (vLLM, SGLang, TensorRT-LLM) expuestas en &lt;code>/metrics&lt;/code> Prometheus-compatibles. Ninguna de las dos basta sola. La métrica clásica de &lt;code>nvidia-smi&lt;/code> llamada &lt;em>GPU utilization&lt;/em> es engañosa para LLMs: marca alto cuando hay &lt;strong>cualquier kernel&lt;/strong> ejecutándose, sin distinguir tensor cores ardiendo de SMs esperando por HBM. La cabina de pilotaje completa tiene &lt;strong>doce métricas DCGM en cuatro familias&lt;/strong> (compute: &lt;code>DCGM_FI_PROF_SM_OCCUPANCY&lt;/code>, &lt;code>DCGM_FI_PROF_PIPE_TENSOR_ACTIVE&lt;/code>, &lt;code>DCGM_FI_PROF_DRAM_ACTIVE&lt;/code>; memoria: &lt;code>DCGM_FI_DEV_FB_USED&lt;/code>, &lt;code>DCGM_FI_DEV_FB_FREE&lt;/code>, &lt;code>DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL&lt;/code>; térmico-energético: &lt;code>DCGM_FI_DEV_GPU_TEMP&lt;/code>, &lt;code>DCGM_FI_DEV_POWER_USAGE&lt;/code>, &lt;code>DCGM_FI_DEV_CLOCK_THROTTLE_REASONS&lt;/code>; salud: &lt;code>DCGM_FI_DEV_XID_ERRORS&lt;/code>, &lt;code>DCGM_FI_DEV_ECC_DBE_VOL_TOTAL&lt;/code>, &lt;code>DCGM_FI_DEV_RETIRED_DBE&lt;/code>) y &lt;strong>cinco métricas del motor vLLM&lt;/strong> (&lt;code>vllm:num_requests_running&lt;/code>, &lt;code>vllm:num_requests_waiting&lt;/code>, &lt;code>vllm:gpu_cache_usage_perc&lt;/code>, &lt;code>vllm:time_to_first_token_seconds&lt;/code>, &lt;code>vllm:time_per_output_token_seconds&lt;/code>). 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 &lt;strong>seis alertas críticas&lt;/strong> que cualquier cluster productivo debe disparar son: HBM &amp;gt; 92 %, throttle por térmico o por power, XID error, ECC double-bit, KV cache pool &amp;gt; 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 &lt;strong>menos de cinco minutos&lt;/strong>, 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.&lt;/p>
&lt;h2 id="estás-aquí-observe-la-otra-mitad-del-tracing">Estás aquí: OBSERVE (la otra mitad del tracing)&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="estás aquí: Observe">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#c9a8e9;stroke-width:3}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#obm)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#obm)}&lt;/style>
&lt;defs>&lt;marker id="obm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">Estás aquí: OBSERVE · métricas (DCGM + motor) complementan al tracing&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box idle"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box active"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box idle"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;/svg>
&lt;/div>
&lt;p>El tracing —ya cubierto en &lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a>— responde &lt;em>qué pasó en esta request concreta&lt;/em>. Las métricas responden &lt;em>qué está pasando en el cluster en agregado&lt;/em>. Son complementarias: una alerta del lado de métricas te dice &amp;ldquo;el clúster está degradando&amp;rdquo;, el tracing te dice &amp;ldquo;y esta es la traza concreta que te lo demuestra&amp;rdquo;. Un cluster sin tracing pero con métricas opera; un cluster sin métricas pero con tracing &lt;strong>no opera, debuggea&lt;/strong>.&lt;/p>
&lt;h2 id="la-analogía-la-cabina-de-un-avión-moderno">La analogía: la cabina de un avión moderno&lt;/h2>
&lt;p>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 &lt;strong>y&lt;/strong> el indicador de actitud, &lt;strong>y&lt;/strong> el de velocidad, &lt;strong>y&lt;/strong> el de viraje, &lt;strong>y&lt;/strong> el de combustible, &lt;strong>y&lt;/strong> los de presión de aceite de cada motor, &lt;strong>y&lt;/strong> las temperaturas de salida de turbina. Cada uno responde una pregunta distinta. Y todos juntos cubren la pregunta operacional: &lt;em>¿está el avión sano, está donde debe, y va donde queremos?&lt;/em>&lt;/p>
&lt;p>La observabilidad de un cluster de inferencia LLM funciona igual. Una sola métrica —&amp;ldquo;GPU utilization 99 %&amp;quot;— 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 &lt;strong>doce instrumentos del lado de hardware más cinco del lado del motor de inferencia&lt;/strong>, organizados en familias que responden preguntas distintas:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Compute y eficiencia&lt;/strong>: &lt;em>¿están los tensor cores haciendo el trabajo que esperamos o están esperando?&lt;/em>&lt;/li>
&lt;li>&lt;strong>Memoria&lt;/strong>: &lt;em>¿queda VRAM para nuevas requests o estamos al borde del OOM?&lt;/em>&lt;/li>
&lt;li>&lt;strong>Térmico y energético&lt;/strong>: &lt;em>¿el hardware está sano o está limitando el throughput silenciosamente?&lt;/em>&lt;/li>
&lt;li>&lt;strong>Salud y errores&lt;/strong>: &lt;em>¿hay degradación del hardware en curso (ECC, XID, NVLink)?&lt;/em>&lt;/li>
&lt;li>&lt;strong>Motor de inferencia&lt;/strong>: &lt;em>¿la cola crece, el KV pool está saturado, el SLO se está cumpliendo?&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>Las cuatro primeras responden a &amp;ldquo;¿la GPU está bien?&amp;rdquo;. La quinta responde a &amp;ldquo;¿está dando el servicio que prometimos?&amp;rdquo;. Las dos preguntas son distintas y ambas deben tener respuesta a un golpe de vista.&lt;/p>
&lt;h2 id="por-qué-nvidia-smi-gpu-util-engaña-en-llms">Por qué &lt;code>nvidia-smi&lt;/code> &lt;code>GPU-Util&lt;/code> engaña en LLMs&lt;/h2>
&lt;p>La métrica clásica que aparece en &lt;code>nvidia-smi&lt;/code> como &lt;code>GPU-Util&lt;/code> corresponde a &lt;code>DCGM_FI_DEV_GPU_UTIL&lt;/code>. Su definición oficial: &amp;ldquo;porcentaje del tiempo durante el cual uno o más kernels estuvieron ejecutándose en la GPU&amp;rdquo;. El problema en LLMs: la fase de decode es &lt;strong>memory-bound&lt;/strong>, 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 &lt;code>GPU-Util&lt;/code> reporta valores cercanos al 100 %. Pero los tensor cores están parados — el cuello de botella es la memoria, no el compute.&lt;/p>
&lt;p>Resultado práctico: el operador ve &amp;ldquo;GPU-Util 99 %&amp;rdquo; en Grafana y asume &amp;ldquo;GPU saturada, no se puede meter más carga&amp;rdquo;. Pero la realidad puede ser &amp;ldquo;compute al 25 %, HBM saturada al 95 %&amp;rdquo;, lo que cambia las decisiones operativas (quantization, batch size, paralelismo). La métrica clásica miente por simplificación.&lt;/p>
&lt;p>Lo correcto es mirar las &lt;strong>tres métricas de profiling DCGM&lt;/strong> del subsistema &lt;code>_FI_PROF_*&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>DCGM_FI_PROF_SM_OCCUPANCY&lt;/code> — ratio de warps activos sobre máximos por SM. &lt;em>¿Hay trabajo paralelo?&lt;/em>&lt;/li>
&lt;li>&lt;code>DCGM_FI_PROF_PIPE_TENSOR_ACTIVE&lt;/code> — % de ciclos con tensor cores efectivamente activos. &lt;em>¿Está el compute trabajando?&lt;/em>&lt;/li>
&lt;li>&lt;code>DCGM_FI_PROF_DRAM_ACTIVE&lt;/code> — % de ciclos con la HBM transfiriendo. &lt;em>¿Está la memoria saturada?&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>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 &amp;ldquo;GPU saturada&amp;rdquo; real para LLMs. Las tres juntas distinguen los regímenes; cada una sola no dice nada accionable.&lt;/p>
&lt;h2 id="cómo-se-montan-en-producción">Cómo se montan en producción&lt;/h2>
&lt;p>La parte de plataforma se cubre en &lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">Cinco niveles de madurez&lt;/a> (nivel 4 — GPU plane) y &lt;a href="https://blog.lo0.es/posts/siete-fases-despliegue-plataforma-llm-on-premise/">Siete fases de despliegue&lt;/a> (fase F5). Para el observador, las piezas clave son:&lt;/p>
&lt;p>&lt;strong>NVIDIA GPU Operator.&lt;/strong> Manifiestos Helm que despliegan en cada nodo GPU: drivers, container toolkit, MIG manager y &lt;strong>DCGM Exporter&lt;/strong>. Este último expone &lt;code>/metrics&lt;/code> en formato Prometheus con todos los &lt;code>DCGM_FI_*&lt;/code> listados arriba. Se scrapea desde el Prometheus interno del cluster.&lt;/p>
&lt;p>&lt;strong>Motor de inferencia.&lt;/strong> vLLM expone &lt;code>/metrics&lt;/code> en el puerto 8000 (default) con métricas &lt;code>vllm:*&lt;/code>. SGLang lo expone también con prefijo &lt;code>sglang:&lt;/code>. TensorRT-LLM lo expone vía Triton Inference Server con prefijo &lt;code>nv_inference:&lt;/code>. 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.&lt;/p>
&lt;p>&lt;strong>ServiceMonitor / PodMonitor.&lt;/strong> Recurso del operador de Prometheus que indica qué scrapear. Ejemplo mínimo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">monitoring.coreos.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PodMonitor&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-inference&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">podMetricsEndpoints&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metrics&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">15s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Dashboards.&lt;/strong> El operador de NVIDIA publica dashboards Grafana de referencia para DCGM en &lt;code>nvidia/dcgm-exporter&lt;/code> (repo oficial). vLLM publica uno en &lt;code>vllm-project/vllm&lt;/code> (carpeta &lt;code>examples/&lt;/code>). Ambos sirven como base; cada equipo añade los paneles propios de su SLO.&lt;/p>
&lt;h2 id="las-doce-métricas-dcgm-organizadas-por-familia">Las doce métricas DCGM organizadas por familia&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="doce métricas DCGM en cuatro familias">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.fc{fill:#dfe9f5;stroke:#356}.fm{fill:#eef0d0;stroke:#7a3}.ft{fill:#f4e3cf;stroke:#a63}.fs{fill:#f6e2e2;stroke:#a33}.title{font:600 13px sans-serif;fill:#222}.fam{font:700 11px sans-serif;fill:#222}.met{font:10px monospace;fill:#222}.note{font:italic 10px sans-serif;fill:#555}&lt;/style>
&lt;text x="410" y="20" text-anchor="middle" class="title">Cabina DCGM: 12 métricas en 4 familias&lt;/text>
&lt;rect x="20" y="40" width="195" height="290" class="b fc"/>
&lt;text x="117" y="60" text-anchor="middle" class="fam">COMPUTE&lt;/text>
&lt;text x="30" y="90" class="met">DCGM_FI_PROF_&lt;/text>&lt;text x="30" y="105" class="met">SM_OCCUPANCY&lt;/text>
&lt;text x="30" y="135" class="met">DCGM_FI_PROF_&lt;/text>&lt;text x="30" y="150" class="met">PIPE_TENSOR_ACTIVE&lt;/text>
&lt;text x="30" y="180" class="met">DCGM_FI_PROF_&lt;/text>&lt;text x="30" y="195" class="met">DRAM_ACTIVE&lt;/text>
&lt;text x="30" y="240" text-anchor="start" class="note">¿Compute trabaja o&lt;/text>
&lt;text x="30" y="254" text-anchor="start" class="note">espera por HBM?&lt;/text>
&lt;rect x="220" y="40" width="195" height="290" class="b fm"/>
&lt;text x="317" y="60" text-anchor="middle" class="fam">MEMORIA&lt;/text>
&lt;text x="230" y="90" class="met">DCGM_FI_DEV_&lt;/text>&lt;text x="230" y="105" class="met">FB_USED&lt;/text>
&lt;text x="230" y="135" class="met">DCGM_FI_DEV_&lt;/text>&lt;text x="230" y="150" class="met">FB_FREE&lt;/text>
&lt;text x="230" y="180" class="met">DCGM_FI_DEV_NVLINK_&lt;/text>&lt;text x="230" y="195" class="met">BANDWIDTH_TOTAL&lt;/text>
&lt;text x="230" y="240" class="note">¿Queda VRAM para&lt;/text>
&lt;text x="230" y="254" class="note">nuevas requests?&lt;/text>
&lt;rect x="420" y="40" width="195" height="290" class="b ft"/>
&lt;text x="517" y="60" text-anchor="middle" class="fam">TÉRMICO · ENERGÉTICO&lt;/text>
&lt;text x="430" y="90" class="met">DCGM_FI_DEV_&lt;/text>&lt;text x="430" y="105" class="met">GPU_TEMP&lt;/text>
&lt;text x="430" y="135" class="met">DCGM_FI_DEV_&lt;/text>&lt;text x="430" y="150" class="met">POWER_USAGE&lt;/text>
&lt;text x="430" y="180" class="met">DCGM_FI_DEV_CLOCK_&lt;/text>&lt;text x="430" y="195" class="met">THROTTLE_REASONS&lt;/text>
&lt;text x="430" y="240" class="note">¿Hardware sano o&lt;/text>
&lt;text x="430" y="254" class="note">limitando silenciosamente?&lt;/text>
&lt;rect x="620" y="40" width="180" height="290" class="b fs"/>
&lt;text x="710" y="60" text-anchor="middle" class="fam">SALUD&lt;/text>
&lt;text x="630" y="90" class="met">DCGM_FI_DEV_&lt;/text>&lt;text x="630" y="105" class="met">XID_ERRORS&lt;/text>
&lt;text x="630" y="135" class="met">DCGM_FI_DEV_&lt;/text>&lt;text x="630" y="150" class="met">ECC_DBE_VOL_TOTAL&lt;/text>
&lt;text x="630" y="180" class="met">DCGM_FI_DEV_&lt;/text>&lt;text x="630" y="195" class="met">RETIRED_DBE&lt;/text>
&lt;text x="630" y="240" class="note">¿Hay degradación&lt;/text>
&lt;text x="630" y="254" class="note">del silicio en curso?&lt;/text>
&lt;text x="410" y="350" text-anchor="middle" class="note">Cada familia responde una pregunta distinta · ninguna basta sola&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h3 id="familia-1--compute">Familia 1 — Compute&lt;/h3>
&lt;p>&lt;strong>&lt;code>DCGM_FI_PROF_SM_OCCUPANCY&lt;/code>&lt;/strong> — Ratio de warps activos por SM sobre el máximo posible. Valor entre 0 y 1.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Verde&lt;/strong>: 0.30–0.70 (régimen típico LLM en decode).&lt;/li>
&lt;li>&lt;strong>Ámbar&lt;/strong>: &amp;lt; 0.20 sostenido (batch demasiado pequeño, GPU infrautilizada en paralelismo).&lt;/li>
&lt;li>&lt;strong>Rojo&lt;/strong>: 0.95 sostenido con DRAM_ACTIVE bajo (kernel patológico saturando SMs).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>&lt;code>DCGM_FI_PROF_PIPE_TENSOR_ACTIVE&lt;/code>&lt;/strong> — % de ciclos con tensor cores ejecutando. La métrica clave de &amp;ldquo;¿el compute está produciendo?&amp;rdquo;.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Verde en prefill&lt;/strong>: 50–80 %.&lt;/li>
&lt;li>&lt;strong>Verde en decode&lt;/strong>: 15–30 % (decode es memory-bound, no es síntoma de problema).&lt;/li>
&lt;li>&lt;strong>Rojo&lt;/strong>: &amp;lt; 5 % sostenido en prefill o el motor no usa los tensor cores (mala config, formato incompatible).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>&lt;code>DCGM_FI_PROF_DRAM_ACTIVE&lt;/code>&lt;/strong> — % de ciclos con HBM transfiriendo datos. Métrica clave para detectar saturación de memoria.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Verde en decode&lt;/strong>: 60–85 %.&lt;/li>
&lt;li>&lt;strong>Ámbar&lt;/strong>: &amp;gt; 90 % sostenido (HBM cuello de botella firme — explica la TPOT alta).&lt;/li>
&lt;li>&lt;strong>Rojo&lt;/strong>: &amp;gt; 95 % sostenido con KV cache pool &amp;lt; 70 % (algo está pidiendo HBM que no es el motor; investigar leaks).&lt;/li>
&lt;/ul>
&lt;h3 id="familia-2--memoria">Familia 2 — Memoria&lt;/h3>
&lt;p>&lt;strong>&lt;code>DCGM_FI_DEV_FB_USED&lt;/code>&lt;/strong> — Frame Buffer (HBM) usado en MiB.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Verde&lt;/strong>: 70–85 % del total.&lt;/li>
&lt;li>&lt;strong>Ámbar&lt;/strong>: 86–92 %.&lt;/li>
&lt;li>&lt;strong>Rojo&lt;/strong>: &amp;gt; 92 % (riesgo de OOM en el siguiente paged-attention allocation).&lt;/li>
&lt;/ul>
&lt;p>PromQL para porcentaje sobre cluster: &lt;code>100 * sum(DCGM_FI_DEV_FB_USED) / sum(DCGM_FI_DEV_FB_TOTAL)&lt;/code>.&lt;/p>
&lt;p>&lt;strong>&lt;code>DCGM_FI_DEV_FB_FREE&lt;/code>&lt;/strong> — Frame Buffer libre. Complementaria de la anterior; útil para alertas absolutas (&lt;code>&amp;lt; 4096 MiB libres&lt;/code>).&lt;/p>
&lt;p>&lt;strong>&lt;code>DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL&lt;/code>&lt;/strong> — 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.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Verde&lt;/strong>: 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.&lt;/li>
&lt;li>&lt;strong>Rojo&lt;/strong>: &amp;gt; 90 % capacidad sostenido (revisar si el modelo cabría con TP menor o pipeline parallel).&lt;/li>
&lt;/ul>
&lt;h3 id="familia-3--térmico-y-energético">Familia 3 — Térmico y energético&lt;/h3>
&lt;p>&lt;strong>&lt;code>DCGM_FI_DEV_GPU_TEMP&lt;/code>&lt;/strong> — Temperatura del die en °C.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Verde&lt;/strong>: &amp;lt; 75 °C.&lt;/li>
&lt;li>&lt;strong>Ámbar&lt;/strong>: 75–82 °C.&lt;/li>
&lt;li>&lt;strong>Rojo&lt;/strong>: &amp;gt; 83 °C (cerca del thermal throttle automático de H100; revisar ventilación, caudal de aire, temperatura de entrada al rack).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>&lt;code>DCGM_FI_DEV_POWER_USAGE&lt;/code>&lt;/strong> — 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.&lt;/p>
&lt;p>&lt;strong>&lt;code>DCGM_FI_DEV_CLOCK_THROTTLE_REASONS&lt;/code>&lt;/strong> — Bitmap codificado con las razones de throttle activas. Es la métrica que &lt;strong>silenciosamente explica&lt;/strong> las degradaciones de TPOT.&lt;/p>
&lt;p>Bits relevantes:&lt;/p>
&lt;ul>
&lt;li>&lt;code>0x0000000000000001&lt;/code> — Idle (no es problema).&lt;/li>
&lt;li>&lt;code>0x0000000000000002&lt;/code> — App clocks setting.&lt;/li>
&lt;li>&lt;code>0x0000000000000004&lt;/code> — SW Power Cap (límite de software, p. ej. por &lt;code>nvidia-smi -pl&lt;/code>).&lt;/li>
&lt;li>&lt;code>0x0000000000000008&lt;/code> — HW Slowdown.&lt;/li>
&lt;li>&lt;code>0x0000000000000010&lt;/code> — Sync Boost (NVIDIA Sync).&lt;/li>
&lt;li>&lt;code>0x0000000000000020&lt;/code> — SW Thermal Slowdown (límite térmico de software).&lt;/li>
&lt;li>&lt;code>0x0000000000000040&lt;/code> — HW Thermal Slowdown (límite térmico de hardware — emergencia).&lt;/li>
&lt;li>&lt;code>0x0000000000000080&lt;/code> — HW Power Brake Slowdown (caída de tensión PSU).&lt;/li>
&lt;li>&lt;code>0x0000000000000100&lt;/code> — Display Clock Setting.&lt;/li>
&lt;/ul>
&lt;p>Cualquier throttle salvo &lt;code>Idle&lt;/code> con valor &amp;gt; 0 sostenido &lt;strong>es alerta&lt;/strong>. La degradación de TPOT con &lt;code>DRAM_ACTIVE&lt;/code> ya alto y throttle térmico activo es el clásico &amp;ldquo;el rack está mal ventilado, no es problema del motor&amp;rdquo;.&lt;/p>
&lt;h3 id="familia-4--salud">Familia 4 — Salud&lt;/h3>
&lt;p>&lt;strong>&lt;code>DCGM_FI_DEV_XID_ERRORS&lt;/code>&lt;/strong> — 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.). &lt;strong>Cualquier incremento es alerta inmediata&lt;/strong>: muchos XID requieren reset del nodo o RMA de la GPU.&lt;/p>
&lt;p>&lt;strong>&lt;code>DCGM_FI_DEV_ECC_DBE_VOL_TOTAL&lt;/code>&lt;/strong> — Errores ECC double-bit volátiles (no corregibles). A diferencia de los single-bit (que ECC corrige silenciosamente y se contabilizan en &lt;code>DCGM_FI_DEV_ECC_SBE_*&lt;/code>), los double-bit &lt;strong>corrompen datos&lt;/strong>. Cualquier valor &amp;gt; 0 es alerta crítica: la GPU debe ser drenada y revisada.&lt;/p>
&lt;p>&lt;strong>&lt;code>DCGM_FI_DEV_RETIRED_DBE&lt;/code>&lt;/strong> — 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.&lt;/p>
&lt;h2 id="las-cinco-métricas-del-motor-de-inferencia-vllm">Las cinco métricas del motor de inferencia (vLLM)&lt;/h2>
&lt;p>Las métricas DCGM responden &amp;ldquo;¿está sana la GPU?&amp;rdquo;. Las del motor responden &amp;ldquo;¿está el servicio cumpliendo el SLO?&amp;rdquo;. Sin ellas, sabes que el hardware funciona pero no sabes si los clientes están contentos.&lt;/p>
&lt;p>&lt;strong>&lt;code>vllm:num_requests_running&lt;/code>&lt;/strong> — Requests actualmente en el batch. Si llega al &lt;code>--max-num-seqs&lt;/code> configurado y no baja, el motor está saturado en concurrencia (revisar VRAM y rebalancear vía autoscaler — ver &lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a>).&lt;/p>
&lt;p>&lt;strong>&lt;code>vllm:num_requests_waiting&lt;/code>&lt;/strong> — Requests en cola, sin entrar al batch. Cualquier valor &amp;gt; 0 sostenido durante minutos indica que el cluster no escala con la carga. &lt;strong>Esta es la métrica primaria para HPA&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>&lt;code>vllm:gpu_cache_usage_perc&lt;/code>&lt;/strong> — % del KV cache pool usado.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Verde&lt;/strong>: 50–80 %.&lt;/li>
&lt;li>&lt;strong>Ámbar&lt;/strong>: 80–92 %.&lt;/li>
&lt;li>&lt;strong>Rojo&lt;/strong>: &amp;gt; 92 % (riesgo de &lt;strong>preempt-on-OOM&lt;/strong>: vLLM tirará requests para liberar memoria, lo que aumenta TTFT visiblemente).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>&lt;code>vllm:time_to_first_token_seconds&lt;/code>&lt;/strong> — Histograma de TTFT por request. Se consume como &lt;code>histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket[5m])))&lt;/code>. Comparado contra el SLO de TTFT P95 dispara la alerta primaria de servicio.&lt;/p>
&lt;p>&lt;strong>&lt;code>vllm:time_per_output_token_seconds&lt;/code>&lt;/strong> — Histograma de TPOT. Equivalente al anterior pero para fluidez de streaming. Comparado contra el SLO de TPOT P95 dispara la alerta secundaria.&lt;/p>
&lt;h2 id="las-seis-alertas-que-deben-pagear-en-producción">Las seis alertas que deben pagear en producción&lt;/h2>
&lt;p>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.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">groups&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gpu-llm-critical&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">rules&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">alert&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GpuHbmNearOom&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">expr&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">100&lt;/span>&lt;span class="w"> &lt;/span>*&lt;span class="w"> &lt;/span>&lt;span class="l">(DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL) &amp;gt; 92&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">for&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">2m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">severity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">critical }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">annotations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">summary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;HBM de {{ $labels.gpu }} en {{ $value }}% — riesgo OOM&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">alert&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GpuThermalOrPowerThrottle&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">expr&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">(DCGM_FI_DEV_CLOCK_THROTTLE_REASONS != 0) and ignoring(reason) (DCGM_FI_DEV_CLOCK_THROTTLE_REASONS != 1)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">for&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">1m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">severity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">warning }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">annotations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">summary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;GPU {{ $labels.gpu }} en throttle (reasons={{ $value }})&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">alert&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GpuXidErrorDetected&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">expr&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">increase(DCGM_FI_DEV_XID_ERRORS[5m]) &amp;gt; 0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">severity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">critical }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">annotations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">summary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;XID error en GPU {{ $labels.gpu }} — investigar inmediatamente&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">alert&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GpuEccDoubleBit&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">expr&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">DCGM_FI_DEV_ECC_DBE_VOL_TOTAL &amp;gt; 0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">severity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">critical }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">annotations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">summary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;ECC double-bit en GPU {{ $labels.gpu }} — drenar nodo&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">alert&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">VllmKvCachePoolNearFull&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">expr&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm:gpu_cache_usage_perc &amp;gt; 0.95&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">for&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">3m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">severity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">warning }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">annotations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">summary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;KV cache pool &amp;gt; 95% en {{ $labels.instance }}&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">alert&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">VllmTtftP95OutOfSlo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">expr&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">histogram_quantile(0.95, sum by(le, instance)(rate(vllm:time_to_first_token_seconds_bucket[5m]))) &amp;gt; 1.5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">for&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">5m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">severity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">warning }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">annotations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">summary&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;TTFT P95 sobre SLO ({{ $value }}s &amp;gt; 1.5s)&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Estas seis cubren el 80 % de los incidentes que afectan a SLO. El 20 % restante exige investigación con tracing (ver &lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a>).&lt;/p>
&lt;h2 id="tabla-maestra-umbrales-y-queries">Tabla maestra: umbrales y queries&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Métrica&lt;/th>
&lt;th>Verde&lt;/th>
&lt;th>Ámbar&lt;/th>
&lt;th>Rojo&lt;/th>
&lt;th>Query base (PromQL)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>SM occupancy&lt;/td>
&lt;td>0.30–0.70&lt;/td>
&lt;td>0.15–0.30&lt;/td>
&lt;td>&amp;lt; 0.10 sostenido&lt;/td>
&lt;td>&lt;code>DCGM_FI_PROF_SM_OCCUPANCY&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Tensor active (decode)&lt;/td>
&lt;td>15–30 %&lt;/td>
&lt;td>&amp;lt; 10 %&lt;/td>
&lt;td>&amp;lt; 3 %&lt;/td>
&lt;td>&lt;code>DCGM_FI_PROF_PIPE_TENSOR_ACTIVE&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>DRAM active&lt;/td>
&lt;td>60–85 %&lt;/td>
&lt;td>85–95 %&lt;/td>
&lt;td>&amp;gt; 95 % con KV bajo&lt;/td>
&lt;td>&lt;code>DCGM_FI_PROF_DRAM_ACTIVE&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>FB used&lt;/td>
&lt;td>70–85 %&lt;/td>
&lt;td>86–92 %&lt;/td>
&lt;td>&amp;gt; 92 %&lt;/td>
&lt;td>&lt;code>100 * DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NVLink BW&lt;/td>
&lt;td>&amp;lt; 70 % cap&lt;/td>
&lt;td>70–90 % cap&lt;/td>
&lt;td>&amp;gt; 90 % cap&lt;/td>
&lt;td>&lt;code>DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GPU temp&lt;/td>
&lt;td>&amp;lt; 75 °C&lt;/td>
&lt;td>75–82 °C&lt;/td>
&lt;td>&amp;gt; 83 °C&lt;/td>
&lt;td>&lt;code>DCGM_FI_DEV_GPU_TEMP&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Power usage&lt;/td>
&lt;td>&amp;lt; 90% TDP&lt;/td>
&lt;td>90–98 % TDP&lt;/td>
&lt;td>&amp;gt; 98 % TDP&lt;/td>
&lt;td>&lt;code>DCGM_FI_DEV_POWER_USAGE&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Throttle reasons&lt;/td>
&lt;td>0 o Idle&lt;/td>
&lt;td>App/SW&lt;/td>
&lt;td>HW Therm/Power&lt;/td>
&lt;td>&lt;code>DCGM_FI_DEV_CLOCK_THROTTLE_REASONS&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>XID errors&lt;/td>
&lt;td>sin cambio&lt;/td>
&lt;td>—&lt;/td>
&lt;td>cualquier delta&lt;/td>
&lt;td>&lt;code>increase(DCGM_FI_DEV_XID_ERRORS[5m])&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ECC DBE&lt;/td>
&lt;td>0&lt;/td>
&lt;td>—&lt;/td>
&lt;td>&amp;gt; 0&lt;/td>
&lt;td>&lt;code>DCGM_FI_DEV_ECC_DBE_VOL_TOTAL&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Retired pages&lt;/td>
&lt;td>&amp;lt; 4&lt;/td>
&lt;td>4–8&lt;/td>
&lt;td>&amp;gt; 8&lt;/td>
&lt;td>&lt;code>DCGM_FI_DEV_RETIRED_DBE&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>KV cache used&lt;/td>
&lt;td>50–80 %&lt;/td>
&lt;td>80–92 %&lt;/td>
&lt;td>&amp;gt; 92 %&lt;/td>
&lt;td>&lt;code>vllm:gpu_cache_usage_perc&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Requests waiting&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1–5 sostenido&lt;/td>
&lt;td>&amp;gt; 10 sostenido&lt;/td>
&lt;td>&lt;code>vllm:num_requests_waiting&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>TTFT P95&lt;/td>
&lt;td>&amp;lt; SLO&lt;/td>
&lt;td>80–100 % SLO&lt;/td>
&lt;td>&amp;gt; SLO&lt;/td>
&lt;td>ver query alerta arriba&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>TPOT P95&lt;/td>
&lt;td>&amp;lt; SLO&lt;/td>
&lt;td>80–100 % SLO&lt;/td>
&lt;td>&amp;gt; SLO&lt;/td>
&lt;td>&lt;code>histogram_quantile(0.95, sum by(le)(rate(vllm:time_per_output_token_seconds_bucket[5m])))&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="tres-pitfalls-que-confunden-al-operador-junior">Tres pitfalls que confunden al operador junior&lt;/h2>
&lt;p>&lt;strong>Pitfall 1 — &amp;ldquo;GPU-Util al 99 % = saturada&amp;rdquo;.&lt;/strong> Como se explicó al inicio: &lt;code>DCGM_FI_DEV_GPU_UTIL&lt;/code> se enciende con cualquier kernel. Lo correcto es mirar las tres &lt;code>_PROF_*&lt;/code> (SM occupancy, tensor active, DRAM active) juntas. &lt;em>GPU util 99 % + tensor active 8 % + DRAM active 92 %&lt;/em> = &amp;ldquo;saturada por memoria, no compute&amp;rdquo;; &lt;em>GPU util 99 % + tensor active 75 % + DRAM active 50 %&lt;/em> = &amp;ldquo;saturada por compute, prefill heavy&amp;rdquo;. Las dos situaciones piden palancas distintas.&lt;/p>
&lt;p>&lt;strong>Pitfall 2 — confundir ECC single-bit (SBE) con double-bit (DBE).&lt;/strong> Los single-bit se corrigen silenciosamente y son &lt;strong>inevitables&lt;/strong> 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.&lt;/p>
&lt;p>&lt;strong>Pitfall 3 — alertar sobre &lt;code>num_requests_waiting &amp;gt; 0&lt;/code> sin contexto.&lt;/strong> Un valor instantáneo de 1 o 2 durante un pico es normal. Lo que importa es la cola &lt;strong>sostenida&lt;/strong>: usar &lt;code>for: 5m&lt;/code> con umbral 3–5. Sin esa ventana, el sistema satura el canal de alertas con ruido.&lt;/p>
&lt;h2 id="aplicado-a-hardware-on-premise-típico">Aplicado a hardware on-premise típico&lt;/h2>
&lt;p>Para un cluster genérico de &lt;strong>4×H100 SXM 80 GB con NVLink intra-nodo&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>DCGM Exporter desplegado vía NVIDIA GPU Operator, un DaemonSet por nodo GPU.&lt;/li>
&lt;li>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).&lt;/li>
&lt;li>Grafana con tres dashboards estándar: hardware GPU (DCGM), motor (vLLM), SLO (TTFT/TPOT/RPS contra objetivos escritos).&lt;/li>
&lt;li>Alertmanager con rotación de guardia y rate-limiting por silencio agrupado por nodo.&lt;/li>
&lt;li>Cardinalidad controlada: &lt;code>gpu&lt;/code> (id local), &lt;code>node&lt;/code>, &lt;code>pod&lt;/code>, &lt;code>model&lt;/code> — no añadir &lt;code>request_id&lt;/code> ni labels de alta cardinalidad a métricas (eso es trabajo del tracing).&lt;/li>
&lt;/ul>
&lt;p>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 &amp;gt; 64 GPUs, considerar Thanos sidecar o VictoriaMetrics. Ver &lt;a href="https://blog.lo0.es/posts/catalogo-herramientas-oss-llmops/">Catálogo de herramientas OSS LLMOps&lt;/a> para alternativas equivalentes.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-artículos">Lo que no hemos cubierto (próximos artículos)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Tracing de cargas LLM&lt;/strong>: ya cubierto en &lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Autoscaling&lt;/strong> basado en estas métricas: ver &lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Runbooks de incident response&lt;/strong>: cómo cada una de estas alertas se traduce a acción concreta (drain, restart, RMA, escalado, rollback).&lt;/li>
&lt;li>&lt;strong>Cost accounting&lt;/strong>: usar &lt;code>DCGM_FI_DEV_POWER_USAGE&lt;/code> y &lt;code>vllm:request_success_total&lt;/code> para showback de coste por tenant.&lt;/li>
&lt;li>&lt;strong>Monitorización de fairness multi-tenant&lt;/strong>: cuando varios tenants comparten cluster, qué métricas detectan que uno está acaparando el KV cache.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a> — la otra mitad de la observabilidad.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning para inferencia LLM on-premise&lt;/a> — qué se dimensionó y, por tanto, qué umbrales son defendibles aquí.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">Continuous batching&lt;/a> — explica por qué &lt;code>num_requests_running&lt;/code>, &lt;code>num_requests_waiting&lt;/code> y &lt;code>gpu_cache_usage_perc&lt;/code> son las métricas operativas del motor.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">Cinco niveles de madurez&lt;/a> — la observabilidad LLM-aware vive en el nivel 4.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">Siete capas del stack de inferencia LLM on-premise&lt;/a> — DCGM Exporter es pieza de la capa de plataforma.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a> — usa estas métricas como input.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/anatomia-metricas-dcgm-vllm-anomalias/">Anatomía de las doce métricas DCGM y cinco vLLM&lt;/a> — 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, &lt;em>Story of Two GPUs&lt;/em>, issues vLLM, KBs Dell/Lenovo).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/runbooks-incident-response-llm-keep-kafka/">Runbooks de incident response para LLM con Keep + Kafka&lt;/a> — 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.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>NVIDIA — &lt;em>DCGM Exporter&lt;/em> (repo &lt;code>nvidia/dcgm-exporter&lt;/code>, métricas y unidades documentadas).&lt;/li>
&lt;li>NVIDIA — &lt;em>DCGM Field Identifiers reference&lt;/em> (lista completa de &lt;code>DCGM_FI_*&lt;/code>).&lt;/li>
&lt;li>NVIDIA — &lt;em>XID Errors documentation&lt;/em> (catálogo de códigos XID y procedimientos de remediación).&lt;/li>
&lt;li>NVIDIA — &lt;em>NVIDIA GPU Operator&lt;/em> (Helm chart oficial).&lt;/li>
&lt;li>vLLM project — &lt;code>examples/production_monitoring/&lt;/code> (PromQL y dashboards Grafana de referencia).&lt;/li>
&lt;li>Prometheus — &lt;em>Histogram and summary best practices&lt;/em> (para construir queries de percentiles defendibles).&lt;/li>
&lt;li>NVIDIA — &lt;em>H100 Tensor Core GPU datasheet&lt;/em> (TDP, HBM bandwidth, NVLink capacities).&lt;/li>
&lt;/ul></description></item><item><title>Capacity planning para inferencia LLM on-premise: cómo dimensionar GPUs a partir de un SLO</title><link>https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/</link><pubDate>Mon, 01 Jun 2026 15:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/</guid><description>&lt;blockquote>
&lt;p>Este post complementa los de &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache&lt;/a> (la pieza que domina el presupuesto de VRAM), &lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">Continuous batching&lt;/a> (lo que define la utilización efectiva del compute) y &lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">Siete capas del stack&lt;/a> (las piezas que el sizing presupone). Antes de leer este, asegúrate de que tu equipo tiene escritos los SLOs que va a perseguir; sin esa entrada el cálculo no es defendible.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>El capacity planning de inferencia LLM no responde a &amp;ldquo;cuántos tokens/segundo da una GPU&amp;rdquo; — esa pregunta carece de respuesta universal porque el throughput depende de la concurrencia, el reparto prefill/decode, la longitud de contexto, el motor de inferencia y la quantization. La pregunta correcta tiene tres entradas (&lt;strong>SLO&lt;/strong>: TTFT P95, TPOT P95, RPS sostenidos), una &lt;strong>referencia de hardware&lt;/strong> (modelo de GPU, VRAM, ancho HBM, FLOPs efectivos) y un &lt;strong>modelo&lt;/strong> (parámetros, arquitectura GQA/MHA/MoE, formato de pesos). El cálculo se resuelve en dos presupuestos acoplados que se cruzan. &lt;strong>Presupuesto de VRAM&lt;/strong>: del total de la GPU restas pesos del modelo y activaciones, lo que queda es &lt;strong>KV cache budget&lt;/strong>, y de ahí derivas la &lt;strong>concurrencia máxima&lt;/strong> posible al contexto promedio que esperas. &lt;strong>Presupuesto de tiempo&lt;/strong>: el motor (vLLM, SGLang, TensorRT-LLM) tiene un techo de tokens/s en decode dado por el ancho de HBM y otro en prefill dado por el FLOP útil; de ahí derivas la &lt;strong>TPOT esperada&lt;/strong> y, dividiendo prefill_tokens entre el throughput de prefill, la &lt;strong>TTFT esperada&lt;/strong>. Ambos presupuestos deben cumplir el SLO &lt;strong>simultáneamente&lt;/strong>: el que esté más ajustado dicta el dimensionamiento. Sobre el ejemplo Llama 70B BF16 con tensor parallel 4 en 4×H100 SXM, una sola réplica satura a ~28 requests concurrentes y entrega ~3 200 tokens/s de decode agregado con TPOT mediano de 35 ms; para 200 RPS sostenidos a un perfil de 800 tokens de prompt + 250 de output, hacen falta entre 4 y 5 réplicas con un colchón del 25 % sobre el pico observado. La quantization (FP8 → INT4) divide entre 1.5 y 4× el coste de VRAM y de tiempo de decode, pero degrada calidad de forma medible — no se asume gratis, se valida con evals. Las cinco trampas habituales: confundir media con P95, ignorar el reparto prefill/decode del workload real, dimensionar sin head-room para retrain ni rollback, olvidar que la GPU al 100 % de SM util no significa nada si la HBM está saturada, y no documentar los supuestos del cálculo (un sizing sin supuestos escritos es un cálculo desechable).&lt;/p>
&lt;h2 id="estás-aquí-deploy-con-un-pie-en-observe">Estás aquí: DEPLOY (con un pie en OBSERVE)&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="estás aquí: Deploy con un pie en Observe">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#ffb347;stroke-width:3}.semiactive{fill:#ffe1b3;stroke-width:2}.idle{fill:#f4f4f4}.lbl{font:600 12px sans-serif;fill:#222}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#cpm)}.cyc{stroke:#888;stroke-width:1.2;fill:none;stroke-dasharray:4 2;marker-end:url(#cpm)}&lt;/style>
&lt;defs>&lt;marker id="cpm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="390" y="20" text-anchor="middle" class="lbl">Estás aquí: capacity planning · cierra DEPLOY y abre la conversación con OBSERVE&lt;/text>
&lt;rect x="30" y="35" width="110" height="35" class="box idle"/>&lt;text x="85" y="58" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="155" y="35" width="110" height="35" class="box idle"/>&lt;text x="210" y="58" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="280" y="35" width="110" height="35" class="box idle"/>&lt;text x="335" y="58" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="405" y="35" width="110" height="35" class="box active"/>&lt;text x="460" y="58" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="530" y="35" width="110" height="35" class="box semiactive"/>&lt;text x="585" y="58" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="655" y="35" width="110" height="35" class="box idle"/>&lt;text x="710" y="58" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;path class="arr" d="M140,52 L155,52"/>&lt;path class="arr" d="M265,52 L280,52"/>&lt;path class="arr" d="M390,52 L405,52"/>&lt;path class="arr" d="M515,52 L530,52"/>&lt;path class="arr" d="M640,52 L655,52"/>
&lt;path class="cyc" d="M710,72 L710,82 L85,82 L85,72"/>
&lt;/svg>
&lt;/div>
&lt;p>El capacity planning es una pieza con doble residencia. Vive en &lt;strong>DEPLOY&lt;/strong> porque sin un sizing válido no se compra hardware ni se configura el motor de inferencia. Pero su &lt;strong>input son las observaciones reales&lt;/strong>: distribución de longitudes de prompt y output, mezcla prefill/decode del workload, P95 reales que ya se están viendo en preproducción. Sin esos datos el cálculo es una servilleta — defendible solo hasta que llegue el primer cliente que no encaja en la media asumida.&lt;/p>
&lt;h2 id="la-analogía-el-hotel-con-habitaciones-de-tamaño-variable">La analogía: el hotel con habitaciones de tamaño variable&lt;/h2>
&lt;p>Imagina un hotel donde las habitaciones no tienen tamaño fijo: cada huésped paga por los metros cuadrados que necesita, y la planta del edificio se reorganiza dinámicamente para acomodar a quien llega. La dirección quiere maximizar ocupación, pero tiene dos restricciones reales y una métrica de calidad.&lt;/p>
&lt;p>&lt;strong>Restricción 1 — espacio físico.&lt;/strong> La planta tiene 1 000 m² totales. Si entra una familia que necesita 200 m², esa familia ocupa esa superficie y no se puede entregar al siguiente huésped. La habitación más grande limita cuántos huéspedes simultáneos caben.&lt;/p>
&lt;p>&lt;strong>Restricción 2 — personal de servicio.&lt;/strong> Hay 10 recepcionistas. Cada uno puede gestionar el check-in de un huésped cada dos minutos. Cuando llegan 60 huéspedes en una hora, los últimos esperan en cola; el tiempo desde que entran a recepción hasta que reciben su llave depende de cuántos hay delante.&lt;/p>
&lt;p>&lt;strong>Métrica de calidad — promesa de tiempo.&lt;/strong> La carta dice &amp;ldquo;check-in en menos de 15 minutos&amp;rdquo;. Si llegan demasiados huéspedes a la vez, esa promesa se rompe aunque haya espacio físico libre.&lt;/p>
&lt;p>El &lt;strong>espacio físico&lt;/strong> es la VRAM de la GPU. Cada &lt;strong>habitación&lt;/strong> es una request con su KV cache (más grande cuanto más larga la conversación). Los &lt;strong>recepcionistas&lt;/strong> son los compute units (Streaming Multiprocessors + Tensor Cores). El &lt;strong>check-in&lt;/strong> es la fase de prefill; las &lt;strong>noches&lt;/strong> que el huésped pasa después son los pasos de decode. La &lt;strong>promesa de 15 minutos&lt;/strong> es el SLO de TTFT P95.&lt;/p>
&lt;p>El capacity planning del hotel es exactamente este: dado el perfil esperado de huéspedes (cuántos llegan por hora, cuánto espacio piden de media, cuántos minutos toleran de espera), calcular cuántas plantas y cuántos recepcionistas hace falta. No se hace estimando &amp;ldquo;habitaciones por hora&amp;rdquo; en abstracto — se hace cruzando los dos presupuestos con la promesa de tiempo. La analogía sostiene el cálculo hasta el final.&lt;/p>
&lt;h2 id="las-tres-entradas-del-slo">Las tres entradas del SLO&lt;/h2>
&lt;p>Antes de poner un solo número en la hoja, hay que escribir las tres dimensiones del SLO. Sin esto el cálculo es estética, no ingeniería.&lt;/p>
&lt;p>&lt;strong>TTFT P95 (Time-To-First-Token).&lt;/strong> El tiempo desde que el cliente envía la request hasta que recibe el primer token. Está dominado por la fase de prefill (procesar el prompt entero de una vez) más la cola del scheduler. Para chat conversacional, un objetivo razonable está entre &lt;strong>0.5 y 2 segundos P95&lt;/strong>. Para asistentes de programación con prompts grandes (5–10 K tokens de contexto), entre &lt;strong>2 y 4 s P95&lt;/strong>. Por debajo de 500 ms entra en regla de UX para conversaciones tipo voz, pero exige compromisos serios de arquitectura.&lt;/p>
&lt;p>&lt;strong>TPOT P95 (Time-Per-Output-Token).&lt;/strong> El tiempo entre tokens consecutivos durante decode. Domina la &amp;ldquo;fluidez percibida&amp;rdquo; del streaming. Por encima de &lt;strong>80 ms/token&lt;/strong> el lector humano percibe pausas; por debajo de &lt;strong>30 ms/token&lt;/strong> la salida fluye más rápido de lo que se lee. Objetivo industrial habitual: &lt;strong>40–60 ms P95&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>RPS sostenidos cumpliendo SLO.&lt;/strong> El throughput que el sistema debe soportar &lt;strong>sin violar&lt;/strong> TTFT ni TPOT. Esto es la métrica clave de DistServe llamada &lt;strong>goodput&lt;/strong> —ver &lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">Continuous batching&lt;/a>—. &amp;ldquo;200 RPS pico&amp;rdquo; no es lo mismo que &amp;ldquo;200 RPS con TTFT P95 ≤ 1.5 s&amp;rdquo;. Sin la condición de SLO, el número de RPS no significa nada.&lt;/p>
&lt;p>Estas tres dimensiones se acompañan de un &lt;strong>perfil de workload&lt;/strong>: distribución de longitudes de prompt y de output. Las medianas no bastan; hace falta P50, P95, P99. Un perfil mal medido es el principal motivo de sizing fallido.&lt;/p>
&lt;h2 id="la-fórmula-central-dos-presupuestos-que-se-cruzan">La fórmula central: dos presupuestos que se cruzan&lt;/h2>
&lt;p>El cálculo se resuelve en dos cuentas independientes que después se cruzan. La menor de las dos manda.&lt;/p>
&lt;h3 id="presupuesto-de-vram">Presupuesto de VRAM&lt;/h3>
&lt;p>Para una GPU con VRAM total $V$, el espacio disponible para KV cache es:&lt;/p>
&lt;p>$$V_{\text{kv}} = V - V_{\text{model}} - V_{\text{activations}} - V_{\text{overhead}}$$&lt;/p>
&lt;p>donde:&lt;/p>
&lt;ul>
&lt;li>$V_{\text{model}}$ es el tamaño de los pesos: para un modelo de $P$ parámetros en formato $b$ bytes/parámetro, $V_{\text{model}} = P \cdot b$. Llama 70B BF16 = $70 \times 10^9 \times 2 = 140$ GB. En tensor parallel TP=4, cada GPU lleva $140 / 4 = 35$ GB.&lt;/li>
&lt;li>$V_{\text{activations}}$ son los buffers intermedios del forward pass. Para vLLM con batch razonable, entre &lt;strong>2 y 6 GB&lt;/strong> por GPU dependiendo de batch size y longitud máxima.&lt;/li>
&lt;li>$V_{\text{overhead}}$ son CUDA context, NCCL buffers, pool de PagedAttention, paged blocks reservados. &lt;strong>2–4 GB&lt;/strong> típicos.&lt;/li>
&lt;/ul>
&lt;p>El KV cache budget por GPU queda como el residuo. Para H100 SXM 80 GB con Llama 70B TP=4 BF16:&lt;/p>
&lt;p>$$V_{\text{kv}} = 80 - 35 - 4 - 3 = 38 \text{ GB por GPU} = 152 \text{ GB agregados sobre TP=4}$$&lt;/p>
&lt;p>El coste por token de KV cache para un modelo con $L$ capas, $H_{\text{kv}}$ heads KV (GQA), dimensión por head $d_h$, en formato $b$ bytes:&lt;/p>
&lt;p>$$\text{kv_per_token} = 2 \cdot L \cdot H_{\text{kv}} \cdot d_h \cdot b$$&lt;/p>
&lt;p>El factor 2 es porque se guardan K y V. Para Llama 70B (L=80, $H_{\text{kv}}$=8 con GQA, $d_h$=128, BF16 = 2 bytes):&lt;/p>
&lt;p>$$\text{kv_per_token} = 2 \cdot 80 \cdot 8 \cdot 128 \cdot 2 = 327,680 \text{ bytes} = 320 \text{ KB/token}$$&lt;/p>
&lt;p>Y la concurrencia máxima al contexto promedio $C$:&lt;/p>
&lt;p>$$N_{\text{max}} = \frac{V_{\text{kv}}}{C \cdot \text{kv_per_token}}$$&lt;/p>
&lt;p>Con $V_{\text{kv}}$ agregado de 152 GB y un contexto promedio de 1 500 tokens (800 prompt + 700 generados en el peor instante de la conversación):&lt;/p>
&lt;p>$$N_{\text{max}} = \frac{152 \times 10^9}{1,500 \cdot 320 \times 10^3} \approx 316 \text{ requests concurrentes}$$&lt;/p>
&lt;p>Este es el &lt;strong>techo físico&lt;/strong> de concurrencia para esa réplica. No es lo que vas a usar — es lo que &lt;strong>no puedes superar&lt;/strong> sin OOM. El número operativo está bastante por debajo (head-room para spikes).&lt;/p>
&lt;h3 id="presupuesto-de-tiempo">Presupuesto de tiempo&lt;/h3>
&lt;p>Aquí entran dos sub-cálculos: el de &lt;strong>decode&lt;/strong> (memory-bound) y el de &lt;strong>prefill&lt;/strong> (compute-bound).&lt;/p>
&lt;p>&lt;strong>Decode TPOT.&lt;/strong> Por cada token que se genera, hay que pasear los pesos del modelo (relevantes para esa request) y leer el KV cache acumulado. El cuello de botella es el ancho de banda HBM. Para una GPU con ancho $B$ GB/s y un modelo de $V_{\text{model_per_gpu}}$ GB de pesos:&lt;/p>
&lt;p>$$\text{tpot}&lt;em>{\text{teórico}} \approx \frac{V&lt;/em>{\text{model_per_gpu}}}{B}$$&lt;/p>
&lt;p>Para H100 SXM con HBM3 a 3.35 TB/s y Llama 70B TP=4 BF16 (35 GB/GPU):&lt;/p>
&lt;p>$$\text{tpot}_{\text{teórico}} \approx \frac{35}{3,350} \approx 10.4 \text{ ms/token}$$&lt;/p>
&lt;p>Este es el &lt;strong>mejor caso teórico&lt;/strong> con batch=1 y eficiencia HBM al 100 %. En la práctica vLLM en H100 con Llama 70B TP=4 alcanza &lt;strong>12–18 ms/token&lt;/strong> a batch bajo y &lt;strong>30–45 ms/token&lt;/strong> a batch alto (con concurrencia 32, los tokens compiten por la HBM compartida). El número operacional defendible: &lt;strong>35 ms/token&lt;/strong> en concurrencia 24–32.&lt;/p>
&lt;p>&lt;strong>Prefill throughput.&lt;/strong> El prefill procesa N tokens de prompt en un único forward pass. Es compute-bound: cuello en FLOPs. Para H100 SXM con 989 TFLOPs BF16 sostenidos y Llama 70B (cada forward pass cuesta aproximadamente $2 \cdot P \cdot N$ FLOPs por sequence de longitud N):&lt;/p>
&lt;p>$$\text{prefill_tps} = \frac{4 \cdot \text{TFLOPs} \cdot \eta}{2 \cdot P} = \frac{4 \cdot 989 \times 10^{12} \cdot 0.5}{2 \cdot 70 \times 10^9} \approx 14,000 \text{ tokens/s}$$&lt;/p>
&lt;p>(el factor 4 son las GPUs en TP, $\eta$ es eficiencia real entre 0.4 y 0.6 en H100). Un prompt de 800 tokens tarda en prefill:&lt;/p>
&lt;p>$$\text{prefill_time} = \frac{800}{14,000} \approx 57 \text{ ms}$$&lt;/p>
&lt;p>Sumando una cola típica de 100–300 ms a concurrencia alta, &lt;strong>TTFT P95 ≈ 350–500 ms&lt;/strong> para ese perfil. Muy por debajo del objetivo de 1.5 s — hay margen.&lt;/p>
&lt;h3 id="el-cruce">El cruce&lt;/h3>
&lt;p>La concurrencia operativa real $N_{\text{op}}$ es el mínimo entre &lt;strong>el techo de VRAM&lt;/strong>, la concurrencia a la que el &lt;strong>TPOT empieza a degradar&lt;/strong> por encima del SLO, y la concurrencia a la que el &lt;strong>TTFT empieza a degradar&lt;/strong> por encima del SLO (cola de prefill). Para el ejemplo:&lt;/p>
&lt;ul>
&lt;li>VRAM techo: 316.&lt;/li>
&lt;li>TPOT degrada a 80 ms (SLO) alrededor de concurrencia &lt;strong>~80–100&lt;/strong> (medido empíricamente con benchmark, no fórmula cerrada).&lt;/li>
&lt;li>TTFT degrada a 1.5 s alrededor de concurrencia &lt;strong>~40–60&lt;/strong> por cola de prefill.&lt;/li>
&lt;/ul>
&lt;p>La concurrencia operativa de la réplica es &lt;strong>~50&lt;/strong>. Aplicando un 25 % de head-room para spikes y rebalanceos, &lt;strong>concurrencia objetivo por réplica ≈ 35–40&lt;/strong>.&lt;/p>
&lt;h2 id="hoja-de-cálculo-paso-a-paso-llama-70b-bf16-en-4h100-sxm">Hoja de cálculo paso a paso: Llama 70B BF16 en 4×H100 SXM&lt;/h2>
&lt;p>Entrada del ejercicio:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>SLO&lt;/strong>: TTFT P95 ≤ 1.5 s; TPOT P95 ≤ 60 ms; &lt;strong>200 RPS sostenidos&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Workload&lt;/strong>: prompt P50=600, P95=1 200, P99=2 500; output P50=180, P95=500, P99=900. Promedio prompt 800, output 250.&lt;/li>
&lt;li>&lt;strong>Hardware genérico&lt;/strong>: 4×H100 SXM 80 GB con NVLink, motor vLLM v1, tensor parallel 4, BF16.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Paso 1 — VRAM por GPU.&lt;/strong> Pesos 35 GB, activaciones 4 GB, overhead 3 GB → KV budget 38 GB/GPU = 152 GB agregados. KV/token a Llama 70B GQA = 320 KB. Techo de tokens vivos en cache: $152 \times 10^9 / 320 \times 10^3 \approx 475,000$ tokens. Al contexto promedio operacional (800 prompt + 200 ya generados = 1 000 tokens vivos por request), techo de concurrencia $\approx 475$.&lt;/p>
&lt;p>&lt;strong>Paso 2 — duración media de una request.&lt;/strong> Prefill 800 tokens / 14 000 tps = 57 ms. Decode 250 tokens × 35 ms/token = 8 750 ms. Total $\approx 8.8$ s por request.&lt;/p>
&lt;p>&lt;strong>Paso 3 — throughput de la réplica.&lt;/strong> Si la réplica sostiene concurrencia operativa 40 y cada request dura 8.8 s, la réplica entrega aproximadamente $40 / 8.8 \approx 4.5$ requests/s en régimen estacionario.&lt;/p>
&lt;p>&lt;strong>Paso 4 — número de réplicas.&lt;/strong> Para 200 RPS objetivo: $200 / 4.5 \approx 45$ réplicas. Eso son &lt;strong>45 × 4 = 180 GPUs&lt;/strong>. Demasiado: este sizing no funciona porque el coste por request es alto.&lt;/p>
&lt;p>&lt;strong>Paso 5 — revisar palancas.&lt;/strong> Antes de comprar más hardware, hay tres palancas que se exploran en este orden:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Quantization.&lt;/strong> Bajar a FP8 reduce pesos a 17.5 GB/GPU (queda más VRAM para KV cache → más concurrencia), aproximadamente duplica tokens/s en decode (HBM saturada por la mitad), y degrada calidad MMLU típicamente 0.5–1.5 puntos en modelos como Llama 70B. Reescribiendo el cálculo en FP8: TPOT baja a ~18 ms, tiempo total por request a 4.7 s, RPS por réplica sube a ~8.5, &lt;strong>réplicas necesarias ≈ 24, equivalente a 96 GPUs&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Speculative decoding.&lt;/strong> Con un drafter pequeño y aceptación del 60–70 %, TPOT efectivo cae 30–40 %. RPS por réplica sube a ~12, &lt;strong>réplicas ≈ 17 = 68 GPUs&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Disaggregated serving.&lt;/strong> Separar prefill workers y decode workers permite escalar cada uno a la mezcla real del workload —ver &lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">Disaggregated serving&lt;/a>—. Suele recortar otro 20–40 % bajo workloads asimétricos.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Paso 6 — sizing recomendado.&lt;/strong> Para el ejemplo, con FP8 + speculative decoding y un head-room del 25 %: &lt;strong>20 réplicas vLLM TP=4 sobre 80 H100 SXM&lt;/strong>. Si el equipo no quiere depender de quantization agresiva (BF16 puro para máxima fidelidad), el cálculo sube a &lt;strong>30 réplicas = 120 GPUs&lt;/strong> y obliga a renegociar SLO o presupuesto.&lt;/p>
&lt;p>&lt;strong>Paso 7 — escribir los supuestos.&lt;/strong> Esta es la parte que ningún sizing válido se salta. En el repo del equipo, junto al cálculo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># sizing/llama70b-prod.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">fecha&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="ld">2026-06-01&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">slo&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ttft_p95_ms&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1500&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tpot_p95_ms&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">60&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">rps_target&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">200&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">workload&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prompt_tokens_p50&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">600&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prompt_tokens_p95&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1200&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">output_tokens_p50&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">180&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">output_tokens_p95&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">500&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">asunto&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">chat productivo con RAG ligero&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">modelo&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">arquitectura&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama-70b-instruct&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">formato_pesos&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">fp8&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">motor&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm-v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">hardware&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">gpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">H100-SXM-80GB&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">topologia&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TP=4 con NVLink intra-nodo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">red_inter_replica&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">25&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GbE&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">optimizaciones&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">paged_attention&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">chunked_prefill&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">speculative_decoding (drafter llama-1.1b, aceptación esperada 65%)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">asunciones_criticas&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">utilizacion_hbm_eficiente&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.55&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">head_room_pico_sobre_p95&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.25&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">aceptacion_speculative_min&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.55&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">plan_validacion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">benchmark vllm bench serve antes de procurement&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">canary 10% durante 7 días post-deploy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Sin este YAML, el cálculo no es reproducible un mes después.&lt;/p>
&lt;h2 id="caso-moe-mixtral-822b-141-b-totales-39-b-activos">Caso MoE: Mixtral 8×22B (~141 B totales, 39 B activos)&lt;/h2>
&lt;p>Los MoE cambian el cálculo en una dimensión clave: los pesos totales son grandes pero los pesos &lt;strong>activos por token&lt;/strong> son pequeños. Para Mixtral 8×22B con top-2 routing:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>VRAM de pesos&lt;/strong>: $141 \times 2 = 282$ GB BF16. Con TP=4 → 70 GB/GPU. No cabe en H100 80 GB con KV cache + activaciones. Hace falta TP=8 (~35 GB/GPU) o FP8 con TP=4 (~35 GB/GPU).&lt;/li>
&lt;li>&lt;strong>Decode TPOT&lt;/strong>: dominado por los pesos &lt;strong>leídos por token&lt;/strong>, que son $\sim 39 / 8 \cdot 2 \approx 9.75$ GB/GPU con TP=4 (un experto top-2 por token, dividido entre 4 GPUs). En H100 con HBM 3.35 TB/s, &lt;strong>TPOT teórico ≈ 3 ms/token&lt;/strong>. En la práctica, 10–20 ms a concurrencia razonable.&lt;/li>
&lt;li>&lt;strong>Prefill&lt;/strong>: similar al modelo denso de los pesos activos, ~39 B FLOPs/token.&lt;/li>
&lt;/ul>
&lt;p>El sizing MoE suele entregar más RPS por GPU que un denso equivalente — el coste por token bajo compensa el extra de VRAM. Ver &lt;a href="https://blog.lo0.es/posts/moe-inference-fundamentos/">MoE inference&lt;/a> para el detalle del routing y por qué el batch alto es decisivo para que cada experto vea suficientes tokens.&lt;/p>
&lt;h2 id="tabla-de-sensibilidad-contexto-y-quantization">Tabla de sensibilidad: contexto y quantization&lt;/h2>
&lt;p>Para Llama 70B sobre 4×H100 SXM (TP=4), concurrencia operativa por réplica con SLO TTFT 1.5 s / TPOT 60 ms:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Contexto promedio&lt;/th>
&lt;th>BF16&lt;/th>
&lt;th>FP8&lt;/th>
&lt;th>INT4 (AWQ)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>500 tokens&lt;/td>
&lt;td>55&lt;/td>
&lt;td>110&lt;/td>
&lt;td>180&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1 000 tokens&lt;/td>
&lt;td>40&lt;/td>
&lt;td>80&lt;/td>
&lt;td>130&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2 000 tokens&lt;/td>
&lt;td>24&lt;/td>
&lt;td>50&lt;/td>
&lt;td>85&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4 000 tokens&lt;/td>
&lt;td>12&lt;/td>
&lt;td>26&lt;/td>
&lt;td>48&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8 000 tokens&lt;/td>
&lt;td>6&lt;/td>
&lt;td>13&lt;/td>
&lt;td>25&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Números aproximados de benchmark vLLM público a junio 2026, con variación ±20 % según versión del motor y headroom adoptado. Para validar en tu hardware: &lt;code>vllm bench serve&lt;/code> con tu perfil de prompts reales.&lt;/p>
&lt;h2 id="las-cinco-trampas-habituales">Las cinco trampas habituales&lt;/h2>
&lt;p>&lt;strong>Trampa 1 — confundir media con P95.&lt;/strong> El throughput medio de una hora puede ser 50 RPS pero el pico de 5 minutos llegar a 180 RPS. Dimensionar contra la media garantiza romper SLO en cada pico. Regla: dimensionar contra P95 horario, con head-room del 20–30 % sobre P95.&lt;/p>
&lt;p>&lt;strong>Trampa 2 — no medir el reparto prefill/decode real.&lt;/strong> Un workload de &amp;ldquo;RAG con respuestas cortas&amp;rdquo; tiene 70–80 % del tiempo de GPU en prefill; un &amp;ldquo;writing assistant que genera ensayos&amp;rdquo; tiene 80 % en decode. Las optimizaciones útiles (chunked prefill vs speculative decoding) cambian radicalmente. Sin medirlo, se compra hardware mal balanceado.&lt;/p>
&lt;p>&lt;strong>Trampa 3 — dimensionar sin head-room para retrain ni rollback.&lt;/strong> El cluster productivo no es solo el motor de inferencia: hay batch de re-embeddings cuando cambia el modelo de embeddings, eval continuo de canary —ver &lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">Canary, blue-green y shadow&lt;/a>—, fine-tune ligero, hot stand-by para rollback. Reservar &lt;strong>15–25 % de capacidad&lt;/strong> para esos workloads no negociables.&lt;/p>
&lt;p>&lt;strong>Trampa 4 — &amp;ldquo;GPU al 100 % de SM utilization&amp;rdquo; como objetivo.&lt;/strong> SM occupancy del 95 % con HBM saturada produce el mismo throughput que SM al 60 % con HBM saturada. El cuello de botella en decode es la HBM. Optimizar para &amp;ldquo;GPU usage 100 %&amp;rdquo; sin mirar HBM utilization y arithmetic intensity hace gastar más en GPU sin ganar throughput. Ver &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a> para qué métricas mirar realmente.&lt;/p>
&lt;p>&lt;strong>Trampa 5 — no documentar los supuestos.&lt;/strong> Un sizing sin YAML reproducible (workload, modelo, motor, head-room, asunciones críticas) deja al equipo sin manera de saber qué cambió cuando el cluster ya no llega a SLO seis meses después. Documentar es barato; perder un trimestre depurando, no.&lt;/p>
&lt;h2 id="aplicado-a-hardware-on-premise-típico">Aplicado a hardware on-premise típico&lt;/h2>
&lt;p>Para un cluster genérico de &lt;strong>4×H100 SXM 80 GB con NVLink intra-nodo y 25 GbE entre nodos&lt;/strong>, las configuraciones recurrentes en mayo 2026 son:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Modelo&lt;/th>
&lt;th>Formato&lt;/th>
&lt;th>TP&lt;/th>
&lt;th>Réplicas que caben&lt;/th>
&lt;th>RPS típico por nodo (ctx 1K)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Llama 8B&lt;/td>
&lt;td>BF16&lt;/td>
&lt;td>1&lt;/td>
&lt;td>4 (una por GPU)&lt;/td>
&lt;td>240–320&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Llama 8B&lt;/td>
&lt;td>FP8&lt;/td>
&lt;td>1&lt;/td>
&lt;td>4&lt;/td>
&lt;td>450–600&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Llama 70B&lt;/td>
&lt;td>BF16&lt;/td>
&lt;td>4&lt;/td>
&lt;td>1&lt;/td>
&lt;td>30–45&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Llama 70B&lt;/td>
&lt;td>FP8&lt;/td>
&lt;td>4&lt;/td>
&lt;td>1&lt;/td>
&lt;td>60–90&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Llama 70B&lt;/td>
&lt;td>INT4 AWQ&lt;/td>
&lt;td>2&lt;/td>
&lt;td>2&lt;/td>
&lt;td>90–130&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Mixtral 8×22B&lt;/td>
&lt;td>FP8&lt;/td>
&lt;td>4&lt;/td>
&lt;td>1&lt;/td>
&lt;td>90–140&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Qwen 72B&lt;/td>
&lt;td>BF16&lt;/td>
&lt;td>4&lt;/td>
&lt;td>1&lt;/td>
&lt;td>28–42&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Estos números son &lt;strong>órdenes de magnitud para empezar la conversación&lt;/strong>, no compromisos. El sizing definitivo se valida con &lt;code>vllm bench serve&lt;/code> o &lt;code>genai-perf&lt;/code> (NVIDIA) usando el perfil de prompts/outputs reales del cliente. La asimetría prefill/decode del workload de cada caso puede mover estos números un 30–50 % arriba o abajo.&lt;/p>
&lt;p>Para clusters de &lt;strong>8×H100 SXM&lt;/strong> (típico de servidores DGX o réplicas equivalentes), las opciones se abren a TP=8 para modelos clase 405B o multi-réplica TP=2 para modelos 70B con mayor densidad. La métrica que decide es siempre la misma: &lt;strong>tokens cumpliendo SLO por kW&lt;/strong> y por euro de hardware amortizado.&lt;/p>
&lt;h2 id="cómo-se-valida-el-sizing-antes-de-comprar">Cómo se valida el sizing antes de comprar&lt;/h2>
&lt;p>El sizing en hoja de cálculo es la primera mitad. La segunda es el benchmark de validación.&lt;/p>
&lt;p>&lt;strong>Stage 1 — sizing servilleta.&lt;/strong> Las fórmulas de este post sobre el SLO y el workload esperado. Salida: número aproximado de réplicas y topología.&lt;/p>
&lt;p>&lt;strong>Stage 2 — micro-benchmark sintético.&lt;/strong> En una GPU prestada o alquilada por días, levantar el motor con el modelo elegido y correr &lt;code>vllm bench serve&lt;/code> con prompts de longitudes representativas. Validar TPOT, prefill TPS y techo de concurrencia. Calibrar el factor de eficiencia HBM ($\eta$) usado en las fórmulas.&lt;/p>
&lt;p>&lt;strong>Stage 3 — load test con tráfico realista.&lt;/strong> Generar tráfico siguiendo la distribución real del workload del cliente (no Poisson, no constante: la traza real). Medir P50/P95/P99 de TTFT, TPOT, throughput. Confirmar el head-room.&lt;/p>
&lt;p>&lt;strong>Stage 4 — canary en producción.&lt;/strong> Con el cluster dimensionado, encaminar el 5–10 % del tráfico real durante 7–14 días antes de cerrar el procurement de hardware adicional. Ver &lt;a href="https://blog.lo0.es/posts/canary-blue-green-shadow-modelos-llm/">Canary, blue-green y shadow&lt;/a> para la mecánica.&lt;/p>
&lt;p>Saltar de Stage 1 a procurement total es la causa más frecuente de cluster sobredimensionado en el 40 % y subdimensionado en el 60 % al mismo tiempo, en regiones distintas del workload. Cuatro semanas de validación bien hechas ahorran cuatro meses de refactor.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-artículos">Lo que no hemos cubierto (próximos artículos)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Las métricas de observabilidad&lt;/strong> que cierran el bucle del sizing en producción — ver &lt;a href="https://blog.lo0.es/posts/observabilidad-gpu-dcgm-llm/">Observabilidad GPU para inferencia LLM&lt;/a>.&lt;/li>
&lt;li>&lt;strong>El autoscaling&lt;/strong> que ajusta réplicas a la curva real de tráfico — ver &lt;a href="https://blog.lo0.es/posts/autoscaling-llm-kubernetes-keda/">Autoscaling LLM en Kubernetes&lt;/a>.&lt;/li>
&lt;li>&lt;strong>El cost accounting&lt;/strong> detallado por tenant (showback / chargeback) sobre el hardware dimensionado.&lt;/li>
&lt;li>&lt;strong>El sizing para fine-tuning continuo&lt;/strong> (PEFT y entrenamiento ligero) que comparte cluster con la inferencia.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache: la memoria de trabajo que sostiene la inferencia LLM&lt;/a> — el componente que domina el presupuesto de VRAM.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/continuous-batching-fundamentos/">Continuous batching&lt;/a> — qué define la utilización efectiva del compute y la métrica goodput.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/disaggregated-serving-prefill-decode/">Disaggregated serving prefill/decode&lt;/a> — palanca avanzada para workloads asimétricos.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/moe-inference-fundamentos/">MoE inference&lt;/a> — cómo cambian las cuentas con modelos MoE.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/quantization-fundamentos-inferencia/">Quantization para inferencia&lt;/a> — qué cuesta y qué ahorra cada formato.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">Siete capas del stack de inferencia LLM on-premise&lt;/a> — las piezas que el sizing presupone.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/entornos-mixtos-nvidia-intel-servidores-nucs/">Entornos mixtos NVIDIA + Intel para inferencia LLM&lt;/a> — el sizing cierra mejor cuando se acepta heterogeneidad: embeddings y reranker en Intel Xeon AMX liberan H100 para el LLM grande, sin comprar más GPU.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>Kwon et al. — &lt;em>vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention&lt;/em> (SOSP 2023).&lt;/li>
&lt;li>Zhong et al. — &lt;em>DistServe: Disaggregating Prefill and Decoding for Goodput-optimized LLM Serving&lt;/em> (OSDI 2024).&lt;/li>
&lt;li>Agrawal et al. — &lt;em>Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve&lt;/em> (OSDI 2024).&lt;/li>
&lt;li>NVIDIA — &lt;em>H100 Tensor Core GPU Architecture Whitepaper&lt;/em> (memoria HBM3, bandwidth, FLOPs sostenidos).&lt;/li>
&lt;li>vLLM project — &lt;code>vllm bench serve&lt;/code> reference (CLI de benchmarking incluida en el repo).&lt;/li>
&lt;li>NVIDIA — &lt;code>genai-perf&lt;/code> (herramienta oficial para benchmark de servicios LLM).&lt;/li>
&lt;/ul></description></item></channel></rss>