Skip to content

Decoding Strategies

Overview

Decoding strategies determine how to select the next token during autoregressive generation. Different strategies offer different trade-offs between quality, diversity, and speed.

  • Greedy: Fastest, lowest diversity, sometimes lower quality
  • Beam Search: Balanced quality and diversity, slower
  • Sampling: Best diversity and quality, slower than greedy
  • Nucleus Sampling: Balanced quality and diversity
  • Trade-off: Speed vs. generation quality

Greedy Decoding

How It Works

At each step, select the token with highest probability:

P(t+1| context) = softmax(logits)[1:vocab_size]
 = [0.3, 0.25, 0.15, 0.1, 0.05,...]

Greedy:
 - Select argmax = token with 0.3 (highest probability)

Result sequence:
 - [The, cat, sat, on, the, mat]
 (always picks most likely next token)

Pseudocode:
def greedy_decode(model, prompt, max_length=100):
 generated = list(prompt)

 for _ in range(max_length):
 # Forward pass
 logits = model(generated)[-1,:] # Last position

 # Select token with highest probability
 next_token = argmax(logits)
 generated.append(next_token)

 return generated

# Cost
# Time
# Diversity
# Quality

Advantages

Fast (no overhead)
Deterministic (reproducible)
Works reasonably well for many tasks

Limitations

No diversity (same output every time)
Can get stuck in repetition loops
May select suboptimal token if high-probability path is bad
Doesn't explore alternatives

How It Works

Instead of tracking 1 hypothesis, track top-k:

Step 1: Initial (beam size=3)
 - Probabilities: [0.3, 0.25, 0.15,...]
 - Keep top-3 hypotheses:
 - [token_1, prob=0.3]
 - [token_2, prob=0.25]
 - [token_3, prob=0.15]

Step 2: Expand each hypothesis
 - For [token_1]: compute next probabilities
 - [token_1 → token_A, prob=0.3×0.4=0.12]
 - [token_1 → token_B, prob=0.3×0.3=0.09]
 - [token_1 → token_C, prob=0.3×0.2=0.06]
 - [token_1 → token_D, prob=0.3×0.1=0.03]
│
 - For [token_2]: compute next probabilities
 - [token_2 → token_A, prob=0.25×0.5=0.125]
 - [token_2 → token_B, prob=0.25×0.3=0.075]
 - [token_2 → token_C, prob=0.25×0.15=0.0375]
 - [token_2 → token_D, prob=0.25×0.05=0.0125]
│
 - For [token_3]:...

Step 3: Select top-3 globally
 - All combinations: 3 × 4 = 12 options
 - Rank by probability:
 - [token_2 → A, 0.125] ← best
 - [token_1 → A, 0.12]
 - [token_2 → B, 0.075]
 - Keep top-3, discard others

Step 4: Repeat until max length or EOS

Implementation

def beam_search(model, prompt, beam_size=3, max_length=100):
 """
 Generate with beam search

 Args:
 beam_size: Number of hypotheses to track (3-5 typical)

 Returns:
 Top-scoring sequences
 """
 vocab_size = model.config.vocab_size
 device = next(model.parameters()).device

 # Initialize beam
 # Each entry: (sequence, score)
 batch_size = 1
 sequences = [(prompt, 0.0)] # Start with initial prompt

 for step in range(max_length):
 # Candidates for this step
 candidates = []

 for seq, seq_score in sequences:
 # Forward pass
 with torch.no_grad():
 logits = model(seq.unsqueeze(0))[0, -1,:]

 # Get log probabilities
 log_probs = F.log_softmax(logits, dim=-1)

 # Get top beam_size next tokens
 top_log_probs, top_indices = torch.topk(log_probs, beam_size)

 # Expand each hypothesis
 for i in range(beam_size):
 new_seq = torch.cat([seq, top_indices[i].unsqueeze(0)])
 new_score = seq_score + top_log_probs[i].item()
 candidates.append((new_seq, new_score))

 # Keep top beam_size candidates globally
 candidates.sort(key=lambda x: x[1], reverse=True)
 sequences = candidates[:beam_size]

 # Return best sequence
 best_seq, best_score = sequences[0]
 return best_seq

# Cost
# Time
# Diversity
# Quality

Analysis

Beam search quality vs speed:

Beam size Quality Speed Diversity Use case
──────────────────────────────────────────────────
1 Low 1x None Baseline (greedy)
3 Medium 3x Low Balanced
5 Slightly 5x Low Deliberate tasks
10 Marginal 10x Low Research only

Observation:
 - Diminishing returns after beam=3
 - beam=5 rarely better than beam=3
 - Speed cost not worth quality gain
 - Default: beam_size=3 for practical use

-

Sampling

Temperature-Based Sampling

Problem with greedy: Deterministic, can't escape bad paths
Problem with beam search: Still limited to high-prob paths

Solution: Sample from distribution!

Standard greedy:
 - P(next) = [0.3, 0.25, 0.15, 0.1,...]
 - Sample argmax = token with 0.3
 - Deterministic

Sampling:
 - P(next) = [0.3, 0.25, 0.15, 0.1,...]
 - Sample from distribution: Can pick any token!
 - 30% chance: pick 0.3 token (most likely)
 - 25% chance: pick 0.25 token
 - 15% chance: pick 0.15 token
 -...
 - Stochastic: different outputs each time!

Temperature control:

T = 0.5 (sharp):
 - Probabilities sharpen: [0.5, 0.3, 0.15,...]
 - Most probable token even more likely
 - Conservative, deterministic behavior

T = 1.0 (original):
 - Probabilities unchanged: [0.3, 0.25, 0.15,...]
 - Standard sampling

T = 2.0 (soft):
 - Probabilities flatten: [0.2, 0.2, 0.15, 0.15,...]
 - More uniform distribution
 - Diverse, creative outputs

Implementation

def sample_decode(model, prompt, max_length=100, temperature=1.0, top_p=0.9):
 """
 Generate with sampling

 Args:
 temperature: Higher = more random
 top_p: Nucleus sampling (only sample from top p% probability)
 """
 generated = list(prompt)

 for _ in range(max_length):
 # Forward pass
 logits = model(generated)[-1,:]

 # Apply temperature
 logits = logits / temperature

 # Get probabilities
 probs = F.softmax(logits, dim=-1)

 # Nucleus sampling (optional, more stable)
 if top_p < 1.0:
 # Sort by probability
 sorted_probs, sorted_indices = torch.sort(probs, descending=True)
 cumsum_probs = torch.cumsum(sorted_probs, dim=-1)

 # Find threshold: keep top p% probability mass
 threshold_idx = torch.searchsorted(cumsum_probs, top_p)
 threshold_prob = sorted_probs[threshold_idx]

 # Zero out low-probability tokens
 probs[probs < threshold_prob] = 0
 probs = probs / probs.sum() # Renormalize

 # Sample from distribution
 next_token = torch.multinomial(probs, num_samples=1)
 generated.append(next_token.item())

 return generated

# Cost
# Time
# Diversity
# Quality

-

Nucleus Sampling (Top-p)

How It Works

Pure sampling can generate low-probability nonsense:

Probabilities: [0.4, 0.3, 0.2, 0.05, 0.03, 0.01, 0.01,...]
 ^ These are terrible but still possible!

Nucleus sampling (top_p=0.9):
 - Find cumulative probability threshold
 - Keep tokens until cumsum reaches top_p
 - 0.4 (sum: 0.4)
 - 0.3 (sum: 0.7)
 - 0.2 (sum: 0.9) ← STOP HERE
 - Drop 0.05, 0.03, 0.01, 0.01 (too low)
 - Sample only from kept tokens
 - Result: Probability = [0.4, 0.3, 0.2, 0, 0, 0,...]

Effect:
 - High probability tokens: always available
 - Medium probability: available if top_p allows
 - Low probability: removed (prevents nonsense)
 - Balances diversity and quality!

Quality

Experiment: LLaMA 7B generation quality

Metric Greedy Beam(3) Sample Nucleus
─────────────────────────────────────────────────────
ROUGE (factual) 34.2 35.1 33.8 34.8
Diversity (TTR) 1.2 1.5 8.4 5.2
Human rating 3.2/5 3.5/5 3.8/5 3.9
Generation time 1x 3x 1x 1x

TTR (Type-Token Ratio): Higher = more diverse vocabulary

Observations:
 - Greedy: Fast, boring, decent quality
 - Beam: Slightly better, slow
 - Sample: Diverse, creative, sometimes weird
 - Nucleus: Best balance of diversity and quality
 - Recommendation: Use nucleus sampling (top_p=0.9)

Comparison and Recommendations

Which to Use?

Task Recommendation
──────────────────────────────────────
Code generation Greedy (most consistent)
Factual QA Beam search (beam_size=3)
Creative writing Nucleus sampling (top_p=0.9)
Chatbot Nucleus sampling (top_p=0.9)
Machine translation Beam search (beam_size=5)
Summarization Nucleus sampling (top_p=0.9)

Parameter Guidelines

Greedy:
 - No parameters (deterministic)

Beam Search:
 - beam_size: 3-5 (default 3)
 - length_penalty: 1.0 (no penalty)
 - > 1.0 favors longer sequences
 - < 1.0 favors shorter sequences
 - Note: Much slower, marginal quality gain over beam_size=1

Temperature Sampling:
 - T = 0.5-0.7: More deterministic, factual
 - T = 1.0: Neutral
 - T = 1.5-2.0: Creative, diverse
 - Usually use T=0.7-0.9

Nucleus Sampling:
 - top_p = 0.8-0.95: Typical range
 - 0.95: More exploratory
 - 0.85: Balanced
 - 0.75: More conservative
 - Default: 0.9 (good balance)

Combined: Temperature + Nucleus
 - Use together for best results!
 - Nucleus removes low-prob tokens
 - Temperature adjusts prob distribution

Key Takeaways

Greedy: Fast, deterministic, limited quality Beam Search: Better quality, 3-5x slower Sampling: Diverse, creative, same speed as greedy Nucleus: Best quality-diversity trade-off Use Nucleus Sampling (top_p=0.9) by default

-