Speculative decoding: 2-3x speedup grátis
- ⬜🚄 vLLM e PagedAttention: serving high-throughput(Local LLMs & Edge AI)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Em julho de 2023, Leviathan, Kalman e Matias (Google Research) publicaram "Fast Inference from Transformers via Speculative Decoding" na ICML. O paper resolveu um problema que parecia fundamental: decoding autoregressive é inerentemente sequencial — cada token depende do anterior. A genialidade foi notar que a maioria dos tokens é fácil: um modelo 50× menor consegue prever a continuação correta na maior parte do tempo. Validar essa previsão custa quase nada. Resultado: 2-3× speedup sem mexer no modelo, sem perder qualidade.
Por que decoding é lento
Em uma GPU H100 com Llama 3.1 70B em FP16, gerar 1 token leva ~30ms. Disso, 99% é tempo de mover pesos da HBM para SRAM — apenas 1% é compute real. A GPU está ociosa 99% do tempo processando matemática que cabe num celular. É memory-bound, não compute-bound.
Essa diferença é o que speculative explora. Validar 5 tokens em paralelo via 1 forward não custa 5× — os pesos são lidos UMA vez e fazem 5× mais trabalho. O custo extra é desprezível em decoding single-batch.
Algoritmo: draft + validate + accept
# Speculative decoding — implementação conceitual
import torch
def speculative_decode(target, draft, prompt, max_tokens=200, gamma=5):
"""gamma = quantos tokens o draft propõe por iteração"""
tokens = prompt.clone()
generated = 0
while generated < max_tokens:
# 1. Draft gera gamma tokens auto-regressivamente
draft_tokens = []
draft_probs = []
x = tokens.clone()
for _ in range(gamma):
logits = draft(x)[:, -1]
probs = logits.softmax(-1)
tok = torch.multinomial(probs, 1)
draft_tokens.append(tok)
draft_probs.append(probs.gather(-1, tok))
x = torch.cat([x, tok], dim=-1)
draft_tokens = torch.cat(draft_tokens, dim=-1) # [batch, gamma]
# 2. Target valida todos em 1 forward
candidate = torch.cat([tokens, draft_tokens], dim=-1)
target_logits = target(candidate)[:, -gamma-1:] # logits nas posições candidatas
target_probs = target_logits.softmax(-1)
# 3. Aceitação token a token
accepted = 0
for i in range(gamma):
tok = draft_tokens[:, i]
p_t = target_probs[:, i].gather(-1, tok.unsqueeze(-1)).squeeze()
p_d = draft_probs[i].squeeze()
r = torch.rand_like(p_t)
if r < min(1.0, p_t / p_d):
# aceita
tokens = torch.cat([tokens, tok.unsqueeze(-1)], dim=-1)
accepted += 1
generated += 1
else:
# rejeita: sample do residual (p_t - p_d)+
residual = torch.relu(target_probs[:, i] - draft_probs[i])
residual = residual / residual.sum()
new_tok = torch.multinomial(residual, 1)
tokens = torch.cat([tokens, new_tok], dim=-1)
generated += 1
break
# 4. Bonus token se aceitou todos
if accepted == gamma and generated < max_tokens:
bonus = torch.multinomial(target_probs[:, -1], 1)
tokens = torch.cat([tokens, bonus], dim=-1)
generated += 1
return tokensA regra é o que preserva a distribuição. Não é "aceite se draft está confiante" ou "aceite se top-1 bate" — é rejection sampling probabilístico exato. Qualquer outra regra introduz viés de qualidade.
Escolha do draft model
| Target | Draft recomendado | Acceptance rate típica | Speedup esperado |
|---|---|---|---|
| Llama 3.1 70B | Llama 3.2 1B-Instruct (Q8) | 70-85% em texto técnico | 2.2-2.8× |
| Llama 3.1 70B | Llama 3.2 3B-Instruct | 78-90% | 2.0-2.5× (draft mais pesado) |
| Llama 3.1 405B | Llama 3.1 8B | 60-75% | 1.8-2.4× |
| Qwen 2.5 72B | Qwen 2.5 0.5B ou 1.5B | 65-80% | 2.0-2.7× |
| Mistral Large 2 | Mistral 7B (mesmo tokenizer) | 55-70% | 1.5-2.0× |
| DeepSeek V3 (MoE) | EAGLE head treinado | 80-92% | 3-5× (EAGLE) |
Regra prática: draft com 1-3% dos parâmetros do target costuma ser o sweet spot. Draft muito pequeno (0.1%) tem acceptance rate baixa; muito grande (10%) come o ganho com seu próprio custo. Sempre meça com tráfego real antes de fixar gamma.
EAGLE: state of the art 2025-2026
EAGLE (Li et al., 2024; v2/v3 em 2024-2025) representa o estado da arte. Em vez de treinar um modelo draft inteiro separado, treina um auto-regression head compacto que prediz a próxima hidden state (não token) baseada em hidden states anteriores e embeddings de tokens já gerados. Compartilha o lm_head do target.
EAGLE-3 (2025) chega a 4-6× speedup em Llama 3.1 70B com acceptance rate de 90%+. vLLM 0.6+ suporta EAGLE nativamente. O custo: treinar o head (~1 GPU-day numa A100). Para quem controla o modelo final, é o ganho mais barato disponível.
Medusa: heads paralelos no target
Cai et al. (Princeton, Together AI 2024) propuseram Medusa: adicionar K heads ao target. Head 1 prediz token na posição t+1 (normal). Head 2 prediz t+2 dado o estado em t. Head k prediz t+k. Em decoding: target faz 1 forward, todos K heads geram em paralelo — produzindo K candidatos. Tree attention seleciona a continuação válida.
| Técnica | Draft separado? | Treina target? | Speedup típico | Quando preferir |
|---|---|---|---|---|
| Speculative clássico | Sim (modelo menor) | Não | 1.5-2.5× | Modelos OSS já existentes; gratuito |
| Medusa | Não (heads no target) | Sim (fine-tune heads) | 2-3× | Você controla o target; simples |
| EAGLE-2/3 | Não (head compacto) | Sim (treina head) | 3-6× | Quer ganho máximo; tem GPU para treinar |
| Lookahead decoding | N-grams locais | Não | 1.3-1.8× | Zero treino; speedup modesto |
| SpS / Self-speculation | Mesmo modelo com layers skip | Não | 1.3-1.7× | Não tem modelo draft compatível |
A geração de um token é descrita como limitada por memória, não por cálculo. Como isso torna a decodificação especulativa possível?
Ativação prática nos engines
# vLLM com speculative decoding clássico
vllm serve meta-llama/Meta-Llama-3.1-70B-Instruct \
--tensor-parallel-size 4 \
--speculative-model meta-llama/Llama-3.2-1B-Instruct \
--num-speculative-tokens 5 \
--use-v2-block-manager \
--enable-prefix-caching
# vLLM com EAGLE
vllm serve meta-llama/Meta-Llama-3.1-70B-Instruct \
--speculative-model yuhuili/EAGLE-LLaMA3.1-70B \
--speculative-draft-tensor-parallel-size 1 \
--num-speculative-tokens 5
# llama.cpp — binário dedicado
./llama-speculative \
-m models/llama-3.1-70b-Q4_K_M.gguf \
-md models/llama-3.2-1b-Q8_0.gguf \
--draft 8 \
--gpu-layers 80 --gpu-layers-draft 16 \
-p "Explique consenso Raft em sistemas distribuídos."
# MLX (Apple Silicon)
mlx_lm.server \
--model mlx-community/Meta-Llama-3.1-70B-Instruct-4bit \
--draft-model mlx-community/Llama-3.2-1B-Instruct-4bit \
--num-draft-tokens 5Métricas: o que medir
Perguntas frequentes
❓ Speculative funciona em batch size grande?
❓ Como saber se draft e target compartilham tokenizer?
❓ Speculative + quantização?
❓ Speculative reduz custo OU latência?
Referências
Fixando
Por que a aceitação usa amostragem por rejeição em vez de "aceitar quando o rascunho está confiante"?
Por que a técnica rende pouco quando o lote de requisições é grande?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…