Gradient Accumulation › The Accumulation Procedure
Language AI HandbookAll chapters Gradient Accumulation › The Accumulation ProcedureOne section · Sections in this chapter3 / 14 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Gradient accumulation works by splitting a logical large batch into a series of smaller micro-batches, running forward and backward passes on each one without applying the optimizer step, and accumulating the resulting gradients. Only after all micro-batches have been processed does the optimizer perform a single weight update using the accumulated gradients.
The procedure follows a precise sequence:
- Zero the gradients at the start of each accumulation cycle. This is the same
optimizer.zero_grad()call you know from ordinary training, but it now occurs once per logical batch rather than once per micro-batch. - Process each micro-batch: run the forward pass, compute the loss, scale the loss by where is the number of accumulation steps, and run the backward pass. The backward pass adds gradients to the gradient buffers without resetting them.
- Step the optimizer once after all micro-batches have been processed. The gradient buffers now contain the accumulated sum, which after the scaling applied to each loss equals the mean gradient over the full logical batch.
- Repeat for the next logical batch.
The memory benefit arises from step 2: at any point in time, only a single micro-batch occupies activation memory. The gradient buffers themselves must still hold the full model's gradients, but activation memory, which is proportional to batch size, is bounded by the micro-batch size.
The key structural difference from ordinary training is the placement of zero_grad() and step(). In ordinary training, both calls appear at every iteration. With gradient accumulation, zero_grad() appears at the start of every -th iteration, and step() appears at the end of every -th iteration. The iterations in between only call backward(), allowing gradients to accumulate.
The Scaling Requirement
The loss scaling in step 2 deserves careful attention. Standard training computes the mean loss over a batch and then backpropagates that mean. When you accumulate gradients across micro-batches, each micro-batch computes its own mean loss. If you simply sum these mean losses and backpropagate each one without scaling, your accumulated gradient is times larger than the gradient you would have computed from the full batch. This means the optimizer takes a step times too large, corrupting training.
To preserve mathematical equivalence with full-batch training, you must divide each micro-batch loss by before calling .backward(). This ensures that the accumulated gradients match the gradient from the combined batch exactly.
Let denote the gradient vector computed from the -th micro-batch with loss scaling applied. The scaled gradient for micro-batch is:
where:
- : the total number of accumulation steps (micro-batches per logical batch)
- : the model parameters
- : the mean cross-entropy loss computed over micro-batch
- : the -th micro-batch, a non-overlapping subset of the full logical batch
- : the gradient operator with respect to
After backward passes, PyTorch's autograd engine has accumulated the sum of all gradient contributions into the parameter gradient buffers:
where:
- : the accumulated gradient stored in the parameter buffers after all backward passes
- : the gradient contribution from micro-batch , already scaled by
If the micro-batches partition evenly (each of size ), the accumulated gradient equals the gradient computed directly on the full batch :
This equality holds because gradient computation is linear: the gradient of a mean is the mean of the gradients. Formally, if is a partition of the full batch with for all , then:
where:
- : the per-sample loss for sample
- : the full logical batch size
- : the size of each micro-batch
The accumulated result is an unbiased estimate of the full-batch gradient, and the optimizer update proceeds exactly as if the large batch had been processed in one shot.
What happens if you forget the scaling? The accumulated gradient becomes times larger than intended, so the optimizer takes an update step times too large. This is equivalent to multiplying the learning rate by , which typically causes training to diverge. This mistake is easy to make, especially when adapting a standard training loop. Always check that the loss passed to .backward() is divided by accumulation_steps before the call.