The problem
Softmax does not change if you add the same number to every logit (section 0). So nothing in the cross-entropy stops all the logits from drifting to huge values, and huge values hurt the precision of BF16 (brain floating point, section 8). The z-loss is a small extra term that penalizes the drift.
For each position , call the log-sum-exp of its logits (section 0): it is the "size" of the logits that softmax removes. The z-loss is
where:
- is the number of positions;
- is a small weight: OLMo 3 trains with (PaLM (Pathways Language Model) used );
- the result is added to the usual mean cross-entropy.
Example. One position with logits and right class 0, .
- and the cross-entropy is .
- : negligible.
- Add 100 to every logit: the cross-entropy is still , but and , about 1,800 times larger. The z-loss grows with the square of the drift, so it pulls the logits back.
The gradient
By the chain rule, with (section 2):
where is the logit of class at position . It is a gradient that always pushes the logits down, in proportion to the probabilities, when is positive.
Example. Two positions, logits
[[2, 1, 0], [12, 10, 11]]and : the gradient is about[[0.016, 0.006, 0.002], [0.083, 0.011, 0.030]]. The second position has larger logits (), so its gradient is larger.
What to implement
For logits of shape and integer targets of shape :
cross_entropy_and_z_loss(logits, targets, z_weight=1e-5): return(ce, z): the mean cross-entropy and , as floats, both computed stably.z_loss_grad(logits, z_weight=1e-5): , shape .
Requirements
- Stable for large logits: log-sum-exp with the maximum subtracted (section 0).
- No Python loops.
Hint
Compute once, a vector of length , and use it for the cross-entropy, for and for the gradient. The softmax is np.exp(logits - log_z[:, None]).