GuideLLM a fondo: validar el SLO bajo carga y dimensionar desde el codo
Notación: importes en euros (N €), decimales con coma. No se usa el símbolo de dólar (en este sitio es delimitador de fórmula).
Qué cubre este artículo
Tercer artículo del track de benchmarking (B3). En B2 se vio el catálogo de herramientas; aquí entramos en GuideLLM a fondo, porque es la que responde la pregunta operativa que dimensiona una plataforma: ¿hasta dónde puedo cargar este motor sin romper el SLO? No es un benchmark de catálogo (“X tokens/s”), es un sweep dirigido por SLO que encuentra el codo —la capacidad segura— y del que sale el número de réplicas y el coste por token reales. Veremos sus modos de carga, cómo se define el SLO, cómo se lee la salida y cómo se convierte el codo en sizing y en euros. Con comandos reales; sin recomendaciones, solo la mecánica.
Por qué GuideLLM
GuideLLM (proyecto vLLM) genera patrones de tráfico realistas y configurables y captura distribuciones completas de TTFT, ITL y comportamiento de extremo a extremo, para evaluación dirigida por SLO, con sweeps reproducibles que identifican el rango de operación seguro (Red Hat). A diferencia de un micro-bench, mide el motor, no el cliente (carga multi-proceso), y a diferencia de MLPerf, dimensiona tu caso concreto (tu modelo, tu SLO, tu carga). Es la herramienta del medio: ni para tunear un flag, ni para comparar fabricantes, sino para decidir cuántas GPUs necesitas.
Los modos de carga (rate-types)
GuideLLM ofrece varios modos a través de --rate-type, y elegir el correcto es la mitad del
trabajo:
--rate-type | Qué hace | Cuándo |
|---|---|---|
| synchronous | una petición cada vez | latencia base, sin concurrencia |
| concurrent | mantiene N peticiones simultáneas fijas | medir a una concurrencia concreta |
| poisson | peticiones por segundo según Poisson | simular tráfico real (llegadas aleatorias) |
| throughput | satura el motor al máximo | capacidad bruta máxima |
| sweep | barrido automático de idle a máximo | hallar el codo (el más útil) |
En modo concurrent, el --rate "1,2,4" lanza tres benchmarks a esas concurrencias
(Medium · GuideLLM en OpenShift).
El modo poisson es el más realista para inferencia online (las peticiones no llegan a intervalos
regulares), y el sweep es el que automatiza la búsqueda del punto de saturación.
El sweep automático: de idle al codo
El modo estrella. Al correr un perfil de sweep, GuideLLM incrementa automáticamente la tasa de petición desde idle hasta el máximo throughput a lo largo de 10 rondas, y produce un informe HTML interactivo con latencia y throughput detallados (Red Hat · GuideLLM en Kubernetes). Durante el sweep, al detectar la saturación identifica la iteración anterior —el codo— y la devuelve como capacidad estimada; si no detecta saturación, hay que extender el sweep más allá del codo.
Ejemplo trabajado: leer un sweep de 10 rondas
Una salida ilustrativa de un sweep sobre un 70B en 8×H100 (SLO: TTFT P99 < 500 ms), por ronda:
| Ronda | Tasa (req/s) | TTFT P99 (ms) | ITL P50 (ms) | Throughput (tok/s) | Goodput (tok/s) |
|---|---|---|---|---|---|
| 1 | 2 | 95 | 19 | 480 | 480 |
| 3 | 8 | 140 | 20 | 1.900 | 1.900 |
| 5 | 14 | 240 | 22 | 3.100 | 3.060 |
| 6 | 17 | 460 | 24 | 3.400 | 3.330 |
| 7 | 20 | 760 | 31 | 3.700 | 2.520 |
| 9 | 26 | 1.500 | 48 | 3.950 | 900 |
| 10 | 30 | 2.400 | 71 | 4.000 | 380 |
Cómo se lee: hasta la ronda 6 (17 req/s) el TTFT P99 cumple el SLO (460 ms) y el goodput ≈ throughput (3.330 ≈ 3.400). En la ronda 7 el P99 ya rompe (760 ms) y el goodput cae a 2.520. El codo está entre la 6 y la 7: la capacidad segura es la de la ronda 6, ~3.330 tok/s de goodput. Las rondas 9 y 10 dan más throughput bruto (3.950, 4.000) pero con goodput de 900 y 380 —el sistema “rinde” sirviendo sobre todo peticiones que incumplen—. Quien reporte “4.000 tok/s” describe la ronda 10, donde el sistema está roto. El número defendible es 3.330 tok/s bajo TTFT P99 < 500 ms, y ese es el que entra en el sizing. Fíjate también en que el sweep tuvo que llegar hasta la ronda 10 (P99 de 2.400 ms) para ver dónde rompía: por eso hay que extenderlo más allá del codo.
Definir el SLO: el número que decide el codo
El “codo” no es absoluto: depende del SLO que definas. Un SLO típico de inferencia online:
| Métrica | Umbral de ejemplo | Qué protege |
|---|---|---|
| TTFT P99 | < 500 ms | la espera al primer token (interactividad) |
| TPOT / ITL P95 | < 50 ms/token | la “velocidad de tecleo” percibida |
| Tasa de error | < 0,1 % | fiabilidad |
El goodput —el throughput que cumple ese SLO— es la métrica que define el codo: la capacidad segura es la máxima carga donde el goodput ≈ throughput. Cambiar el SLO mueve el codo: un SLO de TTFT más estricto (200 ms) da un codo más temprano (menos capacidad segura) que uno laxo (1 s). Por eso el SLO se fija antes del sweep, y se reporta junto al resultado: una capacidad “de 3.330 tok/s” sin decir bajo qué SLO no significa nada.
Ejecución: el comando
Un sweep dirigido por SLO contra un endpoint vLLM:
guidellm benchmark \
--target "http://vllm:8000" \
--rate-type sweep \
--max-seconds 120 \
--data "prompt_tokens=1024,output_tokens=256" \
--output-path resultados.json
Parámetros clave: --rate-type sweep (el barrido automático), --data (la distribución de
longitudes de la carga, que debe parecerse a tu tráfico real), --max-seconds (duración por ronda),
--target (el endpoint). Para el SLO, GuideLLM permite definir las restricciones de latencia que
marcan el goodput. La salida va a --output-path en JSON, YAML o CSV, además del informe HTML
interactivo.
Definir la carga: el --data decide el resultado
El parámetro --data (la distribución de longitudes de prompt y salida) cambia el codo tanto como el
SLO. Tres formas, de menos a más fiel:
| Carga | --data | Fidelidad |
|---|---|---|
| Longitud fija | prompt_tokens=1024,output_tokens=256 | baja: el tráfico real no es fijo |
| Distribución sintética | rangos de longitud | media |
| Trazas reales | dataset de tu tráfico | alta: el codo que aplica |
La trampa: un sweep con prompts cortos de longitud fija da un codo optimista que no se parece a
producción, donde los prompts largos dominan el coste de prefill y adelantan el codo. Para
dimensionar de verdad, alimenta GuideLLM con la distribución real de tu tráfico (longitudes de
prompt y salida medidas en producción). El codo de un sweep solo es tan realista como la carga que
le metes; con --data irreal, dimensionas para un tráfico que no existe.
Además, el perfil de llegada importa: --rate-type poisson simula llegadas aleatorias (como el
tráfico real online), mientras que concurrent mantiene N fijas (como un batch). Un servicio online
medido con concurrent puede dar un codo distinto al real; para inferencia interactiva, poisson
es el perfil honesto.
La salida: qué leer
GuideLLM produce reportes estandarizados y exportables para dashboards, análisis y seguimiento de regresiones, en JSON, YAML y CSV (Red Hat). Lo que importa leer, por ronda del sweep:
- TTFT (P50, P95, P99) — la latencia al primer token.
- ITL/TPOT (P50, P95, P99) — entre tokens.
- Throughput (req/s y tok/s) — el bruto.
- Goodput — el que cumple el SLO (el número honesto).
El informe HTML interactivo permite ver las distribuciones completas (no solo medias), que es donde se ve la cola de latencia que una media oculta. Para el sizing y la comparación, el JSON es lo que se versiona y se mete en el harness reproducible.
Del codo al sizing y al coste
Aquí GuideLLM se conecta con el resto de la serie. Del codo sale el número de réplicas y el coste por token:
El cálculo: si el codo da un goodput de 3.330 tok/s por réplica y tu carga objetivo en pico es de 20.000 tok/s, necesitas 6 réplicas (20.000 ÷ 3.330 ≈ 6,0). Y el coste por token sale del coste de la réplica (de OpenCost, ~11 €/h) dividido por su goodput: ~0,92 €/1M tokens. Es el puente con el capacity planning y con el coste por token: el denominador correcto es el goodput del codo, no el throughput de catálogo. Dimensionar con el throughput bruto te deja corto de capacidad útil y rompe el SLO en producción.
Despliegue como Job de Kubernetes
GuideLLM se ejecuta como un Job de Kubernetes dentro del cluster para benchmarkear modelos servidos por la plataforma de orquestación (Red Hat). Esto es importante para la fidelidad: el generador de carga corre dentro del cluster, en la misma red que el endpoint, así que mide el motor sin la latencia de red de un cliente externo. El Job se parametriza con el target, el rate-type y la carga, y vuelca el JSON a un volumen o a un almacén para versionarlo. Correrlo como Job también facilita la automatización (CronJob para benchmarking periódico, o disparado por CI).
Validación de SLO en CI: detectar regresiones
El uso más valioso a medio plazo: un Job que corre un sweep corto en cada cambio (release de vLLM, cambio de config) y compara el goodput con la línea base. Si el goodput cae más de un umbral o el codo se adelanta, falla el pipeline. Como GuideLLM exporta JSON estandarizado para seguimiento de regresiones, comparar dos corridas es trivial. Así una regresión de rendimiento —que es una regresión de coste y de capacidad— se detecta en el commit, no en producción. No hace falta el sweep completo en cada commit: un sweep corto que cubra el codo basta para detectar la regresión; el exhaustivo, para los releases.
Comparar configuraciones con sweeps
GuideLLM brilla para decidir entre configuraciones corriendo el mismo sweep contra cada una. El
protocolo justo: misma carga (--data), mismo SLO, mismo hardware, variar solo la config. Ejemplos
de decisión que un sweep resuelve con datos:
| Decisión | Qué comparar | Qué revela el codo |
|---|---|---|
| FP16 vs FP8 | dos despliegues del mismo modelo | FP8 suele dar codo más alto (más goodput) |
max-num-seqs | distintos valores del flag | el que maximiza goodput bajo SLO |
| vLLM vs SGLang | dos motores, mismo modelo | qué motor da más goodput a tu SLO |
| tamaño de KV/precisión | configs de KV cache | el efecto en la capacidad |
Cada sweep da un codo; el codo más alto bajo el mismo SLO gana. Es el experimento controlado que llena la fila del cuadro de mando (artículo B8): no “X es más rápido” en abstracto, sino “X da Y tok/s de goodput más bajo este SLO concreto, lo que se traduce en Z réplicas menos y W € menos por millón de tokens”.
Observar los tres ejes durante el sweep
Un truco que conecta con el track de coste y energía: mientras corre el sweep, captura las métricas de GPU con DCGM. Así, de cada ronda sacas a la vez el goodput (GuideLLM), la potencia media (DCGM → J/token) y el coste (precio del nodo → €/token). En una sola campaña de sweep mides los tres ejes del cuadro de mando para cada punto de operación, y quedan coherentes por construcción (mismo instante, misma carga). En vez de un benchmark de rendimiento aislado, obtienes la fila completa —coste, rendimiento, energía— del codo, que es justo lo que la propuesta necesita. Alinea las ventanas temporales (la potencia de DCGM y las métricas de GuideLLM deben cubrir el mismo intervalo) o el J/token no corresponde al goodput medido.
Interpretar las distribuciones, no solo el codo
El codo es el titular, pero el informe HTML de GuideLLM da distribuciones completas, y ahí hay información que el codo resume. Tres lecturas adicionales:
- La forma de la cola. Dos configs con el mismo P99 pueden tener colas distintas: una con P99 a 500 ms y P99,9 a 600 ms es estable; otra con P99 a 500 ms y P99,9 a 3.000 ms tiene una cola larga que afectará a algunos usuarios de forma severa. La media y hasta el P99 lo ocultan; la distribución lo enseña.
- La separación TTFT/ITL. Ver las dos distribuciones por separado dice si el cuello es el prefill (TTFT alto) o el decode (ITL alto), lo que orienta la optimización (más concurrencia vs chunked prefill, etc.).
- La dispersión entre rondas. Si el goodput varía mucho entre rondas a la misma tasa, el sistema es inestable bajo carga —algo que un único número de capacidad no revela—.
El codo dimensiona; las distribuciones diagnostican. Para una propuesta, el codo es el dato; para operar y optimizar, las distribuciones son donde se ve qué arreglar.
GuideLLM en el harness reproducible
GuideLLM encaja como el motor de carga del harness reproducible (artículo S4). El patrón:
- Un script/Job que despliega el motor con la config pineada, calienta, corre el sweep y vuelca
el JSON con todos los metadatos (modelo, versión, hardware,
--data, SLO). - Nombrado por fecha y config para versionar las corridas.
- Almacén (repo git de JSONs o bucket) para reproducir y comparar.
- DCGM capturando en paralelo para añadir la energía a cada punto.
Así, “reproducir el codo de esta config” es un comando, no una tarde, y comparar dos releases es un diff de JSON. Es lo que convierte el benchmarking de una actividad puntual en una capacidad continua —y lo que permite que la cifra de capacidad de la propuesta venga con el banco para reproducirla.
El coste de un sweep (en euros)
Un sweep de 10 rondas a ~2 minutos por ronda ocupa el nodo ~20–30 minutos. A coste amortizado de ~11 €/h, son ~4–5,5 € por sweep completo de un modelo. No es mucho por corrida, pero un programa de benchmarking continuo (cada release, cada config, varios modelos) suma; por eso conviene automatizar el sweep como Job reproducible y correr sweeps cortos en CI (solo alrededor del codo) reservando el exhaustivo para los releases. El coste de medir es parte del coste de la plataforma —y pequeño frente al de dimensionar mal—.
El SLO por caso de uso: el codo se mueve con él
Como el SLO define el codo, distintos casos de uso dan capacidades distintas sobre el mismo hardware. Conviene tenerlo presente al dimensionar:
| Caso de uso | SLO típico | Efecto en el codo |
|---|---|---|
| Chat interactivo | TTFT P99 < 500 ms, ITL < 50 ms | codo temprano (latencia manda) |
| Copilot de código | TTFT P99 < 300 ms | codo aún más temprano |
| Batch / resúmenes | sin SLO de TTFT, maximizar throughput | codo tardío (casi throughput puro) |
| Agente (multi-paso) | latencia por paso acotada | depende del nº de pasos |
La misma GPU sirve más carga de batch que de chat interactivo, porque el SLO de batch es laxo. Por eso dimensionar requiere un sweep por perfil de carga: no hay “la capacidad del nodo”, hay “la capacidad del nodo para este SLO”. Mezclar cargas con SLOs distintos en el mismo pool sin separarlas es una fuente clásica de SLOs rotos —el batch satura y el chat interactivo lo paga—. La solución conecta con el scheduling: separar pools o prioridades por SLO.
GuideLLM vs los otros: cuándo usar cuál
Para situarlo frente a las herramientas de B2:
| Pregunta | Herramienta | Por qué |
|---|---|---|
| “¿Hasta dónde cargo sin romper el SLO?” | GuideLLM | sweep dirigido por SLO, el codo |
| “¿Cuál es la capacidad máxima del endpoint?” | AIPerf | detección automática de saturación |
| “¿Mejora mi cambio de flag en vLLM?” | vllm bench serve | rápido, propio del motor |
| “¿Qué hardware/motor es mejor en abstracto?” | leer MLPerf | comparabilidad cross-vendor |
GuideLLM es la herramienta del dimensionamiento bajo SLO: cuando la pregunta es operativa (cuántas réplicas, qué coste por token a mi SLO), es la respuesta más directa. AIPerf y GuideLLM se solapan (ambos multi-proceso); la diferencia práctica es que GuideLLM está más orientado al SLO y al informe reproducible, y AIPerf a la capacidad máxima con detección automática. Muchos equipos usan los dos; lo que no se debe es comparar un resultado de uno con el de otro como si fueran la misma medida.
Checklist de un sweep defendible
Para que el codo de un sweep sostenga una decisión de sizing:
| Paso | Verificación |
|---|---|
| Carga realista | --data con la distribución de tu tráfico (o poisson) |
| SLO declarado | TTFT/TPOT con percentil y umbral fijados |
| Sweep extendido | llega hasta que el P99 rompe (pasa el codo) |
| Goodput, no throughput | la capacidad es el goodput bajo SLO |
| Job en el cluster | sin latencia de red de cliente externo |
| Salida versionada | JSON con todos los metadatos |
| DCGM en paralelo | energía por punto, opcional pero recomendado |
Si puedes marcar las siete casillas, el codo es un dato auditable; si falta cualquiera, es una anécdota. El sizing de la propuesta cuelga de que estas siete estén verdes.
Límites y trampas (data-driven)
- No extender el sweep más allá del codo. Si no detecta saturación, la capacidad estimada es la última ronda probada, no el codo real. Extiéndelo hasta que el P99 rompa.
- Carga irreal. Un
--datade longitudes fijas no se parece a tu tráfico; usa la distribución real (o poisson) para un codo que aplique. - Reportar throughput, no goodput. El codo se define por el goodput bajo SLO; el throughput bruto sobreestima la capacidad.
- SLO no declarado. Una capacidad sin el SLO bajo el que se midió no es comparable ni defendible.
- Cliente fuera del cluster. Correr GuideLLM desde fuera mete latencia de red en la medida; córrelo como Job dentro del cluster.
Con GuideLLM dominado, tienes la herramienta que convierte el rendimiento en un dato accionable: el codo, el sizing y el coste por token, todo de un sweep reproducible. El siguiente artículo (B4) entra en AIPerf de NVIDIA; este cierra la validación de SLO, que es la que dimensiona la propuesta.
Cierre
GuideLLM responde la única pregunta de rendimiento que dimensiona una plataforma: ¿hasta dónde cargo sin romper el SLO? Y la responde con la disciplina correcta —un sweep de idle a saturación que halla el codo, definido por el goodput bajo un SLO declarado, sobre una carga que se parece a la real—. De ese codo sale todo lo que la propuesta necesita: el número de réplicas, el coste por token (con el goodput como denominador, no el throughput de catálogo) y, si capturas DCGM durante el sweep, también la energía por token. El error que invalida el ejercicio es el de siempre: reportar el throughput máximo en vez del goodput, medir con una carga irreal, o no extender el sweep hasta ver dónde rompe. Hecho bien —como Job dentro del cluster, con la distribución real, el SLO fijado y la salida en JSON versionado— GuideLLM convierte el rendimiento de una cifra de marketing en un dato reproducible que aguanta una auditoría y dimensiona una arquitectura soberana con números. El codo no es el punto más rápido; es el punto donde tu plataforma sigue cumpliendo lo que prometió.
Ver también
- GenAI-Perf a fondo — el perfilador de NVIDIA y cómo se compara con GuideLLM ficha a ficha (rate-types, sweep de concurrencia, métricas).
- Comparativa de motores de serving (vLLM/SGLang/TRT-LLM/Dynamo) — tras validar los SLOs con GuideLLM, esta comparativa decide qué motor cumple la frontera de Pareto goodput-latencia.
- Sesgo de medición y reproducibilidad — los errores de setup que hacen que un sweep de concurrencia resulte no reproducible, aunque la herramienta esté bien configurada.
Fuentes
- Red Hat · GuideLLM: evaluar despliegues LLM para inferencia real — https://developers.redhat.com/articles/2025/06/20/guidellm-evaluate-llm-deployments-real-world-inference
- Red Hat · desplegar y benchmarkear vLLM con GuideLLM en Kubernetes — https://developers.redhat.com/articles/2025/12/24/how-deploy-and-benchmark-vllm-guidellm-kubernetes
- GuideLLM · PyPI — https://pypi.org/project/guidellm/
- GuideLLM · GitHub (proyecto vLLM) — https://github.com/vllm-project/guidellm
- Medium · GuideLLM en OpenShift (rate-types, ejemplo) — https://medium.com/@jajodia.nirjhar/exploring-guidellm-benchmarking-a-live-llm-on-openshift-ccc2d0841794