Chapter 5: Online Softmax
Softmax converts a row of logits into probabilities. In attention, each query has a row of scores over the keys. In language-model sampling, each token position has a row of logits over the vocabulary. The normalization is independent for each row.
Online softmax maintains a running maximum and a running normalization sum as values arrive. Here “online” means processing a sequence incrementally. It does not refer to model training or serving requests over a network.
1. Start with numerically stable softmax
For a row ,
Directly computing can overflow for large positive logits. The largest finite float32 value is approximately , so exceeds that range when is above roughly . This is a limit of the intermediate exponential, even though the final softmax probabilities are between zero and one.
For example, logits are finite in float32, but their exponentials overflow to inf. Dividing those values by their sum gives inf / inf, which produces NaN. Subtracting the same constant from every logit leaves the probabilities unchanged. Choosing the row maximum gives
Every exponent is now nonpositive, so each exponential is at most one. For finite logits, at least one term equals one and .
A straightforward implementation scans the row to find , scans it again to calculate , then scans it once more to write . Can we calculate and together, even though a larger maximum might appear later?
2. Online softmax
Algorithm 1: Online softmax normalizer
Input: a nonempty row of finite logits .
Output: the maximum and the exponential sum relative to .
This algorithm calculates the maximum and normalizer in one pass. After each iteration, is the largest logit seen so far, and is the sum of the processed logits' exponentials with that current maximum subtracted. Only these two scalars persist between iterations. The recurrence follows Online normalizer calculation for softmax.
Here denotes the old maximum, saved before updating . If the next logit introduces a larger maximum, the old exponential terms were calculated relative to and must be converted to the new reference . Each old term receives the same correction:
Because this factor is shared by every previous term, we can multiply their accumulated sum once instead of revisiting those logits:
Then add the new term . If the maximum is unchanged, and the correction factor is one. If the maximum increases, and the correction factor is less than one, shrinking the previous contributions. For the first finite logit, the initialization gives and .
After the pass, the final state is
To produce the complete probability row, use
The normalizer takes one pass. Writing all probabilities still requires another pass over the logits, or retaining them until the final is known.
For a nonempty list of finite logits, the Python implementation follows the same updates:
import math
def online_softmax(x):
m, l = -math.inf, 0.0
for value in x:
old_m = m
m = max(m, value)
l = l * math.exp(old_m - m) + math.exp(value - m)
return [math.exp(value - m) / l for value in x]
print(online_softmax([1000, 1001, 1002]))
# [0.09003057317038046, 0.24472847105479764, 0.6652409557748218]