LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 177 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/24
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernanceData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/16
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/19
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnTransformer Deep DivesDecoding Strategies: Greedy to Nucleus
📝HardNLP Fundamentals

Decoding Strategies: Greedy to Nucleus

Compare decoding strategies for text generation: greedy, beam search, top-k, nucleus (top-p), temperature, repetition controls, and newer variants like min-p.

34 min read
Learning path
Step 96 of 177 in the full curriculum
Mechanistic InterpretabilityScaling Laws & Compute-Optimal Training

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

Transformer internals eventually produce logits. Decoding strategy controls how that final probability distribution becomes one visible completion. Compare greedy decoding, beam search, temperature sampling, top-k, nucleus sampling, and min-p so you can tune output quality intentionally.

The earlier decoding foundations chapter built stable softmax and baseline greedy, sampling, and beam mechanics. Here those mechanics become product decisions: strategy trade-offs, min-p, length penalties, sampler order, and runtime behavior under real output constraints.

An incident-status assistant that hasn't chosen its next token yet may assign a 70% chance to stable, a 20% chance to degraded, and a 10% chance to rollback. Language models do this at every generation step: they produce raw scores for many possible next tokens, convert those scores into probabilities, then use a decoding policy to pick what appears on screen.

That final choice has real product consequences. Runbook assistants usually need stable, factual phrasing, while brainstorming tools need more variety. Code generators often benefit from determinism; story generators may sound lifeless if they always pick the highest-probability token. Decoding is the control surface that turns the same model distribution into those different behaviors.

For how decoding fits into the broader inference pipeline and techniques like speculative decoding, see those articles. Speculative decoding accelerates generation by asking a draft model to propose tokens that the target model can verify in parallel.[1]Reference 1Fast Inference from Transformers via Speculative Decoding.https://arxiv.org/abs/2211.17192

One next-token distribution feeds three policies: greedy takes only degraded at 45 percent, beam search keeps three partial paths with cumulative scores, and nucleus top-p keeps the cumulative 90 percent mass then samples inside it.
One distribution feeds three policies: greedy commits to the top token, beam keeps scored partial paths, and nucleus samples inside cumulative top-p mass.

Greedy decoding

Why can greedy decoding be bad even when it always picks the most likely next token?

Answer

The most likely next token is a local choice. It can lead the model into a generic or repetitive path even when a slightly less likely token would make the full answer more useful. In runbook assistance, that can mean repeatedly choosing safe boilerplate instead of entering the rollback or escalation workflow the user needs.

Human-written text often doesn't follow the model's single most likely continuation. Holtzman et al. show that maximum-likelihood decoding can drift toward bland, repetitive text, which is why pure argmax-style decoding often doesn't sound human.[2]Reference 2The Curious Case of Neural Text Degeneration.https://arxiv.org/abs/1904.09751

greedy-can-miss-higher-scoring-sequence.py
1first_step = {"degraded": 0.55, "needs": 0.45} 2continuations = {"degraded": {".": 0.40}, "needs": {"review": 0.90}} 3 4greedy_first = max(first_step, key=first_step.get) 5path_probabilities = { 6 "degraded .": first_step["degraded"] * continuations["degraded"]["."], 7 "needs review": first_step["needs"] * continuations["needs"]["review"], 8} 9highest_sequence = max(path_probabilities, key=path_probabilities.get) 10 11print(f"greedy first token: {greedy_first}") 12print(f"sequence probabilities: {path_probabilities}") 13print(f"higher-probability sequence: {highest_sequence}") 14 15assert greedy_first == "degraded" 16assert highest_sequence == "needs review"
Local versus sequence score
1greedy first token: degraded 2sequence probabilities: {'degraded .': 0.22000000000000003, 'needs review': 0.405} 3higher-probability sequence: needs review

The algorithm

At each step, select the token with the highest probability:

wt=arg⁡max⁡wP(w∣w<t)w_t = \arg\max_{w} P(w | w_{<t})wt​=argmaxw​P(w∣w<t​)

Reading the formula

At each step, look at every possible next token, pick the one with the highest probability, and commit to it. Simple and fast, but myopic: the next-token winner can still start a weak full sequence.

This basic PyTorch implementation assumes a single prompt in the batch, a model whose forward call returns .logits, and an integer EOS token ID. The loop repeatedly takes the argmax token and stops when it reaches EOS or the maximum number of decoding steps:

reading-the-formula.py
1import torch 2 3def greedy_decode( 4 model: torch.nn.Module, 5 prompt_ids: torch.Tensor, 6 max_length: int, 7 eos_token_id: int 8) -> torch.Tensor: 9 """Generate one sequence with greedy decoding (batch size 1).""" 10 input_ids = prompt_ids 11 with torch.no_grad(): 12 for _ in range(max_length): 13 logits = model(input_ids).logits[:, -1, :] 14 next_token = logits.argmax(dim=-1, keepdim=True) 15 input_ids = torch.cat([input_ids, next_token], dim=-1) 16 if next_token[0, 0].item() == eos_token_id: 17 break 18 return input_ids

Strengths

  • Deterministic and fast: <spandata−glossary="big−o−notation">O(V)</span><span data-glossary="big-o-notation">O(V)</span><spandata−glossary="big−o−notation">O(V)</span> per step (single argmax over vocabulary)
  • Useful baseline for constrained tasks: classification-style outputs or extraction when output variability is undesirable

Limitations

  • Suboptimal globally: The locally best token doesn't imply the best sequence
  • Repetitive in open-ended generation: Maximum-likelihood decoding can fall into loops or generic output in evaluated settings.[2]Reference 2The Curious Case of Neural Text Degeneration.https://arxiv.org/abs/1904.09751

Beam search

Algorithm

Instead of keeping only the best token, maintain B (beam width) candidate sequences and expand each step with next-token candidates. Exact beam search considers the full vocabulary (or a very large candidate set) for every beam, then keeps the top-B extended sequences. The snippet below uses a common pruned expansion: each beam only proposes its top-beam_width tokens before the global top-B reselection. That cheaper demo can miss a high-scoring path that starts with a lower-ranked next token, so it isn't exhaustive beam search. This simplified PyTorch version assumes batch size 1, a model whose forward call returns .logits, and the highest-scoring partial sequences at every step. For readability, it leaves out batching and length normalization, which appear in the next subsection:

algorithm.py
1import torch 2 3def beam_search( 4 model: torch.nn.Module, 5 prompt_ids: torch.Tensor, 6 beam_width: int = 5, 7 max_length: int = 100, 8 eos_token_id: int = 2 9) -> torch.Tensor: 10 """Generate one sequence with beam search (batch size 1).""" 11 # Each beam: (sequence, cumulative_log_prob) 12 beams = [(prompt_ids, 0.0)] 13 completed = [] 14 15 with torch.no_grad(): 16 for _ in range(max_length): 17 all_candidates = [] 18 for seq, score in beams: 19 # If this beam already ended, record it 20 if seq[0, -1].item() == eos_token_id: 21 completed.append((seq, score)) 22 continue 23 24 # After step 1, beams have different lengths, so process each separately. 25 # In production you would use KV caching or padded batching instead. 26 logits = model(seq).logits[:, -1, :] 27 log_probs = torch.log_softmax(logits, dim=-1) 28 top_k = log_probs.topk(beam_width, dim=-1) 29 30 for i in range(beam_width): 31 new_seq = torch.cat([seq, top_k.indices[:, i:i+1]], dim=-1) 32 new_score = score + top_k.values[:, i].item() 33 all_candidates.append((new_seq, new_score)) 34 35 if not all_candidates: 36 beams = [] 37 break 38 39 # Keep top beam_width candidates 40 beams = sorted(all_candidates, key=lambda x: x[1], 41 reverse=True)[:beam_width] 42 43 # Active beams become truncated final candidates at the length cutoff. 44 # Compare them with EOS-completed beams instead of discarding either set. 45 final_candidates = completed + beams 46 return max(final_candidates, key=lambda x: x[1])[0]

An EOS token finalizes a beam early. Reaching max_length also finalizes every beam that is still active, but labels it as truncated in a full implementation. Production beam search usually keeps finished flags on beams still in the active set and applies length normalization continuously, rather than only when a beam is moved to a side list. Selection must compare both completed and still-active sets under the same scoring rule. Returning completed whenever that list is non-empty silently discards active candidates at the cutoff, even when one has the best score.

Length penalty

One common length penalty, used in the Google Neural Machine Translation system, is:[3]Reference 3Google's Neural Machine Translation System: Bridging the Gap between Human and Machine Translation.https://arxiv.org/abs/1609.08144

score(Y)=log⁡P(Y)(5+∣Y∣)α/6α\text{score}(Y) = \frac{\log P(Y)}{(5 + |Y|)^\alpha / 6^\alpha}score(Y)=(5+∣Y∣)α/6αlogP(Y)​

Reading the formula

Without correction, longer sequences receive lower unnormalized probability as each extra token multiplies the sequence probability by a value smaller than 1. The exponent α\alphaα controls how strongly you compensate for that short-sequence bias. When α=0\alpha = 0α=0, there's no correction. Larger values reduce the bias toward short completions.

α\alphaαEffectHow to use it
0No compensation for short-sequence biasUseful baseline
0.6Moderate compensationCandidate value near the range GNMT often found best on its development sets
1.0Stronger compensation for longer outputsCompare on your task before adopting

Tune α\alphaα on a validation set. It isn't a universal translation or summarization default: the right value depends on model behavior, stopping criteria, and the task metric.

beam-length-normalization.py
1def gnmt_score(log_probability, length, alpha=0.6): 2 penalty = ((5 + length) / 6) ** alpha 3 return log_probability / penalty 4 5short = {"text": "late", "log_probability": -1.00, "length": 2} 6complete = {"text": "needs review", "log_probability": -1.08, "length": 5} 7 8raw_choice = max([short, complete], key=lambda item: item["log_probability"])["text"] 9normalized_choice = max( 10 [short, complete], 11 key=lambda item: gnmt_score(item["log_probability"], item["length"]), 12)["text"] 13print(f"raw sequence score chooses: {raw_choice}") 14print(f"length-normalized score chooses: {normalized_choice}") 15 16assert raw_choice == "late" 17assert normalized_choice == "needs review"
Length-normalized ranking
1raw sequence score chooses: late 2length-normalized score chooses: needs review

A summarization model with beam width 8 keeps returning clipped one-line outputs instead of complete summaries. Which scoring term do you inspect first?

Answer

Inspect length normalization or length penalty first. Without it, beam search over-rewards short sequences because every extra token lowers sequence probability, so the beam prefers prematurely ending outputs.

When to use beam search

  • Good fit: machine translation. The output usually has one target meaning to preserve.
  • Good fit: structured summarization. The output should stay close to source evidence.
  • Poor fit: open-ended chat or creative writing. High-likelihood paths often become generic.

Counterintuitively, beam search with larger beams can produce more likely but less interesting text.[4]Reference 4If Beam Search is the Answer, What was the Question?.https://arxiv.org/abs/2010.02650 In open-ended generation, increasing beam width can reduce output quality because the most probable sequence is often generic and repetitive.

Beam width also has a serving cost. A width-BBB search keeps up to BBB active continuations at each step. Efficient runtimes batch those hypotheses and reorder their KV-cache state, but larger beams still increase decoder work and memory pressure relative to greedy decoding.


Temperature scaling

What temperature does

Before adding randomness, you need a dial that controls how much randomness to allow. Temperature is that dial. It reshapes the probability distribution before we sample from it.

Return to our running example. The raw model gives us these probabilities for the next token after "The canary status is":

TokenOriginal Probability
degraded0.45
in0.30
on0.15
broken0.02
tail0.08

This is what happens when we apply different temperatures and then run softmax:

TokenT=0.3T = 0.3T=0.3 (sharp)T=1.0T = 1.0T=1.0 (original)T=1.5T = 1.5T=1.5 (flat)
degraded~0.780.45~0.37
in~0.200.30~0.28
on~0.020.15~0.18
broken~0.000.02~0.05
tail~0.000.08~0.12

For this worked table only, the tail is treated as one aggregated category and each original probability is reshaped proportionally to Pi1/TP_i^{1/T}Pi1/T​ before renormalization. A real decoder divides each individual token logit by TTT before softmax; it never aggregates the tail first. At T=0.3T = 0.3T=0.3, degraded is so dominant that sampling usually picks it. At T=1.5T = 1.5T=1.5, the probabilities spread out, and even broken becomes a possible sample. As T→0T \to 0T→0, decoding collapses toward greedy argmax. As T→∞T \to \inftyT→∞, the distribution approaches uniform.

Temperature controls distribution sharpness like a sampler strictness dial. Low temperature (T<1T < 1T<1) keeps the model focused on the top choices. High temperature (T>1T > 1T>1) spreads probability across more tokens, allowing less common recovery actions when the situation is ambiguous.

The formula

Temperature modifies the logit distribution before softmax (the function that converts raw model scores into probabilities):

P(wi)=exp⁡(zi/T)∑jexp⁡(zj/T)P(w_i) = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}P(wi​)=∑j​exp(zj​/T)exp(zi​/T)​

Reading the formula

Divide the raw logits by temperature TTT before applying softmax. When T<1T < 1T<1, the division amplifies differences between logits, making the top choice even more dominant. When T>1T > 1T>1, it compresses differences, spreading probability more evenly across options. An implementation should special-case temperature=0 as deterministic decoding rather than divide by zero.

This function applies temperature scaling to raw logits. It divides logits by the temperature before they pass through softmax. A temperature below 1.0 sharpens the distribution; a value above 1.0 flattens it:

reading-the-formula-2.py
1import torch 2 3def apply_temperature(logits: torch.Tensor, temperature: float = 1.0) -> torch.Tensor: 4 """Scale logits by temperature. Handle temperature=0 as greedy decoding elsewhere.""" 5 if temperature <= 0: 6 raise ValueError("temperature must be > 0; use greedy decoding for temperature=0") 7 return logits / temperature 8 9scaled = apply_temperature(torch.tensor([2.0, 1.0]), temperature=0.5) 10print("scaled logits:", scaled.tolist()) 11try: 12 apply_temperature(torch.tensor([2.0, 1.0]), temperature=0.0) 13except ValueError as error: 14 print("zero temperature:", error) 15 16assert scaled.tolist() == [4.0, 2.0]
Temperature guard
1scaled logits: [4.0, 2.0] 2zero temperature: temperature must be > 0; use greedy decoding for temperature=0

Mathematical intuition

Entropy measures how spread out a probability distribution is:

H(P)=−∑ipilog⁡2piH(P) = -\sum_i p_i \log_2 p_iH(P)=−∑i​pi​log2​pi​

Low entropy means most probability sits on a few candidates. Higher entropy means more candidates carry meaningful mass. The illustration computes entropy over the same five displayed buckets, including the aggregated tail bucket, so its values are directly comparable across temperatures.

Temperature TTTEffect on DistributionEntropy
T→0T \to 0T→0Approaches one-hot (greedy)near 0
T=1T = 1T=1Original model distributionBaseline
T>1T > 1T>1Flattened (more uniform)Increases
T→∞T \to \inftyT→∞Approaches uniform randomnear log⁡2V\log_2 Vlog2​V
mathematical-intuition.py
1from math import exp 2 3def softmax(logits): 4 shifted = [logit - max(logits) for logit in logits] 5 weights = [exp(logit) for logit in shifted] 6 total = sum(weights) 7 return [weight / total for weight in weights] 8 9def apply_temperature(logits, temperature): 10 if temperature <= 0: 11 raise ValueError("temperature must be > 0; use greedy decoding for temperature=0") 12 return [logit / temperature for logit in logits] 13 14logits = [2.0, 1.0, 0.0] 15low_t = softmax(apply_temperature(logits, 0.5)) 16high_t = softmax(apply_temperature(logits, 2.0)) 17 18assert low_t[0] > high_t[0] 19assert high_t[-1] > low_t[-1] 20assert round(sum(high_t), 12) == 1.0
Lower temperature concentrates probability in one token while higher temperature spreads mass across the same token labels.
As temperature rises, the same token set carries a flatter distribution across both bars and stacked probability mass.

Why it works

Dividing logits by T<1T < 1T<1 amplifies differences between logits, making the distribution peakier. When T>1T > 1T>1, the same operation shrinks differences, making probabilities more similar. This controls the sharpness of the sampling distribution.

Starting sweep candidates

Evaluation sliceTemperature candidatesWhat to measure
Code generation0.0, 0.2, 0.5Tests passed, format validity, diversity
Factual QA / runbook lookup0.0, 0.2, 0.5Grounded accuracy and unsupported claims
General chat / runbook assistance0.3, 0.7, 1.0Helpfulness, repetition, policy adherence
Creative writing / descriptions0.7, 1.0, 1.3Diversity and coherence

Why should one global temperature be questioned in evaluation?

Answer

Different endpoints tolerate different variance. Runbook lookup and rollback eligibility prioritize grounded accuracy; creative drafting may value variety. Evaluate sampler settings per task slice rather than accepting a single preset without measurements.


Top-k Sampling

Algorithm

Top-k sampling was popularized in neural story generation as a way to restrict sampling to the top kkk most probable tokens, then renormalize the distribution.[5]Reference 5Hierarchical Neural Story Generation.https://arxiv.org/abs/1805.04833 The running example shows how that restriction changes the distribution.

Suppose the model gives these probabilities after the prompt "The canary status is":

TokenProbabilityCumulative
degraded0.450.45
in0.300.75
on0.150.90
broken0.020.92
omitted tail tokens, individually below 0.020.08 total1.00

With top-k where k=2k = 2k=2, we keep only degraded and in, renormalize their probabilities to sum to 1, and sample from that smaller set. on, broken, and the tail are locked out.

This implementation of top-k sampling limits the probability distribution to only the kkk most likely next tokens. It masks out all other tokens by setting their logits to negative infinity before applying softmax and sampling from the remaining probability mass:

algorithm-2.py
1import torch 2 3def top_k_sampling(logits: torch.Tensor, k: int = 50) -> torch.Tensor: 4 """Sample from the top-k most probable tokens.""" 5 if k < 1: 6 raise ValueError("k must be >= 1") 7 k = min(k, logits.shape[-1]) 8 top_k_values, top_k_indices = logits.topk(k, dim=-1) 9 filtered = torch.full_like(logits, float('-inf')) 10 filtered.scatter_(dim=-1, index=top_k_indices, src=top_k_values) 11 probs = torch.softmax(filtered, dim=-1) 12 return torch.multinomial(probs, 1)

The fixed-k problem

The primary limitation of top-k sampling is its rigidity. A useful value for kkk varies sharply depending on the context of the generation:

ContextDistribution ShapeCandidate kkk values to evaluate
"The rollback runbook says"Potentially peaked1, 3, 10
"This product is great for"Potentially flatter10, 30, 50
"The service"Prompt-dependentMeasure rather than assume

When the distribution is highly peaked, a large kkk like 50 forces the sampler to include dozens of irrelevant tail tokens. If the temperature is high, those tail tokens can accumulate enough probability mass to be selected, causing off-topic generation. Conversely, when the distribution is flat, a small kkk like 10 can cut off valid continuations and make the output too narrow. This inability to adapt to distribution shape motivated dynamic truncation approaches.

Why does top-k fail differently on peaked and flat distributions?

Answer

Top-k always keeps the same number of tokens. On a peaked distribution, a large k admits irrelevant tail tokens. On a flat distribution, a small k cuts off valid alternatives. The fixed count ignores how confident the model is.

the-fixed-k-problem.py
1def top_k_tokens(tokens_and_probs, k): 2 return [token for token, _ in sorted(tokens_and_probs, key=lambda item: item[1], reverse=True)[:k]] 3 4def top_p_tokens(tokens_and_probs, p): 5 kept = [] 6 cumulative = 0.0 7 for token, probability in sorted(tokens_and_probs, key=lambda item: item[1], reverse=True): 8 kept.append(token) 9 cumulative += probability 10 if cumulative >= p: 11 break 12 return kept 13 14distribution = [ 15 ("degraded", 0.45), 16 ("in", 0.30), 17 ("on", 0.15), 18 ("paused", 0.04), 19 ("held", 0.03), 20 ("broken", 0.02), 21 ("rollback", 0.01), 22] 23 24assert top_k_tokens(distribution, 2) == ["degraded", "in"] 25assert top_p_tokens(distribution, 0.8) == ["degraded", "in", "on"]

Nucleus (top-p) Sampling

Algorithm

Top-k keeps exactly 50 candidate tokens if k=50k = 50k=50, even when only two are plausible or hundreds deserve consideration. Top-p keeps adding candidate tokens until their cumulative probability covers the requested mass. On an easy incident-status reply, only a few candidates qualify; on an ambiguous escalation, the candidate set is much longer. This dynamic threshold adapts naturally to each context.

Instead of fixing kkk, dynamically select the smallest high-probability prefix whose cumulative probability reaches or exceeds threshold ppp. This approach, also known as nucleus sampling, addresses the fixed candidate-count limitation of top-k.[2]Reference 2The Curious Case of Neural Text Degeneration.https://arxiv.org/abs/1904.09751

Using the same probability table:

TokenProbabilityCumulative
degraded0.450.45
in0.300.75
on0.150.90
broken0.020.92
omitted tail tokens, individually below 0.020.08 total1.00

With top-p where p=0.8p = 0.8p=0.8, we include tokens until the cumulative probability reaches 0.8. That means degraded, in, and on are in the nucleus. broken and the tail are excluded. If the distribution were more peaked and degraded had 0.85 probability, the nucleus would contain only that one token.

Vp={w(1),…,w(m)},m=min⁡{j:∑i=1jP(w(i))≥p}V_p = \{w_{(1)}, \ldots, w_{(m)}\}, \qquad m = \min \left\{j : \sum_{i=1}^{j} P(w_{(i)}) \ge p \right\}Vp​={w(1)​,…,w(m)​},m=min{j:i=1∑j​P(w(i)​)≥p}

where w(i)w_{(i)}w(i)​ are sorted by decreasing probability.

nucleus-renormalization.py
1def nucleus_distribution(tokens_and_probs, threshold): 2 kept = [] 3 mass = 0.0 4 for token, probability in sorted(tokens_and_probs, key=lambda item: item[1], reverse=True): 5 kept.append((token, probability)) 6 mass += probability 7 if mass >= threshold: 8 break 9 return [(token, round(probability / mass, 3)) for token, probability in kept] 10 11tokens = [("degraded", 0.45), ("in", 0.30), ("on", 0.15), ("broken", 0.02)] 12nucleus = nucleus_distribution(tokens, threshold=0.8) 13print("renormalized nucleus:", nucleus) 14 15assert nucleus == [("degraded", 0.5), ("in", 0.333), ("on", 0.167)] 16assert round(sum(probability for _, probability in nucleus), 3) == 1.0
Nucleus renormalization
1renormalized nucleus: [('degraded', 0.5), ('in', 0.333), ('on', 0.167)]

Reading the formula

Sort all tokens by probability, then include tokens from the top until their cumulative probability reaches ppp (e.g., 0.9). On easy predictions where one token has 95% probability, only that token qualifies. On hard predictions where many tokens share probability, a large set is included. The candidate set adapts to the distribution shape.

Nucleus sampling is straightforward to implement in PyTorch. The function sorts the logits, computes cumulative probability mass, masks away tokens outside the nucleus, and then samples from the renormalized distribution:

reading-the-formula-3.py
1import torch 2 3def nucleus_sampling(logits: torch.Tensor, p: float = 0.9) -> torch.Tensor: 4 """Top-p (nucleus) sampling: dynamic vocabulary truncation.""" 5 if not 0.0 < p <= 1.0: 6 raise ValueError("p must be in (0, 1]") 7 8 sorted_logits, sorted_indices = torch.sort(logits, descending=True, dim=-1) 9 sorted_probs = torch.softmax(sorted_logits, dim=-1) 10 cumulative_probs = torch.cumsum(sorted_probs, dim=-1) 11 12 # Remove tokens with cumulative probability above threshold 13 sorted_indices_to_remove = cumulative_probs > p 14 # Keep at least one token 15 sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() 16 sorted_indices_to_remove[..., 0] = False 17 18 indices_to_remove = torch.zeros_like(logits, dtype=torch.bool) 19 indices_to_remove.scatter_(dim=-1, index=sorted_indices, src=sorted_indices_to_remove) 20 21 filtered_logits = logits.clone() 22 filtered_logits[indices_to_remove] = float('-inf') 23 probs = torch.softmax(filtered_logits, dim=-1) 24 return torch.multinomial(probs, 1)

How distribution shape changes nucleus size

At the same p=0.9p = 0.9p=0.9 threshold, the number of survivors depends entirely on the probabilities presented at that generation step:

Sorted probabilitiesSmallest prefix reaching 0.9Survivors
0.92, 0.04, 0.02, 0.01, 0.010.921
0.45, 0.30, 0.15, 0.07, 0.030.45 + 0.30 + 0.15 = 0.903
0.22, 0.19, 0.17, 0.15, 0.13, 0.08, 0.060.22 + 0.19 + 0.17 + 0.15 + 0.13 + 0.08 = 0.946

The threshold stays fixed while the candidate count changes. Real nucleus size depends on the model, tokenizer, prompt, temperature, and exact probability shape.

Top-p's weakness: the long tail problem

Top-p has a failure mode to evaluate at high temperatures (T>1T > 1T>1): temperature flattening can cause many individually weak tokens to enter the cumulative-mass set. In those settings, a p=0.9p = 0.9p=0.9 nucleus can expand substantially. This is one motivation for newer confidence-scaled variants such as min-p.

Why can top-p admit too much tail at high temperature?

Answer

High temperature flattens the distribution, so many weak tokens gain enough combined probability mass to enter the cumulative top-p set. The nucleus can become large even though many individual tokens are poor choices.

temperature-expands-a-nucleus.py
1from math import exp 2 3def probabilities(logits, temperature): 4 weights = [exp(logit / temperature) for logit in logits] 5 total = sum(weights) 6 return [weight / total for weight in weights] 7 8def nucleus_size(probs, threshold): 9 mass = 0.0 10 for index, probability in enumerate(sorted(probs, reverse=True), start=1): 11 mass += probability 12 if mass >= threshold: 13 return index 14 15logits = [4.0, 2.0, 1.0, 0.0, -0.5, -1.0] 16focused = nucleus_size(probabilities(logits, 0.7), threshold=0.9) 17flattened = nucleus_size(probabilities(logits, 1.5), threshold=0.9) 18print(f"nucleus size at T=0.7: {focused}") 19print(f"nucleus size at T=1.5: {flattened}") 20 21assert flattened > focused
Temperature changes nucleus size
1nucleus size at T=0.7: 1 2nucleus size at T=1.5: 3

Beyond nucleus: min-p

The insight

Top-p ranks candidate next tokens and keeps enough of them to cover 90% of total probability mass. Concentrated probability produces a small shortlist; spread-out probability produces a long one. Min-p instead says "only keep candidates whose probability is at least 10% of the top-ranked candidate." A dominant top candidate sets a high bar. A weak top candidate lowers the bar. The threshold adapts to relative peak probability, not a fixed cumulative cutoff.

Min-p, proposed by Nguyen et al. and published as an ICLR 2025 oral paper, scales the cutoff by the top token's probability instead of using a cumulative-mass threshold:[6]Reference 6Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs.https://arxiv.org/abs/2407.01082

Vmin⁡-p={w:P(w)≥ρ⋅max⁡vP(v)}V_{\min\text{-}p} = \{w : P(w) \geq \rho \cdot \max_v P(v)\}Vmin-p​={w:P(w)≥ρ⋅maxv​P(v)}

Reading the formula

Find the most likely token, then keep tokens whose probability is at least ρ\rhoρ fraction of that top token's probability. If the top token has 80% probability and ρ=0.1\rho = 0.1ρ=0.1, tokens above 8% are kept. If the top token has 5%, the bar drops to 0.5%, adapting to distribution shape.

What min-p changes relative to top-p

ScenarioTop-p (p=0.9p = 0.9p=0.9)Min-p (ρ=0.1\rho = 0.1ρ=0.1)
Model confident (Pmax⁡=0.8P_{\max} = 0.8Pmax​=0.8)Keeps enough tokens to reach 90% mass, which can still include a long tailKeeps only tokens with P≥0.08P \geq 0.08P≥0.08
Model uncertain (Pmax⁡=0.05P_{\max} = 0.05Pmax​=0.05)Still targets 90% cumulative massLowers the cutoff to P≥0.005P \geq 0.005P≥0.005, so more alternatives survive
High temperature (T=1.5T = 1.5T=1.5)Flattened tails can enter the nucleusRelative cutoff can trim more of that tail
Peaked and flatter distributions show how top-k, top-p, and min-p keep different token sets.
Checkmarks show surviving tokens. Top-k keeps fixed ranks, top-p stops at cumulative mass, and min-p uses a cutoff relative to the strongest token.
what-min-p-changes-relative-to-top-p.py
1def min_p_tokens(tokens_and_probs, rho): 2 max_probability = max(probability for _, probability in tokens_and_probs) 3 threshold = rho * max_probability 4 return [token for token, probability in tokens_and_probs if probability >= threshold] 5 6confident = [("stable", 0.80), ("degraded", 0.09), ("purple", 0.01)] 7uncertain = [("stable", 0.05), ("degraded", 0.04), ("in", 0.03)] 8 9assert min_p_tokens(confident, rho=0.1) == ["stable", "degraded"] 10assert min_p_tokens(uncertain, rho=0.5) == ["stable", "degraded", "in"]

The min-p sampling function dynamically calculates a threshold based on the maximum probability in the distribution. It takes raw logits, a min_p scaling factor, and a temperature as inputs. The function applies temperature, zeroes out token probabilities that fall below the dynamic threshold (calculated as min_p times the maximum probability), and renormalizes before returning a sampled token:

what-min-p-changes-relative-to-top-p-2.py
1import torch 2 3def min_p_sampling(logits: torch.Tensor, min_p: float = 0.1, temperature: float = 1.0) -> torch.Tensor: 4 """Min-p sampling: confidence-scaled dynamic truncation.""" 5 if not 0.0 <= min_p <= 1.0: 6 raise ValueError("min_p must be in [0, 1]") 7 if temperature <= 0: 8 raise ValueError("temperature must be > 0; use greedy decoding for temperature=0") 9 10 # Apply temperature 11 logits = logits / temperature 12 probs = torch.softmax(logits, dim=-1) 13 14 # Dynamic threshold: min_p * max probability 15 max_prob = probs.max(dim=-1, keepdim=True).values 16 threshold = min_p * max_prob 17 18 # Zero out tokens below threshold 19 filtered_probs = probs.clone() 20 filtered_probs[probs < threshold] = 0.0 21 22 # Renormalize and sample 23 filtered_probs = filtered_probs / filtered_probs.sum(dim=-1, keepdim=True) 24 return torch.multinomial(filtered_probs, 1)

When to think about min-p

Min-p is a newer truncation heuristic to evaluate, rather than a universal successor to nucleus sampling. Nguyen et al. propose it for controlling low-probability candidates admitted by top-p at higher temperatures.[6]Reference 6Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs.https://arxiv.org/abs/2407.01082 A subsequent critical reanalysis reports that min-p didn't reliably improve quality-diversity tradeoffs against commonly used samplers in its experiments and disputes broad adoption claims.[7]Reference 7Min-p, Max Exaggeration: A Critical Analysis of Min-p Sampling in Language Modelshttps://arxiv.org/abs/2506.13681 Meanwhile, DeepSeek-R1 reports temperature 0.6 with top-p 0.95 in one published evaluation configuration.[8]Reference 8DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning.https://arxiv.org/abs/2501.12948 Treat any of these settings as experiment inputs, not presets for a different model or product.

MethodThresholdAdapts to distribution shape?Main tradeoff
Top-kFixed kkk tokensNoSimple, but rigid
Top-pFixed ppp cumulative massPartlyDynamic, but can admit a long tail
Min-pρ×Pmax⁡\rho \times P_{\max}ρ×Pmax​YesRelative cutoff; compare empirically with top-p

Repetition penalty

The problem

Autoregressive models can fall into degenerate repetition, where the generation process gets stuck in a loop. For example, a model might repeatedly generate the same phrase:

"The deploy rolled back. The deploy rolled back. The deploy rolled..."

How the penalty works

CTRL introduced penalized sampling and reported that a penalty around 1.2 balanced truthful generation against repetition in its experiments.[9]Reference 9CTRL: A Conditional Transformer Language Model for Controllable Generation.https://arxiv.org/abs/1909.05858 Common runtimes such as Hugging Face Transformers expose a related sign-aware logit processor: divide positive repeated-token logits and multiply negative repeated-token logits by the penalty, moving both downward in preference. This function mirrors that runtime behavior without mutating its input tensor:

how-the-penalty-works.py
1import torch 2 3def apply_repetition_penalty( 4 logits: torch.Tensor, 5 generated_ids: torch.Tensor, 6 penalty: float = 1.2 7) -> torch.Tensor: 8 """Penalize tokens that already appeared in the output (multiplicative).""" 9 if penalty <= 0: 10 raise ValueError("penalty must be positive") 11 12 adjusted = logits.clone() 13 14 for i in range(adjusted.shape[0]): 15 unique_tokens = torch.unique(generated_ids[i]) 16 17 for token_id in unique_tokens: 18 if adjusted[i, token_id] > 0: 19 adjusted[i, token_id] /= penalty 20 else: 21 adjusted[i, token_id] *= penalty 22 23 return adjusted 24 25logits = torch.tensor([[2.4, -0.5, 0.7]]) 26penalized = apply_repetition_penalty(logits, torch.tensor([[0, 1]]), penalty=1.2) 27print("original logits:", logits.tolist()[0]) 28print("penalized logits:", [round(value, 3) for value in penalized.tolist()[0]]) 29 30assert penalized[0, 0].item() < logits[0, 0].item() 31assert penalized[0, 1].item() < logits[0, 1].item() 32assert penalized[0, 2].item() == logits[0, 2].item()
Sign-aware repetition penalty
1original logits: [2.4000000953674316, -0.5, 0.699999988079071] 2penalized logits: [2.0, -0.6, 0.7]

Frequency and presence penalties

A common formulation is:

logitadjusted=logit−α⋅count(token)−β⋅1[count(token)>0]\text{logit}_{\text{adjusted}} = \text{logit} - \alpha \cdot \text{count}(\text{token}) - \beta \cdot \mathbb{1}[\text{count}(\text{token}) > 0]logitadjusted​=logit−α⋅count(token)−β⋅1[count(token)>0]

Reading the formula

Two knobs discourage repetition. The frequency penalty α\alphaα grows with each use: if a token appears 5 times, it gets 5 times the penalty. The presence penalty β\betaβ is a flat one-time penalty the moment a token is used at all, encouraging topic diversity. Exact formulas and defaults vary by stack, but this captures the core idea.


Combining Strategies in Production

In practice, production systems stack several logit transformations together. The high-level mental model is stable:

  1. Start with raw logits.
  2. Apply logit processors such as penalties, masks, or forced-token constraints.
  3. Branch by policy. Deterministic decoding takes argmax from the adjusted logits.
  4. A sampling path applies warpers such as temperature, top-k, top-p, or min-p, then samples from the surviving distribution.

The exact order is implementation-specific. Some stacks apply temperature before truncation, while others place temperature later in the sampler chain. In interviews, don't memorize one canonical order. Know that these controls are layered transformations of the logits, and check the implementation of the stack you're using.

You move a chat product to a new inference engine and outputs change even though temperature and top-p stayed the same. What sampler detail do you verify first?

Answer

Verify the exact order of penalties, masks, temperature, truncation, and sampling. There is no universal sampler order across frameworks, so the same knobs can behave differently when those transformations are applied in a different sequence.

This concrete stack makes that layered mental model visible on one next-token step.

Production sampler stack: raw logits rank A above B, repetition penalty drops repeated A below B, then greedy argmax selects B while top-p keeps B, A, and C and draws C.
One sampler ordering: the penalty lowers repeated A below B, then greedy returns B or top-p keeps B, A, and C before the illustrated draw lands on C.

The corresponding function makes that order executable: apply the repetition penalty, branch to argmax at temperature 0, otherwise scale the logits and run nucleus sampling.

combining-strategies-in-production.py
1import torch 2 3def apply_temperature(logits: torch.Tensor, temperature: float = 1.0) -> torch.Tensor: 4 if temperature <= 0: 5 raise ValueError("temperature must be > 0; use greedy decoding for temperature=0") 6 return logits / temperature 7 8def nucleus_sampling(logits: torch.Tensor, p: float = 0.9) -> torch.Tensor: 9 if not 0.0 < p <= 1.0: 10 raise ValueError("p must be in (0, 1]") 11 12 sorted_logits, sorted_indices = torch.sort(logits, descending=True, dim=-1) 13 sorted_probs = torch.softmax(sorted_logits, dim=-1) 14 cumulative_probs = torch.cumsum(sorted_probs, dim=-1) 15 16 sorted_indices_to_remove = cumulative_probs > p 17 sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() 18 sorted_indices_to_remove[..., 0] = False 19 20 indices_to_remove = torch.zeros_like(logits, dtype=torch.bool) 21 indices_to_remove.scatter_(dim=-1, index=sorted_indices, src=sorted_indices_to_remove) 22 23 filtered_logits = logits.clone() 24 filtered_logits[indices_to_remove] = float("-inf") 25 probs = torch.softmax(filtered_logits, dim=-1) 26 return torch.multinomial(probs, 1) 27 28def apply_repetition_penalty( 29 logits: torch.Tensor, 30 generated_ids: torch.Tensor, 31 penalty: float = 1.2, 32) -> torch.Tensor: 33 if penalty <= 0: 34 raise ValueError("penalty must be positive") 35 36 adjusted = logits.clone() 37 for batch_index in range(adjusted.shape[0]): 38 for token_id in torch.unique(generated_ids[batch_index]): 39 if adjusted[batch_index, token_id] > 0: 40 adjusted[batch_index, token_id] /= penalty 41 else: 42 adjusted[batch_index, token_id] *= penalty 43 return adjusted 44 45def sample_next_token( 46 logits: torch.Tensor, 47 generated_ids: torch.Tensor, 48 temperature: float = 0.8, 49 top_p: float = 0.9, 50 repetition_penalty: float = 1.1, 51) -> torch.Tensor: 52 """One common pipeline: penalties -> temperature -> nucleus sampling.""" 53 logits = apply_repetition_penalty(logits, generated_ids, repetition_penalty) 54 55 if temperature == 0: 56 return logits.argmax(dim=-1, keepdim=True) 57 58 logits = apply_temperature(logits, temperature) 59 return nucleus_sampling(logits, p=top_p) 60 61torch.manual_seed(0) 62logits = torch.tensor([[2.0, 1.2, 0.2, -1.0]]) 63generated_ids = torch.tensor([[0, 1, 1]]) 64 65greedy_token = sample_next_token(logits, generated_ids, temperature=0.0) 66sampled_token = sample_next_token(logits, generated_ids, temperature=0.8, top_p=0.9) 67 68print(f"greedy token id: {greedy_token.item()}") 69print(f"sampled token id: {sampled_token.item()}")
Output
1greedy token id: 0 2sampled token id: 1

Evaluation configurations

Evaluation sliceTemperature candidatesSampler candidatesRepetition-penalty candidatesGoal
Code generation0.0, 0.2, 0.5Greedy, top-p=0.951.0, 1.05Tests and format validity
Factual QA0.0, 0.2, 0.5Greedy, top-p=0.91.0, 1.05Grounded accuracy
Chat0.3, 0.7, 1.0Top-p=0.9, min-p=0.11.0, 1.1Helpfulness and repetition
Creative writing0.7, 1.0, 1.3Top-p=0.95, min-p=0.051.0, 1.1Diversity and coherence
Published DeepSeek-R1 pass@1 setting0.6Top-p=0.95Not reportedOne evaluation configuration[8]Reference 8DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning.https://arxiv.org/abs/2501.12948

The first four rows are candidate sweeps, not recommended defaults. The right settings depend on the model, tokenizer, task, and measured failure costs.


Choosing a policy by task

When choosing a decoding strategy for a new application, first determine whether output variance is allowed and which failures matter. Then compare candidate algorithms on task metrics, repetition, format validity, and latency.

StrategyDeterministic?Adapts to context?Candidate evaluation fit
GreedyYesNoExtraction, classification
Beam searchYesNoTranslation, summarization
Top-kNoNoSimple truncation baseline
Top-pNoYes, by cumulative massGeneral generation
Min-pNoYesConfidence-scaled truncation to compare with top-p
TemperatureModifierModifierControls global diversity
Rep penaltyModifierModifierSuppresses repeated-token reuse

What to check before moving on

DecisionPass bar
Greedy vs samplingYou can name one task where greedy is the right default and one where it will likely sound degenerate.
Beam searchYou can explain why beam width can help translation yet hurt open-ended chat, and when length penalty matters.
TemperatureYou can connect lower or higher temperature to the actual shape change in one worked probability distribution.
Top-k vs top-p vs min-pYou can choose which truncation rule better fits a peaked distribution and which better fits a flat one.
Penalty knobsYou can explain when repetition, frequency, and presence penalties solve style loops versus when they don't touch factual errors.
Production choiceYou can defend one sampler stack for factual QA, one for code, and one for creative chat under a real latency or quality goal.
Implementation bugsYou can name two runtime mistakes that change outputs even when the visible knobs look the same.

Common failures and fixes

Beam search made chat sound robotic

  • Symptom: Answers became safer and more repetitive after you increased beam width.

  • Likely cause: Beam search is pushing toward the most likely overall continuation, which is often generic in open-ended dialogue.

  • Fix: Use beam search for translation or tightly grounded summarization. For chat, switch back to a sampling strategy and tune temperature plus truncation instead.

Temperature tweak didn't fix hallucinations

  • Symptom: You lowered temperature, but the model still states wrong facts confidently.

  • Likely cause: Temperature changes distribution sharpness, not factual grounding. If the model lacks evidence, it can still choose a wrong but high-probability continuation.

  • Fix: Keep decoding conservative for factual tasks, but fix retrieval, prompt grounding, or output constraints rather than treating temperature as a truth knob.

High temperature plus top-p pulled in nonsense tail tokens

  • Symptom: Creative mode started producing weird fragments or off-topic words.

  • Likely cause: Higher temperature flattened the distribution, so top-p admitted a long tail of individually weak candidates.

  • Fix: Lower temperature, tighten top-p, or test min-p so the cutoff tracks the strength of the best token.

Fixed top-k clipped valid options

  • Symptom: Outputs stayed narrow in creative prompts and erratic in factual prompts with the same k.

  • Likely cause: Top-k doesn't adapt to distribution shape. The same cutoff is too small in flat contexts and too large in peaked ones.

  • Fix: Treat top-k as a simple baseline. Compare top-p or min-p when candidate-set size should react to distribution shape.

Same knobs changed behavior after a runtime swap

  • Symptom: Another engine gives different outputs even with matching documented settings.

  • Likely cause: Sampler order, default values, or special cases such as temperature=0 differ across implementations.

  • Fix: Inspect the actual sampler path, log intermediate logits if needed, and validate behavior on representative prompts before blaming the model.

temperature=0 was sent through the sampling math directly

  • Symptom: The runtime crashes, returns NaNs, or behaves inconsistently when someone sets temperature to zero.

  • Likely cause: The implementation divided logits by zero instead of treating temperature=0 as a greedy special case.

  • Fix: Handle temperature=0 explicitly as deterministic decoding, and keep the sampling path only for temperatures greater than zero.

Truncated probabilities no longer sum to one

  • Symptom: Logged or returned survivor probabilities sum to less than one after truncation.

  • Likely cause: Tail probabilities were set to zero, but code that consumes or reports probabilities still expects a normalized distribution. Some sampling APIs accept unnormalized nonnegative weights, but metrics and probability contracts usually don't.

  • Fix: Either mask logits with negative infinity and run softmax again, or divide surviving probabilities by their remaining mass before returning them.


Sources and further study

The papers behind these techniques are cited throughout the article: Holtzman et al. (2020) on neural text degeneration,[2]Reference 2The Curious Case of Neural Text Degeneration.https://arxiv.org/abs/1904.09751 Meister et al. (2020) on beam search paradox,[4]Reference 4If Beam Search is the Answer, What was the Question?.https://arxiv.org/abs/2010.02650 Fan et al. (2018) on top-k sampling,[5]Reference 5Hierarchical Neural Story Generation.https://arxiv.org/abs/1805.04833 Wu et al. (2016) on GNMT beam-search length normalization,[3]Reference 3Google's Neural Machine Translation System: Bridging the Gap between Human and Machine Translation.https://arxiv.org/abs/1609.08144 and Nguyen et al. (2025) on min-p as a newer confidence-scaled variant,[6]Reference 6Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs.https://arxiv.org/abs/2407.01082 along with a 2025 critical reanalysis that questions min-p's reported gains.[7]Reference 7Min-p, Max Exaggeration: A Critical Analysis of Min-p Sampling in Language Modelshttps://arxiv.org/abs/2506.13681

Related articles

  • Inference Mechanics: TTFT, TPS, and KV Cache - How decoding fits into the broader inference pipeline.
  • Speculative Decoding - Accelerating generation by drafting tokens with a smaller model.
  • Perplexity and Language Model Evaluation - Understanding why human text has high perplexity.

Practice drill

Create a decoding audit table for three production routes: factual policy answer, creative merchandising copy, and code completion.

  1. Pick baseline settings for temperature, top-p or top-k, repetition penalty, and stop conditions.
  2. Write two failure examples per route: one too deterministic, one too random or unsupported.
  3. Compare outputs under at least three setting changes, then mark accuracy, diversity, latency, and parseability.
  4. Choose one launch setting per route and write the rollback trigger that would force you to change it.

Good decoding work ends with an eval-backed operating policy, not a favorite temperature.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.At the first step, P(degraded) = 0.55 and P(needs) = 0.45. The only continuations have P(.) = 0.40 after degraded and P(review) = 0.90 after needs. Which result demonstrates the greedy trap?

Correct answer: needs review, because 0.45 x 0.90 exceeds 0.55 x 0.40.

Greedy chooses degraded from the first-step probabilities, but its sequence probability is 0.55 x 0.40 = 0.22. The alternative scores 0.45 x 0.90 = 0.405, showing that the locally strongest token need not begin the strongest full sequence.

2.At a beam search max-length cutoff, two hypotheses already ended with EOS while three are still active. How should the simplified decoder choose its return value?

Correct answer: Finalize the active hypotheses as truncated candidates and compare them with the EOS-completed hypotheses under the same sequence score.

EOS completion and max-length truncation are two ways a candidate can enter the final set. Ignoring either group changes the search result. A production decoder should also retain a finished or truncated flag and apply its configured length normalization consistently.

3.A chatbot repeats the same closing tokens several times. You want the penalty on each token to grow with every reuse. Which control matches that objective?

Correct answer: Frequency penalty, because its subtraction scales with token count.

A frequency penalty subtracts an amount proportional to the token's count, so repeated reuse becomes progressively less attractive. A presence penalty applies one flat deduction after the token has appeared at least once.

4.With top_k=10, a creative assistant misses many plausible continuations, while a factual assistant admits weak tail tokens. What explains both symptoms?

Correct answer: Fixed k ignores distribution shape; compare an adaptive truncation rule.

Top-k always retains the same number of candidates. Ten can be too few when probability is spread across many valid creative choices and too many when a factual distribution is concentrated in only a few plausible tokens.

5.A min-p sampler with rho = 0.1 sees probabilities stable: 0.80, degraded: 0.09, rollback: 0.07, and purple: 0.01. Which tokens survive before renormalization?

Correct answer: stable and degraded

Min-p sets its cutoff to rho x P_max. Here that is 0.1 x 0.80 = 0.08. The probabilities 0.80 and 0.09 meet the cutoff, while 0.07 and 0.01 do not.

6.After a runtime migration, identical nonzero sampler settings produce different candidate probabilities, and temperature=0 produces NaNs. Which audit addresses both symptoms?

Correct answer: Audit processor order and defaults, and route T=0 directly to argmax.

Engines can apply penalties, masks, temperature, and truncation in different orders or with different defaults, which changes candidate probabilities. Temperature zero must bypass division and stochastic sampling and use deterministic argmax handling.

7.A factual bot is accurate at temperature 0.2 and top_p 0.95. After only temperature rises to 1.3, it samples wrong policy continuations. What is the likely decoding cause?

Correct answer: Temperature flattened the distribution, admitting weaker tokens to top-p.

A temperature above 1 compresses logit differences and spreads probability toward weaker candidates. A wide top-p nucleus can then include more of those candidates, increasing factual variance even though the model and grounding are unchanged.

8.Four tokens have probabilities A=0.45, B=0.30, C=0.15, and D=0.10. With top_p=0.8, which distribution is sampled after truncation?

Correct answer: A, B, C with probabilities 0.50, 0.333, and 0.167.

A and B total only 0.75, so C must be included to reach the 0.8 threshold. Their retained mass is 0.90, and dividing each probability by 0.90 gives approximately 0.50, 0.333, and 0.167.

9.A team serves both narrow-target translation and open-ended creative chat. Which evaluation plan matches their different generation goals?

Correct answer: Evaluate beam search for translation and truncation-based sampling for creative chat.

Translation has a constrained target meaning, so retaining several high-probability sequence paths can improve the final result. Creative chat has many acceptable continuations, and wider beams can over-optimize generic high-likelihood text, so sampling is the more appropriate candidate to evaluate.

10.A low-temperature runbook assistant confidently gives the wrong rollback policy because retrieval supplied no runbook evidence. What should be changed first?

Correct answer: Fix retrieval or constraints; decoding cannot supply missing evidence.

Temperature and truncation control which modeled continuations are selected, not whether the model has reliable evidence. If a wrong continuation is already highly probable, making decoding more deterministic can preserve the error rather than correct it.

10 questions remaining.

Next Step
Continue to Scaling Laws & Compute-Optimal Training

You can now trace how transformer internals produce logits and how decoding turns them into output tokens. Next, shift from running a trained model to deciding how to train one: scaling laws explain how parameters, data, and compute should be balanced before an expensive training run.

PreviousMechanistic Interpretability
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Fast Inference from Transformers via Speculative Decoding.

Leviathan, Y., Kalman, M., & Matias, Y. · 2023 · ICML 2023

https://arxiv.org/abs/2211.17192

The Curious Case of Neural Text Degeneration.

Holtzman, A., et al. · 2020 · ICLR 2020

https://arxiv.org/abs/1904.09751

Google's Neural Machine Translation System: Bridging the Gap between Human and Machine Translation.

Wu, Y., et al. · 2016

https://arxiv.org/abs/1609.08144

If Beam Search is the Answer, What was the Question?.

Meister, C., Cotterell, R., & Vieira, T. · 2020 · EMNLP 2020

https://arxiv.org/abs/2010.02650

Hierarchical Neural Story Generation.

Fan, A., Lewis, M., & Dauphin, Y. · 2018 · ACL 2018

https://arxiv.org/abs/1805.04833

Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs.

Nguyen, et al. · 2025 · ICLR 2025 Oral

https://arxiv.org/abs/2407.01082

Min-p, Max Exaggeration: A Critical Analysis of Min-p Sampling in Language Models

Schaeffer, R., Kazdan, J., & Denisov-Blanch, Y. · 2025

https://arxiv.org/abs/2506.13681

DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning.

DeepSeek Team · 2025

https://arxiv.org/abs/2501.12948

CTRL: A Conditional Transformer Language Model for Controllable Generation.

Keskar, N. S., et al. · 2019

https://arxiv.org/abs/1909.05858

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.