Decodificação Especulativa

Visão geral

Decodificação especulativa (speculative decoding) é uma família de técnicas para acelerar a geração de texto autorregressiva usando um modelo “rascunho” rápido para propor múltiplos tokens e, em seguida, usando um modelo “alvo” maior (aquele em que você realmente confia pela qualidade) para verificar esses tokens em menos etapas, mais eficientes para a GPU.

Em alto nível:

  • O modelo rascunho gera rapidamente uma continuação curta (por exemplo, 4–16 tokens).
  • O modelo alvo avalia essa continuação proposta e:
    • aceita tantos tokens quanto forem consistentes com o modelo alvo, e
    • rejeita e corrige quando o rascunho diverge.
  • Se feito corretamente (via amostragem especulativa), a distribuição de saída pode ser exatamente a mesma como se você tivesse amostrado diretamente do modelo alvo — só que mais rápido.

Essa técnica é particularmente importante em Serviço de LLM porque ataca o principal fator de custo na geração interativa: latência token a token sob uma Arquitetura Transformer autorregressiva.

Por que a decodificação autorregressiva é lenta

A maioria dos LLMs gera texto de forma autorregressiva:

  1. Prediz a distribuição do próximo token (p(y_t \mid y_{<t}, x))
  2. Amostra ou escolhe o próximo token
  3. Anexa o token ao contexto
  4. Repete

Mesmo com um Cache KV, cada novo token exige um forward pass (forward pass) que depende dos tokens anteriores. Isso cria dois gargalos práticos:

  • Dependência sequencial: você não consegue obter o token (t+1) antes do token (t).
  • Baixa utilização de GPU em tamanhos de lote pequenos: o serviço interativo frequentemente roda com lotes pequenos e sequências “efetivas” curtas por etapa, o que pode levar a GPUs subutilizadas e alto overhead por token (lançamentos de kernel, movimentação de memória, escalonamento).

A decodificação especulativa reduz o número de “pontos de decisão” do modelo alvo ao permitir que o alvo verifique múltiplos tokens propostos de uma vez.

Ideia central: rascunhar e depois verificar

A decodificação especulativa usa dois modelos:

  • Modelo alvo (p): grande, caro, de alta qualidade. Isso define a distribuição da qual queremos amostrar.
  • Modelo rascunho (q): menor, mais barato, de menor qualidade, mas suficientemente parecido para propor tokens prováveis.

Uma única iteração especulativa funciona assim:

  1. O rascunho produz (k) tokens: (\hat{y}_{t:t+k-1}) amostrando de (q)
  2. O alvo avalia as probabilidades (p(\cdot)) para essas posições
  3. Aceita um prefixo dos tokens do rascunho (possivelmente todos os (k)), e então amostra um token “correto” quando ocorre a primeira rejeição
  4. Repete até alcançar o comprimento solicitado ou uma condição de parada

O ponto-chave é que o modelo alvo muitas vezes consegue avaliar os (k) tokens propostos de um modo mais eficiente em throughput do que fazer (k) etapas separadas por token, e pode fazê-lo com menos pontos de sincronização.

Amostragem especulativa (preservando exatidão)

A forma “exata” mais citada é frequentemente chamada de amostragem especulativa (speculative sampling) (por exemplo, descrita em trabalhos de Chen et al., 2023 e desdobramentos relacionados). “Exata” significa:

O texto gerado tem a mesma distribuição como se você amostrasse diretamente do modelo alvo (p).

Regra de aceitação (intuição)

Se o modelo rascunho propõe um token que o modelo alvo também considera muito provável, devemos aceitá-lo. Se o rascunho propõe algo que o alvo considera improvável, devemos rejeitar e reamostrar adequadamente a partir do alvo para corrigir a distribuição.

Formalmente, para um token proposto (y) em alguma posição, a probabilidade de aceitação é:

[ a = \min\left(1, \frac{p(y)}{q(y)}\right) ]

  • Se (q(y)) estiver próximo de (p(y)), a aceitação é alta.
  • Se o rascunho superestima um token (alto (q(y)), baixo (p(y))), ele é frequentemente rejeitado.

O que acontece na rejeição?

Quando a primeira rejeição ocorre na posição (j) entre os (k) tokens propostos:

  • Tokens antes de (j) são aceitos.
  • Na posição (j), amostramos de uma distribuição corrigida que garante exatidão. Uma forma comum é amostrar da parte positiva de (p - q) (os detalhes variam por implementação), garantindo que a marginal final corresponda a (p).

Esboço de pseudocódigo

Abaixo está um pseudocódigo conceitual para uma etapa especulativa. Implementações reais são fortemente otimizadas e tratam cuidadosamente o cache.

def speculative_step(prefix_tokens, k, draft_model, target_model):
    # 1) Draft proposes k tokens sequentially (fast model)
    draft_tokens = []
    for _ in range(k):
        draft_logits = draft_model(prefix_tokens + draft_tokens)
        y = sample_from_softmax(draft_logits[-1])
        draft_tokens.append(y)

    # 2) Target evaluates probabilities for draft tokens in context
    # In practice: run target on (prefix + draft_tokens) using KV cache for prefix.
    target_logits = target_model(prefix_tokens + draft_tokens)
    draft_logits_target = target_logits[-k:]  # logits aligned to each proposed position

    accepted = []
    for i, y in enumerate(draft_tokens):
        p_y = softmax(draft_logits_target[i])[y]
        q_y = draft_model.prob_of_token(prefix_tokens + accepted, y)  # conceptual
        a = min(1.0, p_y / q_y)

        if random_uniform() < a:
            accepted.append(y)
        else:
            # rejection: sample correction token from target-consistent distribution
            corrected_dist = correction_distribution(draft_logits_target[i], draft_model, prefix_tokens + accepted)
            y_new = sample_from_distribution(corrected_dist)
            return accepted + [y_new]  # stop early after first rejection

    # If all accepted, optionally sample one more token from target to keep progress moving
    next_logits = target_logits[-1]
    y_next = sample_from_softmax(next_logits)
    return accepted + [y_next]

Notas:

  • A “correction_distribution” é a parte sutil necessária para a exatidão.
  • Muitos sistemas em produção implementam uma variante ajustada para velocidade e simplicidade; algumas variantes podem estar aproximadamente corretas em vez de preservar a distribuição exatamente.

Decodificação gulosa e configurações determinísticas

Para decodificação gulosa (greedy decoding) (sempre escolher o argmax), a decodificação especulativa pode ser conceitualmente mais simples:

  • O rascunho propõe (k) tokens de forma gulosa.
  • O alvo verifica se cada token coincide com o que o alvo teria escolhido.
  • Aceita o prefixo correspondente; quando ocorre uma divergência, emite o token guloso do alvo.

Isso é mais fácil do que a amostragem especulativa estocástica porque você não precisa da matemática de aceitação-rejeição — mas só se aplica à decodificação determinística. Muitos sistemas do mundo real usam amostragem (temperatura, top-p) para assistentes de chat, onde o arcabouço de amostragem especulativa exata é importante.

De onde vem o ganho de velocidade

A decodificação especulativa não elimina computação magicamente; o modelo alvo ainda precisa avaliar logits para os tokens gerados. Os ganhos de velocidade vêm de efeitos práticos de sistema:

1) Menos “pontos de decisão do alvo”

A decodificação base exige que o alvo:

  • execute um forward pass,
  • produza logits,
  • amostre um token,
  • repita

A decodificação especulativa tenta reduzir com que frequência o alvo precisa sincronizar e amostrar ao aceitar múltiplos tokens por etapa de verificação do alvo.

2) Melhor eficiência de GPU com trabalho em blocos maiores

Ao verificar (k) tokens do rascunho, o alvo pode processar um “bloco” de novos tokens de um modo que pode ser mais amigável à GPU do que muitas etapas minúsculas — especialmente quando combinado com batching e fusão de kernels em stacks modernos de serving.

Isso interage com:

  • Cache KV: o prefixo é armazenado em cache; apenas novos tokens exigem trabalho incremental
  • Batching e Escalonamento de Inferência: a decodificação especulativa muda o formato do trabalho (menos etapas de verificação, porém maiores, em vez de muitas etapas de token único)

3) O modelo rascunho é mais barato por token

Mesmo que o rascunho gere tokens “desperdiçados” que sejam rejeitados, seu custo é muito menor do que o do modelo alvo. Se as taxas de aceitação forem razoavelmente altas, o overhead do rascunho é compensado por menos iterações do modelo alvo.

Métricas-chave e trade-offs

A decodificação especulativa é governada por um pequeno conjunto de métricas práticas.

Taxa de aceitação

Taxa de aceitação é a fração de tokens do rascunho que o alvo aceita.

  • Alta aceitação (por exemplo, 70–95%) tipicamente produz grandes ganhos de velocidade.
  • Baixa aceitação pode tornar a decodificação especulativa mais lenta do que a base, porque você paga o overhead do rascunho mais rejeições frequentes.

A taxa de aceitação depende de:

  • quão bem o rascunho corresponde à distribuição do alvo
  • configurações de decodificação (temperatura/top-p podem reduzir a aceitação)
  • mudança de domínio (rascunho treinado em estilo/dados diferentes)

Comprimento proposto \(k\)

Um (k) maior significa:

  • mais tokens potenciais aceitos por etapa de verificação (bom),
  • mas mais trabalho de rascunho e maior chance de ao menos uma rejeição (ruim).

Na prática, (k) costuma ser ajustado (e às vezes adaptado online) para maximizar tokens aceitos por unidade de tempo, e não a taxa de aceitação bruta.

Escolha do modelo rascunho

Estratégias comuns:

  • Modelo menor da mesma família (por exemplo, rascunho de 7B para um alvo de 70B)
  • Modelo rascunho destilado (distilled draft model) treinado para imitar os logits do alvo (pode aumentar a aceitação)
  • Modelo rascunho quantizado (quantized draft model) (barato de executar) enquanto mantém o alvo em maior precisão
  • Rascunhos especializados: por exemplo, rascunhos com fine-tuning para código, ou linguagem específica de um domínio

Em geral, o modelo rascunho precisa compartilhar:

  • Tokenizador/vocabulário com o alvo (ou ter um mapeamento cuidadoso)
  • Espaço de saída compatível (mesmos IDs de tokens)

Qualidade e correção

Há duas questões de “qualidade”:

  1. A decodificação especulativa muda a distribuição?

    • Se usar amostragem especulativa exata, não deveria mudar (em teoria).
    • Se usar heurísticas aproximadas, pode alterar levemente as saídas.
  2. Ela “parece” de menor qualidade?

    • Mesmo com amostragem exata, a aleatoriedade pode diferir de execução para execução; usuários podem atribuir diferenças à qualidade.
    • Com métodos aproximados, a qualidade pode degradar se a etapa de correção não for fiel.

Uma boa prática operacional é fazer testes A/B de:

  • métricas de qualidade percebida pelo usuário
  • métricas de toxicidade/segurança (já que o comportamento de amostragem pode mudar)
  • métricas de correspondência exata em benchmarks offline (quando aplicável)

Trade-offs de custo (computação e memória)

A decodificação especulativa adiciona custos:

  • Rodar dois modelos (mesmo que o rascunho seja pequeno) aumenta a pegada de memória.
  • Você mantém dois caches KV (rascunho + alvo), embora o cache do rascunho seja menor.
  • Se rascunho e alvo compartilham uma GPU, pode haver contenção; se em GPUs separadas, há custo adicional de infraestrutura.

Ela é mais atraente quando o modelo alvo é caro o suficiente para que a economia de iterações do alvo domine.

Arquiteturas práticas de serving

GPU única (rascunho + alvo no mesmo dispositivo)

Prós:

  • Implantação mais simples
  • Sem latência de rede entre rascunho e alvo

Contras:

  • Pressão de memória na GPU (dois modelos + caches)
  • Potencialmente menor throughput se o rascunho “roubar” ciclos do alvo

Melhor quando:

  • o rascunho é muito pequeno (ou fortemente quantizado)
  • a GPU tem folga de memória suficiente

Duas GPUs (rascunho em uma, alvo em outra)

Prós:

  • Paralelismo (o rascunho pode rodar enquanto o alvo verifica o bloco anterior)
  • Menos contenção

Contras:

  • Maior complexidade de orquestração
  • Possível overhead de comunicação (tokens precisam ser transferidos entre dispositivos, embora IDs de tokens sejam pequenos)

Melhor quando:

  • o modelo alvo grande satura sua GPU
  • você pode reservar uma GPU menor para o rascunho

Rascunho em CPU + alvo em GPU

Às vezes é viável se o rascunho for extremamente pequeno ou otimizado; frequentemente não é ideal porque a inferência em CPU pode ser lenta demais e adicionar variabilidade. Ainda assim, para certos ambientes (edge, GPU limitada), pode ser uma opção pragmática.

Interação com cache KV e escalonamento

Gerenciamento do cache KV

A decodificação especulativa complica o cache porque:

  • O modelo rascunho avança seu próprio estado interno ao propor tokens.
  • O modelo alvo deve verificar esses tokens; se forem rejeitados, você precisa reverter ou gerenciar cuidadosamente quais entradas de KV são confirmadas.

Implementações em produção frequentemente usam estratégias como:

  • Buffers de staging para entradas de KV “tentativas” até que a aceitação seja finalizada
  • Invalidação eficiente das últimas posições em caso de rejeição
  • Kernels de atenção em blocos que suportem anexar múltiplos tokens

Entender o comportamento do Cache KV é crítico: uma implementação ingênua de “recalcular tudo na rejeição” pode anular os ganhos especulativos.

Batching e escalonamento

Em serving multi-tenant, a decodificação especulativa afeta como as requisições são intercaladas:

  • Cada requisição alterna entre etapas do rascunho e etapas de verificação do alvo.
  • As etapas de verificação podem processar números variáveis de tokens dependendo da aceitação.

Escalonadores podem precisar levar em conta:

  • justiça (impedir que uma requisição monopolize a verificação)
  • throughput (agrupar blocos de verificação de comprimentos semelhantes)
  • latência de cauda (tail latency) (evitar esperar demais para formar lotes)

Isso se conecta diretamente a Batching e Escalonamento de Inferência.

Exemplo trabalhado (estimativa rápida)

Suponha:

  • Modelo alvo: 70B parâmetros
  • Modelo rascunho: 7B parâmetros
  • O rascunho propõe (k = 8) tokens por iteração
  • Taxa média de aceitação: 75%

Então, em média, cada iteração aceita cerca de (8 \times 0.75 = 6) tokens antes de um evento de rejeição/correção (intuição bem aproximada; o comportamento real depende de onde as rejeições ocorrem).

Compare:

  • Base: o alvo precisa fazer ~1 “etapa de decisão” por token → ~6 etapas do alvo para 6 tokens
  • Especulativa: o alvo pode verificar um bloco e confirmar múltiplos tokens com menos pontos de amostragem/sincronização

Mesmo que o alvo ainda compute logits para essas posições, o sistema pode:

  • reduzir o overhead por token,
  • aumentar a utilização da GPU,
  • reduzir a latência ponta a ponta por token gerado.

Se a aceitação cair para 30%, você pode aceitar apenas ~2–3 tokens por bloco e passar mais tempo rascunhando e rejeitando — frequentemente perdendo qualquer benefício.

Armadilhas comuns

  • Rascunho fraco / desalinhado: baixa aceitação, lentidão, ou mudanças na saída.
  • Incompatibilidade de tokenizador: torna a verificação difícil ou impossível sem mapeamento complexo.
  • Lógica de correção incorreta: pode enviesar silenciosamente as saídas para longe da distribuição do alvo.
  • Pressão de memória: dois modelos + caches podem reduzir o máximo de requisições concorrentes e prejudicar o throughput.
  • Ajuste ruim de (k): grande demais aumenta a probabilidade de rejeição; pequeno demais reduz os ganhos potenciais.
  • Efeitos de temperatura/top-p: mais aleatoriedade pode reduzir a aceitação; considere ajustar parâmetros de decodificação para endpoints sensíveis à latência.

Quando a decodificação especulativa é uma boa opção

Use quando:

  • Você tem um modelo alvo grande em que a latência por token é cara.
  • Você consegue rodar um modelo rascunho menor e razoavelmente alinhado.
  • Sua carga de trabalho é sensível à latência (chat, autocompletar) em vez de jobs em lote voltados apenas a throughput.
  • Você tem capacidade de engenharia para integrar e monitorar a complexidade adicional.

Evite ou tenha cautela quando:

  • O modelo alvo já é pequeno/rápido (o overhead do rascunho domina).
  • A carga de trabalho usa prompts/domínios muito diversos (aceitação instável).
  • Você é extremamente limitado por memória.
  • Você exige reprodutibilidade estrita e a implementação especulativa da sua stack altera o comportamento de amostragem.

Abordagens relacionadas e variantes

A decodificação especulativa faz parte de uma categoria mais ampla de técnicas de “gerar múltiplos tokens por etapa cara”:

  • Cabeças de predição multi-token (multi-token prediction heads) (por exemplo, abordagens tipo Medusa): modificam o modelo alvo para predizer diretamente múltiplos tokens futuros. Isso reduz a dependência de um modelo rascunho separado, mas exige mudanças no modelo.
  • Decodificação com lookahead / rascunhos por n-gram: rascunhos muito leves podem ajudar em domínios estreitos (por exemplo, padrões de código), mas a aceitação pode ser baixa em texto aberto.
  • Quantização e otimização de kernels: melhorias ortogonais; a decodificação especulativa frequentemente combina bem com Quantização.

Resumo

A decodificação especulativa acelera a geração de LLM ao permitir que um modelo rascunho barato proponha tokens e um modelo alvo de alta qualidade os verifique, muitas vezes aceitando múltiplos tokens por ciclo de verificação. O melhor cenário é uma latência significativamente menor sem mudança na distribuição do alvo (quando se usa amostragem especulativa exata). Os trade-offs práticos giram em torno da taxa de aceitação, qualidade do rascunho, parâmetro (k), e preocupações de sistema como gerenciamento do cache KV e escalonamento multi-tenant.

Em stacks modernas de serviço de LLM, a decodificação especulativa é uma das técnicas mais impactantes para reduzir a latência interativa — desde que você possa arcar com a complexidade adicional de implementação e operação e valide cuidadosamente qualidade e correção.