Medusa: Multi-Head Decoding for Faster Inference¶
Overview¶
Medusa is a technique that accelerates LLM inference by predicting multiple future tokens in parallel using separate decoding heads, then verifying them. Achieves 2-3x speedup without model changes or distillation.
- Paper: "Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads" (Cai et al., 2023)
- Key Idea: Add auxiliary prediction heads for next N tokens
- Speedup: 2-3x with 1-2% quality loss
- Advantage: Works with any existing LLM without modification
- Trade-off: Increased inference parameters (auxiliary heads)
The Problem: Token-by-Token Generation is Sequential¶
Inference Bottleneck¶
Standard Autoregressive Generation:
Token 1: model(input) → output_1 → sample → token_1
Token 2: model(prev + token_1) → output_2 → sample → token_2
Token 3: model(prev + token_1 + token_2) → output_3 → sample → token_3
...
Timeline:
Step 1: ════ Forward pass (50ms)
Step 2: ════ Forward pass (50ms)
Step 3: ════ Forward pass (50ms)
...
Total for 100 tokens: 100 × 50ms = 5 seconds
Problem:
- Each token takes 50ms (compute)
- Must wait for previous token to continue
- Can't parallelize!
- Throughput: ~20 tokens/sec (very slow!)
Question:
Can we predict multiple tokens before committing to them?
Why Parallel Prediction is Feasible¶
Key observation:
Next token is mostly determined by recent context
Example:
"The capital of France is P" → Next token: "aris" (very predictable)
"Once upon a time" → Next token: hard to predict
But:
We can make EDUCATED GUESSES for next 2-3 tokens!
Correct sequence: "Paris, France"
Guess 1: "P" (likely)
Guess 2: "aris" (likely, follows P)
Guess 3: "," (likely, follows "Paris")
If guesses correct:
- Process in one forward pass instead of 3!
- 3x speedup!
If guess wrong:
- Reject and resample
- Still faster than sequential (amortized)
How Medusa Works¶
Architecture¶
Standard LLM:
- ┌─────────────────────────────────────┐
- Input Embeddings │
- ┤
- Transformer Layers (32 layers) │
- ┤
- Final Layer Norm │
- ┤
- LM Head (output vocabulary) │
- ┤
- Output: vocab_size logits │
- ┘
↓
Sample token t+1
Medusa Modification:
- ┌─────────────────────────────────────┐
- Input Embeddings │
- ┤
- Transformer Layers (32 layers) │
- ┤
- Final Layer Norm │
- ┬───────────┤
- LM Head │ (original)
- ┼───────────┤
- Medusa Head 1 │ (parallel head)
- [small MLP] │
- Output: vocab_size │ (predict t+2)
- ┼───────────┤
- Medusa Head 2 │ (parallel head)
- [small MLP] │
- Output: vocab_size │ (predict t+3)
- ┼───────────┤
- Medusa Head 3 │ (parallel head)
- [small MLP] │
- Output: vocab_size │ (predict t+4)
- ┴───────────┘
↓ ↓ ↓ ↓
Token t+1 Token t+2 Token t+3 Token t+4
(main) (guess) (guess) (guess)
Inference Process¶
Step 1: Forward pass (once)
- Input: prompt + previously accepted tokens
- Output: logits from main head + all Medusa heads
- Time: 50ms (same as before!)
Step 2: Generate candidate tokens
- Main head: sample token t+1 (highest probability)
- Medusa head 1: sample token t+2 (highest probability)
- Medusa head 2: sample token t+3 (highest probability)
- Medusa head 3: sample token t+4 (highest probability)
- Time: <1ms (sampling is fast)
Step 3: Verification
- Check: Is t+1 likely? (Yes, from main head)
- Check: Given t+1, is t+2 likely?
- Forward pass: model(prompt + t+1 + t+2_guess)
- Compare main head output to t+2_guess
- If probability > threshold: ACCEPT t+2
- Else: REJECT, resample
- Check: Given t+1, t+2, is t+3 likely?
- Check: Given t+1, t+2, t+3, is t+4 likely?
- Time: Usually ~50ms for 1-2 verifications
Result:
- If all 4 tokens accepted: 4 tokens in 50ms (4x speedup!)
- If 2 tokens accepted: 2 tokens in 50ms (2x speedup)
- Average: 2-3x speedup
Real sequence vs guesses:
- Model predicts: [high prob for "The"]
- Medusa guesses: [high prob for "The"]
- ACCEPT "The"
│
- Model predicts (given "The"): [high prob for "cat"]
- Medusa guesses: [high prob for "cat"]
- ACCEPT "cat"
│
- Model predicts (given "The cat"): [high prob for "sat"]
- Medusa guesses: [high prob for "jumped"]
- MISMATCH! REJECT jump, sample "sat"
│
Total: 3 tokens generated
- With guessing: 50ms + verification
- Without guessing: 150ms
Implementation¶
class MedusaHead(nn.Module):
"""Auxiliary decoding head for predicting future token"""
def __init__(self, hidden_dim, vocab_size):
super().__init__()
self.head = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, vocab_size)
)
def forward(self, x):
return self.head(x)
class ModelWithMedusa(nn.Module):
def __init__(self, base_model, hidden_dim, vocab_size, num_medusa_heads=3):
super().__init__()
self.base_model = base_model
self.lm_head = base_model.lm_head
# Medusa heads for parallel decoding
self.medusa_heads = nn.ModuleList([
MedusaHead(hidden_dim, vocab_size)
for _ in range(num_medusa_heads)
])
def forward(self, input_ids):
"""
Returns:
- main_logits: (batch, seq_len, vocab_size)
- medusa_logits: list of (batch, seq_len, vocab_size)
"""
# Base model forward
hidden = self.base_model(input_ids, output_hidden_state=True)
last_hidden = hidden.last_hidden_state
# Main head
main_logits = self.lm_head(last_hidden)
# Medusa heads (parallel predictions)
medusa_logits = [head(last_hidden) for head in self.medusa_heads]
return main_logits, medusa_logits
def speculative_decoding_with_medusa(model, prompt, max_tokens=100,
medusa_temperature=0.5,
verify_threshold=0.9):
"""
Generate with Medusa (speculative decoding)
Key parameters:
- medusa_temperature: Lower = more confident guesses
- verify_threshold: Minimum probability to accept guess
"""
generated = []
current_input = prompt
for _ in range(max_tokens):
# Forward pass (single, not multiple!)
main_logits, medusa_logits = model(current_input)
# Latest token predictions
main_pred = main_logits[:, -1, :] # (batch, vocab)
medusa_preds = [m[:, -1, :] for m in medusa_logits]
# Generate candidates
main_token = sample(main_pred, temperature=0.7)
medusa_tokens = [
sample(m, temperature=medusa_temperature)
for m in medusa_preds
]
# Verify candidates
candidates = [main_token] + medusa_tokens
accepted_count = 0
for i, token in enumerate(candidates):
if i == 0:
# Main token: always accept
generated.append(token)
accepted_count += 1
else:
# Verify: recompute with candidate token
test_input = current_input + [generated[-1], token]
test_logits, _ = model(test_input)
# Get probability of this token
token_prob = softmax(test_logits[:, -1, :])[0, token]
if token_prob > verify_threshold:
generated.append(token)
accepted_count += 1
else:
# Reject and stop verifying
break
# Update input for next iteration
current_input = current_input + generated[-accepted_count:]
# Typically accept 2-3 tokens per forward pass
# So 100 tokens takes ~30-50 forward passes instead of 100
return generated
Performance Analysis¶
Speedup Calculation¶
Scenario: Generate 100 tokens with Medusa (3 heads)
Standard generation:
- 100 forward passes × 50ms = 5000ms
- Throughput: 20 tokens/sec
Medusa with 70% acceptance rate:
- Main token: Always accepted
- Head 1: 70% acceptance rate
- Head 2: 50% acceptance rate (70% × 70%)
- Head 3: 35% acceptance rate (70% × 70% × 70%)
- Expected tokens per pass: 1 + 0.7 + 0.5 + 0.35 = 2.55 tokens/pass
- Forward passes needed: 100 / 2.55 ≈ 39 passes
- Verification passes: ~39 × 2 ≈ 78 passes (rough estimate)
- Total passes: ~117 (vs 100 standard)
- But each pass is parallel prediction, not sequential
- Effective speedup: ~2.5x!
- Throughput: 50 tokens/sec (2.5x improvement)
Better scenario (80% acceptance):
- Expected tokens per pass: 1 + 0.8 + 0.64 + 0.51 = 2.95
- Forward passes: 34
- Speedup: ~3x
- Throughput: 60 tokens/sec
Quality Impact¶
Experiment: LLaMA 7B with Medusa
Metric Baseline Medusa Delta
─────────────────────────────────────────────
MMLU Score 45.3% 44.8% -0.5%
Human Preference — Tied Equal
Generation Speed 1x 2.5x +150%
Quality loss:
- Minimal (<1%)
- Acceptance threshold tuning can improve
- Trade: Fast inference worth slight quality dip
Configuration impact:
- 3 heads: 2.5x speedup, 0.5% loss
- 2 heads: 1.8x speedup, 0.2% loss
- 1 head: 1.3x speedup, <0.1% loss
- More heads = more speedup but potentially less accurate guesses
Advantages and Limitations¶
Advantages¶
✅ 2-3x speedup without model changes
✅ Compatible with existing models
✅ Minimal quality loss (<1%)
✅ Works with batching
✅ Orthogonal to other optimizations (can combine with quantization, etc.)
Limitations¶
❌ Requires retraining Medusa heads
❌ Quality degrades for "branching" outputs (multiple valid continuations)
❌ Verification adds some compute overhead
❌ Accepts only when confident (sensitive to threshold)
❌ Less effective with high temperature (random sampling)
When to Use Medusa¶
Use Medusa when:
✅ Inference speed is critical
✅ Can afford to retrain auxiliary heads
✅ Outputs are relatively deterministic
✅ Want simple, model-agnostic solution
Avoid Medusa when:
❌ Quality must be perfect
❌ Outputs are highly variable (creative writing)
❌ Can't modify inference code
Key Takeaways¶
🎯 Parallel prediction: Guess next tokens in parallel
✅ Verify guesses: Expensive but amortized over guesses
⚡ 2-3x speedup with minimal quality loss
🔄 Orthogonal to other optimizations
📊 Trade: Slight quality for major speed gain
Related Notes¶
- Speculative Decoding - Similar concept, different approach
- Continuous Batching - Works together for inference optimization
- Token Merging (Tome) - Another inference acceleration technique
- Llm Inference Optimization - Complete inference stack