Skip to content

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