Entropy, cross-entropy, KL and perplexity
By the end: You can say what each of the four numbers measures, compute them, and tell when a reported one is not comparable with another.
- Surprise and entropy
- Cross-entropy as the cost of a wrong distribution
- KL divergence as the excess, and why it is not a distance
- Perplexity as the exponential of cross-entropy
- Where each of them turns up in practice
New to probability & statistics? Probability is the arithmetic of things that are uncertain: how likely an outcome is, and how that changes once you learn something new. A distribution is the full picture of which outcomes occur and how often. Adult heights cluster near the middle with few people at either extreme, and that shape is the distribution. Statistics runs the same machinery backwards: you hold a sample of outcomes and work out what produced it. Why it matters
Four numbers turn up constantly in model reporting and are routinely confused with one another. This section separates them, starting from the single quantity all four are averages of, and gives each a plain reading: what the floor is, what your model costs above that floor, what the gap between the two is used for, and what a perplexity figure actually tells you. It also covers when two of these numbers cannot be compared at all, which is the mistake most often made with them in public.
Surprise#
The surprise of an outcome is the negative logarithm of the probability you gave it. Something you said was certain surprises you not at all; something you gave one chance in a thousand surprises you a great deal. Everything on this page is an average of that one quantity, taken over a different set of assumptions.
surprise of an outcome -log p
entropy H(p) average surprise, using the true distribution for both
cross-entropy H(p, q) average surprise of your q, when reality is p
KL KL(p || q) = H(p, q) - H(p) the excess you pay for using q
log base 2 gives bits, natural log gives nats. Keep one base throughout.Entropy is how uncertain the truth itself is: a fair coin has one bit, a loaded one has less, a certain outcome has none. It is the floor. No model, however good, can average less surprise than the entropy of what it is predicting, which is why a training loss that stops falling is not necessarily a model that stopped learning.
Compute it once by hand so the units mean something. A fair coin: each side has probability a half, whose negative log base two is one, and the average of one and one is one bit. A coin that lands heads nine times in ten: heads carries a surprise of 0.152 bits and tails carries 3.322, and the average weighted by how often each happens is 0.9 times 0.152 plus 0.1 times 3.322, which is 0.469 bits. Less than half the fair coin, because you can usually guess. Push it to 99 per cent heads and it falls to 0.081 bits, and at certainty it is zero.
import math
def entropy_bits(p):
if p in (0.0, 1.0):
return 0.0
return -(p * math.log2(p) + (1 - p) * math.log2(1 - p))
for p in (0.5, 0.75, 0.9, 0.99, 1.0):
print(p, round(entropy_bits(p), 4))
# 0.5 1.0
# 0.75 0.8113
# 0.9 0.469
# 0.99 0.0808
# 1.0 0.0Cross-entropy and KL#
Cross-entropy is what you actually pay: the average surprise of your predicted distribution against outcomes drawn from the real one. It is the training loss for classification and for next-token prediction. KL divergence is the difference between what you paid and the floor, so it is the part of the loss that is your model's fault rather than the world's.
That split has a direct consequence for how you read a loss number, and it is the reason two training losses are so often compared when they should not be. A loss of 1.6 nats is excellent on text whose own entropy is 1.5 and dreadful on text whose entropy is 0.5, and nothing in the number itself distinguishes the two cases. So a loss curve is only comparable against another run on the same data. A loss that flattens at 1.6 may be a model that has run out of capacity, or a model that has learned everything there is and is now sitting on the floor, and the way to tell them apart is to change the model rather than to stare at the curve.
import math
def kl(p, q):
return sum(pi * math.log(pi / qi) for pi, qi in zip(p, q))
p = [0.7, 0.2, 0.1]
q = [0.5, 0.3, 0.2]
kl(p, q) # 0.085 nats
kl(q, p) # 0.092 nats -- a different number from the same pairThe asymmetry is not a technicality. KL(p || q) punishes q for putting almost no probability where p has some, so minimising it spreads q out to cover everything. KL(q || p) punishes q for putting probability where p has none, so minimising it makes q concentrate on one mode and ignore the rest. Which direction a method uses decides whether its approximation hedges or commits.
- Preference training uses KL from the starting model as a leash, penalising the tuned model for drifting too far from what it began as.
- Distillation trains the student by minimising KL against the teacher's full output distribution, which carries more information than the label alone.
- Monitoring uses it to detect drift, comparing the distribution of today's inputs against the distribution the model was validated on.
Perplexity#
Perplexity is the exponential of cross-entropy, taken in the same base. It exists because a loss of 1.9 nats per token means nothing to anyone, while its exponential does: a perplexity of 8 says the model is as uncertain, on average, as if it were choosing uniformly between 8 equally likely tokens at each step. That is the average branching factor, and it is the only intuition the number carries.
# Natural-log probabilities the model gave to each correct next token.
logprobs = [-0.10, -2.30, -0.69, -1.61, -0.36]
cross_entropy = -sum(logprobs) / len(logprobs)
perplexity = math.exp(cross_entropy)
print(round(cross_entropy, 3), round(perplexity, 3)) # 1.012 2.751
# Comparable across tokenizers, because bytes are not a modelling choice:
n_bytes = 23
print(round(-sum(logprobs) / math.log(2) / n_bytes, 3)) # 0.317The branching-factor reading gives entropy a job at inference time as well as in training. Apply the same average-surprise calculation to the model's next-token distribution and its exponential tells you, in effect, how many tokens the model is really choosing between at that step. That number is what a sampling temperature moves. Dividing the logits by a temperature below one sharpens the distribution and lowers its entropy; dividing by a temperature above one flattens it and raises the entropy.
import numpy as np
logits = np.array([4.0, 3.0, 1.0, 0.5, 0.0])
for T in (0.5, 1.0, 2.0):
z = logits / T
e = np.exp(z - z.max())
probs = e / e.sum()
H = -(probs * np.log(probs)).sum() # entropy in nats
print(T, np.round(probs, 3), round(float(H), 3), round(float(np.exp(H)), 2))
# 0.5 [0.878 0.119 0.002 0.001 0. ] 0.389 1.48
# 1.0 [0.682 0.251 0.034 0.021 0.012] 0.857 2.36
# 2.0 [0.468 0.284 0.104 0.081 0.063] 1.327 3.77Compare perplexity across tokenizers and you have measured the tokenizer
Perplexity is per token, and a different vocabulary splits the same text into a different number of tokens. A model whose tokenizer produces fewer, longer tokens has to carry more information in each one, so its perplexity looks worse on identical text. Two perplexities are comparable only with the same tokenizer and the same test set. Across tokenizers, convert to bits per byte or bits per character.
It is also not a task metric. Perplexity measures how well the model predicts held-out text of the same kind as its training data, which is a different question from whether it does your job. It is a good early warning, a good check on whether quantization damaged something, and a poor argument for shipping.
Where people go wrong
Quoting a perplexity as a quality score. It is comparable only within one tokenizer and one test set, it says nothing about whether the model does your task, and a model can improve on it while getting worse at everything you care about.
If this comes up in an interview
What is perplexity?
The exponential of the average cross-entropy per token. It reads as an average branching factor: a perplexity of 8 means the model is as uncertain as if it were choosing uniformly among 8 tokens at each step. Lower means less surprised by the text.
Then they push further
One model reports 12 and another reports 9. Is the second better?
Only if they share a tokenizer and were measured on the same text. Perplexity is per token, so a vocabulary that splits text into fewer, longer tokens carries more information per token and scores worse for no reason connected to quality.
How would you compare two models with different tokenizers?
Convert to bits per byte or bits per character: total negative log probability over the text, divided by the number of bytes rather than the number of tokens. Bytes are a property of the text, not a modelling choice.
Perplexity improved and your eval got worse. What happened?
They measure different things. Perplexity is fit to held-out text of a similar kind, and a model can predict such text better while doing your task worse, particularly after training on data that resembles the perplexity set more than it resembles production traffic. The task eval is the one that decides.
What gets marked up. Strong answers give the exponential-of-cross-entropy definition, the branching factor reading, and the tokenizer caveat without being prompted.
What is KL divergence?
The average extra surprise from using distribution q when the truth is p: cross-entropy minus entropy. It is zero when the two match and grows as they diverge, and it is measured in bits or nats rather than in any unit of distance.
Then they push further
Why is it not a distance?
It is asymmetric, so KL(p || q) and KL(q || p) are different numbers, and it does not obey the triangle inequality. The direction is a modelling choice: one order makes an approximation spread out to cover everything, the other makes it concentrate on a single mode.
Where would you meet KL divergence in an LLM pipeline?
As the drift penalty in preference training, keeping the tuned model near the one it started from. As the distillation loss against a teacher's full output distribution. And in monitoring, comparing the distribution of live inputs against the one the model was validated on.
Why is cross-entropy the thing being minimised rather than entropy?
Entropy is a property of the data and cannot be changed by the model. Cross-entropy is what the model's own distribution costs, so it is the only part that training can move, and the gap between the two is the KL divergence the model is actually reducing.
Build this: Score the same held-out file with two models that share a tokenizer and compute cross-entropy and perplexity for each. Then repeat with a model using a different tokenizer, see how misleading the raw comparison is, and convert all three to bits per byte.
This is one section of Probability & statistics, 27 hours across 6 sections. Read it in full to get the whole topic in order.