What is top-k sampling? It is a decoding strategy that restricts the next-token prediction to the k most probable tokens at each step, discarding the long tail of the distribution before sampling. By truncating low-probability candidates, top-k reduces the chance of incoherent or hallucinated output while preserving stochasticity.
Unlike greedy decoding (which always picks argmax) or pure multinomial sampling (which considers the full vocabulary), top-k imposes a hard cap on the candidate set. The value of k directly controls the diversity–coherence trade-off: smaller k yields safer, more deterministic text; larger k allows more creative but riskier continuations.
How top-k sampling works
At each decoding step, the model produces a probability distribution P over the vocabulary V (typically 32k–256k tokens). Top-k sampling applies three operations in sequence:
- Sort the vocabulary by descending probability P(token | context).
- Truncate to the top k tokens. All tokens ranked k+1 through |V| receive zero probability mass.
- Renormalize the remaining k probabilities so they sum to 1, then sample from this truncated distribution.
Formally, let V_k be the set of k tokens with highest probability. The sampling distribution becomes:
P_top-k(t | context) = P(t | context) / Σ_{t' ∈ V_k} P(t' | context) if t ∈ V_k
0 otherwise
The temperature parameter T is applied before truncation (standard practice in most implementations):
logits' = logits / T
probs = softmax(logits')
Then top-k filtering runs on probs. This order matters: temperature sharpens or flattens the original distribution, changing which tokens make the top-k cut.
Minimal implementation
import torch
import torch.nn.functional as F
def top_k_logits(logits: torch.Tensor, k: int) -> torch.Tensor:
"""Mask logits so only top-k remain. Returns modified logits."""
if k <= 0:
return logits
# Get the k-th largest logit value per batch item
values, _ = torch.topk(logits, k, dim=-1)
# values[:, -1:] is the threshold (smallest of the top-k)
threshold = values[:, -1:]
# Mask everything below threshold to -inf
return torch.where(logits < threshold, torch.full_like(logits, float('-inf')), logits)
def sample_top_k(logits: torch.Tensor, k: int, temperature: float = 1.0) -> torch.Tensor:
logits = logits / temperature
logits = top_k_logits(logits, k)
probs = F.softmax(logits, dim=-1)
return torch.multinomial(probs, num_samples=1)
This pattern — mask logits to -inf before softmax — is numerically stable and matches what you’ll find in Hugging Face generate, vLLM, and TensorRT-LLM.
Why top-k matters for production systems
Controlling tail risk
LLM vocabularies contain thousands of tokens that are grammatical but semantically nonsense in context (e.g., “the”, “a”, “banana” when completing a SQL query). The aggregate probability mass of this “long tail” can exceed 10–20% even when the top token has 40%+ probability. Pure multinomial sampling will occasionally draw from this tail, producing non-sequiturs or hallucinations. Top-k eliminates that risk by construction.
Predictable latency and memory
Top-k sampling avoids the overhead of nucleus (top-p) sorting when k is small and fixed. The topk kernel is highly optimized on GPU; it runs in O(k log k) or better with specialized implementations. For batch inference with fixed k (e.g., k=50), the per-step cost is deterministic — useful for latency SLAs.
Composition with other parameters
Top-k composes cleanly with temperature, repetition penalty, and logit bias. A common production recipe:
| Parameter | Typical range | Effect |
|---|---|---|
top_k |
20–100 | Hard cap on candidate diversity |
temperature |
0.3–1.0 | Sharpens/flattens before truncation |
repetition_penalty |
1.0–1.2 | Suppresses loops, applied post-top-k |
top_p |
0.9–0.95 (optional) | Secondary filter, applied after top-k |
Applying top-p after top-k (the default in most libraries) means the nucleus operates on an already-truncated distribution. This is usually what you want: top-k removes the absolute garbage, top-p removes the marginal garbage.
Concrete example: completing a code snippet
Prompt:
def fibonacci(n: int) -> int:
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
Assume the model’s next-token distribution (simplified):
| Token | Probability |
|---|---|
\n |
0.35 |
|
0.25 |
# |
0.12 |
def |
0.08 |
if |
0.05 |
return |
0.04 |
else |
0.03 |
banana |
0.01 |
| … | … |
| (30k more) | 0.07 |
With top_k=5, temperature=0.7:
- Temperature 0.7 sharpens the distribution (higher peaks, lower valleys).
- Top-5 tokens kept:
\n,,#,def,if. - Remaining 93% of probability mass (including
bananaand 30k others) zeroed out. - Renormalized probabilities sum to 1.0 over the 5 candidates.
- Sample — most likely
\nor, producing valid Python continuation.
With top_k=200, temperature=1.0:
- No sharpening.
- Top-200 includes
else,return, and many low-prob tokens. banana(rank ~500) still excluded, but weirder continuations possible.- Higher variance in output — sometimes creative, sometimes broken.
With top_k=0 (disabled) + top_p=0.95:
- Nucleus finds smallest set covering 95% cumulative probability.
- If the head is sharp, this might be 3 tokens; if flat, could be 500+.
- Variable candidate set size per step — less predictable latency.
Common misconceptions
“Top-k and top-p do the same thing”
False. Top-k is a fixed-cardinality filter; top-p is a fixed-mass filter. On sharp distributions (code, JSON), top-p may keep only 2–3 tokens while top-k=50 keeps 50. On flat distributions (creative writing), top-p=0.95 may keep 200+ tokens while top-k=50 keeps 50. They answer different questions:
- Top-k: “Never consider more than k options.”
- Top-p: “Never consider options that collectively exceed (1-p) probability.”
“Top-k=1 is the same as greedy decoding”
Mostly true, but not identical. With top_k=1 and temperature > 0, you still run softmax + multinomial over a single token — which always returns that token. The difference is numerical: greedy uses argmax directly on logits; top-k=1 goes through softmax. Results match unless logits contain inf/nan or you’re using float8 quantization where softmax behaves differently. For practical purposes, treat them as equivalent.
“Higher top-k always means more creative”
Only up to a point. Once k exceeds the “effective vocabulary” — the number of tokens with non-negligible probability in context — increasing k further adds only noise tokens. For typical English contexts at temperature 0.7, the effective vocabulary is often 30–80 tokens. Setting top_k=500 vs top_k=100 rarely changes output quality but does increase the chance of sampling a hallucination token that happened to sneak into the top 500.
“Top-k prevents repetition”
It does not. Repetition arises from the model assigning high probability to recently generated tokens (a known transformer pathology). Top-k merely limits which high-probability tokens are eligible. If the model puts 0.6 probability on “the” and 0.3 on “the” again, top-k=10 keeps both. Use repetition_penalty, frequency_penalty, or presence_penalty for actual repetition control.
“You must choose either top-k or top-p”
Most production stacks apply both sequentially: top-k first (hard cap), then top-p (adaptive mass cap). This combination — sometimes called “top-k + top-p” or “truncated nucleus” — gives you a safety ceiling (k) and a quality floor (p). The Hugging Face generate default (top_k=50, top_p=1.0) effectively disables top-p; setting top_p=0.95 enables the combo.
Practical tuning guidelines
| Use case | Recommended starting point |
|---|---|
| Code generation (strict) | top_k=20, temperature=0.2 |
| Code generation (flexible) | top_k=50, temperature=0.4 |
| Structured output (JSON, SQL) | top_k=10, temperature=0.1 |
| Creative writing | top_k=100, temperature=0.8, top_p=0.95 |
| Chat / general purpose | top_k=50, temperature=0.7, top_p=0.95 |
| Classification / extraction | top_k=1, temperature=0 (greedy) |
Tune in this order:
- Set
temperaturefor desired randomness level. - Set
top_kto eliminate the long tail (start at 50). - Add
top_p=0.95if you want adaptive truncation on top of the hard cap. - Add penalties only if you observe repetition loops.
Interaction with provider defaults
Different inference endpoints ship different defaults. OpenAI’s chat.completions does not expose top-k directly (only top_p). Anthropic exposes top_k. Open-source servers (vLLM, TGI, TensorRT-LLM) expose both. If you’re routing traffic across multiple providers — as you might through a gateway that normalizes the OpenAI-compatible interface — verify each provider’s default top_k and whether they apply top-p before or after. The order changes the effective distribution.
Summary
Top-k sampling is a hard cardinality constraint on the next-token candidate set. It removes the long tail of low-probability tokens that cause hallucinations and non-sequiturs, while preserving stochasticity within the retained head. The parameter k is a direct knob on the diversity–coherence trade-off: lower for code and structured output, higher for creative tasks. Combine with temperature (applied first) and optionally top-p (applied after) for a complete decoding strategy. Avoid the misconception that top-k alone controls repetition — it doesn’t — and don’t assume higher k always improves quality past the effective vocabulary size.