ml-models / Language models / Decoding and sampling core

Decoding and sampling

A language model outputs a probability for every possible next token. Decoding is the rule that turns those probabilities into text: always take the likeliest token, search for the likeliest whole sentence, or draw at random with the odds reshaped by temperature, top-k and top-p. This page works every rule on one six-word vocabulary so you can see exactly what each one does to the numbers, then shows constrained and speculative decoding.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. Greedy and beam search
  5. Temperature
  6. Top-k and top-p (nucleus)
  7. Repetition penalties
  8. Constrained decoding
  9. Speculative decoding
  10. Worked example
  11. One step, every rule
  12. Beam search beats greedy
  13. Speculative decoding preserves the target
  14. Implementation
  15. Tradeoffs
  16. Check yourself
  17. Connections
  18. Read first
  19. Read next
  20. References

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.

The decoding pipeline for one step Logits from the model pass through optional adjustments in order: a repetition penalty, constraint masking, division by the temperature, then softmax to probabilities, then a top-k or top-p cut with renormalisation, and finally either an argmax or a random draw to pick the next token, which is appended to the sequence and fed back to the model. logits [V] penalty / mask ÷ temperature softmax top-k / top-p cut argmax or draw next token append, repeat the order matters: temperature before the cut changes what the cut keeps

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.

One distribution under three temperatures Three groups of six bars for the words the, a, cat, dog, sat, ran. At temperature 0.5 the first word has probability 0.76; at 1 it has 0.50; at 2 it has 0.33. Lower temperatures concentrate probability on the leading words. T = 0.5 the a cat dog sat ran T = 1 the a cat dog sat ran T = 2 the a cat dog sat ran bar height = probability (tallest bar at T = 0.5 is 0.76)

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.

ComponentShape/ParamsMeaningSource
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).

RuleProbabilitiesNote
Softmax, T = 10.504, 0.226, 0.124, 0.102, 0.031, 0.014entropy 1.932 bits
T = 0.50.765, 0.154, 0.046, 0.031, 0.003, 0.001entropy 1.104 bits
T = 20.330, 0.221, 0.164, 0.148, 0.081, 0.055entropy 2.369 bits
Top-k, k = 30.590, 0.265, 0.145, 0.000, 0.000, 0.000kept 0, 1, 2; mass before renormalising 0.854
Top-p, p = 0.90.527, 0.237, 0.130, 0.106, 0.000, 0.000kept 4 tokens; mass 0.956
Penalty θ = 1.5 on the, cat0.294, 0.359, 0.116, 0.161, 0.049, 0.022logits become 2.0, 2.2, 1.07, 1.4, 0.2, -0.6
Only cat, dog, sat allowed0.000, 0.000, 0.484, 0.396, 0.119, 0.000others 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.

Target, draft and the overlap that is accepted For each of the four words, two bars: the target probability and the draft probability. The shared part, the smaller of the two, is accepted directly and totals 0.75. Where the target is larger than the draft, the excess is the residual that replaces rejected drafts. a p 0.45 q 0.25 b p 0.30 q 0.40 c p 0.15 q 0.10 d p 0.10 q 0.25 solid = target p, dashed = draft q

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

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.