Problem
The decoder-only model hands back a score (a logit) for each of its tens of thousands of vocabulary entries. To write a sentence we must choose one token, feed it back, and choose again. The choice is not obvious. Always taking the top token is repetitive and can talk itself into a loop. Sampling straight from the probabilities sometimes picks a very unlikely word, and one odd word can derail everything after it. Different tasks want different behaviour: a code completion should be near-deterministic, a story should surprise you.
Intuition
Think of the model's output as a weighted die with one face per word. Decoding is how you roll it. Greedy always shows the heaviest face. Temperature reshapes the die: low temperature makes heavy faces heavier, high temperature evens the faces out. Top-k and top-p cut away the long tail of silly faces before rolling. Beam search does not roll at all; it keeps a few promising partial sentences and extends them in parallel, because the best first word does not always lead to the best sentence.
Real libraries apply these steps as a chain of logit processors; the order shown is the common one, not a law.
Mechanics
Greedy and beam search
Greedy decoding is argmax of the logits at the last position. It is fast and
deterministic, but it optimises one step at a time, while what we want is the sequence with the
highest total probability, the product of its step probabilities (the sum of their logs).
Beam search keeps the B best partial sequences by total
log-probability. At each step it extends every beam with every token, scores the results, and
keeps the best B. With B=1 it is greedy. Beam search is a heuristic: it
is not guaranteed to find the best sequence either, only a better one.
Temperature
Divide the logits by a temperature T before the softmax: pi = exp(zi/T) / Σj exp(zj/T). As T→0 all the mass goes to the largest logit (greedy); T=1 is the model's own distribution; as T→∞ it approaches uniform. The name comes from the Boltzmann distribution in physics, where the same formula has the temperature as a divisor.
Top-k and top-p (nucleus)
Top-k keeps only the k highest-probability tokens and renormalises. Top-p keeps the smallest set of top tokens whose probabilities add up to at least p, so the number kept adapts: a confident step keeps a few tokens, an uncertain one keeps many. Both finish by dividing the survivors by their total so they sum to 1.
Repetition penalties
Models are prone to repeating themselves. A repetition penalty lowers the logit of tokens already generated. One common form (the idea comes from the CTRL model's "penalized sampling") divides a positive logit by a penalty θ>1 and multiplies a negative one by θ, so that the token gets less likely in both cases. This page uses that form with θ=1.5.
Constrained decoding
When the output must follow a format (valid JSON, a grammar, one of a few labels), set the logit of every token that would break the format to −∞ before the softmax. Those tokens get probability exactly 0, and the remaining ones are renormalised. Each step needs the set of allowed tokens, which a grammar or state machine supplies from what has been written so far.
Speculative decoding
Generation is slow because each token needs a pass through the big model. Speculative decoding has a small, cheap draft model propose several tokens, then lets the big target model score all of them in one pass. For a drafted token x with draft probability q(x) and target probability p(x), accept it with probability min(1, p(x)/q(x)). On rejection, draw instead from the residual distribution max(0, p−q), renormalised. The result: the final tokens follow the target's distribution exactly, so quality is unchanged and only speed varies. The check on this page confirms it for one step.
| Component | Shape/Params | Meaning | Source |
|---|---|---|---|
logits.argmax(-1) |
[B, V] → [B] |
Greedy decoding. | Categorical.mode |
Categorical(logits=…) |
logits […, V], normalised with logsumexp |
A distribution over the vocabulary; logits need not sum to 1. |
categorical.py |
Categorical.sample |
sample_shape → samples of shape […] |
Calls torch.multinomial with replacement on the probabilities. |
lines 144-149 |
torch.multinomial |
[B, V] weights → [B, n] indices |
Draws indices; rows need not sum to 1 but must be non-negative, finite, with a non-zero sum. | _torch_docs.py |
torch.topk |
[B, V] → values and indices [B, k] |
The k largest entries; indices of ties are not guaranteed stable. | _torch_docs.py |
F.softmax(x, dim=-1) |
[B, V] |
exp(xi)/Σ exp(xj) along dim. |
functional.py |
Categorical.entropy |
[…] |
−Σ p log p, a measure of how spread out the step is. | lines 159-163 |
Worked example
One step, every rule
The model gives these logits for the next word after "the cat …": the 3.0, a 2.2, cat 1.6, dog 1.4, sat 0.2, ran -0.6. Each row below is computed by the page's helpers (probabilities for the, a, cat, dog, sat, ran).
| Rule | Probabilities | Note |
|---|---|---|
| Softmax, T = 1 | 0.504, 0.226, 0.124, 0.102, 0.031, 0.014 | entropy 1.932 bits |
| T = 0.5 | 0.765, 0.154, 0.046, 0.031, 0.003, 0.001 | entropy 1.104 bits |
| T = 2 | 0.330, 0.221, 0.164, 0.148, 0.081, 0.055 | entropy 2.369 bits |
| Top-k, k = 3 | 0.590, 0.265, 0.145, 0.000, 0.000, 0.000 | kept 0, 1, 2; mass before renormalising 0.854 |
| Top-p, p = 0.9 | 0.527, 0.237, 0.130, 0.106, 0.000, 0.000 | kept 4 tokens; mass 0.956 |
| Penalty θ = 1.5 on the, cat | 0.294, 0.359, 0.116, 0.161, 0.049, 0.022 | logits become 2.0, 2.2, 1.07, 1.4, 0.2, -0.6 |
| Only cat, dog, sat allowed | 0.000, 0.000, 0.484, 0.396, 0.119, 0.000 | others are exactly 0 |
Greedy picks the. Drawing 1000 samples from the T = 1 row with a fixed seed gives frequencies 0.509, 0.225, 0.123, 0.101, 0.030, 0.012, close to the probabilities but not equal: sampling is random, and averages converge slowly. Note that the top-p row kept 4 tokens where top-k kept 3; with p = 0.9, the first three tokens only reach 0.854, so the fourth is needed.
Beam search beats greedy
Take a tiny model with start symbol <s>: it says x with probability
0.5, y with 0.4, and ends with 0.1. After x
it ends with 0.4; after y it always ends. Greedy takes x (the
larger first step) and then end: <s> → x → end, probability
0.20. Beam search with width 2 keeps both first words and finds
<s> → y → end, probability 0.40. The best first
word did not lead to the best sentence.
Speculative decoding preserves the target
Vocabulary a, b, c, d. Target p = [0.450, 0.300, 0.150, 0.100], draft q = [0.250, 0.400, 0.100, 0.250]. The chance a drafted token is accepted is Σ min(p, q) = 0.75. On rejection we draw from the residual [0.800, 0.000, 0.200, 0.000]. Adding the two routes, P(output = x) = min(p, q)(x) + (1 − 0.75) · residual(x), gives [0.450, 0.300, 0.150, 0.100], identical to p. With acceptance rate α per token and γ drafted tokens, and assuming each is accepted independently with probability α, one target pass yields 1 + α + … + αγ tokens on average: for γ = 4 that is 1.94 at α = 0.5, 3.05 at α = 0.75 and 4.10 at α = 0.9.
Implementation
import torch
import torch.nn.functional as F
def sample_next(logits, temperature=1.0, top_k=None, top_p=None):
# logits: [B, V] for the last position
if temperature == 0:
return logits.argmax(-1, keepdim=True) # greedy
logits = logits / temperature
if top_k is not None:
kth = torch.topk(logits, top_k).values[..., -1, None]
logits = logits.masked_fill(logits < kth, float("-inf"))
if top_p is not None:
sorted_logits, order = torch.sort(logits, descending=True)
probs = F.softmax(sorted_logits, dim=-1)
before = probs.cumsum(-1) - probs # mass strictly ahead
sorted_logits = sorted_logits.masked_fill(before >= top_p, float("-inf"))
logits = torch.full_like(logits, float("-inf")).scatter(-1, order, sorted_logits)
probs = F.softmax(logits, dim=-1)
return torch.multinomial(probs, num_samples=1) # [B, 1]
# repetition penalty on tokens already in `ids` ([B, T]):
def penalize(logits, ids, theta=1.5):
seen = logits.gather(-1, ids)
seen = torch.where(seen > 0, seen / theta, seen * theta)
return logits.scatter(-1, ids, seen)
The mask for top-p keeps a token while the mass strictly ahead of it is still below
p, so the token that crosses the threshold is kept, matching "smallest set whose
mass reaches p". Ties in topk are not stable, so top-k can keep slightly more than
k tokens here when logits tie at the cut. torch.multinomial accepts unnormalised
weights, but softmax output is already normalised.
Tradeoffs
- Greedy and beam suit tasks with one right answer (translation, extraction). They are repetitive for open-ended text, and wide beams can favour short or generic outputs, so implementations usually add length normalisation.
- Temperature trades diversity for reliability. It is not calibration: dividing logits does not make the model more correct, only more or less committed.
- Top-k versus top-p. A fixed k keeps too many tokens when the model is sure and too few when it is not. Top-p adapts to that, which is why it is the usual default. Both are heuristics with no guarantee of better text.
- Penalties stop loops but also penalise words that should repeat (names, code identifiers). Keep θ small.
- Constrained decoding guarantees format but can force the model into low-probability continuations if the constraint disagrees with what it wanted to write.
- Speculative decoding is lossless in distribution but only helps when the draft model is much cheaper and agrees with the target often (high α); a poor draft wastes compute. The speed-up also depends on how verification of γ tokens costs compared with one, which is a hardware question (see the KV cache).
Check yourself
What does temperature 0 mean in practice?
Dividing by zero is undefined, so libraries treat it as the limit T→0: argmax, i.e. greedy decoding.
Why renormalise after top-k or top-p?
The kept probabilities sum to less than 1 (here 0.854 for k = 3), and sampling needs a distribution that sums to 1.
Why does speculative decoding need the residual distribution?
Accepted drafts under-represent tokens where the target is larger than the draft. The residual max(0, p−q) supplies exactly that missing mass, so the total is p.
Connections
Read first
Read next
- The KV cache advanced
References
- Holtzman, Buys, Du, Forbes and Choi. The Curious Case of Neural Text Degeneration (nucleus sampling). ICLR, 2020 (arXiv 1904.09751).
- Fan, Lewis and Dauphin. Hierarchical Neural Story Generation (top-k sampling). ACL, 2018 (arXiv 1805.04833).
- Leviathan, Kalman and Matias. Fast Inference from Transformers via Speculative Decoding. ICML, 2023 (arXiv 2211.17192).
- Chen et al. Accelerating Large Language Model Decoding with Speculative Sampling. 2023 (arXiv 2302.01318).
- Keskar, McCann, Varshney, Xiong and Socher. CTRL: A Conditional Transformer Language Model for Controllable Generation. 2019 (arXiv 1909.05858).
- Willard and Louf. Efficient Guided Generation for Large Language Models. 2023 (arXiv 2307.09702).
- PyTorch v2.15.0:
categorical.py,multinomial,topk,softmax.