The problem
A language model predicts the next token at every position of every text in the batch. So instead of a table of logits it outputs a 3-D array:
logitsof shape : texts, positions per text, scores per position (one per token of the vocabulary);targetsof shape : the right next token at every position.
The loss is the cross-entropy of the previous exercise, averaged over all positions, with two extras that real training loops use. This exercise reproduces exactly what PyTorch's torch.nn.CrossEntropyLoss does with them.
Extra 1: ignored positions
Some positions must not count. Padding tokens are added only so that every text in the batch has the same length ; in fine-tuning, the prompt tokens are not something the model should learn to write. Their target is set to a special value, ignore_index (default -100), and:
- they add nothing to the loss;
- the mean is taken over the positions that are not ignored.
Example. One text (), positions, tokens,
targets = [[0, 2, -100]]: the last position is padding. The logits of the first two positions are[2, 1, 0]and[0, 0, 0], whose losses are and (computed in the previous exercise). So , and the loss is , whatever the logits of the padding position are.
Extra 2: label smoothing
Label smoothing with a parameter (epsilon, between 0 and 1) makes the target less absolute: instead of "100% the right token", it becomes on the right token plus spread evenly over the whole vocabulary. The loss of one position becomes:
and the loss of the batch is the mean over the kept positions:
where:
- is the softmax of the position's logits, and is its log-softmax with the sign flipped;
- is the right token of the position, so is the usual cross-entropy;
- the second term is times the average of over all tokens;
- is the number of positions that are not ignored.
With this is plain cross-entropy.
Example. Same batch, .
- Position 0: , right token 0. .
- Position 1: all logits equal, so for every token and .
- Position 2 is ignored. Mean of the other two: .
What to implement
lm_cross_entropy(logits, targets, ignore_index=-100, label_smoothing=0.0): return the single number .
Requirements
- Stable for any logits: compute the log-softmax with log-sum-exp.
ignore_indexcan be any integer, including-100. Never use an ignored target as an index. With a vocabulary of 100 tokens or more,logp[..., -100]does not fail: it silently reads a real token, the 100th from the end. With a smaller vocabulary it raisesIndexError. Remove the ignored positions first.- If every position is ignored, return
0.0(there is nothing to learn from), notnan. - No Python loops over positions.
Hints
- Flatten first:
logitsto shape andtargetsto , withreshape. Now it is a table again, as in the previous exercise. keep = targets != ignore_indexis a boolean mask;logits[keep]andtargets[keep]keep only the rows that count.- The second term of is
label_smoothingtimes the mean of the log-softmax row, with the sign flipped.