PT|EN
Pular para o conteúdo

9 min de leitura

🇧🇷 Português  |  🇺🇸 Read in English

llama.cpp KV cache VRAM com Devstral 16 GB: de 0,33 para 19,3 tok/s

A máquina estava funcionando com llama.cpp, Devstral e 16 GB de VRAM. O modelo carregou sem erro. A primeira geração chegou com resposta correta em português.

E foi completamente inútil.

Neste post, descrevo o hardware que montamos para rodar LLM local, explico por que um modelo que cabe no SSD pode paralisar a GPU, mostro como a conta do cache KV decidiu qual configuração ficou em produção, e por que um dos dois modelos disponíveis não tem solução estrutural neste hardware.

Sintoma medido: 0,33 tok/s

Em 17 de setembro de 2026, na primeira tarde de testes, o servidor carregou o modelo Qwen3-30B-A3B com contexto de 8.192 tokens. A resposta chegou em português, correta, depois de longos segundos.

Medi: 0,33 tok/s de geração. Mais de três segundos por token. Uma resposta de 400 tokens levaria 20 minutos.

O monitor da GPU marcava aproximadamente 0% de utilização. O processo estava ativo, o modelo carregado, e a placa descansando enquanto a CPU esperava.

A hipótese errada

A suposição inicial era simples: modelo de 12,9 GB num disco, GPU com 16 GB de VRAM. A conta fechava. Com o arquivo inteiro na placa, a GPU deveria dominar a geração.

Estava errado. O que não entrou na conta foi o KV cache.

Causa raiz: a conta do KV

Cada modelo de linguagem com atenção multi-cabeça guarda, para cada token no contexto, os vetores K e V de todas as camadas. O Qwen3-30B-A3B tem 40 camadas de transformador, 8 cabeças KV por camada, com dimensão interna 128. Em fp16 (dois bytes por valor), o cache de uma única posição de contexto ocupa:

40 camadas × 8 cabeças × 128 dimensões × 2 (K e V) × 2 bytes = 163.840 bytes por token.

Com 8.192 tokens de contexto, o KV cache ocupa 1,31 GB. Os pesos do modelo já usavam 12,9 GB dos 16.311 MiB disponíveis. A soma ultrapassa o limite por cerca de 400 MB. O driver da NVIDIA derramou o excesso para a RAM do sistema sem nenhum aviso de erro.

O problema não é o tamanho do modelo. É o KV cache.

A largura de banda da VRAM de uma RTX 5060 Ti é de aproximadamente 448 GB/s. A RAM DDR4 a 3200 MT/s em dual channel atinge aproximadamente 40 GB/s no melhor caso real. Quando o KV cache fica na RAM, a GPU precisa buscar todos os vetores de atenção pela interface de memória do sistema a cada token gerado. O resultado direto: GPU em aproximadamente 0% e 0,33 tok/s de geração, com o gargalo sendo a RAM.

A via de dados que ninguém mencionou: PCIe

Existe um segundo gargalo que não aparece nas especificações do modelo: a interface PCIe.

O servidor usa PCIe 4.0 x8, que oferece aproximadamente 16 GB/s de banda bidirecional. Quando o KV cache está na RAM e a GPU precisa acessá-lo a cada camada de atenção, o caminho é: GPU requisita dado pela interface PCIe, a CPU busca na RAM, devolve pela mesma interface. Com 448 GB/s de banda interna e 16 GB/s de entrada, a GPU fica limitada pela porta de entrada, não pelo processamento interno.

O log do llama.cpp registra o número de tensor splits quando o modelo não cabe inteiramente na GPU:

graph splits (tensors moved to host): N

Com KV na VRAM: graph splits: 0. Com KV na RAM: o valor sobe com o tamanho do contexto. Esse campo é o indicador mais direto de que o gargalo é de transferência, não de computação.

A cura: ctx 4096

Reduzindo o contexto para 4.096 tokens, o KV cache passou para 655 MB. Com os 12,9 GB dos pesos, o total ficou em 13,6 GB, abaixo dos 16.311 MiB disponíveis. O servidor alocou 16.002 MiB, margem de 309 MB.

O log de inicialização confirmou:

INFO [server_context_init_load_model] llama_new_context_with_model:
  KV self size = 640.00 MiB, K (f16): 320.00 MiB, V (f16): 320.00 MiB
  graph splits (tensors moved to host): 0

Nenhum tensor foi para o host. Toda a computação passou a acontecer na GPU.

Régua que prova: 19,3 tok/s

Com o mesmo hardware, o mesmo modelo, o mesmo prompt, e apenas a mudança de contexto de 8.192 para 4.096:

37,2 tok/s de prompt e 19,3 tok/s de geração. GPU a 99%.

A diferença entre 0,33 tok/s e 19,3 tok/s, no mesmo hardware, veio de fazer caber o KV cache dentro da VRAM.

Calculando o KV para qualquer modelo

A fórmula é direta. Para qualquer modelo com atenção multi-cabeça:

bytes_por_token = n_layers × n_kv_heads × head_dim × 2 (K e V) × bytes_por_valor

Em fp16: bytes_por_valor = 2. Em q8_0: bytes_por_valor = 1. Em q4_0: bytes_por_valor = 0,5.

O resultado, multiplicado pelo ctx máximo desejado, é o que o KV cache vai ocupar. Esse número soma com os pesos do modelo para determinar se tudo cabe na VRAM.

Para o Devstral Small 2 24B (40 camadas, 8 cabeças KV, dimensão 128), em fp16, a conta é idêntica: 160 KB por token. Em q8_0, metade: 80 KB por token.

O caso sem saída: Devstral neste hardware

O Devstral Small 2 24B tem pesos de 14,6 GB em Q4_K_M. A VRAM disponível é 16.311 MiB (17,1 GB). Sobram 1,5 GB para o KV cache.

Com 160 KB por token em fp16 e 1,5 GB disponíveis, o contexto máximo possível dentro da VRAM é de aproximadamente 9.375 tokens. Esse valor ignora a memória de contexto do CUDA, os buffers de ativação e a sobrecarga do servidor: na prática, nada sobe para esse contexto sem derramar para a RAM.

A solução que funciona: KV quantizado em q8_0, que reduz o cache para 80 KB por token. Com ctx 12.288 e q8_0, o KV ocupa 983 MB, e o total (pesos + KV) fica em aproximadamente 15,6 GB, dentro dos 16,311 MiB. É exatamente o que a config C3P faz, e que os testes de 18/09/2026 confirmaram: 15.097 MiB alocados, 26,3 tok/s com duas chamadas em fila.

O Devstral só funciona neste hardware com KV quantizado. Não é uma limitação de configuração, é uma conta de memória.

O que a máquina é

Para contextualizar: Ryzen 7 5700X (8 núcleos, 16 threads), 64 GB DDR4 a 3200 MT/s em dual channel, SSD WD Green SN3000 de 1 TB, Windows 10 Pro. GPU RTX 5060 Ti 16 GB (16.311 MiB disponíveis) com driver 616.92 e limite de potência de 180 W. Interface PCIe 4.0 x8 (aproximadamente 16 GB/s bidirecional).

O servidor llama.cpp roda no build 11024. Um script PowerShell lê um arquivo de configuração no boot e reinicia o processo em 5 segundos se ele cair. O acesso é por VPN privada.

A máquina tinha pouco mais de um dia quando esses testes começaram: Windows instalado em 16/09 às 20h49, pasta de trabalho criada em 17/09 às 9h50, primeiro download do llama.cpp em 17/09 às 10h04.

O que custou

Meio dia de testes para descobrir que a conta que quebrou a primeira configuração não eram os pesos do modelo, eram os 160 KB de cache por token no contexto. A documentação do llama.cpp menciona os parâmetros, mas não faz a aritmética do que acontece quando a soma ultrapassa a VRAM.

O segundo custo foi entender que a largura de banda da RAM, e não apenas o tamanho da VRAM, define a velocidade de geração quando qualquer parte do KV cache escapa para o sistema. A diferença de 448 GB/s para 40 GB/s não é marginal: é um fator de 11 no teto teórico de leitura sequencial, e ela aparece como tempo de espera de CPU no monitor da GPU.

Os modelos em disco são: Devstral Small 2 24B Q4_K_M (13,3 GB no disco, 14,6 GB após load) e Qwen3-30B-A3B UD-Q3_K_XL (12,9 GB). Com a conta do KV para ctx 4096, o Qwen3 cabe com folga. O Devstral só cabe com KV quantizado em q8_0. Esse trade-off foi o início dos testes com as configurações seguintes.

No próximo post: três chamadas simultâneas que devolveram HTTP 500 ao mesmo tempo, e como passamos de 5,8 tok/s para 25,7 tok/s mudando apenas como o servidor distribui o KV cache entre slots.


Assine a newsletter para acompanhar a série.


Notas
[1] Medição em 17/09/2026, supervisor.log e saída direta do llama-server. Hardware descrito por leitura do sistema em 18/09/2026.
[2] Cálculo de KV: Qwen3-30B-A3B e Devstral Small 2 24B, ambos com 40 camadas, 8 cabeças KV, dimensão 128, fp16 (2 bytes). Fonte: estrutura dos modelos confirmada em 17/09/2026.
[3] Largura de banda: RTX 5060 Ti (~448 GB/s) e DDR4 3200 MT/s dual channel (~40 GB/s). PCIe 4.0 x8 (~16 GB/s). Valores aproximados de especificações públicas; nenhum benchmark de largura de banda foi rodado nesta máquina.
[4] Config C3P: ctx 12288, KV q8_0, flash-attn on, 1 slot. 15.097 MiB alocados, 25,7 tok/s em chamada isolada, 26,3 tok/s em 2 chamadas em fila. Medição em 18/09/2026.


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).