Logo NascimentoIgor Nascimento / Blog
Inferência

TTFT e TPOT: as duas físicas que governam a latência de um LLM

Não adianta o painel mostrar 200 tokens por segundo se o usuário encara uma tela em branco por oito segundos antes da primeira palavra. Servir um modelo de linguagem envolve duas fases com gargalos físicos diferentes, que respondem a otimizações diferentes e que competem entre si. Uma conversa sobre prefill e decode, com os números de um servidor real em duas RTX 3060, e sobre como não gastar dinheiro no gargalo errado.

InferênciaInfraestrutura13 min de leituraIgor Nascimento

O erro de resumir performance a um número

Não adianta o monitor de recursos mostrar que o cluster bate 200 tokens por segundo se o usuário fica encarando uma tela em branco por oito segundos antes de a primeira palavra aparecer. Esse é o erro mais comum de quem começa a servir modelos de linguagem: resumir performance a um número só. A experiência percebida não é governada por uma velocidade, e sim por duas fases matematicamente distintas, com gargalos físicos diferentes, que respondem a otimizações diferentes e que frequentemente competem entre si.

Eu demorei para internalizar isso, e só internalizei olhando a telemetria do nosso próprio servidor. Então vou começar pelas duas fases e terminar nos números.

Fase 1, prefill: processar o prompt inteiro de uma vez

Quando você envia uma mensagem, o modelo precisa processar todos os tokens de entrada para construir o estado inicial: as matrizes de chave e valor de cada camada, que vão alimentar toda a geração seguinte. O tempo até o primeiro token aparecer é o TTFT, Time To First Token.

A característica decisiva dessa fase é que ela é massivamente paralelizável. Todos os tokens do prompt entram juntos na GPU e viram multiplicações de matrizes grandes, exatamente o formato que os Tensor Cores foram construídos para devorar. O custo aproximado é:

\[ \text{FLOPs}_{\text{prefill}} \approx 2 \cdot P \cdot n_{\text{tokens}} \]

em que \(P\) é o número de parâmetros do modelo. O trabalho cresce linearmente com o tamanho do prompt, mais a parcela da atenção, que cresce com o quadrado do comprimento e passa a dominar em contextos realmente longos. Por isso o prefill é limitado por computação: o teto é a capacidade bruta de cálculo do chip. Prompt gigantesco ou hardware sem TFLOPs suficientes, e o TTFT explode. É a sensação de congelamento.

Fase 2, decode: um token de cada vez

Depois que o primeiro token sai, o jogo muda por completo. A geração é autorregressiva: para produzir o token seguinte, a rede precisa percorrer todos os seus pesos de novo. Uma passagem inteira pelo modelo para produzir uma única palavra. A métrica aqui é o TPOT, Time Per Output Token, também chamado de latência entre tokens.

E aqui está o ponto que muita gente não internaliza: nessa fase, a GPU quase não calcula. Ela lê. Cada token gerado exige transportar todos os pesos da VRAM até os núcleos de cálculo e usar cada peso em uma única operação antes de descartá-lo. A intensidade aritmética, FLOPs por byte lido, despenca, e o chip fica ocioso esperando memória. O teto de velocidade de uma única sequência é, portanto, uma conta de banda:

\[ \text{TPOT}_{\min} = \frac{P \cdot b}{\text{BW}} \]

com \(b\) sendo os bytes por parâmetro (2 em FP16, 1 em INT8 ou FP8, cerca de 0,5 em INT4) e \(\text{BW}\) a largura de banda da VRAM.

A conta com hardware real

Uma RTX 3060 de 12 GB entrega cerca de 360 GB/s. Um modelo quantizado ocupando 10,8 GB precisa de 10,8 GB lidos por token, o que dá um teto de aproximadamente 33 tokens por segundo, por sequência, no melhor cenário imaginável. Distribuindo o modelo em duas 3060 com paralelismo de tensor, cada placa carrega metade e as bandas somam: o teto sobe para perto de 66, e na prática fica abaixo por causa da sincronização entre as placas. Nenhum ajuste de software quebra esse limite. Ele é física.

A diferença em números medidos

A melhor ilustração que já vi dessa assimetria é a telemetria do nosso servidor vLLM em operação. No mesmo instante, com o mesmo modelo, nas mesmas GPUs:

Cerca de 136 vezes de diferença entre processar entrada e produzir saída. Não são duas velocidades do mesmo processo. São dois regimes físicos distintos rodando no mesmo silício.

DimensãoPrefill (TTFT)Decode (TPOT)
Tokens por passoTodos os do promptUm por sequência
Gargalo dominanteComputação (TFLOPs)Banda de memória (GB/s)
Intensidade aritméticaAltaBaixíssima
Efeito de prompt longoCresce muito, até quadráticoCresce pouco, só o KV cache
Efeito de batch maiorSatura rápidoGanho quase linear
O que melhoraPrefix caching, chunked prefill, mais TFLOPsMais banda, quantização, batching, decodificação especulativa
Medido no painel12.968,6 tok/s95 tok/s

As alavancas que mexem o ponteiro

Prefix caching

Prompt de sistema fixo, instruções de ferramenta, histórico da conversa: tudo isso é recalculado do zero a cada requisição, a menos que o motor guarde o KV cache do prefixo comum. Com cache, o prefill só processa o sufixo novo. No mesmo painel, uma taxa de acerto de 97% no cache apareceu ao lado de um primeiro token em 0,87 segundo. Numa janela com 85,5% de acerto, o primeiro token foi a 1,72 segundo. A relação é direta e mensurável, e é a alavanca mais barata de todas.

Continuous batching

Em vez de montar um lote, processá-lo inteiro e só então aceitar o próximo, o escalonador insere requisições novas no ciclo de geração que já está rodando e remove as que terminaram. Como o decode é limitado por memória, rodar várias sequências ao mesmo tempo custa quase a mesma leitura de pesos: o throughput agregado sobe muito sem penalizar o TPOT individual na mesma proporção. É a única forma honesta de anunciar "160 tokens por segundo" sem mentir sobre a latência de cada usuário.

Chunked prefill

Um prompt de 40 mil tokens chegando no meio da geração de outra pessoa trava o ciclo e produz engasgos visíveis para quem já estava recebendo texto. Fatiar o prefill em pedaços e intercalá-los com passos de decode protege o TPOT de quem está no meio da resposta, ao custo de um TTFT um pouco maior para quem acabou de chegar.

Decodificação especulativa

Um modelo pequeno propõe vários tokens, o modelo grande verifica todos de uma vez. Como a verificação é uma operação paralela, ela ataca exatamente o gargalo do decode: amortiza uma leitura de pesos por vários tokens aceitos.

Desagregação de prefill e decode

Em escala, a solução mais radical: colocar as duas fases em nós diferentes, porque uma quer FLOPs e a outra quer banda, e otimizar hardware para as duas no mesmo lugar significa pagar caro nas duas pontas.

O trade-off de que ninguém escapa

Aumentar o batch melhora o throughput total e piora a latência individual. Priorizar quem acabou de chegar melhora o TTFT médio e piora o TPOT de quem já estava gerando. Não existe configuração ótima universal. Existe a configuração certa para o seu padrão de uso, e ela muda quando o padrão muda.

Goodput, não throughput

A métrica que uso para decidir é a vazão de requisições que respeitaram seus limites de latência, não a vazão bruta. Um servidor que faz 300 tokens por segundo violando o TTFT de metade dos usuários tem goodput pior que um que faz 180 respeitando todo mundo. E meça P95 e P99, não a média. A média esconde justamente o caso que faz o usuário reclamar.

Antes de comprar mais GPU

Se o seu problema é TTFT alto, comprar banda de memória não resolve. Investigue prefix caching, o tamanho real dos prompts e chunked prefill. Se o problema é texto saindo devagar, comprar TFLOPs não resolve. O caminho é banda, quantização e batching. Entender a divisão entre processar entrada e produzir saída é o primeiro passo para não gastar dinheiro no gargalo errado.

Resumo da conversa