PT|EN
Pular para o conteúdo

8 min de leitura

🇧🇷 Português  |  🇺🇸 Read in English

llama-server HTTP 500 context size has been exceeded: buffer KV compartilhado e como resolver

Mandei três requisições simultâneas para o llama-server. As três falharam juntas, 246 segundos depois de começar. Nenhuma retornou resultado parcial. O processo não caiu, o modelo não travou. O servidor continuou respondendo para chamadas novas, como se nada tivesse acontecido.

O erro era "Context size has been exceeded". E a causa não estava em nenhuma das três chamadas individualmente.

Sintoma medido: 0/3 com HTTP 500 aos 246 s

Os testes anteriores mediram o servidor com chamadas isoladas. O próximo passo era concorrência: três chamadas em paralelo, representando um workload real.

O prompt de produção que eu estimava em aproximadamente 3.000 tokens foi medido: 4.362 tokens de fato (resposta esperada de 1.738 tokens). Com esse número real em mãos, três chamadas simultâneas deveriam consumir cerca de 13.000 tokens de contexto ao mesmo tempo.

Resultado: 3 de 3 retornaram HTTP 500 “Context size has been exceeded” após 246 segundos.[1] A chamada isolada com a mesma configuração marcou 5,84 tok/s. Chamadas concorrentes: zero entregas.

A hipótese errada

A primeira leitura foi “o servidor ficou sem memória”. A segunda foi “o modelo é lento demais para três chamadas ao mesmo tempo”.

As duas estavam erradas. O processo continuava respondendo. A GPU não saturou. O problema não era o hardware.

Causa raiz: o buffer KV compartilhado

O llama-server, sem configuração explícita de paralelismo, abre 4 slots de contexto. Quando a flag --no-kv-unified não está presente, o servidor cria um único buffer KV compartilhado por todos os slots. O tamanho total desse buffer, na configuração padrão, era 16.384 tokens.

Com três chamadas de 4.362 tokens de prompt cada, mais os tokens de resposta em geração, o buffer compartilhado se esgotou antes que qualquer das três terminasse. O servidor não tem como liberar espaço parcialmente para uma chamada deixar outra terminar. Quando o buffer transborda, todas as requisições em voo recebem o mesmo erro ao mesmo tempo.

É como ter três pessoas escrevendo no mesmo bloco de notas de tamanho fixo. Não importa que cada uma tenha solicitado um pedaço separado: se o total ultrapassar o bloco, ninguém termina.

Por que o continuous batching não resolveu

O llama-server tem continuous batching ativo por padrão: o servidor pode processar tokens de múltiplos slots em paralelo no mesmo passo de inferência, aproveitando a GPU para várias requisições ao mesmo tempo. Isso melhora a utilização quando as chamadas têm tamanhos diferentes.

O que o continuous batching não resolve é o limite físico do buffer KV. O batching inteligente de tokens não aloca memória a mais: ele distribui melhor a computação dentro do que já foi alocado. Se o total de tokens em voo supera o buffer, o batching entra depois de o esgotamento já ter acontecido.

A distinção prática: continuous batching é otimização de throughput quando há espaço. KV unificado é a política de alocação desse espaço. As duas configurações são independentes.

Três configurações, três resultados

Testei três abordagens com o mesmo hardware e o mesmo modelo (Devstral Small 2 24B):

Config original: 4 slots automáticos, KV unificado de 16.384 tokens, KV na RAM.
Chamada isolada: 5,84 tok/s. Três simultâneas: 0/3 (HTTP 500 aos 246 s).

Config C2: --parallel 3 --no-kv-unified, 9.216 tokens por slot (3 × 3.072).
Cada slot tem seu próprio buffer de contexto fixo, sem compartilhamento. Chamada isolada: 7,93 tok/s. Três simultâneas: 3/3 OK, 2,15 tok/s cada, 615 s no total.[1]

Config C3P: 1 slot, ctx 12288, KV quantizado em q8_0 na GPU (sem --no-kv-offload).
Com KV na VRAM em precisão reduzida, 1 slot de 12.288 tokens ocupa 983 MB em vez dos 1,97 GB que ocuparia em fp16. Uma chamada isolada: 25,7 tok/s, concluída em 72 s. Duas em fila: 2/2 OK, 26,3 tok/s. VRAM alocada: 15.097 de 16.311 MiB.[1]

O comando que resumiu a diferença:

# 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

Como dimensionar o ctx para evitar o estouro

A lição direta é que o tamanho do buffer KV precisa ser calculado a partir do tamanho real das chamadas, não de estimativas.

O processo: medir o prompt real de produção antes de configurar o servidor. No caso deste workload, a estimativa inicial era 3.000 tokens; a medição real retornou 4.362. Essa diferença de 45% teria deixado qualquer buffer dimensionado pela estimativa subdimensionado desde o início.

Com o tamanho real medido, o dimensionamento correto para N slots é:

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 um servidor com carga variável, usar o p95 (e não o p50) como base é a diferença entre um buffer que às vezes estoura e um que não estoura na grande maioria dos casos.

A conta da memória

O KV cache do Devstral Small 2 24B tem 40 camadas, 8 cabeças KV, dimensão 128. Em fp16, o cache ocupa 160 KB por token. Em q8_0, metade: 80 KB. Para um contexto de 12.288 tokens: 80 KB × 12.288 = 983 MB em q8_0, contra os 1,97 GB que seriam necessários em fp16.

Essa diferença de quase um gigabyte fez o KV cache caber na VRAM junto com os pesos do modelo. E KV na VRAM significa que a GPU não precisa buscar os vetores de atenção na RAM a cada token.

Régua que prova: de 5,84 para 25,7 tok/s

A diferença entre a config original e a C3P:

Config original, chamada isolada: 5,84 tok/s (KV na RAM, margem zero para concorrência).
C3P, chamada isolada: 25,7 tok/s (KV quantizado na GPU, 1 slot, fila).

A nota honesta: a diferença de 5,84 para 7,93 tok/s entre a config original e a C2 pode ter outro consumidor de GPU no momento da medição. O salto de 7,93 para 25,7 tok/s vem da mudança de KV na RAM para KV na GPU, que é a diferença estrutural.

O que a configuração C3P sacrificou: concorrência verdadeira. Com 1 slot, as chamadas são serializadas. A segunda chamada espera a primeira terminar. Para workloads onde latência de cada resposta individual importa mais que throughput simultâneo, isso é um trade-off real. Para workloads de lote, onde o importante é que todas as chamadas terminem e nenhuma seja descartada, a serialização em alta velocidade vence a concorrência lenta.

O que custou

O prompt estimado em aproximadamente 3.000 tokens tinha 4.362. O custo de usar a estimativa em vez de medir: descobrir o bug apenas quando três chamadas simultâneas falharam ao mesmo tempo em produção.

A segunda lição: o erro "Context size has been exceeded" não informa qual das chamadas causou o estouro. Com buffer compartilhado, todas recebem o mesmo erro. Sem saber que o buffer era compartilhado, a interpretação óbvia é “uma das chamadas foi longa demais”, não “duas chamadas curtas somadas esgotaram o buffer comum”.

Medir o prompt real antes de testar concorrência teria evitado o ciclo de hipóteses erradas. A medição leva um comando:

llama-server --model modelo.gguf --tokenize --tokens-per-line 0 < prompt.txt | wc -l

No próximo post: o modelo que escreve 16 travessões por resposta, como identificar quais tokens do vocabulário BPE contêm o caractere, e como bani-los no nível do logit.


Assine a newsletter para acompanhar a série.


Notas
[1] Medições em 18/09/2026, dados brutos em bench_results.jsonl e lab_run.log. Configurações testadas no mesmo hardware (RTX 5060 Ti 16 GB), mesmo modelo (Devstral Small 2 24B Q4_K_M), mesmo prompt de produção real (4.362 tokens). A ressalva sobre 5,84 vs 7,93 tok/s está documentada no relatório fonte.
[2] Config C3P: ctx 12288, KV q8_0, flash-attn on, 1 slot. VRAM 15.097 de 16.311 MiB. Medição 18/09/2026 21:03-21:05.


Newsletter

Uma decisão técnica por post. Nada mais.



Dupla confirmação. Sem spam. Cancele quando quiser.

Mais posts no Diário da Máquina de LLM →

Newsletter

Uma decisão técnica por post. Nada mais.

Bastidores reais de quem constrói empresa operada por IA. Sem hype.

Dupla confirmação. Sem spam. Cancele quando quiser. Dados usados exclusivamente para esta newsletter (LGPD).