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 196 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/9
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningBayesian Inference from EvidenceStatistics 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/15
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageStatistical Learning and GeneralizationConformal Prediction and UncertaintyClustering and PCAProbabilistic Generative ModelsCore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingCausal Inference and CounterfactualsPyTorch 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/25
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernancePrivacy-Preserving Machine LearningData 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/17
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 TuningTraining Run OperationsReward 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/31
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceAccelerator Architecture Field GuideCorrect GPU Profiling and BenchmarkingGPU Kernel Performance Engineering LabGPU Parallel Primitives LabGPU Compilers and Kernel LanguagesGEMM From Naive CUDA to Tensor CoresLow-Precision GPU ProgrammingGPU Runtime Execution LabAI-Generated Kernel EngineeringModel 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 & AutoscalingGPU Failure OperationsDistributed Inference Data PlaneServing Benchmark EngineeringA/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
LearnML Algorithms & EvaluationDecoding Algorithms
🚀MediumInference Optimization

Decoding Algorithms

Explore exponential token search spaces. Implement stable softmax, greedy and beam search with length and repetition penalties, top-k/top-p/min-p sampling, and test-time reasoning verifiers.

28 min read
Learning path
Step 42 of 196 in the full curriculum
Core Retrieval AlgorithmsExperiment Design and A/B Testing

Personalize this lesson

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

The rotation guide won its place in the context prompt:

Create a replacement key, deploy it, then revoke the old key.

That passage still isn't the answer. In Core Retrieval Algorithms, retrieval picked the relevant documentation for How do I rotate an API key? It didn't generate the next token. To complete The first rotation step is, the language model might score create (supported), revoke (real action, but dangerous if done first), or quota (a fluent leak from an unrelated dashboard). Only one candidate preserves system uptime.

Decoding translates raw model scores into generated text. The exact same scores can produce radically different completions depending on whether we greedily grab the largest value, sample from a probability distribution, explore a beam of partial paths, or guide reasoning rollouts with a verifier. None of those search algorithms turns an unsupported hallucination into ground truth.

We'll work with explicit score vectors rather than downloading a multi-gigabyte checkpoint. That lets us inspect every probability, track numerical boundaries, and evaluate the decoder independently from base model weights.

The autoregressive search space: ∣V∣T|V|^T∣V∣T exponential token trees

An autoregressive model doesn't generate an entire sentence in one shot. It predicts text sequentially, token by token. Given an input prompt x=(x1,…,xM)x = (x_1, \dots, x_M)x=(x1​,…,xM​), the model computes conditional probabilities over the vocabulary VVV at each step ttt:

P(y1:T∣x)=∏t=1TP(yt∣x,y<t)P(y_{1:T} \mid x) = \prod_{t=1}^T P(y_t \mid x, y_{<t})P(y1:T​∣x)=t=1∏T​P(yt​∣x,y<t​)

Working in log-probability space turns this product into a sum, preventing floating-point underflow across long answers:

log⁡P(y1:T∣x)=∑t=1Tlog⁡P(yt∣x,y<t)\log P(y_{1:T} \mid x) = \sum_{t=1}^T \log P(y_t \mid x, y_{<t})logP(y1:T​∣x)=t=1∑T​logP(yt​∣x,y<t​)

Finding the globally optimal completion means finding the sequence y∗y^*y∗ that maximizes this joint log-probability:

y∗=arg⁡max⁡y∈Y∑t=1Tlog⁡P(yt∣x,y<t)y^* = \arg\max_{y \in \mathcal{Y}} \sum_{t=1}^T \log P(y_t \mid x, y_{<t})y∗=argy∈Ymax​t=1∑T​logP(yt​∣x,y<t​)

That objective sounds straightforward until you inspect the size of the search space Y\mathcal{Y}Y. Modern tokenizer vocabularies contain between ∣V∣=32,000|V| = 32{,}000∣V∣=32,000 and ∣V∣=128,000|V| = 128{,}000∣V∣=128,000 tokens. If the generation horizon is T=100T = 100T=100 tokens, the complete tree contains:

∣Y∣=∣V∣T≈32,000100≈10450possible sequence paths|\mathcal{Y}| = |V|^T \approx 32{,}000^{100} \approx 10^{450} \quad \text{possible sequence paths}∣Y∣=∣V∣T≈32,000100≈10450possible sequence paths

The observable universe contains roughly 108010^{80}1080 atoms. Evaluating every path in this tree is computationally impossible. Because language model probabilities change conditionally after every appended token, we can't use dynamic programming algorithms like Viterbi decoding without Markovian independence assumptions that transformers violate.

Every decoding algorithm is therefore an approximation. Some use greedy or beam search to hunt for high-probability sequences; others sample stochastically to preserve human-like diversity; modern reasoning systems use verifiers and tree search to explore multi-step deductions.

Taxonomy of LLM decoding algorithms across four regimes: 1. Deterministic Search (Greedy argmax and Beam Search with length normalization and repetition penalty); 2. Stochastic Sampling (Temperature scaling, Top-k rank cutoff, Top-p cumulative nucleus, and Min-p relative thresholding); 3. Grammar-Constrained Decoding (FSM vocabulary masking for JSON Schema, regex, and ASTs); 4. Test-Time Search and Reasoning (Best-of-N rejection sampling with ORMs, Process Reward Models for step verification, and Tree of Thoughts MCTS rollouts).
The decoding spectrum: inference engines trade compute, latency, and entropy across four regimes. Deterministic search targets sequence likelihood; sampling controls expressive diversity; grammar masks enforce schema contracts; test-time search uses verifiers to explore multi-step reasoning trees.

From raw logits to numerically stable probabilities

For clear arithmetic, treat each word below as a single token. Our prompt ends with:

rotation-prompt.txt
1Evidence: Create a replacement key, deploy it, then revoke the old key. 2Answer: The first rotation step is

Suppose the model outputs these raw prediction scores, called logits, for the immediate next token:

TokenLogitRole in rotation runbook
create3.0Supported by evidence as the initial action.
revoke2.5Valid runbook action, but performing it first causes an outage.
rename1.0Fluent distractor if account settings leaked into context.
quota-1.0Irrelevant tail token from an API dashboard.

Logits aren't probabilities: they're real numbers that can be negative and don't sum to one. Softmax converts unbounded logits into a normalized probability distribution:

pi=exp⁡(zi−m)∑jexp⁡(zj−m)wherem=max⁡jzjp_i = \frac{\exp(z_i - m)}{\sum_j \exp(z_j - m)} \qquad \text{where} \qquad m = \max_j z_jpi​=∑j​exp(zj​−m)exp(zi​−m)​wherem=jmax​zj​

Subtracting the maximum logit mmm doesn't alter the output probabilities. Because exp⁡(zi−m)=exp⁡(zi)⋅exp⁡(−m)\exp(z_i - m) = \exp(z_i) \cdot \exp(-m)exp(zi​−m)=exp(zi​)⋅exp(−m), the constant factor exp⁡(−m)\exp(-m)exp(−m) factors out of both numerator and denominator and cancels cleanly.

This shift prevents exponential overflow when logits grow large, a practical necessity in FP16 and FP32 inference engines.[1]Reference 1Accurate Computation of the Log-Sum-Exp and Softmax Functionshttps://arxiv.org/abs/1909.03469 Without it, evaluating exp⁡(1000)\exp(1000)exp(1000) crashes floating-point hardware with an overflow exception.

stable-softmax.py
1import numpy as np 2 3tokens = ["create", "revoke", "rename", "quota"] 4logits = [3.0, 2.5, 1.0, -1.0] 5 6def softmax(scores) -> np.ndarray: 7 scores = np.asarray(scores, dtype=np.float64) 8 if scores.ndim != 1 or scores.size == 0: 9 raise ValueError("scores must be a nonempty vector") 10 if np.isnan(scores).any() or np.isposinf(scores).any(): 11 raise ValueError("NaN and positive infinity are invalid logits") 12 if not np.isfinite(scores).any(): 13 raise ValueError("all tokens are masked") 14 # Negative infinity represents an intentional mask and gets zero weight. 15 weights = np.exp(scores - scores.max()) 16 return weights / weights.sum() 17 18probabilities = softmax(logits) 19for token, score, probability in zip(tokens, logits, probabilities): 20 print(f"{token:8} logit={score:>4.1f} probability={probability:.3f}") 21print(f"sum={sum(probabilities):.3f}")
Output
1create logit= 3.0 probability=0.568 2revoke logit= 2.5 probability=0.345 3rename logit= 1.0 probability=0.077 4quota logit=-1.0 probability=0.010 5sum=1.000

create is the distribution's mode at 0.568. But revoke still holds 0.345 of the total mass: over a third of the probability distribution points to an action that revokes active credentials before new keys are deployed.

Sampling unconstrained from this distribution will trigger production incidents in roughly one out of every three requests.

Numerical stability under extreme ranges

The max shift isn't an optional speed trick. Directly calling exp(1000) overflows standard 64-bit floating-point math, even though the resulting probabilities are mathematically well behaved.

avoid-softmax-overflow.py
1from math import exp 2 3large_logits = [1000.0, 999.0, 998.0] 4 5try: 6 raw_weights = [exp(score) for score in large_logits] 7 naive = [weight / sum(raw_weights) for weight in raw_weights] 8 print("naive:", naive) 9except OverflowError: 10 print("naive softmax: overflow") 11 12print("stable:", softmax(large_logits).round(3).tolist()) 13print("extreme gap:", softmax([0.0, -1000.0]).tolist()) 14try: 15 softmax([-np.inf, -np.inf]) 16except ValueError as exc: 17 print("rejected:", str(exc))
Output
1naive softmax: overflow 2stable: [0.665, 0.245, 0.09] 3extreme gap: [1.0, 0.0] 4rejected: all tokens are masked

The max subtraction eliminates exponential overflow, but hardware floating-point numbers still have finite precision. An extreme negative gap like exp(-1000) safely underflows to 0.0.

When every token is masked to negative infinity, total probability mass collapses to zero: the decoder must handle that condition explicitly rather than dividing zero by zero.

Deterministic search: greedy myopic traps and beam exploration

After picking a token, the runtime appends it to the prefix and feeds the extended sequence back into the model to predict the subsequent position. Repeating that process builds the autoregressive loop.

In serving runtimes, a key-value (KV) cache retains previous attention keys and values so earlier positions aren't recalculated from scratch.[2]Reference 2Efficient Memory Management for Large Language Model Serving with PagedAttention.https://arxiv.org/abs/2309.06180 That architecture optimizes inference throughput, but it doesn't choose the token. Selecting tokens remains the responsibility of the decoding algorithm.

Greedy decoding picks the token with the highest logit at every step:

yt=arg⁡max⁡w∈VP(w∣x,y<t)y_t = \arg\max_{w \in V} P(w \mid x, y_{<t})yt​=argw∈Vmax​P(w∣x,y<t​)
greedy-generation-loop.py
1example_logits = [ 2 {"create": 3.0, "revoke": 2.5, "rename": 1.0, "quota": -1.0}, 3 {".": 4.0, "then": 1.5, "after": 0.5}, 4] 5 6def greedy_token(scores: dict[str, float]) -> str: 7 return max(scores, key=scores.get) 8 9generated = [] 10for step, scores in enumerate(example_logits, start=1): 11 chosen = greedy_token(scores) 12 generated.append(chosen) 13 print(f"step {step}: chose {chosen!r} from {len(scores)} candidates") 14 15answer = "The first rotation step is " + " ".join(generated).replace(" .", ".") 16print(answer)
Output
1step 1: chose 'create' from 4 candidates 2step 2: chose '.' from 3 candidates 3The first rotation step is create.

Greedy generation is completely deterministic. Given identical inputs, hardware, and tie-breaking rules, it always returns the exact same sequence. It requires no hyperparameters, adds zero search overhead, and works well for structured code generation or focused factual extraction.

Yet greedy decoding suffers from a fundamental search failure: the myopic trap. Because greedy search commits irreversibly to the immediate highest-scoring token at step ttt, it can miss globally higher-probability sequences that begin with a slightly lower-scoring first token.

Beam search tracks parallel hypotheses

Beam search mitigates greedy myopia by maintaining a set of BBB active hypotheses, where BBB is the beam width.

At each step, the algorithm:

  1. Expands all BBB currently retained partial sequences by considering all possible continuations in VVV.
  2. Computes the cumulative log-probability for each candidate extension: ∑τ=1tlog⁡P(yτ∣x,y<τ)\sum_{\tau=1}^t \log P(y_\tau \mid x, y_{<\tau})∑τ=1t​logP(yτ​∣x,y<τ​).
  3. Prunes the pool back to the top BBB highest-scoring paths.

Consider an example where the prompt prefix is You should. Greedy immediately grabs create at step 1 because 0.40>0.350.40 \gt 0.350.40>0.35. But look at what happens at step 2:

Candidate pathStep 1 probStep 2 continuationStep 2 probJoint probability P(y1,y2∣x)P(y_1, y_2 \mid x)P(y1​,y2​∣x)
create →\to→ .0.40.0.200.40×0.20=0.0800.40 \times 0.20 = \mathbf{0.080}0.40×0.20=0.080 (Greedy)
first →\to→ create0.35create0.700.35×0.70=0.2450.35 \times 0.70 = \mathbf{0.245}0.35×0.70=0.245 (Beam winner)
revoke →\to→ .0.25.0.300.25×0.30=0.0750.25 \times 0.30 = 0.0750.25×0.30=0.075 (Pruned)

Greedy gets trapped with create . at joint probability 0.0800.0800.080. A beam search with width B=2B = 2B=2 retains both create and first through step 1. At step 2, it evaluates the joint paths and discovers first create at 0.2450.2450.245: more than triple the joint probability of the greedy choice.

Two-step beam search pruning tree from prefix You should. Width B=2 keeps create (0.40) and first (0.35) while pruning revoke (0.25). At step 2, greedy is trapped at create period (joint p=0.080), while beam search discovers first create (joint p=0.245), a 3.06x probability advantage. Explanatory panels detail the myopic greedy trap and length normalization.
Greedy search commits to create at step 1 because 0.40 > 0.35, landing on create . at joint score 0.080. Width-2 beam search keeps first alive, unlocking first create at joint score 0.245 (a 3.06× probability advantage). Length normalization and repetition penalties keep longer beams from collapsing into brevity or loops.
two-step-beam-search.py
1from math import exp, isclose, log 2 3first_step = {"create": 0.40, "first": 0.35, "revoke": 0.25} 4second_step = { 5 "create": {".": 0.20, "a": 0.19, "the": 0.18, "your": 0.17, "another": 0.14, "one": 0.12}, 6 "first": {"create": 0.70, "deploy": 0.20, "revoke": 0.10}, 7 "revoke": {".": 0.30, "the": 0.25, "your": 0.25, "it": 0.20}, 8} 9 10def beam_search(distributions, width: int, steps: int): 11 if type(width) is not int or width < 1: 12 raise ValueError("beam width must be a positive integer") 13 beam = [((), 0.0)] 14 for _ in range(steps): 15 candidates = [] 16 for prefix, score in beam: 17 probs = distributions[prefix] 18 assert isclose(sum(probs.values()), 1.0) 19 assert all(0 <= p <= 1 for p in probs.values()) 20 for token, p in probs.items(): 21 if p > 0: 22 candidates.append((prefix + (token,), score + log(p))) 23 beam = sorted(candidates, key=lambda row: row[1], reverse=True)[:width] 24 return beam 25 26tree = {(): first_step, **{(token,): probs for token, probs in second_step.items()}} 27greedy_first = max(first_step, key=first_step.get) 28greedy_second = max(second_step[greedy_first], key=second_step[greedy_first].get) 29greedy_prob = first_step[greedy_first] * second_step[greedy_first][greedy_second] 30 31winner, winner_log_p = beam_search(tree, width=2, steps=2)[0] 32print("greedy:", f"{greedy_first} {greedy_second}", f"probability={greedy_prob:.3f}") 33print("beam: ", " ".join(winner), f"probability={exp(winner_log_p):.3f}")
Output
1greedy: create . probability=0.080 2beam: first create probability=0.245

Beam search remains an approximate heuristic. Once a prefix falls outside the top BBB hypotheses at any step, the search discards it permanently. Even if that pruned prefix would have unlocked a continuation with probability 1.01.01.0, beam search can never recover it.

beam-can-prune-the-best-path.py
1counterexample = { 2 (): {"A": 0.5, "B": 0.3, "C": 0.2}, 3 ("A",): {str(i): 0.1 for i in range(10)}, 4 ("B",): {str(i): 0.2 for i in range(5)}, 5 ("C",): {"win": 1.0}, 6} 7for width in (2, 3): 8 path, score = beam_search(counterexample, width, steps=2)[0] 9 print(f"width={width}: {' '.join(path)} probability={exp(score):.3f}")
Output
1width=2: B 0 probability=0.060 2width=3: C win probability=0.200

Width B=2B = 2B=2 prunes branch C at step 1 because 0.2<0.30.2 \lt 0.30.2<0.3. It then settles for B 0 at joint probability 0.060. Expanding width to B=3B = 3B=3 preserves C, discovering the global optimum C win at joint probability 0.200.

Scaling beam width increases hypothesis coverage, but running BBB concurrent beams multiplies memory footprint and KV-cache storage by BBB.

Length normalization and repetition penalties

Unconstrained beam search encounters two notorious failure modes in production generation: short-sequence bias and degenerative repetition loops.

Length normalization prevents premature termination

Every generated token multiplies the joint probability by a conditional value P(yt∣… )≤1P(y_t \mid \dots) \le 1P(yt​∣…)≤1. In log space, adding non-positive log-probabilities causes cumulative path scores to decay monotonically with sequence length:

∑t=1L+1log⁡P(yt)≤∑t=1Llog⁡P(yt)≤0\sum_{t=1}^{L+1} \log P(y_t) \le \sum_{t=1}^L \log P(y_t) \le 0t=1∑L+1​logP(yt​)≤t=1∑L​logP(yt​)≤0

A completed two-token answer almost always has a higher raw joint log-probability than an insightful 15-token explanation. Raw beam search is artificially biased toward emitting the end-of-sequence token ⟨EOS⟩\langle\text{EOS}\rangle⟨EOS⟩ as early as possible.

To compare sequences of different lengths fairly, we apply a length penalty LP(L)\text{LP}(L)LP(L). Wu et al. developed the Google Neural Machine Translation length penalty:[3]Reference 3Google's Neural Machine Translation System: Bridging the Gap between Human and Machine Translation.https://arxiv.org/abs/1609.08144

S(Y)=∑t=1Llog⁡P(yt∣x,y<t)LP(L)whereLP(L)=(5+L)α(5+1)αS(Y) = \frac{\sum_{t=1}^L \log P(y_t \mid x, y_{<t})}{\text{LP}(L)} \qquad \text{where} \qquad \text{LP}(L) = \frac{(5 + L)^\alpha}{(5 + 1)^\alpha}S(Y)=LP(L)∑t=1L​logP(yt​∣x,y<t​)​whereLP(L)=(5+1)α(5+L)α​

The exponent α∈[0.6,1.0]\alpha \in [0.6, 1.0]α∈[0.6,1.0] tunes length tolerance. At α=0\alpha = 0α=0, there is no penalty; at α=1\alpha = 1α=1, the score approaches average log-probability per token.[4]Reference 4Text Generation.https://huggingface.co/docs/transformers/en/main_classes/text_generation

length-aware-sequence-score.py
1candidates = [ 2 {"text": "create <EOS>", "log_probability": -1.0, "tokens": 2}, 3 {"text": "create deploy then revoke <EOS>", "log_probability": -1.6, "tokens": 5}, 4] 5 6raw_winner = max(candidates, key=lambda row: row["log_probability"]) 7normalized_winner = max(candidates, key=lambda row: row["log_probability"] / row["tokens"]) 8 9print("raw winner: ", raw_winner["text"]) 10print("length-normalized winner: ", normalized_winner["text"]) 11for row in candidates: 12 average = row["log_probability"] / row["tokens"] 13 print(f"{row['text']:31} raw={row['log_probability']:.3f} average={average:.3f}")
Output
1raw winner: create <EOS> 2length-normalized winner: create deploy then revoke <EOS> 3create <EOS> raw=-1.000 average=-0.500 4create deploy then revoke <EOS> raw=-1.600 average=-0.320

Raw scoring prefers the terse -1.000 completion. Length normalization divides by token count: -1.600 / 5 = -0.320 beats -1.000 / 2 = -0.500, correctly selecting the complete procedure.

Repetition penalties break self-reinforcing loops

In open-ended tasks like dialogue or report generation, standard beam search frequently degenerates into repetitive loops:

The incident team notified the manager and notified the manager and notified the manager...

Language models attend to their own generated tokens. Once an n-gram repeats, self-attention reinforces that pattern, assigning high conditional probabilities to repeating it again. Maximization-based search greedily exploits this loop.[5]Reference 5The Curious Case of Neural Text Degeneration.https://arxiv.org/abs/1904.09751

Keskar et al. introduced a multiplicative logit penalty θ>1\theta > 1θ>1 that discounts any token that has already appeared in the generated prefix:[6]Reference 6CTRL: A Conditional Transformer Language Model for Controllable Generation.https://arxiv.org/abs/1909.05858

zw←{zw/θif zw>0zw⋅θif zw≤0∀w∈generated sequencez_w \leftarrow \begin{cases} z_w / \theta & \text{if } z_w > 0 \\ z_w \cdot \theta & \text{if } z_w \le 0 \end{cases} \qquad \forall w \in \text{generated sequence}zw​←{zw​/θzw​⋅θ​if zw​>0if zw​≤0​∀w∈generated sequence

Dividing positive logits by θ\thetaθ lowers their probability; multiplying negative logits by θ\thetaθ pushes them further negative. This penalizes repetition without banning words outright.

repetition-penalty.py
1def apply_repetition_penalty( 2 logits: dict[str, float], sequence: list[str], theta: float = 1.2 3) -> dict[str, float]: 4 penalized = dict(logits) 5 for token in set(sequence): 6 if token in penalized: 7 score = penalized[token] 8 penalized[token] = score / theta if score > 0 else score * theta 9 return penalized 10 11step_logits = {"create": 3.0, "deploy": 2.8, "revoke": 2.5} 12print("unpenalized choice:", max(step_logits, key=step_logits.get)) 13 14# After 'create' was emitted at step 1, apply repetition penalty: 15penalized = apply_repetition_penalty(step_logits, ["create"], theta=1.2) 16print("penalized 'create' logit:", f"{penalized['create']:.2f}") 17print("penalized choice: ", max(penalized, key=penalized.get))
Output
1unpenalized choice: create 2penalized 'create' logit: 2.50 3penalized choice: deploy

With θ=1.2\theta = 1.2θ=1.2, the logit for create drops from 3.0 to 2.50. That demotes create below deploy (2.8), steering the decoder to the next required operation instead of stuttering on the same verb.

Temperature reshapes entropy

Stochastic sampling trades exact log-likelihood maximization for linguistic variety. The first parameter governing this tradeoff is temperature T>0T > 0T>0, which scales logits prior to softmax:

pi(T)=exp⁡(zi/T)∑jexp⁡(zj/T)p_i(T) = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}pi​(T)=∑j​exp(zj​/T)exp(zi​/T)​
  • As T→0T \to 0T→0, the probability distribution collapses into a one-hot vector on the mode arg⁡max⁡jzj\arg\max_j z_jargmaxj​zj​. The sampler behaves like greedy search.
  • When T=1.0T = 1.0T=1.0, probabilities match the model's raw unscaled distribution.
  • As T→∞T \to \inftyT→∞, logits are squashed toward zero, converting the distribution into uniform random noise where every token has probability 1/∣V∣1 / |V|1/∣V∣.

We measure the dispersion of the distribution using Shannon entropy: H(p)=−∑ipilog⁡piH(p) = -\sum_i p_i \log p_iH(p)=−∑i​pi​logpi​. When using natural logarithms, entropy is expressed in nats. Higher entropy means greater output unpredictability.

temperature-and-entropy.py
1tokens = ["create", "revoke", "rename", "quota"] 2logits = [3.0, 2.5, 1.0, -1.0] 3 4def entropy(probabilities) -> float: 5 positive = np.asarray(probabilities)[np.asarray(probabilities) > 0] 6 return float(-np.sum(positive * np.log(positive))) 7 8for temperature in (0.5, 1.0, 2.0): 9 probabilities = softmax([score / temperature for score in logits]) 10 table = dict(zip(tokens, probabilities)) 11 print( 12 f"T={temperature:.1f}: create={table['create']:.3f} " 13 f"quota={table['quota']:.3f} entropy={entropy(probabilities):.3f}" 14 )
Output
1T=0.5: create=0.721 quota=0.000 entropy=0.647 2T=1.0: create=0.568 quota=0.010 entropy=0.933 3T=2.0: create=0.438 quota=0.059 entropy=1.190

At T=0.5T = 0.5T=0.5, entropy falls to 0.6470.6470.647 nats and create claims 72.1%72.1\%72.1% of the probability mass. At T=2.0T = 2.0T=2.0, entropy rises to 1.1901.1901.190 nats: the irrelevant tail token quota jumps from 1%1\%1% to nearly 6%6\%6%.

Lowering temperature sharpens the distribution, but it doesn't remove bad tokens mathematically. As long as T>0T > 0T>0, every token with a finite logit retains non-zero probability.

Does setting temperature to 0.2 eliminate the risk of sampling an unsupported tail token?

Answer

No. Temperature compresses logit gaps but leaves every finite token with positive probability. Even at T = 0.2, tail tokens retain non-zero mass. To guarantee that low-probability candidates cannot be sampled, you must use truncation filters or grammar constraints.

Seeding and production reproducibility

Sampling draws random tokens according to model probabilities. In local development, passing a fixed random seed allows repeatable draws from a static distribution:

seeded-sampling.py
1from random import Random 2 3tokens = ["create", "revoke", "rename", "quota"] 4probabilities = [0.568, 0.345, 0.077, 0.010] 5 6def draw_sequence(seed: int, length: int = 8) -> list[str]: 7 rng = Random(seed) 8 return rng.choices(tokens, weights=probabilities, k=length) 9 10for seed in (7, 7, 21): 11 print(f"seed={seed}: {' '.join(draw_sequence(seed))}")
Output
1seed=7: create create revoke create create create create create 2seed=7: create create revoke create create create create create 3seed=21: create revoke revoke create create revoke revoke create

Local seeds ensure test determinism. But in production GPU clusters, exact byte-for-byte reproducibility is difficult to guarantee.

Parallel matrix multiplications (such as cuBLAS GEMM or FlashAttention) perform floating-point additions across distributed GPU threads. Because floating-point addition is non-associative, small variations in dynamic request batching or CUDA thread scheduling alter intermediate sums slightly. Those micro-variations can flip borderline logits and steer sampling onto entirely different paths.

Hosted model providers also update inference backends and quantization layers over time. For example, Anthropic's Claude Sonnet 5 rejects custom temperature, top_p, and top_k parameters entirely, returning a 400 Bad Request if developers attempt to override default values.[7]Reference 7What's new in Claude Sonnet 5https://platform.claude.com/docs/en/about-claude/models/whats-new-sonnet-5

Production evaluation harnesses must log model version hashes, prompt snapshots, decoder parameters, and vendor response headers alongside each generated answer.

Truncating the long tail: Top-k, Top-p, and Min-p

Unconstrained sampling from the entire vocabulary inevitably pulls from what Holtzman et al. call the unreliable tail.[5]Reference 5The Curious Case of Neural Text Degeneration.https://arxiv.org/abs/1904.09751 Even if an absurd token has a tiny probability of p=0.002p = 0.002p=0.002, over an answer of 500 tokens the probability of drawing at least one tail token is:

1−(1−0.002)500≈63.2%1 - (1 - 0.002)^{500} \approx 63.2\%1−(1−0.002)500≈63.2%

To prevent tail pollution, inference engines truncate the vocabulary before drawing tokens.

Top-k truncation: fixed rank cutoff

Top-k sampling retains only the kkk highest-probability tokens, setting the probability of all other tokens to zero and renormalizing the remaining mass:[8]Reference 8Hierarchical Neural Story Generation.https://arxiv.org/abs/1805.04833

V(k)={i∈V:rank(pi)≤k}V^{(k)} = \{i \in V : \text{rank}(p_i) \le k\}V(k)={i∈V:rank(pi​)≤k}
top-k-filter.py
1from math import isclose, isfinite 2 3peaked = {"create": 0.568, "revoke": 0.345, "rename": 0.077, "quota": 0.010} 4 5def ranked_distribution(probabilities: dict[str, float]) -> list[tuple[str, float]]: 6 values = list(probabilities.values()) 7 if not values or any(not isfinite(p) or p < 0 for p in values): 8 raise ValueError("probabilities must be finite and nonnegative") 9 if not isclose(sum(values), 1.0, rel_tol=1e-12, abs_tol=1e-12): 10 raise ValueError("probabilities must sum to one") 11 return sorted( 12 ((token, p) for token, p in probabilities.items() if p > 0), 13 key=lambda row: row[1], reverse=True, 14 ) 15 16def top_k(probabilities: dict[str, float], k: int) -> list[tuple[str, float]]: 17 if type(k) is not int or k < 1: 18 raise ValueError("k must be a positive integer") 19 ranked = ranked_distribution(probabilities)[:k] 20 retained_mass = sum(prob for _, prob in ranked) 21 return [(token, prob / retained_mass) for token, prob in ranked] 22 23for token, prob in top_k(peaked, k=2): 24 print(f"{token:8} renormalized={prob:.3f}")
Output
1create renormalized=0.622 2revoke renormalized=0.378

Top-k sampling has a major structural flaw: fixed cardinality.

When the model is confident and the distribution is steep (for instance, completing The capital of France is), the single correct token might hold 99%99\%99% of the mass. A setting of k=50k = 50k=50 forces the sampler to keep 49 garbage tokens.

Conversely, when the distribution is flat because many synonyms are equally valid, k=50k = 50k=50 arbitrarily cuts off good candidates that happen to rank 51st.

Top-p (Nucleus) sampling: dynamic cumulative thresholding

Top-p sampling, or nucleus sampling, solves the fixed-cardinality problem by accumulating mass. It selects the smallest set of ranked tokens whose cumulative probability reaches a threshold p∈(0,1]p \in (0, 1]p∈(0,1]:[5]Reference 5The Curious Case of Neural Text Degeneration.https://arxiv.org/abs/1904.09751

V(p)=smallest subset of V such that ∑i∈V(p)pi≥pV^{(p)} = \text{smallest subset of } V \text{ such that } \sum_{i \in V^{(p)}} p_i \ge pV(p)=smallest subset of V such that i∈V(p)∑​pi​≥p
top-p-adapts-to-the-distribution.py
1def nucleus(probabilities: dict[str, float], threshold: float) -> list[str]: 2 if not isfinite(threshold) or not 0 < threshold <= 1: 3 raise ValueError("top-p threshold must be in (0, 1]") 4 ranked = ranked_distribution(probabilities) 5 if threshold == 1: 6 return [token for token, _ in ranked] 7 kept = [] 8 cumulative = 0.0 9 for token, prob in ranked: 10 kept.append(token) 11 cumulative += prob 12 if cumulative >= threshold: 13 break 14 return kept 15 16peaked = {"create": 0.568, "revoke": 0.345, "rename": 0.077, "quota": 0.010} 17flat = {"create": 0.28, "revoke": 0.25, "rename": 0.24, "quota": 0.23} 18 19print("peaked p=0.90:", nucleus(peaked, 0.90)) 20print("flat p=0.90:", nucleus(flat, 0.90))
Output
1peaked p=0.90: ['create', 'revoke'] 2flat p=0.90: ['create', 'revoke', 'rename', 'quota']

At p=0.90p = 0.90p=0.90, Top-p keeps only 2 tokens for the peaked distribution (0.568+0.345=0.913≥0.900.568 + 0.345 = 0.913 \ge 0.900.568+0.345=0.913≥0.90). For the flat distribution, it dynamically expands to keep all 4 tokens. Top-p adapts candidate pool size to the model's certainty.

Min-p sampling: relative probability filtering

Top-p works well at moderate temperatures (T≈0.7T \approx 0.7T≈0.7), but it struggles at higher temperatures (T≥1.2T \ge 1.2T≥1.2). High temperatures flatten the entire vocabulary tail. Because hundreds of near-zero probabilities accumulate slowly, reaching p=0.90p = 0.90p=0.90 forces Top-p to retain a massive pool of noisy tail tokens, triggering syntactic degeneration.

Min-p sampling sets its truncation threshold relative to the top candidate's probability.[9]Reference 9Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs.https://arxiv.org/abs/2407.01082 Instead of fixing rank kkk or cumulative mass ppp, it keeps only tokens whose probability is at least a fraction pbasep_{\text{base}}pbase​ of the maximum token's probability:

V(min-p)={i∈V:pi≥pbase⋅max⁡jpj}V^{(\text{min-}p)} = \{i \in V : p_i \ge p_{\text{base}} \cdot \max_j p_j\}V(min-p)={i∈V:pi​≥pbase​⋅jmax​pj​}
min-p-filter.py
1def min_p(probabilities: dict[str, float], base_threshold: float) -> list[str]: 2 if not isfinite(base_threshold) or not 0 <= base_threshold <= 1: 3 raise ValueError("min-p threshold must be in [0, 1]") 4 ranked = ranked_distribution(probabilities) 5 max_p = ranked[0][1] 6 cutoff = base_threshold * max_p 7 return [token for token, p in ranked if p >= cutoff] 8 9peaked = {"create": 0.568, "revoke": 0.345, "rename": 0.077, "quota": 0.010} 10flat = {"create": 0.28, "revoke": 0.25, "rename": 0.24, "quota": 0.23} 11 12print("peaked min-p=0.10:", min_p(peaked, 0.10)) 13print("flat min-p=0.10:", min_p(flat, 0.10))
Output
1peaked min-p=0.10: ['create', 'revoke', 'rename'] 2flat min-p=0.10: ['create', 'revoke', 'rename', 'quota']

Under the peaked distribution, the top token is create at 0.568. With pbase=0.10p_{\text{base}} = 0.10pbase​=0.10, the absolute cutoff is 0.10×0.568=0.05680.10 \times 0.568 = 0.05680.10×0.568=0.0568. rename (0.0770.0770.077) survives, but quota (0.0100.0100.010) is discarded.

Under the flat distribution, the top token is 0.28, establishing a cutoff of 0.0280.0280.028. All four tokens easily clear that bar.

Min-p scales its cutoff with the model's confidence. When the model is certain (max⁡p=0.90\max p = 0.90maxp=0.90), the cutoff jumps to 0.0900.0900.090, aggressively pruning the tail. When the model is uncertain (max⁡p=0.10\max p = 0.10maxp=0.10), the cutoff falls to 0.0100.0100.010, preserving healthy exploration.

Hugging Face includes native support for min_p directly in GenerationConfig.[4]Reference 4Text Generation.https://huggingface.co/docs/transformers/en/main_classes/text_generation

Side-by-side comparison of sampling truncation filters on peaked versus flat token distributions. On a peaked distribution (create 0.568, revoke 0.345, rename 0.077, quota 0.010), Top-k (k=2) and Top-p (p=0.90) retain 2 tokens while Min-p (0.10) retains 3. On a flat distribution (0.28, 0.25, 0.24, 0.23), Top-k prematurely truncates to 2 tokens, while Top-p and Min-p dynamically expand to retain all 4 viable candidates. Bottom panels detail the truncation mechanisms and failure modes.
Comparing probability truncation across confidence regimes: under a peaked distribution, Top-k and Top-p keep 2 tokens while Min-p keeps 3. Under a flat distribution, Top-k's rigid count discards 47% of viable probability mass, whereas Top-p and Min-p dynamically adapt their candidate pool to preserve all 4 candidates.

Boundary cases in filter implementations

Production filter implementations must handle boundary conditions predictably:

filter-boundaries.py
1boundary = {"create": 0.5, "revoke": 0.25, "rename": 0.25, "quota": 0.0} 2print("top-k k=2: ", [token for token, _ in top_k(boundary, 2)]) 3print("top-p p=.75:", nucleus(boundary, 0.75)) 4print("top-p p=1: ", nucleus(boundary, 1.0)) 5print("min-p=.5: ", min_p(boundary, 0.5)) 6print("min-p=1: ", min_p({"create": 0.5, "revoke": 0.5}, 1.0)) 7 8for label, call in [ 9 ("k=0", lambda: top_k(boundary, 0)), 10 ("p=0", lambda: nucleus(boundary, 0)), 11 ("zero mass", lambda: min_p({"create": 0.0}, 0.1)), 12]: 13 try: 14 call() 15 except ValueError as exc: 16 print(label, "REJECT", str(exc))
Output
1top-k k=2: ['create', 'revoke'] 2top-p p=.75: ['create', 'revoke'] 3top-p p=1: ['create', 'revoke', 'rename'] 4min-p=.5: ['create', 'revoke', 'rename'] 5min-p=1: ['create', 'revoke'] 6k=0 REJECT k must be a positive integer 7p=0 REJECT top-p threshold must be in (0, 1] 8zero mass REJECT probabilities must sum to one

These checks protect against silent generation failures. In production pipelines, apply temperature scaling before running truncation filters, and renormalize surviving candidates before drawing.

Diagram showing Scale logits: z / T, Stable softmax, Truncate candidates (Top-k / Top-p / Min-p), and Renormalize & draw sample.
Scale logits: z / T, Stable softmax, Truncate candidates (Top-k / Top-p / Min-p), and Renormalize & draw sample.

Grammar-constrained decoding: syntax enforcement versus semantic truth

Production applications frequently require models to produce structured outputs: valid JSON, strict enum values, or executable SQL. Left unconstrained, a model might emit valid JSON for 200 tokens and then break the parser with a trailing comma or unescaped quote.

Constrained decoding prevents syntax violations by applying a formal grammar (Context-Free Grammar, JSON Schema, or Regular Expression) directly to the decoding loop.[10]Reference 10Efficient Guided Generation for Large Language Models.https://arxiv.org/abs/2307.09702[11]Reference 11XGrammar: Flexible and Efficient Structured Generation Engine for Large Language Modelshttps://arxiv.org/abs/2411.15100[12]Reference 12llama.cpp Grammars Documentationhttps://github.com/ggml-org/llama.cpp/blob/master/grammars/README.md

The grammar compiler compiles the schema into a Finite-State Machine (FSM) or pushdown automaton. At each decoding step:

  1. The FSM inspects its current state and identifies the set of allowable next characters or bytes.
  2. The engine maps those allowed transitions against the tokenizer vocabulary.
  3. Any token that would transition the output into an invalid state has its logit forced to −∞-\infty−∞ prior to softmax:
zi←{ziif token i is a valid grammar transition−∞otherwisez_i \leftarrow \begin{cases} z_i & \text{if token } i \text{ is a valid grammar transition} \\ -\infty & \text{otherwise} \end{cases}zi​←{zi​−∞​if token i is a valid grammar transitionotherwise​

Because masked tokens receive an exponent of zero (exp⁡(−∞)=0\exp(-\infty) = 0exp(−∞)=0), their probability drops to zero. The model is mathematically incapable of emitting a syntax error.

mask-invalid-step-tokens.py
1logits = {"quota": 6.0, "revoke": 4.0, "create": 3.0, "rename": 2.0} 2schema_values = {"create", "revoke", "rename"} 3supported_by_guide = {"create"} 4 5def constrained_greedy(scores: dict[str, float], allowed: set[str]) -> str: 6 candidates = {token: score for token, score in scores.items() if token in allowed} 7 if not candidates: 8 raise ValueError("constraint removed every token") 9 return max(candidates, key=candidates.get) 10 11print("unconstrained: ", max(logits, key=logits.get)) 12print("schema-only: ", constrained_greedy(logits, schema_values)) 13print("evidence-supported:", constrained_greedy(logits, supported_by_guide))
Output
1unconstrained: quota 2schema-only: revoke 3evidence-supported: create

Notice the clear distinction between the second and third lines:

  • Unconstrained decoding picks quota because it has the highest logit (6.0).
  • Schema-constrained decoding masks quota, but picks revoke (4.0) because revoke is a valid enum field in the JSON Schema.
  • Evidence-supported decoding picks create because external validation confirmed it as the correct initial runbook step.

This illustrates the core boundary of constrained decoding: syntax enforcement is not semantic verification.[13]Reference 13Structured outputshttps://developers.openai.com/api/docs/guides/structured-outputs

An engine using OpenAI Structured Outputs or Outlines will guarantee valid JSON format with 100% syntactic reliability. But if the model chooses to hallucinate incorrect data inside a syntactically valid field, grammar masking can't detect the error. Verifying factual accuracy requires external validation or reasoning search.

Test-time search and reasoning: verifiers and thought trees

Recent advances in reasoning models (OpenAI o1/o3, DeepSeek-R1) shift computational effort from training time to inference time. Snell et al. demonstrated that scaling inference compute via test-time search can outperform scaling pretraining compute by 10× on complex reasoning tasks.[14]Reference 14Scaling LLM Test-Time Compute Optimally Can Be More Effective Than Scaling Model Parameters.https://arxiv.org/abs/2408.03314

Instead of searching blindly across raw token spaces, test-time search operates across complete trajectories or discrete reasoning steps.

Best-of-N rejection sampling with Outcome Reward Models

In Best-of-N sampling, the engine samples NNN complete, independent candidate answers at temperature T>0T > 0T>0. An Outcome Reward Model (ORM) scores each finished sequence, and the highest-scoring candidate is selected as the final answer:[15]Reference 15Training Verifiers to Solve Math Word Problems (GSM8K).https://arxiv.org/abs/2110.14168

y∗=arg⁡max⁡y(k)∈{y(1),…,y(N)}ORM(x,y(k))y^* = \arg\max_{y^{(k)} \in \{y^{(1)}, \dots, y^{(N)}\}} \text{ORM}(x, y^{(k)})y∗=argy(k)∈{y(1),…,y(N)}max​ORM(x,y(k))

Alternatively, in domains with verifiable ground truth (such as competitive programming or mathematical proofs), an automated checker (unit test runner or formal theorem prover) evaluates candidates directly.

Process Reward Models catch intermediate reasoning failures

While ORMs evaluate the final answer, they suffer from a severe credit assignment problem on multi-step reasoning.[16]Reference 16Let's Verify Step by Step.https://arxiv.org/abs/2305.20050 A model might follow flawed logic for 10 steps, make an offsetting arithmetic mistake, and stumble upon the correct final answer (a false positive). Conversely, a model might execute 10 brilliant deductive steps, make a minor typo on step 11, and receive a score of zero (a false negative).

Process Reward Models (PRMs) evaluate each intermediate reasoning step sts_tst​ individually:

rt=PRM(x,s1,s2,…,st)∈[0,1]r_t = \text{PRM}(x, s_1, s_2, \dots, s_t) \in [0, 1]rt​=PRM(x,s1​,s2​,…,st​)∈[0,1]
orm-vs-prm-verification.py
1candidates = [ 2 { 3 "id": "plan_A", 4 "steps": ["Create replacement key", "Deploy key to cluster", "Revoke old key"], 5 "prm_steps": [0.95, 0.92, 0.98], 6 "orm_score": 0.96, 7 }, 8 { 9 "id": "plan_B", 10 "steps": ["Revoke old key immediately", "Create replacement key", "Deploy key to cluster"], 11 "prm_steps": [0.05, 0.90, 0.92], 12 "orm_score": 0.72, 13 }, 14] 15 16def prm_min_score(traj: dict) -> float: 17 return min(traj["prm_steps"]) 18 19orm_best = max(candidates, key=lambda t: t["orm_score"]) 20prm_best = max(candidates, key=prm_min_score) 21 22print(f"ORM best: {orm_best['id']} (score={orm_best['orm_score']:.2f})") 23print(f"PRM best: {prm_best['id']} (min_step={prm_min_score(prm_best):.2f})") 24for t in candidates: 25 print(f"{t['id']}: ORM={t['orm_score']:.2f} PRM_min={prm_min_score(t):.2f} first_step={t['steps'][0]!r}")
Output
1ORM best: plan_A (score=0.96) 2PRM best: plan_A (min_step=0.92) 3plan_A: ORM=0.96 PRM_min=0.92 first_step='Create replacement key' 4plan_B: ORM=0.72 PRM_min=0.05 first_step='Revoke old key immediately'

Look closely at plan_B: step 1 revokes the old key before deploying a replacement. That immediately causes an active production outage.

A naive ORM that only evaluates whether all three rotation actions were mentioned scores plan_B at 0.72. But the PRM immediately catches the operational failure on step 1, scoring it at 0.05 and disqualifying the dangerous plan.

Tree of Thoughts and Monte Carlo Tree Search

Rather than sampling flat complete trajectories, advanced inference runtimes treat reasoning as search over a tree of thoughts.[17]Reference 17Tree of Thoughts: Deliberate Problem Solving with Large Language Models.https://arxiv.org/abs/2305.10601

Each node in the tree represents a partial reasoning state (a thought step or sub-goal). The search engine uses PRM step scores or heuristic value functions to:

  • Expand: Propose kkk candidate next reasoning thoughts.
  • Evaluate: Score the validity of each thought using a PRM.
  • Backtrack: When all extensions from a reasoning branch yield poor scores (rt<0.2r_t < 0.2rt​<0.2), backtrack to a parent node and explore alternative deductions.
  • Roll out: Use Monte Carlo Tree Search (MCTS) to simulate continuations toward the final solution.[18]Reference 18Mastering Chess and Shogi by Self-Play with a General Reinforcement Learning Algorithm.https://arxiv.org/abs/1712.01815

By operating at the thought level rather than the token level, test-time search navigates the exponential token tree with deliberate, System 2 problem solving.

Speculative decoding: lossless serving optimization

Deterministic search, stochastic sampling, and test-time reasoning all alter the text the model outputs. Speculative decoding solves a completely different challenge: reducing inference latency without changing the output distribution by even a single bit.[19]Reference 19Fast Inference from Transformers via Speculative Decoding.https://arxiv.org/abs/2211.17192

Standard autoregressive decoding requires one forward pass of the multi-billion-parameter target model per generated token. Because memory bandwidth limits GPU processing when generating tokens one at a time, execution arithmetic intensity is low.

Speculative decoding pairs the large target model MtargetM_{\text{target}}Mtarget​ with a small, lightweight draft model MdraftM_{\text{draft}}Mdraft​:

  1. Draft phase: The small draft model autoregressively proposes a burst of KKK candidate tokens quickly.
  2. Verification phase: The large target model processes all KKK proposed tokens in a single parallel forward pass, evaluating their exact conditional probabilities: p(y1),p(y2∣y1),…,p(yK∣y<K)p(y_1), p(y_2 \mid y_1), \dots, p(y_K \mid y_{<K})p(y1​),p(y2​∣y1​),…,p(yK​∣y<K​).
  3. Modified rejection sampling: For each draft token i∈{1,…,K}i \in \{1, \dots, K\}i∈{1,…,K}, if the target probability p(x)≥q(x)p(x) \ge q(x)p(x)≥q(x) (where qqq is the draft probability), the token is accepted. If p(x)<q(x)p(x) < q(x)p(x)<q(x), the token is accepted with probability p(x)/q(x)p(x) / q(x)p(x)/q(x).
  4. Correction: If a token is rejected at index jjj, the remaining draft tokens j+1…Kj+1 \dots Kj+1…K are discarded. The target model draws a replacement token from the adjusted residual distribution max⁡(0,p(x)−q(x))\max(0, p(x) - q(x))max(0,p(x)−q(x)) without requiring another forward pass.

This verification math proves that the output text matches the exact sampling distribution of the large target model.

Speculative decoding provides a 2× to 3× wall-clock speedup on memory-bound workloads, but it doesn't change model behavior or fix factual errors.

TechniquePrimary operational objectiveChanges output distribution?
Greedy SearchLow-latency local likelihood maximizationYes (mode collapse)
Beam SearchSequence-level joint likelihood explorationYes (maximization bias)
Temperature / Top-p / Min-pTail suppression and diversity controlYes (entropy modulation)
Grammar-Constrained FSM100% schema and syntax validityYes (hard negative masks)
Test-Time Search (PRM / MCTS)Multi-step reasoning and verificationYes (trajectory reranking)
Exact Speculative DecodingServing throughput and latency accelerationNo (lossless equivalence)

Production generation audits and offline evaluation

When tweaking retrieval algorithms, prompt templates, or inference engines, teams frequently alter decoding configurations simultaneously. If retrieval embeddings and temperature change in the same deployment, you can't tell which change caused an answer regression.

A generation audit record must capture:

  • Prompt hash, model identifier, and runtime build version.
  • Retrieval evidence snapshot ID and document chunks.
  • Decoding configuration: temperature, top-k, top-p, min-p, beam width, and length penalty.
  • Repetition penalty θ\thetaθ and grammar FSM schema version.
  • Random seed and engine stop reason (e.g. stop_token, length_limit).
  • Ground truth verification judgments.

To audit decoder mechanics, run controlled simulations against fixed distributions before conducting full user-facing experiments.

decoder-receipt-evaluation.py
1from math import exp 2from random import Random 3 4def distribution(temperature): 5 if not isfinite(temperature) or temperature <= 0: 6 raise ValueError("temperature must be finite and positive") 7 scores = dict(create=3.0, revoke=2.5, rename=1.0, quota=-1.0) 8 weights = {token: exp((z - max(scores.values())) / temperature) for token, z in scores.items()} 9 total = sum(weights.values()) 10 return {token: weight / total for token, weight in weights.items()} 11 12base = distribution(1.0) 13kept = nucleus(base, 0.9) 14retained_mass = sum(base[token] for token in kept) 15policies = { 16 "greedy": {max(base, key=base.get): 1.0}, 17 "sample T=2": distribution(2.0), 18 "top-p p=.9": {token: base[token] / retained_mass for token in kept}, 19} 20 21for label, probabilities in policies.items(): 22 rng = Random(7) 23 outputs = rng.choices(list(probabilities), weights=list(probabilities.values()), k=2000) 24 observed = outputs.count("create") / len(outputs) 25 print(f"{label:12} expected={probabilities.get('create', 0):.3f} observed={observed:.3f}")
Output
1greedy expected=1.000 observed=1.000 2sample T=2 expected=0.438 observed=0.457 3top-p p=.9 expected=0.622 observed=0.621

Greedy outputs the supported action create 100% of the time. Top-p at p=0.90p = 0.90p=0.90 retains both create and revoke, selecting create in 62.1%62.1\%62.1% of draws and emitting the dangerous revoke in roughly 37.9%37.9\%37.9%. Sampling at T=2.0T = 2.0T=2.0 drops the supported action rate to 45.7%45.7\%45.7%.

Decoders only manipulate scores: they don't understand infrastructure safety. If the model had assigned a higher logit to revoke, greedy decoding would execute the dangerous step every single time. Real-world reliability requires pairing appropriate decoding algorithms with grounded evidence retrieval and verified system testing.

Complete the lesson

Mastery Check

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

1.A decoder converts next-token logits [1000.0, 999.0, 998.0] into a softmax distribution without overflowing floating-point hardware. Which implementation preserves exact mathematical probabilities while preventing overflow?

Correct answer: Subtract the maximum logit from all scores before exponentiation, then divide each shifted exponential by their sum.

Subtracting max(z) multiplies every term by exp(-max(z)), which cancels completely between numerator and denominator. This prevents numerical overflow by capping the largest exponent at exp(0) = 1.0.

2.An autoregressive vocabulary contains 32,000 tokens and generations run for 100 steps. Why is global exact search for the sequence maximizing joint probability computationally impossible in production transformers?

Correct answer: The complete token tree contains 32,000^100 paths and transformer attention invalidates Markovian independence assumptions.

The exponential space |V|^T contains roughly 10^450 paths, far exceeding the number of atoms in the universe. Because self-attention depends on all previous tokens, Viterbi dynamic programming cannot be applied.

3.A generation loop produces logits create = 3.0, revoke = 2.5, rename = 1.0, and quota = -1.0 at step 1. After appending create, step 2 logits are . = 4.0, then = 1.5, and after = 0.5. What text does greedy decoding produce?

Correct answer: The first rotation step is create.

Greedy decoding takes the argmax token at each position. It selects 'create' at step 1 (logit 3.0) and '.' at step 2 (logit 4.0), yielding 'The first rotation step is create.'

4.At step 1, candidate paths have probabilities create = 0.40 and first = 0.35. Their step 2 continuations are create . with p = 0.20 and first create with p = 0.70. How do greedy decoding and beam search with width B=2 compare?

Correct answer: Greedy yields 'create .' (p=0.080); beam search yields 'first create' (p=0.245) by tracking both initial hypotheses.

Greedy commits myopically to 'create' at step 1 (0.40 * 0.20 = 0.080). Beam search retains both 'create' and 'first', discovering 'first create' at joint probability 0.35 * 0.70 = 0.245.

5.Why does unnormalized beam search systematically favor shorter answers over longer, more descriptive answers?

Correct answer: Adding tokens multiplies by conditional probabilities <= 1, causing cumulative log-probabilities to decay monotonically.

Because each added token multiplies joint probability by a factor <= 1, cumulative log-probabilities decrease with length. Unnormalized beam search prematurely chooses early EOS tokens to minimize decay.

6.How does Keskar repetition penalty theta > 1 discourage language models from entering degenerate repetitive n-gram loops?

Correct answer: It discounts logits of previously generated tokens by dividing positive scores and multiplying negative scores by theta.

Keskar repetition penalty scales logits of tokens already present in the sequence (z/theta for positive, z*theta for negative). This lowers their likelihood of reselection without strictly banning words.

7.For a peaked distribution {create: 0.568, revoke: 0.345, rename: 0.077, quota: 0.010} and flat distribution {create: 0.28, revoke: 0.25, rename: 0.24, quota: 0.23}, which behavior correctly contrasts Top-k (k=2) and Top-p (p=0.90)?

Correct answer: Top-k retains 2 tokens in both distributions; Top-p retains 2 tokens for peaked and all 4 tokens for flat.

Top-k is fixed by rank and keeps 2 tokens regardless of entropy. Top-p accumulates probability until reaching 0.90: peaked needs only 2 tokens (0.568+0.345=0.913), while flat requires all 4 tokens to reach 0.90.

8.A JSON Schema constrains the first_step field to enum [create, revoke, rename]. Logits are quota = 6.0, revoke = 4.0, and create = 3.0. The retrieved runbook only supports create. What does schema-constrained greedy decoding output?

Correct answer: It outputs revoke because quota is masked out by the schema, but the model logit for revoke exceeds create.

Constrained decoding enforces syntax, not semantic truth. The schema masks out 'quota', leaving 'revoke' (logit 4.0) and 'create' (logit 3.0). Greedy selection picks 'revoke', generating a valid but wrong action.

9.In multi-step reasoning evaluation, what primary failure mode of Outcome Reward Models (ORMs) do Process Reward Models (PRMs) resolve?

Correct answer: ORMs score only the final output, suffering from credit assignment ambiguity when flawed reasoning produces a lucky answer.

ORMs evaluate only the final answer, so they miss catastrophic intermediate mistakes (like revoking keys before deploying replacements) or penalize correct derivations with minor final typos. PRMs score each step.

10.A serving infrastructure team implements exact speculative decoding using a small draft model and large target model. What effect should the team expect?

Correct answer: Wall-clock generation speed improves while preserving the exact output probability distribution of the target model.

Exact speculative decoding uses modified rejection sampling to ensure that the accepted tokens follow the exact target model distribution, achieving a 2x to 3x speedup with zero quality degradation.

10 questions remaining.

Next Step
Continue to Experiment Design and A/B Testing

Hold the generator fixed while testing one retrieval change. The next lesson uses an <span data-glossary="ab-testing">A/B test</span> to ask whether <span data-glossary="query-rewriting">query rewriting</span> improves key-rotation answers, without confusing that effect with a different sampler.

PreviousCore Retrieval Algorithms
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Accurate Computation of the Log-Sum-Exp and Softmax Functions

Blanchard, P., Higham, D. J., & Higham, N. J. · 2019

https://arxiv.org/abs/1909.03469

Efficient Memory Management for Large Language Model Serving with PagedAttention.

Kwon, W., et al. · 2023 · SOSP 2023

https://arxiv.org/abs/2309.06180

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

Text Generation.

Hugging Face. · 2026 · Official documentation

https://huggingface.co/docs/transformers/en/main_classes/text_generation

The Curious Case of Neural Text Degeneration.

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

https://arxiv.org/abs/1904.09751

CTRL: A Conditional Transformer Language Model for Controllable Generation.

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

https://arxiv.org/abs/1909.05858

What's new in Claude Sonnet 5

Anthropic · 2026

https://platform.claude.com/docs/en/about-claude/models/whats-new-sonnet-5

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

Efficient Guided Generation for Large Language Models.

Willard, B. T. & Louf, R. · 2023 · arXiv preprint

https://arxiv.org/abs/2307.09702

XGrammar: Flexible and Efficient Structured Generation Engine for Large Language Models

Dong, Y., Ruan, C. F., Cai, Y., et al. · 2024

https://arxiv.org/abs/2411.15100

llama.cpp Grammars Documentation

ggml-org · 2026

https://github.com/ggml-org/llama.cpp/blob/master/grammars/README.md

Structured outputs

OpenAI · 2024

https://developers.openai.com/api/docs/guides/structured-outputs

Scaling LLM Test-Time Compute Optimally Can Be More Effective Than Scaling Model Parameters.

Snell, C., et al. · 2024 · arXiv preprint

https://arxiv.org/abs/2408.03314

Training Verifiers to Solve Math Word Problems (GSM8K).

Cobbe, K., et al. · 2021

https://arxiv.org/abs/2110.14168

Let's Verify Step by Step.

Lightman, H., et al. · 2023 · ICLR

https://arxiv.org/abs/2305.20050

Tree of Thoughts: Deliberate Problem Solving with Large Language Models.

Yao, S., et al. · 2023 · NeurIPS

https://arxiv.org/abs/2305.10601

Mastering Chess and Shogi by Self-Play with a General Reinforcement Learning Algorithm.

Silver, D., et al. · 2017 · arXiv preprint

https://arxiv.org/abs/1712.01815

Fast Inference from Transformers via Speculative Decoding.

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

https://arxiv.org/abs/2211.17192

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.