Capítulo 7 — Estratégias Avançadas de Batching
Sétimo post do passeio capítulo a capítulo pelo LLM Primer VI: Escalando Sistemas de IA. O capítulo que mostra por que o batching não é uma otimização, mas o movimento estrutural que torna a decodificação limitada por largura de banda tratável — e por que "lote" (batch) é um verbo, não um substantivo.
Por que este capítulo existe
Um único passo de decodificação lê dezenas de gigabytes de pesos da HBM para produzir um token. Se a GPU está transmitindo esses pesos de qualquer jeito, ela pode produzir um token para muitas requisições no mesmo passo, a custo marginal quase zero. Batching, portanto, não é uma otimização no sentido comum — é a única maneira de tornar a decodificação economicamente tratável em hardware limitado por largura de banda de memória. Mas a maneira óbvia de fazer batching colapsa ao contato com o mundo real, porque as requisições têm comprimentos diferentes e terminam em momentos diferentes. O Capítulo 7 percorre o caminho do esquema ingênuo que quase funciona até o escalonador em nível de iteração que os motores de produção de fato rodam, e nomeia a dívida que esse escalonador cria para o Capítulo 8 pagar.
7.1 O batching estático é derrotado pelo problema do que termina primeiro
O batching estático é o que todo mundo escreve primeiro: acumular requisições até atingir o batch B ou um timeout, rodar prefill em todo o lote no comprimento S_max, e rodar um loop de decodificação até que toda sequência do lote emita EOS. Os dois custos são catastróficos em combinação. O padding — o prompt de 50 tokens no mesmo lote que o prompt de 4.000 tokens paga 80 vezes o trabalho de prefill de que precisa. O que termina primeiro — o loop de decodificação roda até que todas as sequências terminem, então um lote com uma requisição de 20 tokens e outra de 2.000 tokens passa 99% do tempo com 31 slots produzindo tokens que serão descartados. A vazão média parece aceitável e a latência de cauda é dominada por qualquer requisição longa que por acaso caiu naquele lote. O batching dinâmico (timeout variável, admitir chegadas atrasadas no lote pendente) suaviza a espera mas não muda o esquema fundamental; ele herda o mesmo problema do que termina primeiro assim que a decodificação começa.
7.2 O batching contínuo escalona por iteração, não por requisição
O batching contínuo — o escalonamento em nível de iteração do Orca, o in-flight batching da NVIDIA, "o truque central" do vLLM e do TGI — é a mudança de variável. Em vez de comprometer um lote pela duração da geração, o escalonador revisita a composição do lote a cada passo de decodificação. Após cada passo: sequências terminadas deixam seus slots e liberam o KV; novas sequências da fila entram nos slots liberados; o passo conjunto de decodificação roda sobre o novo conjunto ativo. Duas propriedades da decodificação em LLM tornam isso possível. Cada passo de decodificação é estruturalmente a mesma operação — o kernel não se importa qual sequência está no slot 7 versus no slot 11. E cada sequência carrega seu próprio KV cache de forma independente, então sua memória simplesmente fica disponível quando ela sai. A unidade de justiça vira a iteração, não a requisição: uma requisição de 20 tokens termina em cerca de 20 iterações independentemente de quem mais está no lote, e uma de 2.000 tokens ocupa um slot pelo tempo que precisar sem manter as outras reféns. Em tráfego real de cauda longa — misturas de turnos curtos de chat e completions longas de RAG — a utilização da GPU em um modelo de 70B sobe da faixa de 10 a 20% típica do batching estático para 60 a 80%, e a latência p99 aperta drasticamente.
7.3 O chunked prefill unifica prefill e decodificação no mesmo chip
O batching contínuo ainda deixa uma tensão. Novas chegadas precisam de prefill, que é limitado por compute e prefere sequências longas por passo. Sequências em voo precisam de decodificação, que é limitada por largura de banda de memória e prefere lotes largos. Rodar um prefill completo de um prompt novo de 4.000 tokens no mesmo forward pass que o passo de decodificação de trinta sequências em andamento ou atrasa as decodificações em andamento (TTFT ruim para os usuários existentes) ou atrasa o prefill da nova chegada (TTFT ruim para o novo usuário). O chunked prefill divide o prefill em pedaços — digamos, 512 tokens por vez — e intercala esses pedaços com os passos de decodificação das sequências ativas. O forward pass único agora carrega ao mesmo tempo algum trabalho de prefill de novo prompt e algum trabalho de decodificação em andamento, e as duas fases compartilham o mesmo streaming de pesos. O caráter compute-bound do prefill absorve a aritmética; a demanda de largura de banda da decodificação é amortizada em mais trabalho útil por byte. As duas fases deixam de ser antagônicas em um único chip. O caso restante — tráfego suficiente para elas ainda brigarem — é o setup para o serving desagregado no Capítulo 12.
O que o Capítulo 7 prepara
O batching contínuo faz seu trabalho e, ao fazê-lo, expõe o KV cache como a restrição vinculante da concorrência. Cada sequência ativa carrega seu próprio KV, dimensionado em proporção ao seu comprimento atual; sequências entram e saem a cada passo; o motor não pode saber de antemão quão longa qualquer uma delas será. Um layout ingênuo de uma laje por slot devolve a maior parte do ganho do batching. O Capítulo 8 traz a solução do sistema operacional: dividir o cache em pequenos blocos físicos, desacoplá-los das posições lógicas de token com uma tabela de páginas, e deixar uma política de evicção reclamar ou compartilhar blocos entre sequências. O PagedAttention é o movimento que torna o problema de KV do batching contínuo tratável.
Próximo — Capítulo 8: Gerenciamento de KV Cache de Próxima Geração. PagedAttention, evicção H2O, InfiniGen e a economia do prefix caching.