The problem
Language models are trained with batches far too large to fit in memory at once. Gradient accumulation splits a batch into smaller micro-batches: each gets its own forward and backward pass, their gradients are combined, and the optimizer takes one step. Done right, the result is exactly the gradient of the whole batch.
You are given grad_fn(x, y), which returns (loss, grad) for one micro-batch: the mean loss over its targets, and the gradient of that mean (a NumPy array).
Combining means correctly
The loss of the whole batch is the mean over all its targets. If micro-batch has targets and mean loss , its targets add up to , so:
where:
- is the number of targets of micro-batch (
y.size); - is the total number of targets;
- is the micro-batch's share of the targets: its weight.
The gradient is a sum of the same kind, so the same weights apply to the micro-batch gradients.
Example. Two micro-batches: the first has 3 targets and mean loss , the second 1 target and mean loss . So .
- Right: weights and , loss . Check: the four targets have losses adding up to , and .
- Wrong: the plain average of the two means, , counts the single target of the second micro-batch as much as the three of the first.
When all micro-batches have the same size, the weights are equal and the plain average is right. The last micro-batch is often smaller, and that is where the bug hides.
What to implement
accumulate(grad_fn, micro_batches): micro_batches is a list of (x, y) pairs. Return (loss, grad) for the whole batch: the mean loss over all targets and its gradient.
Requirements
- Weight each micro-batch by
y.sizedivided by the total, as above. - Call
grad_fnexactly once per micro-batch.
Hint
Compute the total number of targets first, in a separate pass over the list: then each micro-batch's weight is known when you call grad_fn on it.