<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tpot on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/tpot/</link><description>Recent content in Tpot on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Mon, 01 Jun 2026 15:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/tpot/index.xml" rel="self" type="application/rss+xml"/><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>