KV cache, prefill e decode — a física da inferência

Esta é uma nota Magus: um aprofundamento da nota-mãe sobre atenção. Lá você aprende o que é a atenção e como o Transformer é montado. Aqui a pergunta muda: quando o modelo está rodando de verdade, o que custa caro — e por quê? Se você ainda não viu Query/Key/Value e a fórmula softmax(QKᵀ/√d_k)V, leia a nota 04 primeiro; o resto daqui assume isso.

TL;DR

A mesma fórmula de atenção roda sob duas físicas completamente diferentes durante a inferência. No prefill (processar o prompt), todos os tokens entram em paralelo: a GPU faz matmuls densos e fica compute-bound — limitada pela velocidade de cálculo. No decode (gerar a resposta token a token), cada token novo precisa “reler” todo o passado: a GPU fica memory-bound — limitada pela velocidade de leitura de memória. O que torna o decode memory-bound é o KV cache: a estrutura que guarda as Keys e Values de todos os tokens já vistos para não recomputá-los. Entender esse cache explica metade da engenharia de inferência moderna — e por que contexto longo é caro de um jeito que o tamanho do modelo não captura.

Por que isso importa

Quase toda métrica de produção de um LLM — latência, custo por token, quantos usuários cabem numa GPU, por que a primeira palavra demora e as seguintes saem rápido — cai diretamente desta divisão. Quem só conhece “o modelo tem N parâmetros” não consegue explicar por que dobrar o contexto pode quebrar o orçamento de memória enquanto o número de parâmetros não muda. A resposta mora aqui.

O custo quadrático — a conta que assombra o contexto

Antes das duas fases, é preciso ver de onde vem a pressão. O cálculo Q·Kᵀ compara cada token com todos os outros. Dobre a sequência e você não dobra o trabalho: você o quadruplica.

Tokens no contextoComparaçõesCusto relativo
1.0001.000.000
10.000100.000.000100×
100.00010.000.000.00010.000×
1.000.0001.000.000.000.0001.000.000×

É o famoso O(n²). É por isso que contextos de 1M de tokens exigem hardware especializado e a pilha de otimizações que detalho no broto irmão atenção eficiente.

As duas fases da atenção: prefill e decode

A inferência de um LLM não é um processo único. Ela tem dois atos, com gargalos opostos.

graph TD
    subgraph "PREFILL — processa o prompt inteiro"
        A["Prompt: 'Explique a relatividade geral\nem termos simples...\n(2000 tokens)'"] --> B["Atenção em paralelo\n sobre todos os 2000 tokens\n(matmuls densos, GPU quase 100%)"]
        B --> C["KV cache populado\n+ primeiro token gerado\n'A'"]
    end
    subgraph "DECODE — gera token a token"
        D["Token novo: 'A'"] --> E["Atende a TODO o\nKV cache (2000+ tokens)"]
        E --> F["Lê GBs de memória\npara gerar 1 token\n(GPU esperando memória)"]
        F --> G["Próximo token: 'relatividade'\n→ volta ao decode"]
        G --> E
    end
    C --> D
    style B fill:#99ccff,stroke:#0066cc
    style F fill:#ffcc99,stroke:#cc6600
FaseO que aconteceGargaloGPU utilization (H100)
PrefillO prompt inteiro é processado em paralelo — matmuls densos sobre milhares de tokensCompute-bound: limitado pela velocidade de cálculo~90-95%
DecodeCada token novo atende a todo o KV cache acumulado — uma passagem de olho por toda a memóriaMemory-bound: limitado pela velocidade de leitura de memória~10-30%

A intuição: no prefill, a GPU tem milhares de tokens para mastigar de uma vez — trabalho denso e paralelo, exatamente o que ela adora; ela passa quase todo o tempo calculando. No decode, ela gera um token de cada vez, e para isso precisa varrer o KV cache inteiro da memória. O cálculo em si é minúsculo; o tempo vai quase todo em esperar a memória chegar. A GPU fica ociosa, faminta por dados.

Uma metáfora

Prefill é ler um livro inteiro de uma vez com os olhos voando pela página — limitado pela velocidade de leitura do cérebro (compute). Decode é escrever a continuação palavra por palavra, relendo todo o livro a cada nova palavra — limitado pela velocidade de folhear (memória). É a releitura que mata; o KV cache existe para baratear essa releitura.

As consequências operacionais da divisão

Essa divisão explica fatos de produção que parecem desconexos quando você não sabe a causa:

TTFT e tokens/s são métricas completamente independentes:

graph LR
    subgraph "Métricas de inferência"
        T1["TTFT\n(Time-to-First-Token)\nMede o prefill\nReduz com batching menor\ne contexto curto"]
        T2["Tokens/s (throughput)\nMede o decode\nAumenta com batching maior\n(amortiza leituras de memória)"]
    end
    T1 -. "não correlacionados\nse a arquitetura não mudar" .-> T2

Um modelo pode ter TTFT alto (prefill lento) e throughput alto (decode rápido), ou o inverso. Ajustar um sem cuidado pode degradar o outro.

Armadilha: TTFT e throughput não são correlacionados

É tentador tratar “latência do LLM” como uma métrica única. Não é. TTFT mede o prefill (compute-bound); tokens/s mede o decode (memory-bound). Otimizar um não move o outro na mesma direção — reduzir o batch size pode melhorar o TTFT de uma request individual e ao mesmo tempo piorar o throughput agregado do servidor. Confundir as duas métricas leva a otimizar a coisa errada para o problema que o usuário realmente sente.

Batching melhora o throughput, mas não o TTFT:

Quando 8 usuários fazem decode simultaneamente, a GPU carrega o KV cache dos 8 em batch — a leitura de memória é amortizada entre 8 requests, dividindo o custo por 8. O throughput melhora linearmente com o batch size (até o limite de VRAM). O TTFT de cada request individual, porém, não melhora — o prefill é inerentemente sequencial dentro de uma request.

Prefill-decode disaggregation — a consequência arquitetural mais profunda:

graph LR
    subgraph "Infraestrutura moderna de LLM serving"
        U["Request do\nusuário"] --> P["GPU de Prefill\nOtimizada para compute\n(H100 SXM, NVLink)"]
        P -- "KV cache transferido" --> D["GPU de Decode\nOtimizada para memory bandwidth\n(H100 NVL, HBM3)"]
        D --> R["Tokens gerados\n→ usuário"]
    end
    style P fill:#99ccff,stroke:#0066cc
    style D fill:#ffcc99,stroke:#cc6600

Separar as duas fases em GPUs distintas (cada uma otimizada para seu gargalo) é a direção da infraestrutura de produção de 2025-2026. O custo: transferência do KV cache entre GPUs (que pode ser GBs por request).

O KV cache — o monstro de memória que governa a inferência

Você acabou de ver que o decode é memory-bound. O KV cache é a razão exata disso — e entender essa única estrutura explica metade da engenharia de inferência moderna.

O problema: Na geração autoregressiva, cada token novo precisa atender a todos os anteriores — ou seja, precisa das Keys e Values de todo o passado. Sem cache, a cada passo o modelo recomputaria K e V da sequência inteira: gerar o token 1.000 recomputaria 999 pares K/V; o token 1.001, mais 1.000… um desperdício O(n²) só para reconstruir o que já se sabia.

A solução: Computa-se K e V de cada token uma única vez, quando ele entra, e guarda-se na VRAM. Cada token novo só calcula o seu Q, K, V; os K/V antigos vêm do cache. O custo de gerar um token cai de O(n²) para O(n) — ao preço de carregar o cache inteiro da memória a cada passo. É exatamente esse carregamento que torna o decode memory-bound.

O custo: O cache cresce linearmente com o contexto, e a conta é brutal:

O 2 é um para K e outro para V. Para modelos reais (FP16, 2 bytes):

ModeloConfigKV/tokenCache p/ 100k tokens
Llama 2 70B (MHA)80 cam · 64 heads · d=128~2,5 MB~250 GB
Llama 3 70B (GQA)80 cam · 8 KV heads · d=128~0,31 MB~31 GB
xychart-beta
    title "KV cache (GB) por contexto — Llama 3 70B (GQA, 8 KV heads)"
    x-axis ["4k tokens", "16k tokens", "32k tokens", "64k tokens", "128k tokens"]
    y-axis "GB" 0 --> 40
    line [1.2, 5, 10, 20, 39]

Uma H100 tem 80 GB. Com os pesos do modelo ocupando ~35 GB (Llama 3 70B em BF16), sobram ~45 GB para o KV cache — o que corresponde a ~145k tokens de contexto em GQA. Um único usuário com contexto de 200k tokens já esgota a GPU. É por isso que toda a engenharia de inferência gira em torno de encolher esse cache.

Armadilha: dobrar o contexto não dobra o custo de compute

A tentação é pensar “2× mais tokens no prompt = 2× mais caro para rodar”. Falso em dois sentidos opostos. No prefill, o custo é O(n²) — dobrar o contexto quadruplica o compute, não duplica. No decode, o “custo” que explode não é compute, é memória: o KV cache cresce linearmente, mas é a VRAM disponível (fixa, ~80 GB numa H100) que quebra primeiro, muito antes de o compute virar o gargalo. Confundir “mais tokens” com “mais compute proporcional” é o erro clássico de quem estima capacidade de servir sem separar as duas físicas.

O cache é por-request e por-token

Diferente dos pesos do modelo (fixos e compartilhados entre todos os usuários), o KV cache é privado de cada conversa e cresce com cada token gerado. É por isso que servir muitos usuários com contextos longos esgota a VRAM muito antes de esgotar o compute — e por que prompt caching (reaproveitar o prefill de prefixos repetidos) virou uma alavanca econômica central.

Como explicar em inglês

LLM inference has two physically distinct phases. The prefill phase processes the entire prompt in parallel — massive matrix multiplications that keep the GPU compute-bound. The decode phase generates one token at a time, and each token must attend to every previously generated token via the KV cache: the GPU becomes memory-bound, spending most of its time waiting for data from HBM rather than calculating. The KV cache stores the Key and Value tensors of every seen token to avoid recomputing them, but grows linearly with context length — this is the single biggest factor explaining why doubling the context window can break the memory budget without changing the model size.

PTEN
PrefillPrefill
Decodificação (geração)Decode / decoding
Limitado por computeCompute-bound
Limitado por memóriaMemory-bound
Cache de Key/ValueKV cache
Tempo até o primeiro tokenTime-to-first-token (TTFT)
Tokens por segundoTokens per second (throughput)
Largura de banda de memóriaMemory bandwidth
Desagregação prefill-decodePrefill-decode disaggregation
Atenção por páginaPaged attention
Batching de requestsRequest batching

Ver mais

O que vem a seguir

Você já sabe por que o KV cache existe e por que ele cresce até quebrar o orçamento de VRAM. A pergunta natural agora é: dá para encolher esse cache sem perder qualidade? É exatamente aí que mora o broto irmão 04b - Encolhendo o KV cache — MHA, MQA, GQA, MLA — a família de variantes (Multi-Head, Multi-Query, Grouped-Query, Multi-Head Latent Attention) que ataca o tamanho do cache compartilhando ou comprimindo Keys e Values entre heads, trocando um pouco de expressividade por memória de sobra.

Veja também

Referências