Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path →
Softmax with Temperature
The dial that controls how "creative" a language model is: temperature scales the logits before the softmax. Low temperature sharpens the distribution toward the top token (greedy, deterministic); high temperature flattens it toward uniform (random, diverse). Implement it — numerically stable. Solve it in Python or TypeScript, with hidden tests.
The problem
Implement softmax_temp(logits, temperature): divide each logit by temperature, then apply the softmax to turn the scaled logits into a probability distribution (positive values summing to 1). Do it in a numerically stable way by subtracting the max before exponentiating. Lower temperature → sharper (more peaked) distribution; higher temperature → flatter.
Your turn — write it
Edit the stub, hit Run (or ⌘/Ctrl + Enter), and watch the hidden tests. Stuck? open the hints under the problem, or Reveal solution is one click away.
Implement softmax_temp(logits, temperature): scale by dividing by the temperature, subtract the max for stability, exponentiate, and normalise by the sum.
Examples and constraints 4 examples
logits = [5, 5, 5, 5], temperature = 2.0[0.25, 0.25, 0.25, 0.25]logits = [2, 1, 0], temperature = 0.5≈ [0.867, 0.117, 0.016]logits = [2, 1, 0], temperature = 2.0≈ [0.506, 0.307, 0.186]logits = [1000, 999], temperature = 1.0≈ [0.731, 0.269]- Return a list of probabilities that are all ≥ 0 and sum to 1.
- Subtract
max(scaled_logits)beforeexpto avoid overflow — this does not change the result, only its stability. - Temperature < 1 sharpens the distribution (toward argmax); temperature > 1 flattens it (toward uniform).
Hints 4 ideas
- First scale:
z[i] = logits[i] / temperature. - Subtract the maximum of
zfrom every element before exponentiating — this prevents overflow and leaves the probabilities unchanged. - Exponentiate each shifted value, then divide by the sum of all of them so the result sums to 1.
- Sanity check: at high temperature the outputs approach uniform; at low temperature the largest logit dominates.
Approach, complexity & discussion — open after you solve
Start from brute force
The obvious approach types in the textbook formula: exp(xᵢ / T) / Σ exp(xⱼ / T). In exact arithmetic it is correct, and it costs O(n) time and O(n) space for the exponentials. In floating point it breaks at both ends. Large inputs overflow: Python’s math.exp(1000) raises OverflowError, and JavaScript’s Math.exp(1000) is Infinity, so the ratio becomes NaN. A small temperature makes this easy to hit, since a logit of 20 at T = 0.01 becomes 2000. Very negative inputs underflow instead: for [-1000, -1001] every exponential is 0.0 and the division is by zero.
The approach
Two facts carry the solution. First, temperature divides the logits before softmax: z = x / T scales every gap between logits by 1/T, so T < 1 widens the gaps (sharper) and T > 1 shrinks them (flatter), while the ranking is unchanged for any positive T. Second, softmax ignores a constant subtracted from every input, because the factor exp(−m) cancels between numerator and denominator. Choosing m = max(z) makes the largest shifted value exactly 0, so no exp can overflow, and it makes the sum at least 1, so it cannot underflow to 0.
The reference does exactly this: z = [x / temperature for x in logits], m = max(z), exps = [math.exp(x - m) for x in z], then each entry divided by total = sum(exps). The result is non-negative, sums to 1 up to rounding, and keeps the order of the logits.
Walk through an example
For the second example, [2, 1, 0] at T = 0.5, the gaps double before exponentiating, and the last two rows run the same logits at T = 2 for contrast.
| step | operation | token 0 | token 1 | token 2 |
|---|---|---|---|---|
| 1 | z = x / 0.5 | 4.0 | 2.0 | 0.0 |
| 2 | z − m, with m = 4.0 | 0.0 | −2.0 | −4.0 |
| 3 | exp | 1.0 | 0.1353 | 0.0183 |
| 4 | ÷ total = 1.1537 | 0.8668 | 0.1173 | 0.0159 |
| 5 | at T = 2: z = x / 2 | 1.0 | 0.5 | 0.0 |
| 6 | at T = 2: final, total = 1.9744 | 0.5065 | 0.3072 | 0.1863 |
Complexity
The reference makes a constant number of passes over the n logits (scale, max, exponentiate, sum, normalize): O(n) time and O(n) space for z and exps. The naive formula has the same O(n) cost; its problem is numerical, not speed. Sampling a token from the result is one more O(n) pass.
Edge cases to test
- Large logits:
softmax_temp([1000, 999], 1.0)returns[0.7311…, 0.2689…]. Without the shift, Python’smath.exp(1000)raisesOverflowError, and in JavaScript the result isNaN. - Very negative logits:
[-1000, -1001]gives the same[0.7311…, 0.2689…]. Unshifted, both exponentials underflow to 0.0 and the division raisesZeroDivisionError. temperature = 0is not handled: Python raisesZeroDivisionError, and TypeScript returnsNaNfor every entry. A caller that uses T = 0 to mean greedy decoding must branch to argmax before calling.- A negative temperature flips the ranking:
softmax_temp([2, 1, 0], -1.0)returns[0.0900…, 0.2447…, 0.6652…], so the lowest logit is now the most likely. Validatetemperature > 0. - A tiny temperature approaches argmax and splits ties evenly: at
T = 0.01,[2, 1, 0]gives about[1.0, 3.7e-44, 1.4e-87], while[2, 2, 0]gives about[0.5, 0.5, 6.9e-88]. - A masked logit of
-infgets probability exactly 0 ([-inf, 1.0, 2.0]gives[0.0, 0.2689…, 0.7311…]), but a+inflogit or an all--infrow givesNaN, becauseinf − infisNaN.
Common mistakes
- Multiplying by
Tinstead of dividing — that inverts the effect. - Dropping the max-subtraction and overflowing on large logits.
- Passing
T = 0straight through: Python raisesZeroDivisionErrorand TypeScript returnsNaN. The limit as T → 0 is argmax (shared evenly among tied maxima), so branch to argmax instead of dividing.
Follow-ups an interviewer asks next
- How do you sample a token from the result?
- Draw
uuniformly from [0, 1) and walk the cumulative sum of the probabilities until it passesu; that index is the sampled token, at O(n) per draw. For many draws from one distribution, precompute the cumulative sums once and binary-search each draw in O(log n), or build an alias table for O(1) draws. - How do temperature, top-k and top-p fit together?
- Temperature reshapes the whole distribution. Top-k keeps only the k most likely tokens, and top-p (nucleus sampling) keeps the smallest set whose probabilities add up to at least p. A common order is temperature first, then truncation, then renormalize and sample. Truncation cuts the long tail that a high temperature would otherwise make likely.
- Does the max shift still matter on a GPU in half precision?
- Even more. float16 tops out at 65504, so
expoverflows once its input passes about 11. Fused GPU softmax kernels apply the same shift, and the online softmax used in FlashAttention keeps a running maximum and rescales the partial sums as each block of scores arrives. - Why is temperature used in knowledge distillation?
- Dividing both teacher and student logits by a T above 1 softens the teacher’s distribution, so the small probabilities it gives wrong classes, which carry information about how classes relate, become large enough to learn from. Hinton, Vinyals and Dean’s 2015 distillation paper did this and scaled the soft-target loss by T² so its gradients keep a comparable size.
Where this shows up
Temperature is the sampling dial in every LLM API: keep it low for factual and code tasks where you want the top token, raise it for creative diversity. The key insight is that it reshapes the probability distribution — it is randomness control, not a “creativity” knob — which is what lets one model serve both careful and exploratory generation.
Reference solution
Reference solution — Python
import math
def softmax_temp(logits, temperature):
z = [x / temperature for x in logits] # scale by temperature
m = max(z) # subtract max for stability
exps = [math.exp(x - m) for x in z]
total = sum(exps)
return [e / total for e in exps] # normalise to sum 1
print(softmax_temp([5, 5, 5, 5], 2.0)) # -> [0.25, 0.25, 0.25, 0.25]
print(softmax_temp([2, 1, 0], 0.5)) # -> ~[0.867, 0.117, 0.016]Reference solution — TypeScript
function softmaxTemp(logits: number[], temperature: number): number[] {
const z = logits.map((x) => x / temperature); // scale by temperature
const m = Math.max(...z); // subtract max for stability
const exps = z.map((x) => Math.exp(x - m));
const total = exps.reduce((a, b) => a + b, 0);
return exps.map((e) => e / total); // normalise to sum 1
}
console.log(softmaxTemp([5, 5, 5, 5], 2.0)); // -> [0.25, 0.25, 0.25, 0.25]
console.log(softmaxTemp([2, 1, 0], 0.5)); // -> ~[0.867, 0.117, 0.016]Questions about Softmax with Temperature
What does temperature do in softmax?
Temperature divides every logit before the softmax. Dividing by a number less than 1 stretches the gaps between logits, so the largest takes more of the probability and the distribution gets sharper; dividing by a number greater than 1 shrinks the gaps and flattens it toward uniform. In the second example, logits 2, 1 and 0 give the top token about 0.867 at temperature 0.5, while at temperature 2 the same token gets about 0.506. For any positive temperature the order of the tokens never changes, only how much probability separates them.
Why subtract the maximum logit before exponentiating?
Because exp overflows quickly: in double precision, exp of anything above about 709.8 is too large to represent, so a logit of 1000, or a logit of 20 at temperature 0.01, breaks the naive formula. Subtracting the same number from every input does not change softmax, because the common factor cancels between numerator and denominator. Subtracting the maximum makes the largest value exactly 0, so no exp can overflow, and it keeps the sum at least 1, so the division is never by zero. It costs one extra pass over the logits.
What happens when the temperature is 0?
Mathematically, as the temperature approaches 0 the distribution approaches argmax: all the probability goes to the largest logit, shared equally if several tie. Literally dividing by 0 fails, though. The Python reference raises ZeroDivisionError and the TypeScript reference returns NaN for every entry. Decoders that accept a temperature of 0 therefore treat it as a special case and simply pick the highest-scoring token, which is greedy decoding. If your code allows a temperature of 0, branch to argmax before the division.
Does a low temperature make a language model more accurate?
Not by itself. A low temperature makes the output more deterministic, because the model picks its most likely token more often, which tends to suit tasks with one right answer, such as extraction or code. It cannot add knowledge the model lacks: if the most likely token is wrong, a low temperature makes the model wrong more consistently. Higher temperatures add diversity, which helps brainstorming and is used when sampling several candidates to vote on or rerank. Temperature controls randomness; it is not a quality dial.
Explore the topic
See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.
More Challenges
- SoftmaxThe function at the end of every classifier and language model: turn raw scores (logits) into a probability distribution. Implement the numerically stable version so big logits do not overflow. Solve it in Python or TypeScript.Read →
- Cosine SimilarityThe measure behind every embedding search and RAG system: how aligned are two vectors, ignoring their length? Dot product over the product of magnitudes — 1 identical, 0 orthogonal, -1 opposite. Solve it in Python or TypeScript.Read →
- K-Nearest NeighborsThe simplest classifier there is: to label a new point, look at the k closest labeled points and let them vote. No training, no weights — just Euclidean distances, with deterministic tie-breaks. The whole model is the dataset. Solve it in Python or TypeScript.Read →
- Matrix ExponentiationRaise a matrix to the n-th power in O(log n) multiplies instead of n — the trick that computes the billionth Fibonacci number almost instantly. Fast exponentiation, lifted from numbers to matrices. Solve it in Python or TypeScript, with hidden tests.Read →
- Skip List Insert & SearchO(log n) search and insert from nothing but linked lists and express lanes — the structure behind Redis sorted sets. Here node heights are given, so it’s fully deterministic. Solve it in Python or TypeScript, with hidden tests.Read →
- Reservoir SamplingPick k items uniformly at random from a stream of unknown length — one pass, O(k) memory. Here the random draws are supplied, so the result is deterministic and testable. Solve it in Python or TypeScript, with hidden tests.Read →