Skip to content

Distributions, Softmax, and the Chain Rule of Words

At each generation step, an autoregressive language model produces a logit for every token in its vocabulary. The decoder uses these scores to choose the next token.

Applying softmax turns the logits into a probability distribution. Temperature can reshape that distribution, and the chain rule connects these next-token probabilities to the probability of a sequence.

This post is 1.3 in the series. I'll assume you've read 1.1 (Vectors and Spaces) and 1.2 (Norms, Dots, and Similarity), though this post needs less linear algebra. If you know what a vector is, you're good.


What is a probability distribution?

A discrete probability distribution is a list of non-negative numbers that sum to 1. Each number represents the probability of one possible outcome.

Roll a fair die. The distribution is [1/6,1/6,1/6,1/6,1/6,1/6][1/6, 1/6, 1/6, 1/6, 1/6, 1/6]. All six entries are non-negative and sum to 1. Roll the die many times and the empirical frequencies should get closer to those probabilities; any finite sample will be noisy.

Now think about a language model. At each step, the model needs to pick the next token from its vocabulary. (Tokens, not words. A token is the model's unit of output, often a word piece or a chunk of bytes. We'll get into tokenization in Arc 3, but for now think of each token as one possible next thing the model can emit.) If the vocabulary has 50,000 tokens, then the distribution is a list of 50,000 numbers, each one representing the probability of that particular token being next. The constraints are the same: every number is ≥0\geq 0, and they all sum to 1.

That constraint is rigid, but it admits a lot of different shapes. Here's the same six-token mini-vocabulary wearing four different distributions. Watch the bars rearrange themselves and notice the sum in the corner: it's locked at 1.000 through every morph.

sum = 1.000
p(token)
the
0.167
a
0.167
of
0.167
cat
0.167
sat
0.167
ran
0.167
uniform
Same constraint, many shapes. A probability distribution is just non-negative mass that sums to 1 — uniform, spiky, bimodal, long-tailed all satisfy the same rule.

This specific setup, where you're picking one option from a set of discrete choices, has a name: the categorical distribution. It's a generalization of the coin flip. A coin flip is a categorical distribution over two outcomes. A die roll is categorical over six. A language model's output is categorical over its entire vocabulary.

The decoder chooses from this distribution by sampling or another rule. The distribution and the selection rule are separate parts of generation.

But there's a problem. Neural networks don't naturally output valid probability distributions. They output arbitrary real numbers, some positive, some negative, some huge, some tiny. These raw outputs are called logits, and softmax converts them into a valid distribution. Greedy decoding can also select the largest logit directly.


Softmax: turning logits into probabilities

Given a vector of logits z=[z1,z2,…,zn]z = [z_1, z_2, \ldots, z_n], softmax converts them into a probability distribution:

pi=ezi∑j=1nezjp_i = \frac{e^{z_i}}{\sum_{j=1}^{n} e^{z_j}}

Each output pip_i is between 0 and 1, and all the pip_i values sum to 1.

Why exponentiate? Taking the absolute value of each logit and dividing by the sum might seem sufficient. That would also give you non-negative numbers that sum to 1.

A few reasons. First, the exponential function maps any real number to a strictly positive number. e−100e^{-100} is tiny but still positive. e100e^{100} is enormous. It preserves the ordering of the inputs: if zi>zjz_i > z_j, then ezi>ezje^{z_i} > e^{z_j}, so the higher logit always gets the higher probability.

Absolute-value normalization loses ordering: logits −5 and +5 receive the same weight, and shifting every logit changes the distribution. Softmax preserves order and is invariant to a shared shift.

Exponentials also turn additive logit differences into multiplicative probability ratios. For any two tokens ii and jj,

pipj=ezi−zj\frac{p_i}{p_j} = e^{z_i - z_j}

A logit gap of 11 means the higher-logit token is e≈2.72×e \approx 2.72\times as likely as its rival. A gap of 22 is e2≈7.39×e^2 \approx 7.39\times. A gap of 55 is about 148×148\times. This is the right way to read logits: as log-relative preference scores, where only differences matter.

That last sentence has a precise consequence: adding the same constant to every logit doesn't change the distribution at all.

softmax(z)=softmax(z+c)\text{softmax}(z) = \text{softmax}(z + c)

The same factor ece^c shows up in every numerator and in the denominator and cancels. This invariance is why the numerical-stability trick below works, and it's why people say "logits are only defined up to an additive constant."

Softmax is also smooth and differentiable, so gradients can pass through it during training. Its Jacobian is ∂pi/∂zj=pi(δij−pj)\partial p_i / \partial z_j = p_i(\delta_{ij} - p_j).

The comparison below applies both normalizers to the same changing logits.

same logits, two normalizers
-1.0
0.0
1.0
2.0
3.0
w₁
w₂
w₃
w₄
w₅
abs and divide|z| / Σ|z|
w₁
14.3%
w₂
0.0%
w₃
14.3%
w₄
28.6%
w₅
42.9%

Absolute values lose the sign. w₅ takes 43%, w₄ takes 29%. Ratio ≈ 1.50×.

softmaxeᶻ / Σeᶻ
w₁
1.2%
w₂
3.2%
w₃
8.6%
w₄
23.4%
w₅
63.6%

Ratio of the two largest probabilities ≈ 2.72×. Softmax preserves the logit ordering.

The right-most logit (violet) varies between 1.5 and 5.0.
Compare the weights given to negative logits and their positive counterparts.

Let's run through a concrete example. Say we have five logits coming out of a language model: [2.0,1.0,0.5,−1.0,3.0][2.0, 1.0, 0.5, -1.0, 3.0], corresponding to tokens ["the", "cat", "dog", "ran", "sat"].

First we exponentiate each: [e2,e1,e0.5,e−1,e3]=[7.39,2.72,1.65,0.37,20.09][e^2, e^1, e^{0.5}, e^{-1}, e^3] = [7.39, 2.72, 1.65, 0.37, 20.09].

Using the unrounded exponentials, their sum is approximately 32.2132.21.

Then we divide each by the sum: [0.229,0.084,0.051,0.011,0.624][0.229, 0.084, 0.051, 0.011, 0.624].

So "sat" (logit 3.0) gets 62.4% of the probability mass. "the" (logit 2.0) gets 22.9%. And "ran" (logit -1.0) is down at 1.1%. The distribution is valid, it sums to 1, and the ordering from the logits is preserved.


Temperature: sharpening and flattening

The temperature parameter TT controls how concentrated the distribution is. We divide the logits by TT before applying softmax:

pi=ezi/T∑j=1nezj/Tp_i = \frac{e^{z_i / T}}{\sum_{j=1}^{n} e^{z_j / T}}

When T=1T = 1, you get the standard softmax. When T<1T < 1, you're dividing by a number less than 1, which increases the magnitude of the logits. This amplifies the differences between them, making the distribution sharper. The winning logit grabs even more probability mass. When T>1T > 1, you're shrinking the logits toward zero, squashing the differences, and the distribution flattens out toward uniform.

As T→0+T \to 0^+, the distribution concentrates on the highest logit (or shares mass equally among tied maxima). As T→∞T \to \infty, every token gets equal probability (uniform distribution). Temperature is a knob that smoothly interpolates between "always pick the most likely token" and "pick any token at random." Strictly speaking, T=0T = 0 is a divide-by-zero, so the formula doesn't apply; a decoder can instead define temperature=0 as a separate greedy-argmax mode, with its own tie-breaking rule.

For any positive TT, the highest-logit token stays the highest-probability token. Temperature reshapes the sharpness of the distribution but never reorders it.

T =1.0
Logits→softmax(z / T)→Probabilities
the2.0
22.9%
cat1.0
8.4%
dog0.5
5.1%
ran-1.0
1.1%
sat3.0
62.4%
entropy: 1.51 bits

Drag the temperature slider to see how T reshapes the distribution. Low T → peaky. High T → flat.

Notice how at T=0.5T = 0.5, "sat" dominates with over 85% of the probability. At T=2.0T = 2.0, the distribution is much more spread out, and even "ran" has a meaningful probability. This is why temperature matters for generation: low temperature concentrates sampling on the highest-scoring tokens, while high temperature gives lower-scoring tokens more chance.


The numerical stability trick

Large positive logits can overflow when exponentiated. e100e^{100} is already about 2.69×10432.69 \times 10^{43}, and if your logits are large, you'll overflow to infinity.

The fix is simple: subtract the maximum logit from all logits before exponentiating. Since:

ezi∑jezj=ezi−max⁡(z)∑jezj−max⁡(z)\frac{e^{z_i}}{\sum_j e^{z_j}} = \frac{e^{z_i - \max(z)}}{\sum_j e^{z_j - \max(z)}}

(the emax⁡(z)e^{\max(z)} cancels out of numerator and denominator), this gives the same result in exact arithmetic but the largest exponent is now 0, so nothing overflows. Very small probabilities can still underflow, which is one reason to compute log probabilities directly.

In real training and inference code, you also see frameworks skip the explicit softmax altogether and work in log space. PyTorch's log_softmax and cross_entropy both fuse the max-subtract trick with a log⁡∑ezj\log\sum e^{z_j} computed in a numerically safe way, which avoids ever materializing tiny probabilities or huge exponentials. CrossEntropyLoss in particular expects raw logits and is equivalent to LogSoftmax followed by negative log-likelihood; you almost never apply softmax yourself before the loss.

import numpy as np
 
def softmax(logits, temperature=1.0):
    """Numerically stable softmax with temperature."""
    if temperature <= 0:
        raise ValueError("temperature must be positive")
    z = np.array(logits, dtype=float) / temperature
    z = z - np.max(z)         # stability trick: shift so max is 0
    exp_z = np.exp(z)
    return exp_z / exp_z.sum()
 
# Try it
logits = [2.0, 1.0, 0.5, -1.0, 3.0]
print(softmax(logits, temperature=1.0))
# [0.229  0.084  0.051  0.011  0.624]
 
print(softmax(logits, temperature=0.5))
# [0.117  0.016  0.006  0.000  0.862]
 
print(softmax(logits, temperature=2.0))
# [0.253  0.154  0.120  0.056  0.417]

The chain rule and next-token prediction

The chain rule of probability says any joint distribution over a sequence factorizes into a product of conditionals:

P(x1,x2,…,xn)=∏t=1nP(xt∣x<t)P(x_1, x_2, \ldots, x_n) = \prod_{t=1}^{n} P(x_t \mid x_{<t})

where x<tx_{<t} is shorthand for (x1,…,xt−1)(x_1, \ldots, x_{t-1}). This is an exact identity. It's a theorem of probability and it holds for any joint distribution over an ordered sequence, language or otherwise.

The model doesn't know the true conditionals P(xt∣x<t)P(x_t \mid x_{<t}). It learns approximations. I'll write the learned distributions as qθ(xt∣x<t)q_\theta(x_t \mid x_{<t}), where θ\theta is the model's parameters. The model's probability for the whole sequence is the product of its own learned conditionals:

qθ(x1,…,xn)=∏t=1nqθ(xt∣x<t)q_\theta(x_1, \ldots, x_n) = \prod_{t=1}^{n} q_\theta(x_t \mid x_{<t})

By the chain rule, maximizing log⁡qθ(x1,…,xn)\log q_\theta(x_1, \ldots, x_n) is the same as maximizing ∑tlog⁡qθ(xt∣x<t)\sum_t \log q_\theta(x_t \mid x_{<t}). Training with the sum of per-position next-token log losses therefore maximizes joint-sequence likelihood. The next post derives that loss.

If you want a probability for a complete finite sequence rather than an open-ended stream, you have to mark where it ends. The standard fix is an end-of-sequence token ⟨eos⟩\langle\text{eos}\rangle in the vocabulary, so the final factor qθ(⟨eos⟩∣x≤n)q_\theta(\langle\text{eos}\rangle \mid x_{\le n}) closes the sequence. (Equivalently, you can condition on the sequence length nn explicitly.) Without one of these, the probabilities don't sum to 1 over the space of all sequences of all lengths.

For prompt-conditioned generation, distinguish modeling the full joint qθ(x1,…,xn)q_\theta(x_1, \ldots, x_n) from modeling a completion conditioned on a prompt. If the first kk tokens are a prompt cc and the rest are the generated continuation yy, the quantity you actually care about at inference and in completion-style training is

qθ(y∣c)=∏tqθ(yt∣c,y<t)q_\theta(y \mid c) = \prod_{t} q_\theta(y_t \mid c, y_{<t})

Same factorization, different domain. For this conditional completion objective, only the yty_t factors contribute to the loss. Other training recipes also score prompt tokens.

context
∅
P(w1 | context)
step 1 / 5
the
0.050
a
0.040
I
0.030
it
0.020
we
0.015
of
0.012
the chosen, p = 0.050
running product · selected candidates shown above
(P(the|∅)= 0.050)×(P(cat|the)= 0.020)×(P(sat|the cat)= 0.080)×(P(on|the cat sat)= 0.200)×(P(the|the cat sat on)= 0.450)= 0.000e+0
Chosen probabilities for a fixed toy continuation. Each step adds a token to the context and a factor to the product. Only selected candidates are shown; the bars do not show the whole vocabulary. Use step to inspect the factors.

Worked example. Suppose the model assigns the following conditionals to a five-token sequence (these are the model's qθq_\theta values, not ground truth):

qθ(the)=0.05qθ(cat∣the)=0.02qθ(sat∣the, cat)=0.08qθ(on∣the, cat, sat)=0.20qθ(the∣the, cat, sat, on)=0.45\begin{aligned} q_\theta(\text{the}) &= 0.05 \\ q_\theta(\text{cat} \mid \text{the}) &= 0.02 \\ q_\theta(\text{sat} \mid \text{the, cat}) &= 0.08 \\ q_\theta(\text{on} \mid \text{the, cat, sat}) &= 0.20 \\ q_\theta(\text{the} \mid \text{the, cat, sat, on}) &= 0.45 \end{aligned}

The joint probability of the sequence under the model is the product, 0.05×0.02×0.08×0.20×0.45≈7.2×10−60.05 \times 0.02 \times 0.08 \times 0.20 \times 0.45 \approx 7.2 \times 10^{-6}. Sequence joint probabilities are usually small for the same reason: many factors less than 1 multiplied together. In practice we work in log space, where the product becomes a sum:

log⁡qθ(x1,…,x5)=∑t=15log⁡qθ(xt∣x<t)≈(−3.00)+(−3.91)+(−2.53)+(−1.61)+(−0.80)=−11.84\log q_\theta(x_1, \ldots, x_5) = \sum_{t=1}^{5} \log q_\theta(x_t \mid x_{<t}) \approx (-3.00) + (-3.91) + (-2.53) + (-1.61) + (-0.80) = -11.84

Per-position log probabilities just add. That additivity is what cross-entropy loss accumulates across positions, and it's why "average loss per token" is a meaningful quantity to report and compare across sequences of different lengths.

import numpy as np
 
def sequence_log_prob(token_probs):
    """
    Given a list of P(w_t | w_1, ..., w_{t-1}) values,
    compute the log probability of the full sequence.
    
    The chain rule says:
    P(sequence) = prod(token_probs)
    log P(sequence) = sum(log(token_probs))
    """
    return sum(np.log(p) for p in token_probs)
 
# "the cat sat on the" example
token_probs = [0.05, 0.02, 0.08, 0.20, 0.45]
log_prob = sequence_log_prob(token_probs)
print(f"log P(sequence) = {log_prob:.2f}")    # -11.84
print(f"P(sequence) = {np.exp(log_prob):.6e}")  # 7.20e-06
print(f"perplexity = {np.exp(-log_prob / len(token_probs)):.1f}")  # 10.7

What actually happens at inference

The model receives a sequence of token IDs. It passes them through an embedding layer, then through many layers of attention and feed-forward networks. At the end, the final hidden state for the last token position gets projected through a linear layer into a vector of size ∣V∣|V|, where VV is the vocabulary. This vector is the logits.

Softmax shows up twice in transformer inference: there's a softmax inside every attention layer (turning query–key dot products into attention weights over values) and a separate softmax at the very end (turning final logits into a distribution over the vocabulary). This post is about the second one. The attention softmax has its own post in Arc 4.

Then we apply softmax (possibly with a temperature parameter), and now we have a valid probability distribution over the entire vocabulary. We can sample from it (randomly draw a token according to the probabilities), or we can take the argmax (greedy decoding), or we can use more sophisticated strategies like top-k or nucleus (top-p) sampling, which we'll cover in Arc 5.

The sampled token gets appended to the input, and the whole process repeats for the next position. That's one step of the chain rule being evaluated. In efficient implementations, the model doesn't recompute the entire prompt from scratch on every step. It caches the attention keys and values from previous tokens (the KV cache) and only runs the new token through the network. The chain-rule view stays exact; the runtime view is much faster. The KV cache gets its own post in Arc 5.

When sampling with fresh random draws, the same distribution can produce different tokens. For repeated independent draws from a fixed distribution, empirical frequencies converge to those probabilities; a finite sample is noisy. A fixed random seed can reproduce the same draws.

The visualization below draws from a fixed distribution. Faint bars mark the target probabilities; opaque bars show empirical frequencies as draws accumulate.

same distribution, fresh draw every step
samples: 0
the
0.0%/45
a
0.0%/20
that
0.0%/13
my
0.0%/12
this
0.0%/10
recent draws →
∅
The faint pills mark the model's actual probabilities. The opaque bars are the empirical frequencies from the draws so far. More draws tend to bring the frequencies closer to the probabilities, though sampling fluctuations remain.
The animation restarts after 240 independent draws. Compare the accumulated frequencies with the fixed target bars.

Misconceptions

"A token probability is the probability that an answer is correct." A probability of 0.9 describes the model’s next-token distribution. Factual correctness of a whole answer is a different event and needs its own evaluation. Softmax alone does not guarantee that predictions are calibrated against observed outcomes.

"You always pick the highest probability token." Greedy decoding (always taking the argmax) is just one of many strategies, and it isn't always the right one. For open-ended generation it tends to produce repetitive text, so chat and creative-writing systems usually sample from a modified distribution: temperature reshapes the whole distribution, top-k restricts sampling to the kk most likely tokens, and top-p (nucleus sampling) keeps the smallest set of tokens whose cumulative probability exceeds pp. These are often combined, and changing both can make their effects harder to isolate. For coding, extraction, classification, structured output, or anything that wants a single canonical answer, low-temperature or constrained or greedy decoding is often preferred. The point is that the model produces a full distribution; what to do with it is a separate design choice that depends on the task.

What's next

The next post derives cross-entropy and loss from the probabilities a model assigns to observed tokens.


Additional reading (and watching)