The bug report said: “The model is broken. It gives a different answer every time I ask the same question.” I’ve gotten some version of this from three different engineers now, and each time the fix is the same — not a code change, but a change in how they think about what a language model actually is. Because the model isn’t broken. It’s doing exactly what it was built to do. The expectation is what’s broken.

Traditional software is a vending machine: press B4, get the same chips every time. Same input, same output, forever. That determinism is so deeply baked into how engineers think that when an LLM returns “Sure, here’s an email…” one moment and “I’d be happy to help you draft that…” the next — same prompt, same model, same settings — it feels like a defect. It isn’t. A language model doesn’t retrieve answers. It rolls them, one token at a time, from a set of loaded dice it learned during training. This is the guide to why that happens, how to control it, and the surprising truth that you can’t fully turn it off.

TL;DR

→ LLMs don’t store answers — they predict the next token as a probability distribution over the whole vocabulary, then sample one token from it, append it, and repeat. Different samples → different answers.

→ At each step the model outputs raw scores (logits) for every possible token. Softmax turns those into probabilities. A decoder picks one. That pick is where variation enters.

→ Temperature reshapes the probability distribution before sampling. Low temperature (→0) sharpens it toward the single most likely token (predictable, repetitive). High temperature (0.8–1.2) flattens it (diverse, creative, riskier).

→ top_p (nucleus sampling) and top_k limit which tokens are even eligible — they cut the long tail of unlikely tokens so the model can’t wander into nonsense.

→ The counterintuitive part: even at temperature 0 (greedy decoding), you are not guaranteed identical output. Floating-point rounding and how the server batches your request with others introduce tiny variations that can cascade into different tokens.

→ This is a feature, not a bug. If the model always picked the single highest-probability token, every answer would collapse into the same bland, repetitive text. Sampling is what gives it range.

→ To maximize reproducibility: pin a dated model version (not “latest”), set temperature 0 and top_p 1, use a seed if the API offers one, and design your tests to accept semantic equivalence — not byte-for-byte matches.

The core idea: the model predicts, it doesn’t retrieve

Here’s the mental shift that fixes the “it’s broken” reaction. When you ask an LLM a question, it does not look up a stored answer. Before it writes a single word, it scores every token in its vocabulary — tens of thousands of possible next pieces of text — by how well each would continue what’s been written so far. Those raw scores are called logits. They’re just the model’s unnormalized confidence in each candidate token.

Then a function called softmax converts those scores into a proper probability distribution: numbers between 0 and 1 that sum to 1. Maybe “Sure” gets 18%, “I’d” gets 15%, “Happy” gets 9%, and a long tail of thousands of other tokens splits the rest. A decoder then samples one token from that distribution, appends it to the context, and the whole loop runs again for the next token. And the next. Hundreds of times.

The key realization: for almost any interesting prompt, there is no single correct next token. There are thousands of valid continuations. An email can open with a greeting, a question, a bold statement, an apology — all reasonable. The model has learned that they’re all plausible, and it assigns each a probability. When it samples, it might pick “Sure” this time and “I’d” the next. From that one different first token, the entire rest of the response can diverge. That’s not the model malfunctioning. That’s the model exploring the space of good answers.

Temperature: the dial that reshapes the dice

Llm temperature dial x class=

Temperature doesn’t add randomness — it reshapes the probability distribution the model samples from. Low = sharp spike, one clear winner. High = flattened, many contenders.

People call temperature “the creativity slider.” That’s directionally right but explains nothing about what’s actually happening. Mechanically, temperature is a number the logits are divided by before softmax converts them to probabilities.

Divide by a small number (temperature near 0) and the differences between logits get exaggerated. The most likely token’s probability balloons toward 100% and everything else shrinks toward 0. The distribution becomes a single tall spike. The model has almost no choice but to pick that top token every time — predictable, consistent, and at the extreme, repetitive and a little robotic.

Divide by a larger number (temperature around 1) and the differences compress. The gap between the top token and the runners-up narrows, so more tokens become live options. The distribution flattens. Now the model genuinely might pick the second or fifth most likely token, which is where variety, surprise, and “creativity” come from — along with a higher chance of an odd or wrong choice.

My rule of thumb from production use: temperature 0–0.3 for anything where correctness and consistency matter (classification, extraction, structured output, factual Q&A). 0.7–0.9 for drafting, brainstorming, and copy where you want variety. Above 1.0 only when you’re deliberately chasing unusual output and can tolerate the misfires.

top_p and top_k: fencing off the nonsense

Temperature reshapes the whole distribution, but two other levers control which tokens are even allowed into the drawing.

top_k is the blunt version: keep only the k most likely tokens, discard the rest, sample from what’s left. top_k = 40 means “only ever consider the 40 best options.” It stops the model from occasionally grabbing a bizarre low-probability token from the tail.

top_p (nucleus sampling) is smarter and more common. Instead of a fixed count, it keeps the smallest set of top tokens whose probabilities add up to p. top_p = 0.9 means “keep adding tokens from most-likely down until we’ve accounted for 90% of the probability mass, then sample only from that set.” When the model is confident (one token has most of the mass), the nucleus is tiny. When it’s uncertain (mass spread over many tokens), the nucleus is larger. It adapts to how sure the model is.

In practice you usually tune temperature or top_p, not both aggressively. A common safe setting for consistent output is temperature 0 with top_p 1; a common creative setting is temperature 0.8 with top_p 0.9.

The part that surprises even experienced engineers

Llm temp zero myth x class=

Temperature 0 gets you close to deterministic, not all the way. Floating-point rounding and server-side batching introduce tiny variations that can tip a near-tie to a different token.

Here’s the thing almost everyone gets wrong, including people who’ve shipped LLM features: setting temperature to 0 does not guarantee you’ll get the same answer twice. Temperature 0 means greedy decoding — always take the single highest-probability token — so in theory there’s only one path. In practice, reproducibility still breaks, and it’s worth understanding why because it will bite you during evaluation and debugging.

The first reason is floating-point arithmetic. A forward pass through a large model is billions of arithmetic operations, and computers represent numbers with finite precision. Tiny rounding errors accumulate. When the top two candidate tokens are nearly tied — say 42.7% versus 42.6% — a rounding difference of a hair can flip which one “wins” the greedy pick. That one flipped token cascades: the next context is now different, so the whole rest of the output can diverge.

The second reason is subtler and more modern: batch variance. When you send a prompt to a hosted model, the server doesn’t process it alone — it batches your request with other users’ requests for GPU efficiency. The exact composition of that batch changes the order and grouping of the underlying matrix operations, and because floating-point addition isn’t perfectly associative (a + b + c can differ slightly from c + b + a at the bit level), the logits come out microscopically different depending on who else you were batched with. You changed nothing; the server’s batching did.

How bad is it? One 2026 benchmark sent the same prompt ten times at temperature 0 across several models and measured byte-for-byte identical responses. On an open-ended prompt, results ranged from around 70% identical on one model down to essentially 0% on others — the longer and more open-ended the prompt, the faster determinism collapsed. Another team ran a single prompt a thousand times at temperature 0 and got around 80 distinct outputs. It’s fixable with special deterministic inference kernels, but that’s an infrastructure choice most hosted APIs don’t make by default because it’s slower.

Why this is a feature, not a bug

It’s tempting to see all this as a flaw to be stamped out. But step back: if a model always emitted the single most probable token, it would be nearly useless for most of what we use it for. Ask it to write three taglines and you’d get the same one three times. Ask for brainstorming and it would give one rigid answer. The probabilistic sampling is precisely what lets a model produce a greeting one way and a different, equally-good way the next — what makes it feel like it has range instead of a single canned response per prompt.

The variation isn’t randomness in the “anything goes” sense. It’s controlled exploration of a space of good answers, bounded by the probabilities the model learned. Turn the temperature down when you need the boundaries tight; turn it up when you want the model to roam. The dial is the point.

The gotchas nobody warns you about

“Same answer” is the wrong test. If your evaluation checks whether the model returns byte-identical output across runs, it will fail for reasons that have nothing to do with quality. Test for semantic equivalence — does the answer mean the same thing, contain the same facts, pass the same downstream parse — not exact string match.

Structured output is where variation actually hurts. A human reading two differently-worded but equivalent answers doesn’t care. A downstream system parsing the model’s output with a regex absolutely does. If run one returns {"status": "approved"} and run two returns The status is approved., your parser breaks. This is why low temperature plus a strict output schema (or structured-output / JSON mode) matters so much for anything programmatic.

“latest” is a moving target. If you pin your app to a model alias like “latest” or an undated name, the provider can update the underlying model and your outputs shift overnight — a different kind of non-determinism entirely, at the version level. Pin a specific dated model identifier so you control when the model changes.

Reasoning models add a hidden layer. Models with extended thinking generate a hidden chain-of-thought before the final answer. That internal reasoning is itself sampled, so even more variation can accumulate before you see the first visible token. Same principles, more surface area.

How to get the most reproducible output you can

You can’t make a hosted LLM perfectly deterministic, but you can get close enough for most needs. Pin a specific dated model version rather than a moving alias. Set temperature to 0 and top_p to 1. Use the API’s seed parameter if it offers one, and record any response fingerprint the provider returns so you can tell when the underlying system changed. For self-hosted models, pin the inference engine version, the numeric precision (bf16 vs fp16), and the batch settings — and if you truly need bit-for-bit reproducibility (for audits, evaluation, or RL training), look into the batch-invariant deterministic kernels that some inference stacks now support, accepting that they run a bit slower.

Then, most importantly, build your evaluation to tolerate the residual variation. Assert on meaning, structure, and facts — not on exact wording. The teams that fight non-determinism with string equality lose; the teams that design around semantic equivalence ship.

The one principle

A language model is a probability engine, not a lookup table. Different answers to the same prompt aren’t a malfunction — they’re the visible result of sampling from a distribution of good continuations. Control the spread with temperature and top_p, pin your versions, and test for meaning rather than exact text. Once you stop expecting a vending machine and start treating it like a set of well-trained dice, almost everything about its behavior makes sense.

Related reading: Why AI Agents Forget: Memory Architecture in AI Agents · Why Your RAG Pipeline Is Failing Silently · OpenAI API: temperature, top_p, and seed parameters · Anthropic Claude API reference