Temperature and Sampling, Demystified

Share on LinkedIn Share on X Share on Reddit Share on HN Share on Bluesky

You set temperature to 0.7 because the blog post said to. Your chatbot gives different answers to the same question. Your extraction pipeline occasionally returns malformed JSON. Your creative writing feature produces the same three opening sentences repeatedly.

Temperature and sampling parameters control how the model chooses the next token from its probability distribution. They are not magic knobs — they are precise mathematical operations on logits. Understanding what they do lets you pick the right settings per task instead of copying defaults.

From logits to tokens

At each generation step, the model outputs a logit vector — one score per token in the vocabulary (50,000–128,000 entries). Sampling converts logits to a token:

logits → softmax(temperature) → filter (top-k/top-p) → sample → next token

Every parameter in that pipeline changes which tokens are eligible and how likely each is.

Temperature: sharpening or flattening the distribution

Temperature divides logits before softmax:

import torch
import torch.nn.functional as F

logits = model_output  # shape: [vocab_size]

def sample_with_temperature(logits, temperature=1.0):
    scaled = logits / temperature
    probs = F.softmax(scaled, dim=-1)
    return torch.multinomial(probs, num_samples=1)

# temperature = 0.01 ≈ greedy (always picks highest)
# temperature = 1.0  = model's natural distribution
# temperature = 2.0  = flatter, more random
Temperature Effect Use case
0.0 Greedy — always highest probability token Extraction, SQL, classification, code
0.1–0.3 Low randomness, mostly deterministic Factual Q&A, summarization
0.5–0.7 Moderate creativity Chatbots, general assistants
0.8–1.0 High diversity Creative writing, brainstorming
> 1.0 Very flat distribution, often incoherent Rarely useful in production

At temperature 0, "The capital of France is" almost always produces " Paris". At temperature 1.5, it might produce " Lyon", " a city", or " located".

Top-k: hard cap on candidates

Top-k keeps only the K highest-probability tokens and zeroes the rest:

def top_k_filter(logits, k=40):
    top_k_values, top_k_indices = torch.topk(logits, k)
    filtered = torch.full_like(logits, float('-inf'))
    filtered.scatter_(0, top_k_indices, top_k_values)
    return filtered

With k=40, the model samples only from the 40 most likely tokens. This prevents low-probability tail tokens (which cause hallucinations and incoherence) from being selected.

Typical values: k=40–50 for general use, k=1 equivalent to greedy.

Top-p (nucleus sampling): adaptive filtering

Top-p selects the smallest token set whose cumulative probability exceeds p:

def top_p_filter(logits, p=0.9):
    sorted_logits, sorted_indices = torch.sort(logits, descending=True)
    cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)

    # Remove tokens beyond the nucleus
    sorted_indices_to_remove = cumulative_probs > p
    sorted_indices_to_remove[1:] = sorted_indices_to_remove[:-1].clone()
    sorted_indices_to_remove[0] = False

    filtered = logits.clone()
    filtered[sorted_indices[sorted_indices_to_remove]] = float('-inf')
    return filtered

When the model is confident (one token at 95%), top-p=0.9 might keep only 2–3 tokens. When uncertain (flat distribution), it might keep 500+. This adaptiveness is why top-p generally outperforms fixed top-k.

Most production configs use top-p=0.9 or top-p=0.95 combined with temperature.

Frequency and presence penalties

These modify logits based on tokens already generated:

# Conceptual: reduce logit of tokens that already appeared
for token_id in generated_tokens:
    if token_id in logits:
        count = generated_tokens.count(token_id)
        logits[token_id] -= frequency_penalty * count  # per occurrence
        logits[token_id] -= presence_penalty              # flat penalty if seen

Use frequency penalty 0.3–0.6 when the model repeats phrases in long outputs. Avoid high penalties on extraction tasks — they can suppress valid repeated field names.

Recommended settings by task

TASK_CONFIGS = {
    "extraction": {"temperature": 0.0, "top_p": 1.0},
    "classification": {"temperature": 0.0, "top_p": 1.0},
    "code_generation": {"temperature": 0.2, "top_p": 0.95},
    "summarization": {"temperature": 0.3, "top_p": 0.9},
    "chatbot": {"temperature": 0.7, "top_p": 0.9},
    "creative_writing": {"temperature": 0.9, "top_p": 0.95, "frequency_penalty": 0.5},
}

Do not tune these in isolation. Temperature and top-p interact — low temperature with low top-p is nearly greedy; high temperature with high top-p produces chaos.

Reproducibility in production

For deterministic pipelines:

# OpenAI
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[...],
    temperature=0,
    seed=42,  # fixed seed for reproducibility
)

# vLLM
params = SamplingParams(temperature=0.0, seed=42)

Log the full sampling configuration with every request. When output quality shifts after a model update, you need to know exactly which parameters were used to isolate whether the model or the settings changed.

Resources

Production notes for LLM stacks

When llm-temperature-sampling-explained sits on an inference or RAG path, treat user prompts and retrieved chunks as untrusted input. Log correlation IDs and policy decisions—not raw prompts—in production telemetry. Gate risky operations behind explicit authorization at the gateway, not inside ad-hoc tool handlers.

Roll out changes with shadow mode first: record what would have happened under the new rule without blocking traffic. Compare deny rates, latency impact, and false positives for at least one business week before enforcing. Pair enforcement with a runbook entry: symptom, dashboard, rollback (feature flag or config), and owner.

Load-test with production-shaped concurrency. LLM workloads burst differently from CRUD APIs—tail latency and token throttling dominate. If temperature and sampling, demystified protects an invariant (security, billing, data residency), prove the invariant with an automated test that fails CI when someone removes the check.

What teams get wrong

Teams copy a reference architecture without matching their compliance tier, then discover in audit that logs, backups, or support exports reintroduced the data they thought they had eliminated. Another pattern: shipping the demo integration without idempotency, then fighting duplicate side effects when clients retry on model timeouts.

Document the tradeoff you chose—strictness vs recall, cost vs quality, sync vs async—and the metric that tells you if the choice still holds six months later.

Frequently asked questions

Should I always use temperature 0 for deterministic output?

Temperature 0 (greedy decoding) picks the highest-probability token at each step. Use it for extraction, classification, code generation, and any task where consistency matters. It can produce repetitive text in open-ended generation because the model never explores alternative phrasings.

What is the difference between top-p and top-k?

Top-k limits sampling to the K highest-probability tokens (e.g., top 40). Top-p (nucleus sampling) dynamically selects the smallest set of tokens whose cumulative probability exceeds P (e.g., 0.9). Top-p adapts to the distribution shape — narrow when the model is confident, wide when uncertain.

Why does my output change between identical requests at temperature 0?

True greedy decoding at temperature 0 should be deterministic on the same hardware and software version. Non-determinism usually comes from floating-point operation ordering in batched inference, different CUDA kernels, or the provider not actually using temperature 0 internally. Set a fixed seed where supported for reproducibility.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →