| name | speculative-decoding |
| description | Accelerate LLM inference using speculative decoding, Medusa multiple heads, and lookahead decoding techniques. Use when optimizing inference speed (1.5-3.6× speedup), reducing latency for real-time applications, or deploying models with limited compute. Covers draft models, tree-based attention, Jacobi iteration, parallel token generation, and production deployment strategies. |
| version | 1.0.0 |
| author | Orchestra Research |
| license | MIT |
| tags | ["Emerging Techniques","Speculative Decoding","Medusa","Lookahead Decoding","Fast Inference","Draft Models","Tree Attention","Parallel Generation","Latency Reduction","Inference Optimization"] |
| dependencies | ["transformers","torch"] |
Speculative Decoding: Accelerating LLM Inference
When to Use This Skill
Use Speculative Decoding when you need to:
- Speed up inference by 1.5-3.6× without quality loss
- Reduce latency for real-time applications (chatbots, code generation)
- Optimize throughput for high-volume serving
- Deploy efficiently on limited hardware
- Generate faster without changing model architecture
Key Techniques: Draft model speculative decoding, Medusa (multiple heads), Lookahead Decoding (Jacobi iteration)
Papers: Medusa (arXiv 2401.10774), Lookahead Decoding (ICML 2024), Speculative Decoding Survey (ACL 2024)
Installation
pip install transformers accelerate
git clone https://github.com/FasterDecoding/Medusa
cd Medusa
pip install -e .
git clone https://github.com/hao-ai-lab/LookaheadDecoding
cd LookaheadDecoding
pip install -e .
pip install vllm
Quick Start
Basic Speculative Decoding (Draft Model)
from transformers import AutoModelForCausalLM, AutoTokenizer
target_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-70b-hf",
device_map="auto",
torch_dtype=torch.float16
)
draft_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
device_map="auto",
torch_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-70b-hf")
prompt = "Explain quantum computing in simple terms:"
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
outputs = target_model.generate(
**inputs,
assistant_model=draft_model,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)
Medusa (Multiple Decoding Heads)
from medusa.model.medusa_model import MedusaModel
model = MedusaModel.from_pretrained(
"FasterDecoding/medusa-vicuna-7b-v1.3",
torch_dtype=torch.float16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("FasterDecoding/medusa-vicuna-7b-v1.3")
prompt = "Write a Python function to calculate fibonacci numbers:"
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
outputs = model.medusa_generate(
**inputs,
max_new_tokens=256,
temperature=0.7,
posterior_threshold=0.09,
posterior_alpha=0.3,
)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
Lookahead Decoding (Jacobi Iteration)
from lookahead.lookahead_decoding import LookaheadDecoding
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
torch_dtype=torch.float16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
lookahead = LookaheadDecoding(
model=model,
tokenizer=tokenizer,
window_size=15,
ngram_size=5,
guess_size=5
)
prompt = "Implement quicksort in Python:"
output = lookahead.generate(prompt, max_new_tokens=256)
print(output)
Core Concepts
1. Speculative Decoding (Draft Model)
Idea: Use small draft model to generate candidates, large target model to verify in parallel.
Algorithm:
- Draft model generates K tokens speculatively
- Target model evaluates all K tokens in parallel (single forward pass)
- Accept tokens where draft and target agree
- Reject first disagreement, continue from there
def speculative_decode(target_model, draft_model, prompt, K=4):
"""Speculative decoding algorithm."""
draft_tokens = draft_model.generate(prompt, max_new_tokens=K)
target_logits = target_model(draft_tokens)
accepted = []
for i in range(K):
p_draft = softmax(draft_model.logits[i])
p_target = softmax(target_logits[i])
if random.random() < min(1, p_target[draft_tokens[i]] / p_draft[draft_tokens[i]]):
accepted.append(draft_tokens[i])
else:
break
return accepted
Performance:
- Speedup: 1.5-2× with good draft model
- Zero quality loss (mathematically equivalent to target model)
- Best when draft model is 5-10× smaller than target
2. Medusa (Multiple Decoding Heads)
Source: arXiv 2401.10774 (2024)
Innovation: Add multiple prediction heads to existing model, predict future tokens without separate draft model.
Architecture:
Input → Base LLM (frozen) → Hidden State
├→ Head 1 (predicts token t+1)
├→ Head 2 (predicts token t+2)
├→ Head 3 (predicts token t+3)
└→ Head 4 (predicts token t+4)
Training:
- Medusa-1: Freeze base LLM, train only heads
- Medusa-2: Fine-tune base LLM + heads together
- 2.3-3.6× speedup, better quality
Tree-based Attention:
Advantages:
- No separate draft model needed
- Minimal training (only heads)
- Compatible with any LLM
3. Lookahead Decoding (Jacobi Iteration)
Source: ICML 2024
Core idea: Reformulate autoregressive decoding as solving system of equations, solve in parallel using Jacobi iteration.
Mathematical formulation:
Traditional: y_t = f(x, y_1, ..., y_{t-1}) (sequential)
Jacobi: y_t^{(k+1)} = f(x, y_1^{(k)}, ..., y_{t-1}^{(k)}) (parallel)
Two branches:
-
Lookahead Branch: Generate n-grams in parallel
- Window size W: How many steps to look ahead
- N-gram size N: How many past tokens to use
-
Verification Branch: Verify promising n-grams
- Match n-grams with generated tokens
- Accept if first token matches
class LookaheadDecoding:
def __init__(self, model, window_size=15, ngram_size=5):
self.model = model
self.W = window_size
self.N = ngram_size
def generate_step(self, tokens):
candidates = {}
for w in range(1, self.W + 1):
for n in range(1, self.N + 1):
ngram = self.generate_ngram(tokens, start=w, length=n)
candidates[(w, n)] = ngram
verified = []
for ngram in candidates.values():
if ngram[0] == tokens[-1]:
if self.verify(tokens, ngram):
verified.append(ngram)
return max(verified, key=len) if verified else [.model.generate_next(tokens)]
Performance:
- Speedup: 1.5-2.3× (up to 3.6× for code generation)
- No draft model or training needed
- Works out-of-the-box with any model
Method Comparison
| Method | Speedup | Training Needed | Draft Model | Quality Loss |
|---|
| Draft Model Speculative | 1.5-2× | No | Yes (external) | None |
| Medusa | 2-3.6× | Minimal (heads only) | No (built-in heads) | None |
| Lookahead | 1.5-2.3× | None | No | None |
| Naive Batching | 1.2-1.5× | No | No | None |
Advanced Patterns
Training Medusa Heads
from medusa.model.medusa_model import MedusaModel
from medusa.model.kv_cache import initialize_past_key_values
import torch.nn as nn
base_model = AutoModelForCausalLM.from_pretrained(
"lmsys/vicuna-7b-v1.3",
torch_dtype=torch.float16
)
num_heads = 4
medusa_heads = nn.ModuleList([
nn.Linear(base_model.config.hidden_size, base_model.config.vocab_size, bias=False)
for _ in range(num_heads)
])
for param in base_model.parameters():
param.requires_grad = False
optimizer = torch.optim.Adam(medusa_heads.parameters(), lr=1e-3)
for batch in dataloader:
hidden_states = base_model(**batch, output_hidden_states=True).hidden_states[-1]
loss = 0
for i, head in enumerate(medusa_heads):
logits = head(hidden_states)
target = batch['input_ids'][:, i+1:]
loss += F.cross_entropy(logits[:, :-i-1], target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
Hybrid: Speculative + Medusa
draft_medusa = MedusaModel.from_pretrained("medusa-vicuna-7b")
target_model = AutoModelForCausalLM.from_pretrained("vicuna-33b")
draft_tokens = draft_medusa.medusa_generate(prompt, max_new_tokens=5)
outputs = target_model.generate(
prompt,
assistant_model=draft_medusa,
max_new_tokens=256
)
Optimal Draft Model Selection
def select_draft_model(target_model_size, target):
"""Select optimal draft model for speculative decoding."""
if target_model_size == "70B":
return "7B"
elif target_model_size == "33B":
return "7B"
elif target_model_size == "13B":
return "1B"
else:
return None
draft = select_draft_model("70B", target_model)
Best Practices
1. Choose the Right Method
if deploying_new_model:
use_method = "Medusa"
elif have_small_version_of_model:
use_method = "Draft Model Speculative"
elif want_plug_and_play:
use_method = "Lookahead Decoding"
2. Hyperparameter Tuning
Draft Model Speculative:
K = 4
K = 2
K = 8
Medusa:
posterior_threshold = 0.09
posterior_threshold = 0.05
posterior_threshold = 0.15
medusa_choices = [[0], [0, 0], [0, 1], [0, 0, 0]]
Lookahead:
W, N = 15, 5
W, N = 10, 5
W, N = 7, 5
3. Production Deployment
from vllm import LLM, SamplingParams
llm = LLM(
model="meta-llama/Llama-2-70b-hf",
speculative_model="meta-llama/Llama-2-7b-hf",
num_speculative_tokens=5,
use_v2_block_manager=True,
)
prompts = ["Tell me about AI:", "Explain quantum physics:"]
sampling_params = SamplingParams(temperature=0.7, max_tokens=256)
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
print(output.outputs[0].text)
Resources
See Also
references/draft_model.md - Draft model selection and training
references/medusa.md - Medusa architecture and training
references/lookahead.md - Lookahead decoding implementation details