Este artículo es de Cristian Gutiérrez, ingeniero en Helmcode, y se publicó primero en su blog .
Antes de calcular cuántos usuarios aguanta tu GPU tienes que saber si son personas o agentes, porque una petición de un agente como Claude Code llega con unos 100.000 tokens, cuarenta veces un chat. Este artículo va de servir agentes con vLLM en tu propia infraestructura: qué cargan a la GPU, qué los hace viables, qué límite se acaba antes, y cómo hacer la misma cuenta con una gráfica de casa.
Es la charla Gigas vs tokens: desplegando un LLM en tu propia infra, que di en la Murcia Tech Talk del CEEIM, en el Campus de Espinardo. Es la misma que llevé al AWS Community Day con dos partes nuevas, una sobre qué pasa cuando los que usan la GPU son agentes y otra sobre la cuenta en una gráfica de 16 GB. La cuenta de qué hay dentro de una GPU de 96 GB, los dos límites y las preemptions las conté con detalle en ¿Cuántos usuarios aguanta tu GPU? Dimensionar un LLM por tokens , así que aquí las resumo. Las slides están en crstian.me/talks .
Todos los números salen de las máquinas de Helmcode, de producción o de un banco de pruebas con la misma GPU (una RTX PRO 6000 Blackwell de 96 GB), y en cada caso digo de cuál.
Lo importante
- Un agente no es un chat. Una sesión de Claude Code son unas 174 peticiones al modelo y cada una llega con unos 100.000 tokens, cuarenta veces un chat.
- Lo que hace viables a los agentes es el prefix caching. En nuestra producción el 81 % de lo que entra ya estaba en la caché. El problema es que esa caché es lo primero que se tira cuando falta memoria.
- Con agentes lo que se acaba antes suele ser el cálculo, no la memoria. El modelo denso tarda 88 s en empezar a contestar con la memoria al 43 %.
- MTP acelera escribir, no leer. Con un agente va más rápido; con 32, el primer token tarda más del doble.
- Para casa, llama.cpp y GGUF en 4 bits. En una gráfica de 16 GB caben unos 560.000 tokens con el modelo adecuado, que son cinco agentes o doscientos chats.
Los conceptos
Antes de entrar en la cuenta, cinco minutos de conceptos que salen todo el rato.
Todo se cuenta en tokens

Un modelo de lenguaje no ve palabras, ve tokens, que son trozos de texto. Este es el troceado de verdad con el tokenizador del modelo que tenemos en producción: "Cuántos usuarios aguanta mi GPU" son cinco palabras y diez tokens, y la misma pregunta en inglés son ocho. La barra baja que llevan algunos trozos es el espacio de delante, que va pegado a la palabra.
Los tokenizadores se entrenan sobre todo con texto en inglés, así que en español pagamos más tokens por lo mismo (la regla de que un token son unos tres cuartos de palabra es para inglés). Lo que le mandas al modelo es el prompt y lo que te devuelve es la respuesta, y las dos cosas se cuentan en tokens. El contexto son todos los tokens de una conversación, lo que ha dicho el usuario, lo que ha respondido el modelo y el historial entero, y cada modelo tiene un máximo que viene en su config.json. El que tenemos en producción admite hasta 262.144.
Un modelo es un montón de números
Esos números son los parámetros, o pesos, los valores que el modelo aprendió al entrenar. La B es de billion, miles de millones, así que un modelo 8B tiene ocho mil millones de parámetros. Se cargan enteros en la memoria de la GPU al arrancar y ya no cambian.
Generar un token es pasarlo por el modelo capa a capa, multiplicándolo por sus pesos. En un modelo denso pasa por todos los parámetros. En un MoE (mezcla de expertos) cada capa tiene muchos expertos y un router que elige unos pocos para cada token, así que cada token solo pasa por una parte pequeña del modelo: ocupa como uno grande y calcula como uno pequeño.
Cada parámetro ocupa según cómo lo guardes. El modelo sale del entrenamiento en 16 bits, dos bytes por parámetro, y cuantizar es guardarlo con menos bits: con 8 bits es un byte y con 4, medio. Se pierde algo de precisión y se gana mucha memoria. Lo que ocupa el modelo es fácil de calcular, parámetros por bytes por parámetro.
Todo esto viene en el nombre del modelo

El modelo que tenemos en producción, y el de toda la charla, es Qwen3.6-35B-A3B-NVFP4 , y su nombre lleva justo esas tres cosas:
- 35B son unos treinta y cinco mil millones de parámetros, que es lo que ocupa en memoria.
- A3B son los activos, tres mil millones. Es un MoE con 256 expertos por capa, y el router elige 8 para cada token. Los treinta y cinco mil millones tienen que estar cargados siempre, porque el siguiente token puede pedir cualquier experto.
- NVFP4 es la cuantización, el formato de 4 bits de NVIDIA, medio byte por parámetro.
Treinta y cinco mil millones por medio byte son unos 18 GB, y en la práctica son unos 20, porque no todo va en 4 bits: la atención va en FP8, los embeddings y el encoder de visión se quedan en 16 bits y cada bloque de 16 pesos guarda su escala. Cómo lo pasamos a NVFP4 en una sola RTX PRO 6000 está en este otro artículo .
Como lo voy a comparar todo el rato, el otro es Qwen3.8-27B-NVFP4, de la misma familia. No lleva A porque es denso: en cada token trabajan sus veintisiete mil millones.
Prefill y decode

Una petición tiene dos fases muy distintas, y la diferencia importa en todo lo que viene.
El prefill es leer lo que le mandas, el prompt entero, de golpe y en paralelo. Con un prompt de cien mil tokens es mucho cálculo y tarda. Lo que tarda en salir la primera palabra se llama TTFT (tiempo hasta el primer token), que es la cola más el prefill.
El decode es escribir la respuesta palabra a palabra. Para cada palabra nueva la GPU se lee el modelo entero de su memoria, y aquí lo que limita no es el cálculo sino lo rápido que se lee la memoria (los GB/s de la tarjeta). El ritmo de salida se mide como ITL, el tiempo entre un token y el siguiente.
La KV-caché

Para mí es el concepto más importante de la charla. Para generar cada palabra el modelo vuelve a mirar toda la conversación anterior, y como recalcularla entera cada vez sería lentísimo, guarda un resumen de cada token que ya ha visto. Ese almacén es la KV-caché (por key y value, los dos vectores que guarda por token).
Crece con cada token que escribe el usuario y con cada token que responde el modelo. Cada usuario que entra ocupa su propio sitio, y esa memoria no se libera hasta que termina su petición.
Cuantizar hace que quepa más y vaya más rápido

En 16 bits nuestro Qwen de 35B ocuparía unos 65 GiB, y en NVFP4 se queda en 20,4.
La KV-caché también se cuantiza, y es otra decisión aparte. En 16 bits cada token ocuparía 20 KB y la tenemos en FP8, a 10 KB, con lo que en los mismos 61 GiB caben el doble de tokens, 6 millones en vez de 3. Para dimensionar es la que más pesa, y además va más rápido, porque en el decode hay menos bytes que leer en cada paso.
Cuantizar la KV-caché también puede dar bastantes problemas. Se pierde precisión en lo que el modelo recuerda de la conversación y se nota más cuanto más larga es, así que yo mediría la calidad antes de cambiar de formato.
Lo que se pierde al pasar a 4 bits

Esta es la tabla que publica NVIDIA para nuestro modelo, el mismo NVFP4 que tenemos en producción: ocho benchmarks, en 16 bits y en 4. Lo máximo que pierde son ocho décimas, en contexto largo nada, y en dos sube (que es ruido de la medida).
Hay que mirar de dónde sale. Lo midió NVIDIA con benchmarks en inglés, y para cuantizar calibró con noticias de la CNN, también en inglés. El número del benchmark no es el tuyo: antes de cambiar de formato pruébalo con tus tareas y en tu idioma, porque puede variar bastante.
Leer el modelo para uno o para 128 cuesta lo mismo

En cada paso de decode la GPU se lee el modelo una vez, da igual para cuántas conversaciones, y saca una palabra para cada una. Lo medí en el banco de pruebas con el denso de 27B, con prompts de 2.000 tokens y respuestas de 500:
| Conversaciones a la vez | Tokens/s por usuario | Tokens/s en total | Cada paso |
|---|---|---|---|
| 1 | 64 | 64 | ~16 ms |
| 8 | ~60 | ~470 | casi igual |
| 128 | 12 | ~1.570 | 81 ms |
Con ocho a la vez el paso casi no crece, es trabajo casi gratis (como una paella para 128: encender el fuego cuesta lo mismo para uno que para todos). Con 128 la KV-caché y las cuentas de cada conversación ya pesan más que leer el modelo, cada paso tarda unas cinco veces más y a cada persona le llegan 12 tokens por segundo.
El máximo de conversaciones a la vez lo pone el operador con --max-num-seqs, y en producción lo tenemos en 128. Es una decisión de producto: más gente o más rápido para cada uno.
Si vienes de infra, ya sabes servir esto
Todo esto ya lo conoces con otros nombres:
| Inferencia | Lo que ya conoces |
|---|---|
| vLLM | El motor, un gunicorn especializado con API de OpenAI, /metrics y /health |
| LiteLLM | El API gateway de delante: keys, equipos y presupuestos |
| max-num-seqs | El maxConnections: cuántas conversaciones a la vez |
| KV-caché | La sesión de cada conexión en RAM, que crece con cada mensaje y no se libera hasta cerrarla |
| Preemption | Un eviction: se echa una conversación para hacer sitio y vuelve a la cola |
| TTFT · ITL | Tu latencia: lo que tarda la primera palabra y el hueco entre palabras |
Cómo sirve vLLM la petición de un agente
Esta slide es nueva y es la que más me gusta, porque junta todos los conceptos de arriba siguiendo el viaje de una sola petición:
- El agente manda la conversación entera, el historial más lo nuevo. Eso es el prompt.
- La petición espera en la cola. vLLM corre en la CPU del servidor, recibe la petición y la deja en la RAM hasta que haya sitio en la GPU, o sea, que no estén ocupadas las 128 plazas y que quede memoria.
- Si el principio de la petición ya pasó por la GPU hace poco y su KV-caché sigue en memoria, esa parte no se vuelve a calcular.
- En el prefill se lee todo lo nuevo de golpe. Los núcleos de la GPU traen los pesos de la memoria, hacen las cuentas de todos los tokens nuevos a la vez y guardan su KV-caché. Cuando sale la primera palabra, eso es el TTFT.
- En el decode sale una palabra por paso. En cada paso los núcleos traen los pesos una sola vez para todas las conversaciones, más la KV-caché de cada una, y sacan una palabra para cada una. El ritmo al que salen es el ITL.
- Cuando acaba, su KV-caché se queda como caché por si la siguiente petición empieza igual, hasta que otra conversación necesite el sitio.
La cuenta, en corto
Que el modelo quepa en la GPU no te dice para cuánta gente sirve. Cuánto ocupa un token lo sacas del config.json, y con nuestro modelo son unos 10 KB porque solo 10 de sus 40 capas guardan memoria. Su primo denso, Qwen3.8-27B, siendo más pequeño ocupa 34 KB por token.

En la misma GPU eso son 6,08 millones de tokens de conversación con uno y 1,9 millones con el otro. Y hay dos límites, los dos en tokens: cuántos caben, que es memoria y se calcula, y cuántos da tiempo a procesar, que es cálculo y hay que medirlo. El detalle de la cuenta está en el post del AWS Community Day .
Un agente no es un chat

Un agente es un modelo dentro de un bucle. El modelo decide qué hacer y el programa que tiene alrededor, el harness (Claude Code, OpenCode, el que sea), lo ejecuta con sus herramientas y le devuelve el resultado.
Si le pides que arregle el CSS de una portada, eso son varias peticiones seguidas, una para leer el fichero, otra para editarlo y otra para comprobar el build. Cada una lleva delante todo lo anterior: las instrucciones, el fichero entero y lo que el modelo ha ido pensando. Para el servidor, una tarea de una frase son decenas de peticiones con toda la conversación delante.
Cuántas peticiones son

Hay un dataset público con sesiones reales de Claude Code que publicaron SemiAnalysis y Weka, y es el que usé en el banco de pruebas:
| Sesiones | 393 |
| Peticiones al modelo | 68.266 |
| Peticiones por sesión | ~174 |
| Lanzadas por subagentes | 58 % |
| Tokens de entrada por petición (media) | ~101.000 |
Un chat normal trae unos 2.500 tokens y una petición de un agente, cuarenta veces más. Así que antes de preguntarte cuántos usuarios te entran en una GPU, tienes que saber si son personas o agentes. En nuestra producción hay de las dos cosas: la mitad de las peticiones traen menos de 2.000 tokens y casi una de cada cuatro pasa de 50.000.
Lo que ya se leyó no se vuelve a leer

Lo que salva a los agentes es que cada petición se parece muchísimo a la anterior, todo lo de antes más un trozo nuevo. vLLM lo aprovecha con el prefix caching (--enable-prefix-caching): si el principio de la petición ya lo procesó hace poco, reutiliza esa KV-caché y solo hace el prefill de lo nuevo.
En nuestra producción, en una semana, el 81 % de lo que entra ya estaba en la caché. Solo hay que leer una de cada cinco partes.
El problema es dónde vive esa caché. Vive en el hueco de la KV-caché que dejan libre las conversaciones que están en marcha, así que cuando ellas necesitan sitio lo guardado es lo primero que se tira. En ese momento cada vuelta del agente vuelve a leer sus cien mil tokens enteros, que es justo el trabajo que más cálculo cuesta.
Con agentes se acaba antes el cálculo
En el post anterior conté cómo una pregunta corta pasaba de 7 segundos a casi 10 minutos en el banco de pruebas, y que el primer tramo de esa subida pasaba con la memoria a medias y sin ninguna preemption. Lo que se acababa era el tiempo de GPU, porque cada conversación larga hay que leerla entera antes de contestar.

Reproduciendo en el banco el tráfico real de producción, con la misma GPU y los mismos flags, el primer token (p99) queda así:
- Denso, Qwen3.8 27B: 88 segundos, con la memoria al 43 % y cero preemptions.
- MoE, Qwen3.6 35B-A3B: 23 segundos, con la memoria al 8 %.
Con 24 sesiones reales de agentes de programación a la vez, al denso le llegan 3 tokens por segundo a cada usuario, que es inservible, y al MoE 30. En cada token del MoE solo trabajan 3.000 millones de parámetros, así que gana por los dos lados: menos memoria por token y menos cálculo por token.
MTP acelera escribir, no leer

En todas las charlas sale la misma pregunta: qué pasa si activo MTP. MTP es decodificación especulativa, el modelo adivina varias palabras por delante y luego las comprueba todas de golpe, y los Qwen nuevos ya lo traen de serie. Lo medí en el banco con el denso de 27B:
| Sin MTP | Con MTP | |
|---|---|---|
| 1 agente, tokens/s escribiendo | 60 | 87 |
| 32 agentes, primer token (p50) | 42 s | 109 s |
| Tokens que caben en la KV-caché | 1.905.817 | 1.700.274 (−11 %) |
Con un agente acelera el decode. Con muchos, las adivinanzas gastan cálculo, que es justo lo que ya faltaba, y el primer token tarda más del doble. Además los pesos de MTP le quitan un 11 % de sitio a las conversaciones.
Así que MTP ayuda cuando te falta velocidad para escribir (pocos usuarios y respuestas largas) y estorba cuando te falta cálculo (muchos agentes y contexto largo). En el MoE, con uno o dos usuarios, incluso frena: escribía tan rápido que pasó de 220 a 162 tokens por segundo. En producción no lo tenemos activado porque nuestro caso es el de muchos agentes, y además allí rompía las llamadas a herramientas.
La misma cuenta en una GPU de 16 GB
Esta es la otra parte nueva de la charla: la misma cuenta, pero con una gráfica normal. Sirve para entender el método con una tarjeta que tienes encima de la mesa antes de dimensionar nada en serio.
Motores para producción y para casa

| Con gente, en producción | En casa |
|---|---|
| vLLM: el de esta charla. Batching, KV-caché por bloques y prefix caching | llama.cpp: el motor de casi todo lo local. Funciona en una gráfica de jugar, en CPU o en un Mac |
| SGLang: muy parecido, muy fino reutilizando prefijos | Ollama: llama.cpp por debajo, con un comando |
| TensorRT-LLM: el de NVIDIA, el más rápido en sus tarjetas y el más difícil de montar | LM Studio: una aplicación de escritorio con su ventana de chat |
| Formato de Hugging Face (safetensors), cuantizado para la GPU: FP8, NVFP4 | GGUF: el modelo cuantizado y empaquetado en un solo fichero |
Para los que venimos de infra lo mejor es que todos sirven la misma API, la de OpenAI, así que cambias de motor y tu aplicación ni se entera.
Cuánto se pierde en GGUF

Unsloth midió los GGUF del denso de 27B. Cada punto es un fichero: cuanto más a la derecha, más grande, y cuanto más arriba, más se parece al original. Lo que mide es cuántas veces elige la misma siguiente palabra que el modelo en 16 bits, no cuántas tareas resuelve.
| Cuantización | Tamaño | Igual que BF16 |
|---|---|---|
| Q8 (~8 bits) | 27,7 GB | 98,7 % |
| Q4_K_M (~4 bits) | 16,1 GB | 95,8 % |
| IQ2_XXS (~2 bits) | 7,3 GB | 79,1 % |
De 8 a 4 bits se pierden tres puntos por la mitad de memoria, que es casi gratis. De 4 a 2 se pierden veinte. La curva no baja en línea recta: por debajo de unos 3 bits se desploma, así que para casa yo me quedaría con 4 bits. Esta gráfica es de un empaquetador y no una prueba independiente.
La cuenta con un modelo de 9B

Para casa no cojo el de 27B, cojo uno pequeño y muy descargado, Ornith 1.5 9B en Q4_K_M, y sigo la misma receta:
- Los pesos son 5,78 GB de fichero, unos 5,4 GiB en la tarjeta.
- El motor y su estado se llevan alrededor de 1 GiB, más medio de margen. Esto es una estimación: mira tu log.
- Para conversaciones quedan 9,1 GiB.
- Para la memoria por token, su
config.jsondice 32 capas y solo 8 guardan memoria (layer_types), con 4 cabezas KV de 256. Con la caché enq8_0(32 valores en 34 bytes) salen unos 17 KiB por token, la mitad que el denso de 27B. - Tokens que caben: 9,1 GiB ÷ 17 KiB, unos 560.000 tokens.
La cuenta del paso 4:
2 × 8 × 4 × 256 × 34/32 = 17.408 bytes ≈ 17 KiB por token Eso da para cinco agentes de 100.000 tokens a la vez, o unos doscientos chats, en una gráfica de jugar. Elegir bien el modelo cuenta más que la tarjeta: este 9B guarda la mitad por token que un denso de 27B.
Con qué arrancarlo
En casa, llama.cpp. Estos flags son la cuenta de arriba: el fichero, medio millón de tokens repartidos en cinco conversaciones de cien mil, y la KV-caché en q8_0:
llama-server \
-hf ornith-ai/Ornith-1.5-9B-GGUF:Q4_K_M \
--ctx-size 500000 --parallel 5 \
--cache-type-k q8_0 --cache-type-v q8_0 Con --parallel, llama.cpp reparte el contexto entre esas conversaciones. Hace batching continuo, pero no tiene la KV-caché por bloques ni el planificador de vLLM: está pensado para que te quepa, no para muchos usuarios.
Si hay gente detrás, vLLM. Estos son los flags que tenemos en producción:
vllm serve /models/qwen3.6-35b-a3b-nvfp4 \
--max-model-len 262144 \
--max-num-seqs 128 \
--kv-cache-dtype fp8 \
--enable-prefix-caching La receta en cinco pasos
Los tres primeros se calculan y los dos últimos se miden:
- Pesos: parámetros por bytes por parámetro, que lo sacas del nombre del modelo.
- Memoria por token: del
config.json, mirando cuántas capas guardan memoria. - Tokens que caben: lo que sobra de la GPU entre la memoria por token, o directamente la línea
GPU KV cache sizedel log de vLLM. - Usuarios: tokens que caben entre tokens por usuario. Y antes de eso, saber si son personas o agentes.
- Tokens por segundo: carga la GPU con tus conversaciones más largas (por ejemplo con AIPerf ) y mira el primer token. Si se dispara antes de que se llene la memoria, tu límite es el cálculo.
Preguntas frecuentes
¿Cuántas peticiones hace un agente como Claude Code?
En un dataset público de 393 sesiones reales de Claude Code salen de media unas 174 peticiones por sesión, con unos 101.000 tokens de entrada cada una. Más de la mitad las lanzan subagentes.
¿Qué es el prefix caching de vLLM?
Es reutilizar la KV-caché del principio de una petición cuando ya se procesó hace poco, para hacer el prefill solo de lo nuevo. Con agentes es lo que más ayuda: en nuestra producción el 81 % de los tokens de entrada ya estaba en la caché.
¿Merece la pena activar MTP?
Si tienes pocos usuarios y respuestas largas, sí, porque acelera la escritura. Con muchos agentes y contexto largo no: gasta cálculo, retrasa el primer token y le quita memoria a las conversaciones.
¿llama.cpp o vLLM?
llama.cpp para correr un modelo en casa, en cualquier GPU, CPU o Mac, con pocos usuarios. vLLM para servir a mucha gente a la vez en GPUs de servidor. Los dos hablan la API de OpenAI.
¿Qué cuantización GGUF elijo?
Q4_K_M, que usa la mitad de memoria que 8 bits perdiendo muy poco. Por debajo de 3 bits la calidad cae en picado.
¿Cuánta VRAM necesito para un LLM en local?
La de los pesos, más la KV-caché de las conversaciones que quieras tener abiertas, más un poco para el motor. Con un modelo de 9B en Q4_K_M y 16 GB caben unos 560.000 tokens de conversación.
Si quieres ver esto funcionando con mucha gente encima, es lo que corre en NaN . Gracias a Murcia Tech por la invitación.