8 min de lectura
🇪🇸 Español | 🇺🇸 Read in English
llama-server HTTP 500 context size has been exceeded: búfer KV compartido y cómo resolverlo
Envié tres peticiones simultáneas al llama-server. Las tres fallaron juntas, 246 segundos después de empezar. Ninguna devolvió un resultado parcial. El proceso no se cayó, el modelo no se colgó. El servidor siguió respondiendo a nuevas llamadas, como si nada hubiera pasado.
El error era "Context size has been exceeded". Y la causa no estaba en ninguna de las tres llamadas individualmente.
Síntoma medido: 0/3 con HTTP 500 a los 246 s
Las pruebas anteriores midieron el servidor con llamadas aisladas. El siguiente paso era la concurrencia: tres llamadas en paralelo, representando una carga de trabajo real.
El prompt de producción que yo estimaba en aproximadamente 3.000 tokens fue medido: 4.362 tokens de hecho (respuesta esperada de 1.738 tokens). Con este número real en mano, tres llamadas simultáneas deberían consumir cerca de 13.000 tokens de contexto al mismo tiempo.
Resultado: 3 de 3 devolvieron HTTP 500 “Context size has been exceeded” después de 246 segundos.[1] La llamada aislada con la misma configuración marcó 5,84 tok/s. Llamadas concurrentes: cero entregas.
La hipótesis equivocada
La primera interpretación fue “el servidor se quedó sin memoria”. La segunda fue “el modelo es demasiado lento para tres llamadas al mismo tiempo”.
Ambas estaban equivocadas. El proceso seguía respondiendo. La GPU no se saturó. El problema no era el hardware.
Causa raíz: el búfer KV compartido
El llama-server, sin una configuración explícita de paralelismo, abre 4 slots de contexto. Cuando la bandera --no-kv-unified no está presente, el servidor crea un único búfer KV compartido por todos los slots. El tamaño total de este búfer, en la configuración por defecto, era de 16.384 tokens.
Con tres llamadas de 4.362 tokens de prompt cada una, más los tokens de respuesta en generación, el búfer compartido se agotó antes de que cualquiera de las tres terminara. El servidor no tiene cómo liberar espacio parcialmente para que una llamada deje terminar a otra. Cuando el búfer se desborda, todas las peticiones en curso reciben el mismo error al mismo tiempo.
Es como tener a tres personas escribiendo en el mismo bloc de notas de tamaño fijo. No importa que cada una haya solicitado un trozo separado: si el total sobrepasa el bloc, nadie termina.
Por qué el continuous batching no lo resolvió
El llama-server tiene continuous batching activo por defecto: el servidor puede procesar tokens de múltiples slots en paralelo en el mismo paso de inferencia, aprovechando la GPU para varias peticiones al mismo tiempo. Esto mejora la utilización cuando las llamadas tienen tamaños diferentes.
Lo que el continuous batching no resuelve es el límite físico del búfer KV. El batching inteligente de tokens no asigna más memoria: distribuye mejor la computación dentro de lo que ya fue asignado. Si el total de tokens en curso supera el búfer, el batching entra después de que el agotamiento ya ha ocurrido.
La distinción práctica: continuous batching es una optimización de throughput cuando hay espacio. KV unificado es la política de asignación de ese espacio. Ambas configuraciones son independientes.
Tres configuraciones, tres resultados
Probé tres enfoques con el mismo hardware y el mismo modelo (Devstral Small 2 24B):
Config. original: 4 slots automáticos, KV unificado de 16.384 tokens, KV en la RAM.
Llamada aislada: 5,84 tok/s. Tres simultáneas: 0/3 (HTTP 500 a los 246 s).
Config. C2: --parallel 3 --no-kv-unified, 9.216 tokens por slot (3 × 3.072).
Cada slot tiene su propio búfer de contexto fijo, sin ser compartido. Llamada aislada: 7,93 tok/s. Tres simultáneas: 3/3 OK, 2,15 tok/s cada una, 615 s en total.[1]
Config. C3P: 1 slot, ctx 12288, KV cuantizado en q8_0 en la GPU (sin --no-kv-offload).
Con KV en la VRAM en precisión reducida, 1 slot de 12.288 tokens ocupa 983 MB en lugar de los 1,97 GB que ocuparía en fp16. Una llamada aislada: 25,7 tok/s, completada en 72 s. Dos en cola: 2/2 OK, 26,3 tok/s. VRAM asignada: 15.097 de 16.311 MiB.[1]
El comando que resumió la diferencia:
# config original (falha com 2+ chamadas longas)
llama-server --model devstral.gguf --ctx-size 16384
# C3P (produção atual)
llama-server --model devstral.gguf --ctx-size 12288 --slots 1 \
-ctk q8_0 -ctv q8_0
Cómo dimensionar el ctx para evitar el desbordamiento
La lección directa es que el tamaño del búfer KV debe calcularse a partir del tamaño real de las llamadas, no de estimaciones.
El proceso: medir el prompt real de producción antes de configurar el servidor. En el caso de esta carga de trabajo, la estimación inicial era de 3.000 tokens; la medición real devolvió 4.362. Esa diferencia del 45 % habría dejado cualquier búfer dimensionado por la estimación subdimensionado desde el principio.
Con el tamaño real medido, el dimensionamiento correcto para N slots es:
ctx_por_slot = p95_de_prompt_mais_resposta
ctx_total = ctx_por_slot × N_slots (para KV unificado)
ctx_total = ctx_por_slot (para KV por slot, --no-kv-unified)
Para un servidor con carga variable, usar el p95 (y no el p50) como base es la diferencia entre un búfer que a veces se desborda y uno que no se desborda en la gran mayoría de los casos.
El cálculo de la memoria
El KV cache del Devstral Small 2 24B tiene 40 capas, 8 cabezas KV, dimensión 128. En fp16, el caché ocupa 160 KB por token. En q8_0, la mitad: 80 KB. Para un contexto de 12.288 tokens: 80 KB × 12.288 = 983 MB en q8_0, frente a los 1,97 GB que serían necesarios en fp16.
Esa diferencia de casi un gigabyte hizo que el KV cache cupiera en la VRAM junto con los pesos del modelo. Y KV en la VRAM significa que la GPU no necesita buscar los vectores de atención en la RAM en cada token.
La prueba definitiva: de 5,84 a 25,7 tok/s
La diferencia entre la config. original y la C3P:
Config. original, llamada aislada: 5,84 tok/s (KV en la RAM, margen cero para concurrencia).
C3P, llamada aislada: 25,7 tok/s (KV cuantizado en la GPU, 1 slot, cola).
La nota honesta: la diferencia de 5,84 a 7,93 tok/s entre la config. original y la C2 puede deberse a otro consumidor de GPU en el momento de la medición. El salto de 7,93 a 25,7 tok/s proviene del cambio de KV en la RAM a KV en la GPU, que es la diferencia estructural.
Lo que la configuración C3P sacrificó: concurrencia verdadera. Con 1 slot, las llamadas se serializan. La segunda llamada espera a que la primera termine. Para cargas de trabajo donde la latencia de cada respuesta individual importa más que el throughput simultáneo, esto es un trade-off real. Para cargas de trabajo por lotes, donde lo importante es que todas las llamadas terminen y ninguna sea descartada, la serialización a alta velocidad le gana a la concurrencia lenta.
Lo que costó
El prompt estimado en aproximadamente 3.000 tokens tenía 4.362. El costo de usar la estimación en lugar de medir: descubrir el bug solo cuando tres llamadas simultáneas fallaron al mismo tiempo en producción.
La segunda lección: el error "Context size has been exceeded" no informa cuál de las llamadas causó el desbordamiento. Con un búfer compartido, todas reciben el mismo error. Sin saber que el búfer era compartido, la interpretación obvia es “una de las llamadas fue demasiado larga”, no “dos llamadas cortas sumadas agotaron el búfer común”.
Medir el prompt real antes de probar la concurrencia habría evitado el ciclo de hipótesis equivocadas. La medición toma un comando:
llama-server --model modelo.gguf --tokenize --tokens-per-line 0 < prompt.txt | wc -l
En el próximo post: el modelo que escribe 16 guiones largos por respuesta, cómo identificar qué tokens del vocabulario BPE contienen el carácter y cómo prohibirlos a nivel de logit.
Suscríbete a la newsletter para seguir la serie.
Notas
[1] Mediciones del 18/09/2026, datos brutos en bench_results.jsonl y lab_run.log. Configuraciones probadas en el mismo hardware (RTX 5060 Ti 16 GB), mismo modelo (Devstral Small 2 24B Q4_K_M), mismo prompt de producción real (4.362 tokens). La salvedad sobre 5,84 vs. 7,93 tok/s está documentada en el informe fuente.
[2] Config. C3P: ctx 12288, KV q8_0, flash-attn on, 1 slot. VRAM 15.097 de 16.311 MiB. Medición 18/09/2026 21:03-21:05.
Newsletter
Una decisión técnica por post. Nada más.
Doble confirmación. Sin spam. Cancela cuando quieras.
Más posts en el Diario de la Máquina de LLM →
Newsletter
Una decisión técnica por publicación. Nada más.
Bastidores reales de quien construye una empresa operada por IA. Sin humo.
Doble confirmación. Sin spam. Cancela cuando quieras. Tu correo se usa solo para esta newsletter.