Skip to content

Medusa

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

-