Language models do not produce finalized text in a single step. At their core, modern autoregressive Transformer architectures compute unnormalized log probabilities—known as logits—over a discrete vocabulary for every position in a sequence. The process of transforming these raw logit vectors into human-readable text is governed entirely by the decoding algorithm.
While model fine-tuning and prompt engineering dictate what a model knows, the choice of decoding strategy determines how the model expresses that knowledge. The selection of decoding hyperparameters directly influences output quality, stylistic diversity, structural fidelity, and inference latency. Selecting the wrong decoding parameters can lead to repetitive loops, unpredictable hallucinations, or severe computational bloat.
This report provides a technical analysis of modern decoding strategies, detailing their mathematical foundations, implementation mechanics, operational tradeoffs, and role in modern production LLM serving stacks.
Executive Overview
At each generation step, an autoregressive language model processes an input sequence $mathbfx_<t = (x_1, x2, dots, xt-1)$ and outputs a logit vector $mathbfz_t in mathbbR^V$, where $|V|$ represents the size of the vocabulary. The decoding algorithm uses $mathbfz_t$ to choose the next token $x_t in V$, appends $x_t$ to the context, and repeats the process until a predefined termination criterion is satisfied.
+-------------------------------------------------------+
| Autoregressive LLM Forward Pass |
+-------------------------------------------------------+
|
v
Raw Logits Vector (z_t)
|
+----------------------+----------------------+
| |
v v
[ Deterministic ] [ Stochastic ]
- Greedy Search - Temperature Scaling
- Beam Search - Top-k Truncation
- Top-p (Nucleus) Sampling
| |
+----------------------+----------------------+
|
v
Logit Masking & Penalties
- Repetition Penalties
- Structured Grammars / JSON Masks
|
v
Next Token Selection (x_t)
Decoding algorithms fall along a spectrum between deterministic search and stochastic sampling:
- Deterministic Algorithms (Greedy Search, Beam Search): Select tokens strictly based on highest cumulative or local probability. These methods are useful for factual extraction, translation, and tasks with constrained target spaces, but they risk falling into repetitive loops or generating sterile text.
- Stochastic Algorithms (Temperature, Top-$k$, Nucleus/Top-$p$): Sample from the predicted probability distribution. By introducing controlled variance, sampling improves creative writing and conversational engagement, though it risks producing low-probability, incorrect tokens if unconstrained.
- Constrained and Guided Decoding: Applies dynamically generated masks to $mathbfz_t$ prior to sampling. This forces outputs to adhere strictly to formal context-free grammars (CFGs), JSON schemas, or explicit regular expressions.
Detailed Technical Breakdown & Implementation
1. Reading Logits and Foundational Inference
To inspect the raw output of a model, we perform a single forward pass and evaluate the output tensor shape. For a vocabulary size $|V|$, the raw model outputs a tensor of shape [batch_size, sequence_length, vocab_size]. For next-token generation, only the logit vector at the final position $t-1$ is evaluated.
The following PyTorch code demonstrates how to extract next-token logits using Hugging Face’s transformers library:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# Load a lightweight causal language model for local evaluation
model_name = "sshleifer/tiny-gpt2"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
model.eval()
prompt = "A language model is"
input_ids = tokenizer(prompt, return_tensors="pt").input_ids
with torch.no_grad():
outputs = model(input_ids)
# Extract logits for the final token in the sequence
# Shape: [batch_size, sequence_length, vocab_size] -> [1, vocab_size]
next_token_logits = outputs.logits[:, -1, :]
print(f"Logits shape at index t-1: next_token_logits.shape")
# Convert raw logits to a probability distribution via Softmax
probs = torch.softmax(next_token_logits, dim=-1)
2. Greedy Decoding
Greedy decoding selects the token with the highest logit value at each step:
$$xt = argmaxvi in V (mathbfzt, i)$$
Because $argmax operatornamesoftmax(mathbfz_t) = argmax (mathbfz_t)$, calculating the full softmax distribution is mathematically unnecessary for greedy selection.
@torch.no_grad()
def greedy_decode(model, tokenizer, prompt: str, max_new_tokens: int = 30) -> str:
input_ids = tokenizer(prompt, return_tensors="pt").input_ids
for _ in range(max_new_tokens):
outputs = model(input_ids)
next_token_logits = outputs.logits[:, -1, :]
# Select token with the maximum logit value
next_token = next_token_logits.argmax(dim=-1, keepdim=True)
# Append selected token to the input sequence
input_ids = torch.cat([input_ids, next_token], dim=1)
# Early stopping on EOS token
if next_token.item() == tokenizer.eos_token_id:
break
return tokenizer.decode(input_ids[0], skip_special_tokens=True)
- Advantages: Zero hyperparameter tuning required; fully deterministic output; efficient step-wise execution.
- Disadvantages: Suffers from local search myopia. Choosing the most probable token at step $t$ can lead to low-probability states at step $t+1$, often resulting in repetitive loops (e.g., "in the future of the future of the future").
3. Temperature-Scaled Stochastic Sampling
Temperature scaling modifies the sharpness of the probability distribution by dividing the logit vector by a positive scalar $T > 0$ before applying the softmax transformation:
$$P(x_t = vi mid mathbfx<t) = fracexp(mathbfzt, i / T)sumj=1^V exp(mathbfz_t, j / T)$$
Logit Distributions across Different Temperatures
--------------------------------------------------
Logits (z): [ 2.0, 1.0, 0.1 ]
T = 0.5 (Sharpened) T = 1.0 (Standard) T = 2.0 (Flattened)
+------------------+ +------------------+ +------------------+
| Token 0: 84.1% | | Token 0: 65.9% | | Token 0: 46.8% |
| Token 1: 11.4% | | Token 1: 24.2% | | Token 1: 28.4% |
| Token 2: 4.5% | | Token 2: 9.9% | | Token 2: 24.8% |
+------------------+ +------------------+ +------------------+
- Low Temperature ($T < 1.0$): Sharpens the distribution, concentrating probability mass on top-scoring tokens. As $T to 0$, behavior converges toward greedy search.
- High Temperature ($T > 1.0$): Flattens the distribution, assigning more uniform weight to lower-scoring tokens. This increases output variance, but setting $T$ too high risks generating ungrammatical or nonsensical output.
@torch.no_grad()
def temperature_decode(
model, tokenizer, prompt: str, temperature: float = 0.8, max_new_tokens: int = 30
) -> str:
input_ids = tokenizer(prompt, return_tensors="pt").input_ids
assert temperature > 0.0, "Temperature must be strictly positive."
for _ in range(max_new_tokens):
outputs = model(input_ids)
# Scale logits by temperature
logits = outputs.logits[:, -1, :] / temperature
probs = torch.softmax(logits, dim=-1)
# Sample from scaled probability distribution
next_token = torch.multinomial(probs, num_samples=1)
input_ids = torch.cat([input_ids, next_token], dim=1)
if next_token.item() == tokenizer.eos_token_id:
break
return tokenizer.decode(input_ids[0], skip_special_tokens=True)
4. Truncation Strategies: Top-$k$ and Nucleus (Top-$p$) Sampling
Unconstrained temperature sampling still leaves open the possibility of drawing from the long tail of low-probability tokens. Truncation strategies eliminate this tail by filtering candidate tokens before applying softmax.
Top-$k$ Sampling
Top-$k$ sampling restricts candidate selection to the $k$ most probable tokens:
$$V^(k) = argtop_k (mathbfz_t)$$
The remaining logits are set to $-infty$, zeroing out their probability mass post-softmax.
@torch.no_grad()
def top_k_sample(logits: torch.Tensor, k: int) -> torch.Tensor:
# Retain only top-k logits, set all others to -inf
values, indices = torch.topk(logits, k)
min_values = values[:, [-1]]
filtered_logits = torch.where(
logits < min_values,
torch.tensor(float('-inf'), device=logits.device),
logits
)
probs = torch.softmax(filtered_logits, dim=-1)
return torch.multinomial(probs, num_samples=1)
Limitation: Top-$k$ uses a fixed candidate count regardless of context. If the model’s prediction distribution is very confident (e.g., $P(text"United") = 0.99$), setting $k=50$ forces the sampler to consider 49 unlikely alternatives. Conversely, if the true distribution is broad, $k=50$ may truncate plausible candidate tokens.
Nucleus (Top-$p$) Sampling
Nucleus sampling solves this issue by dynamically sizing the candidate set. It retains the smallest set of top tokens $V^(p)$ whose cumulative probability meets or exceeds a target threshold $p in (0, 1]$:
$$sumv in V^(p) P(v mid mathbfx<t) ge p$$
Confident Context ("The capital of France is...")
+---------------------------------------+
| "Paris" (p=0.92) | -> Cutoff reached immediately
+---------------------------------------+ Candidate pool size = 1
Uncertain Context ("The scientific reason was...")
+---------------------------------------+
| "due" (0.30) | "that" (0.25) | ... | -> Requires 8 tokens to reach
+---------------------------------------+ Cumulative Probability >= 0.90
Unified Temperature, Top-$k$, and Top-$p$ Implementation
In modern inference pipelines, temperature scaling, top-$k$, and top-$p$ filtering are applied in a strict, sequential order:

@torch.no_grad()
def sample_nucleus_topk(
logits: torch.Tensor,
temperature: float = 1.0,
top_k: int = 0,
top_p: float = 0.9
) -> torch.Tensor:
# 1. Apply Temperature Scaling
logits = logits / temperature
# 2. Apply Top-k Filtering
if top_k > 0:
top_k = min(top_k, logits.size(-1))
indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1:]
logits[indices_to_remove] = float('-inf')
# 3. Apply Top-p (Nucleus) Filtering
if top_p < 1.0:
sorted_logits, sorted_indices = torch.sort(logits, descending=True, dim=-1)
sorted_probs = torch.softmax(sorted_logits, dim=-1)
cumulative_probs = torch.cumsum(sorted_probs, dim=-1)
# Shift the masks to preserve the first token above the threshold
sorted_indices_to_remove = cumulative_probs > top_p
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
sorted_indices_to_remove[..., 0] = False
# Scatter removed masks back to original logit positions
indices_to_remove = sorted_indices_to_remove.scatter(
dim=-1, index=sorted_indices, src=sorted_indices_to_remove
)
logits[indices_to_remove] = float('-inf')
probs = torch.softmax(logits, dim=-1)
return torch.multinomial(probs, num_samples=1)
5. Repetition Penalties
To prevent autoregressive loops, explicit penalty multipliers can be applied to tokens that have already been generated. The multi-step logit penalty proposed by Keskar et al. adjusts candidate logits based on prior presence:
$$z_i’ = begincases
z_i / theta & textif z_i > 0
z_i cdot theta & textif z_i < 0
endcases quad forall i in U$$
where $U$ is the set of unique token IDs present in the generated history, and $theta ge 1.0$ is the repetition penalty multiplier.
@torch.no_grad()
def apply_repetition_penalty(
logits: torch.Tensor, generated_ids: torch.Tensor, penalty: float = 1.15
) -> torch.Tensor:
if penalty == 1.0:
return logits
logits = logits.clone()
# Extract set of distinct tokens previously generated
unique_ids = torch.unique(generated_ids)
for token_id in unique_ids:
logit_val = logits[0, token_id]
if logit_val > 0:
logits[0, token_id] = logit_val / penalty
else:
logits[0, token_id] = logit_val * penalty
return logits
Operational Caution: Setting $theta$ too high ($theta > 1.3$) can disrupt model output for domain-specific tasks. Code generation, legal syntax, and structured data formats inherently require token repetition. Over-penalizing previously generated tokens can force the model to introduce ungrammatical synonyms or invent non-existent identifiers.
6. Sequence-Level Search: Beam Search
Unlike greedy search, which tracks only a single state, beam search maintains a set of $B$ working hypotheses (called the beam width). At step $t$, the algorithm expands all $B$ hypotheses across the full vocabulary $|V|$, scores the resulting $B times |V|$ candidate sequences by their cumulative log probability, and retains the top $B$ candidates.
The score for a sequence $Y_t = (y_1, dots, y_t)$ is computed as:
$$textScore(Yt) = sumi=1^t log P(yi mid y<i, mathbfx)$$
@torch.no_grad()
def beam_search_decode(
model, tokenizer, prompt: str, num_beams: int = 3, max_new_tokens: int = 20
) -> str:
input_ids = tokenizer(prompt, return_tensors="pt").input_ids
# Maintain list of tuples: (cumulative_log_prob, sequence_tensor)
beams = [(0.0, input_ids)]
for _ in range(max_new_tokens):
candidates = []
for score, seq in beams:
# End search early for beams that hit the EOS token
if seq[0, -1].item() == tokenizer.eos_token_id:
candidates.append((score, seq))
continue
outputs = model(seq)
logits = outputs.logits[:, -1, :]
log_probs = torch.log_softmax(logits, dim=-1)
# Select top K candidates for the current beam branch
top_log_probs, top_indices = torch.topk(log_probs, num_beams, dim=-1)
for val, idx in zip(top_log_probs[0], top_indices[0]):
next_seq = torch.cat([seq, idx.view(1, 1)], dim=1)
candidates.append((score + val.item(), next_seq))
# Retain top B sequences across all candidate branches
beams = sorted(candidates, key=lambda x: x[0], reverse=True)[:num_beams]
best_score, best_seq = beams[0]
return tokenizer.decode(best_seq[0], skip_special_tokens=True)
7. Structured Decoding Constraints
Modern production applications often require language models to output structured data (e.g., JSON adhering to a target schema or SQL queries matching a database grammar). Unconstrained sampling relies entirely on prompt instruction adherence, which can fail unpredictably.
Guided Decoding solves this by inserting a dynamic constraint engine between the raw model outputs and the token sampler.
+-------------------------------------------------------+
| Raw Model Forward Pass (Logits) |
+-------------------------------------------------------+
|
v
+-------------------------------------------------------+
| Grammar / Schema Filter Engine |
| Evaluates valid transitions using FSM or Trie |
+-------------------------------------------------------+
|
Valid Tokens (0) | Invalid Tokens (-inf)
+---------------+---------------+
| |
v v
+-----------------------+ +-----------------------+
| Keep Raw Logit Value | | Set Logit = -infinity |
+-----------------------+ +-----------------------+
| |
+---------------+---------------+
|
v
+-------------------------------------------------------+
| Softmax & Next-Token Sampling |
+-------------------------------------------------------+
- State Tracking: The engine maintains an internal state using a Deterministic Finite Automaton (DFA) or Context-Free Grammar parser (e.g., via the Lark parser).
- Logit Masking: Before calling
softmax(), the engine looks up the set of syntactically valid next tokens based on the current parser state. - Hard Masking: Logits for all syntactically invalid tokens are masked to $-infty$. This guarantees that the sampled token strictly obeys the grammar rules.
The following simplified code demonstrates token-level masking to force the model to output one of a set of valid strings:
@torch.no_grad()
def choose_constrained_label(model, tokenizer, prompt: str, labels: list[str]) -> str:
input_ids = tokenizer(prompt, return_tensors="pt").input_ids
outputs = model(input_ids)
logits = outputs.logits[:, -1, :]
label_scores = []
for label in labels:
# Resolve target label to token sequence
label_ids = tokenizer.encode(label, add_special_tokens=False)
assert len(label_ids) == 1, f"Label 'label' spans multiple tokens."
token_id = label_ids[0]
label_scores.append((logits[0, token_id].item(), label))
# Return label associated with the highest logit value
_, best_label = max(label_scores)
return best_label
Supporting Context, Benchmarks & Operational Metrics
Selecting a decoding strategy introduces operational tradeoffs between quality, latency, memory consumption, and runtime complexity.
| Decoding Strategy | Compute Complexity (per step) | KV-Cache Footprint Memory | Primary Use Case | Primary Failure Modes | ||||
|---|---|---|---|---|---|---|---|---|
| Greedy Search | $O(1)$ | $O(L cdot D)$ | Factual extraction, deterministic classification | Degenerative repetition, local search myopia | ||||
| Temperature / Top-$p$ | $O( | V | log | V | )$ | $O(L cdot D)$ | Chat, summarization, creative text generation | Stochastic hallucinations, potential output divergence |
| Beam Search ($B$) | $O(B cdot | V | log | V | )$ | $O(B cdot L cdot D)$ | Machine translation, structured summarization | Repetitive generic phrasing, high memory overhead |
| Grammar Masking | $O(textDFA State Lookups)$ | $O(L cdot D)$ | Strict JSON / SQL structured output | Latency overhead during mask generation |
Key Tradeoffs
Beam Search Latency and Memory Overhead
Beam search scale requirements can quickly strain production inference servers. Running a beam search of size $B=4$ multiplies KV-cache memory usage by 4x per request. It also limits standard batching optimizations because candidates within a beam must be evaluated simultaneously.
Because modern instruct-tuned chat models concentrate most of their probability mass on high-confidence output trajectories, beam search rarely yields noticeable quality gains in conversational settings. As a result, it has largely been phased out in production LLM inference services.
KV-Cache Memory Footprint vs Sequence Length (Batch Size = 1)
+-------------------------------------------------------------------+
4 | [Beam Search B=4]|
| .../ |
3 | .../ |
| .../ |
2 | .../ |
| .../ |
1 | ........................ ... [Standard Sampling / Greedy] |
|___________________________________________________________________|
0 512 1024 2048
Sequence Length (Tokens)
Grammar Masking Overhead
While schema-constrained decoding guarantees valid outputs (avoiding runtime parsing failures), it introduces compute overhead at each generation step. Building and updating token bitmasks for high-complexity context-free grammars (CFGs) over large vocabularies ($|V| ge 128,000$) can add measurable overhead to per-token processing time (TPOT). Production runtime engines (such as vLLM, TensorRT-LLM, and Outlines) mitigate this latency by pre-compiling JSON schemas into finite-state machines and caching valid transition paths.
Industry Perspectives & Production Best Practices
Major AI research groups and serving framework maintainers share key best practices regarding decoding strategy deployment:
1. Model Calibration Shifted Best Practices
Historical guidance (e.g., setting $T=0.7$, top-$p=0.9$ for GPT-2/GPT-3) has evolved. Modern instruction-tuned models (such as Llama-3, Mistral, and Claude) undergo extensive RLHF/DPO training, resulting in tightly calibrated logit distributions. High temperatures ($T > 0.8$) can degrade performance on reasoning tasks (GSM8K, HumanEval). Many modern production deployments default to low temperatures ($T in [0.0, 0.2]$) or greedy decoding for reasoning and code generation tasks.
2. Structured Outputs in API Gateways
To eliminate downstream JSON parsing errors, enterprise platform providers (e.g., OpenAI, Anthropic, vLLM engine hosts) now run grammar-constrained decoding directly on their inference nodes. Rather than attempting to patch invalid responses via retry logic, constrained decoding guarantees schema compliance during generation.
3. Separation of Repetition Penalty and Sampling
Engineers warn against relying heavily on repetition penalties to fix poor generation quality. Severe penalties can disrupt valid code structures, leading to ungrammatical syntax or hallucinated variable names. Repetition issues are often better addressed by increasing top-$p$ truncation bounds or fixing prompt context quality.
Future Outlook
Decoding algorithms are shifting from static, hand-tuned samplers to dynamic, runtime-aware optimization engines:
Next-Generation Decoding Paradigms
----------------------------------
[ Speculative Decoding ] [ Dynamic Samplers ] [ Hardware-Level Constraints ]
+----------------------+ +------------------+ +----------------------------+
| Draft model generates| | Min-P & Tail- | | Trie/DFA masks computed |
| candidate tokens; | | Free Sampling | | directly in CUDA/C++ loops |
| target model verifies| | adapt dynamically| | inside vLLM & TRT-LLM |
+----------------------+ +------------------+ +----------------------------+
- Speculative Decoding & Multi-Token Heads: Fast target model verification techniques leverage lightweight draft models (or parallel prediction heads like Medusa) to propose token sequences. The main model then evaluates candidate tokens in parallel, using modified greedy or stochastic verification criteria to achieve 2x–3x throughput gains without quality degradation.
- Dynamic Truncation Metrics (e.g., Min-$P$ Sampling): Min-$p$ sampling filters out tokens whose probability falls below a threshold scaled relative to the top candidate’s probability:
$$Ptextthreshold = ptextscale times P(x_textmax)$$
This provides a more adaptive candidate cutoff than standard top-$p$, scaling smoothly between narrow and broad probability distributions. - **In
