Guias de IA · 12 min de leitura

O que é KV cache? Como LLMs reutilizam estados de atenção

Entenda o que é KV cache, como acelera a decodificação de LLMs, cresce com o contexto e se relaciona ao cache de prompts e aos custos de API.

Ilustração de uma sequência de blocos coloridos de tokens entrando em placas de cache empilhadas que emitem um fluxo de saída luminoso mais rápido

KV cache (cache Key-Value) é uma otimização de inferência na qual um modelo de linguagem de grande porte armazena, em cada camada, os tensores de atenção Key e Value calculados para tokens já processados. Durante a decodificação autorregressiva, o modelo reutiliza esses estados armazenados em vez de recalcular todos os tokens anteriores a cada etapa, reduzindo cálculos redundantes à medida que as sequências crescem.

Seja ao chamar APIs hospedadas ou executar modelos de pesos abertos em suas próprias GPUs, o KV cache está no centro da latência, da pressão sobre a memória e da taxa de processamento. Entender seu funcionamento explica por que o tempo até o primeiro token é percebido de forma diferente da velocidade de streaming, por que contextos longos consomem tanta VRAM e por que o cache de prompts entre solicitações de API exige prefixos exatamente iguais.

Como a atenção funciona sem KV cache?

Sem KV cache, um Transformer autorregressivo precisa recalcular do zero as projeções de atenção de toda a sequência sempre que gera um novo token em um modelo de IA.

Em cada camada do decodificador Transformer, a autoatenção projeta a representação de cada token de entrada em três vetores:

  • Query ($Q$): representa a posição atual que busca contexto relevante.
  • Key ($K$): representa o que cada posição oferece para correspondência com as Query.
  • Value ($V$): contém as informações agregadas depois que os pesos de atenção são calculados a partir dos produtos escalares Query-Key.

Como modelos de linguagem apenas com decodificador geram texto um token por vez, para gerar o token $t+1$ basta que o vetor Query do token mais recente atenda às Key e aos Value dos tokens de $1$ a $t$. Se o ambiente de execução descartar essas Key e esses Value depois de emitir o token $t$, a etapa $t+1$ precisará passar cada token do prefixo novamente por todas as camadas apenas para recriar exatamente os mesmos tensores $K$ e $V$. À medida que o prompt e a saída aumentam, esse trabalho repetido cresce quadraticamente ao longo de toda a geração.

Por que a atenção causal torna o KV cache possível?

O mascaramento causal garante que os tensores Key e Value de um token dependam apenas dos tokens anteriores, nunca dos tokens gerados depois.

Em um decodificador causal, da esquerda para a direita, o token $i$ é impedido de atender a qualquer posição $j > i$. Como as informações só avançam na sequência:

  1. A representação do token $i$ na camada $l$ fica completamente definida depois que os tokens $1 \dots i$ são processados.
  2. Acrescentar o token $i+1$ ao fim da sequência não altera os estados ocultos, as Key ou os Value das posições $1 \dots i$ em nenhuma camada.
  3. O ambiente de execução pode acrescentar os vetores recém-calculados $K_{i+1}$ e $V_{i+1}$ a um buffer por camada, o KV cache, e passar somente o token mais recente à próxima etapa de decodificação.

Como documentado no guia de KV cache do Hugging Face Transformers e na visão geral da arquitetura de cache do Transformers, o cache transforma cada etapa de decodificação de uma passagem pela sequência completa em uma atualização incremental de um único token, que lê as Key e os Value anteriores da memória.

Como Prefill e Decode usam o KV cache de formas diferentes?

A inferência de LLM se divide em duas fases distintas, Prefill e Decode, que interagem com o KV cache de maneiras fundamentalmente diferentes e encontram gargalos de hardware diferentes.

Fase de inferência O que acontece com o KV cache Principal gargalo de hardware Métrica visível ao usuário
Prefill (processamento do prompt) Calcula $K$ e $V$ em paralelo para todos os tokens do prompt e preenche o cache inicial em todas as camadas. Limitado por computação (grandes multiplicações de matrizes em todo o prompt). Tempo até o primeiro token (TTFT).
Decode (geração de tokens) Calcula $Q$, $K$ e $V$ para um novo token por vez, acrescenta a nova entrada $K$/$V$ e lê todo o histórico em cache. Limitado pela largura de banda da memória (carregamento dos pesos do modelo e dos estados KV crescentes a cada etapa). Latência entre tokens / velocidade de streaming da saída.

Como explica a explicação técnica da NVIDIA sobre Prefill, Decode e KV cache, a fase Prefill processa todos os tokens de entrada simultaneamente. Como o prompt completo é conhecido de antemão, as GPUs podem agrupar as projeções em operações densas de matrizes, saturar as unidades de computação e preencher o KV cache em uma passagem. Prompts longos, como contextos com vários documentos em pipelines de geração aumentada por recuperação (RAG), aumentam diretamente o trabalho de Prefill e o tempo até o primeiro token.

Após a emissão do primeiro token de saída, a inferência entra na fase Decode. Cada etapa processa apenas um novo token por sequência, acrescenta seus vetores Key e Value ao cache e lê todos os tensores $K$ e $V$ armazenados anteriormente na memória de alta largura de banda da GPU (HBM) para calcular a atenção. Como a intensidade aritmética por etapa é baixa enquanto as leituras de memória crescem com o comprimento da sequência e o tamanho do lote, a decodificação logo passa a ser limitada pela largura de banda da memória e pela VRAM disponível, em vez dos FLOPs brutos de computação.

Quanta memória um KV cache usa?

O espaço ocupado por um KV cache na memória cresce linearmente com o comprimento da sequência, o tamanho do lote, o número de camadas e o número de cabeças de atenção Key-Value.

A partir das dimensões dos tensores usados em caches Transformer padrão, nos quais cada camada armazena um tensor Key e um tensor Value com dimensões de lote, cabeças KV, comprimento da sequência e dimensão da cabeça, o tamanho do cache por sequência pode ser expresso simbolicamente assim:

bytes ≈ 2 × layers × kv_heads × head_dim × seq_len × bytes_per_element

Cada termo da fórmula representa um fator que arquitetos de modelos e engenheiros de inferência ajustam para controlar a memória:

  • 2: considera o armazenamento dos tensores Key (K) e Value (V).
  • layers: número total de blocos decodificadores Transformer no modelo.
  • kv_heads e head_dim: número de cabeças de atenção Key/Value por camada e dimensão de cada cabeça.
  • seq_len: total de tokens em cache, incluindo tokens do prompt e tokens gerados até o momento.
  • bytes_per_element: precisão numérica dos tensores armazenados, por exemplo, 2 bytes para FP16/BF16, 1 byte para formatos de 8 bits ou frações menores com quantização de KV cache em 4 bits, como NVFP4.

Reduzindo kv_heads: MHA, MQA e GQA

Na Multi-Head Attention clássica (MHA), cada cabeça Query tem sua própria cabeça Key e sua própria cabeça Value (kv_heads = query_heads). Quando as janelas de contexto e os tamanhos de lote aumentaram, armazenar projeções K e V separadas para cada cabeça se tornou um grande gargalo de memória.

Duas mudanças de arquitetura reduzem diretamente kv_heads na fórmula acima:

Além de GQA, os motores de inferência reduzem bytes_per_element ao quantizar os tensores K e V em cache para formatos de menor precisão, permitindo que contextos mais longos ou lotes simultâneos maiores caibam na memória da GPU.

Como os motores de serviço gerenciam a memória do KV cache em escala?

Motores de serviço em produção gerenciam a memória do KV cache dividindo tensores contíguos em blocos de tamanho fixo, para que GPUs possam acomodar mais solicitações simultâneas sem desperdiçar VRAM.

Em implementações ingênuas, um ambiente de serviço pré-aloca uma área contígua de memória da GPU dimensionada para o comprimento máximo possível da sequência de cada solicitação. Como os comprimentos reais de saída não são conhecidos antecipadamente, a pré-alocação causa fragmentação interna e externa grave da memória: espaços reservados ficam sem uso enquanto novas solicitações são impedidas de executar.

Kwon et al. abordaram esse gargalo com PagedAttention em Efficient Memory Management for Large Language Model Serving with PagedAttention (arXiv:2309.06180), o mecanismo central do vLLM. Inspirado na memória virtual e na paginação dos sistemas operacionais:

  • Blocos não contíguos de tamanho fixo: PagedAttention divide o KV cache de cada solicitação em blocos de tamanho fixo que guardam Key e Value para um número definido de tokens, mapeados por uma tabela de blocos lógicos para físicos.
  • Alocação sob demanda: novos blocos físicos são alocados apenas quando novos tokens são gerados, eliminando praticamente a fragmentação interna.
  • Compartilhamento de prefixo por bloco: quando várias solicitações compartilham um prefixo de prompt idêntico, como um prompt de sistema ou exemplos few-shot compartilhados, suas tabelas de blocos lógicos podem apontar para os mesmos blocos KV físicos na memória, usando cópia na escrita apenas quando as sequências divergem.

Na avaliação do artigo sobre vLLM (arXiv:2309.06180), esse gerenciamento paginado de KV alcançou uma taxa de processamento 2–4× maior que FasterTransformer e Orca com latência comparável, acomodando lotes consideravelmente maiores na mesma memória de GPU. Pilhas modernas de serviço, incluindo vLLM e o sistema de reutilização de KV cache do NVIDIA TensorRT-LLM, usam gerenciamento de KV baseado em blocos para reutilizar prefixos compartilhados e maximizar a concorrência.

Qual é a diferença entre KV cache e cache de prompts?

Um KV cache é o mecanismo de inferência subjacente por solicitação que reutiliza estados de atenção entre etapas de decodificação. Já o cache de prompts é um recurso de serviço e API entre solicitações que preserva e reutiliza esses blocos KV entre chamadas separadas com um prefixo idêntico.

Como desenvolvedores frequentemente encontram os dois termos ao otimizar latência e gastos, é útil separar o que acontece dentro de uma chamada de geração do que acontece entre várias solicitações de API:

Dimensão KV cache (decodificação dentro da solicitação) Cache de prompts (reutilização de prefixo entre solicitações)
Escopo Uma solicitação, ao longo das etapas de geração de tokens. Várias solicitações em um curto período de retenção.
O que evita Recalcular tokens anteriores na mesma resposta ao decodificar cada novo token. Executar novamente o Prefill em prefixos estáticos de prompt repetidos entre chamadas separadas.
Ciclo de vida Criado durante Prefill, ampliado durante Decode e liberado ou removido quando a solicitação termina. Mantido em armazenamento de blocos (memória da GPU ou do host), com política de remoção e TTL para chamadas futuras.
Visibilidade na cobrança Integrado à inferência padrão; não é cobrado como item separado. Frequentemente oferecido por provedores de API com preços reduzidos para tokens de entrada em cache e TTFT menor.

A atenção causal impõe uma regra rigorosa ao cache de prompts: o prefixo deve corresponder exatamente desde o primeiro token. Como os tensores Key e Value de cada token dependem da sequência exata de tokens anteriores, alterar um único carimbo de data e hora, ID de usuário ou instrução no início de um prompt invalida os estados $K$ e $V$ em cache de todos os tokens seguintes.

Para conhecer regras de retenção, limites mínimos de tokens e descontos específicos de cada provedor, consulte nosso guia completo sobre o que é cache de prompts e como os provedores o cobram.

Recomendações práticas para desenvolvedores e equipes

Entender o comportamento do KV cache se traduz diretamente em uma arquitetura de prompts melhor, latência mais estável e menores gastos com infraestrutura:

  1. Mantenha o conteúdo estático no início do prompt: coloque instruções de sistema, esquemas de ferramentas e documentos de referência compartilhados antes de qualquer entrada dinâmica do usuário ou metadado específico da solicitação. Um prefixo idêntico permite que APIs hospedadas e motores locais como vLLM ou TensorRT-LLM reutilizem blocos KV em cache em vez de executar passagens Prefill redundantes.
  2. Observe como o comprimento do contexto afeta a concorrência em GPUs hospedadas por você: como o tamanho do KV cache cresce linearmente com seq_len, dobrar o comprimento médio do contexto reduz pela metade o número de solicitações simultâneas que cabem na memória da GPU. Ao escolher modelos de pesos abertos para hospedagem própria, prefira arquiteturas com Grouped-Query Attention (GQA) e ambientes que suportem alocação paginada e quantização de KV.
  3. Reduza o contexto desnecessário mesmo com janelas de contexto enormes: fornecer históricos enormes em cada turno aumenta a latência de Prefill, a pressão sobre a memória KV e as cobranças por tokens. Combine cache de prefixos com recuperação direcionada e resumos periódicos, estratégias centrais abordadas em nosso guia sobre como reduzir custos de APIs de IA e em nossa comparação de preços de APIs de IA.

Perguntas frequentes

O KV cache altera a qualidade da saída do modelo?

Um KV cache padrão mantido na precisão de inferência nativa do modelo, como FP16 ou BF16, é matematicamente equivalente a recalcular o prefixo a cada etapa, pois o mascaramento causal impede que tokens futuros alterem vetores Key e Value anteriores. Compromissos de qualidade surgem apenas ao aplicar quantização com perda do KV cache, como compressão de 8 ou 4 bits, ou remover tokens antigos para economizar memória.

Por que um KV cache não pode reutilizar tokens no meio de um prompt se o início mudou?

Em um Transformer causal, a representação de cada token incorpora atenção sobre todos os tokens anteriores da sequência. Se os primeiros dez tokens de um prompt mudarem, os estados ocultos e, portanto, as projeções Key e Value do décimo primeiro token em diante também mudarão. Como esses estados dependem do prefixo exato anterior, motores de serviço só podem reutilizar blocos KV em cache entre solicitações quando o prefixo corresponde desde o início.

O KV cache armazena os pesos do modelo?

Não. Os pesos do modelo são parâmetros estáticos aprendidos durante o treinamento e compartilhados por todas as solicitações atendidas pela GPU. O KV cache contém tensores de ativação dinâmicos específicos de cada solicitação, ou seja, as projeções Key e Value dos tokens atualmente no contexto. Durante a inferência, a memória total da GPU precisa acomodar os pesos estáticos do modelo, as ativações temporárias do ambiente e os KV caches dinâmicos de todas as solicitações ativas no lote.

Por que gerar mais tokens de saída fica mais lento quando há alta concorrência?

Durante a fase Decode, cada etapa de um novo token exige a leitura dos tensores Key e Value armazenados para todos os tokens anteriores de cada solicitação ativa no lote. À medida que aumentam os comprimentos das sequências e o número de solicitações simultâneas, cresce o volume de dados KV transferidos da memória da GPU em cada etapa, saturando a largura de banda e obrigando o escalonador a limitar o tamanho do lote ou esperar pelas leituras de memória.

Fontes

Escrito por

Chunyu Cai

Chunyu Cai é CEO da Lotook, LLC e empreendedor. Escreve sobre modelos de IA, agentes e como eles mudam o trabalho cotidiano. Chunyu Cai tem um mestrado pela instituição Imperial College London.