Take one optimiser step on a batch that is too large to run at once.
A GPU often cannot hold a full batch. The usual fix is gradient
accumulation: split the batch into micro-batches, run backward on each one,
and let the gradients add up in .grad. Then take one optimiser step. Done
right, the step is exactly the same as one step on the full batch.
Write accumulate_step(model, optimizer, loss_fn, X, y, micro_batch):
X and y into consecutive chunks of micro_batch rows. The last
chunk can be smaller.backward().optimizer.step() once.float.The key detail is the scale. loss_fn returns the mean over its chunk.
The full-batch loss is the mean over all n rows, so each chunk's loss must
be weighted by len(chunk) / n before backward():
full_loss = sum over chunks of loss_fn(chunk) * len(chunk) / n
Dividing by the number of chunks instead is only correct when all chunks are the same size.
def accumulate_step(model, optimizer, loss_fn, X, y, micro_batch):
Input
X has 10 rows, micro_batch = 4
Output
the model runs on chunks of 4, 4 and 2 rows,
the parameters end where one full-batch step puts them,
and the return value is the full-batch loss before the step
Reasoning
The chunk losses are weighted by 4/10, 4/10 and 2/10. These weights
turn three chunk means into one mean over all 10 rows, so the summed
gradient is the full-batch gradient.
.grad may hold old values when your function is called.loss_fn can be any loss with mean reduction, for example nn.MSELoss()
or nn.CrossEntropyLoss().micro_batch is at least n, there is one chunk.Run it in Colab
Google Colab has PyTorch and a free GPU, and needs nothing installed. Take a code, open the notebook, and paste the code into its first cell. Grading happens there and the result comes back here.
In Colab, the grading cell is your Submit. It runs the hidden cases there, and the result shows here on its own.
⇧↵run cell