The problem: batch size you can't afford
Some models train best at a large batch size — transformers especially — but a large batch may simply not fit in GPU memory. Gradient accumulation decouples the statistical batch from the physical one: you run several small micro-batches, sum their gradients, and only call the optimizer once the sum represents the batch you wanted. The weight update is (almost) identical to having run one big batch.
How the math stays honest
Two details make it correct. First, divide each micro-batch's loss by accumulation_steps so the summed gradient is an average, not a sum — otherwise your effective learning rate scales with the accumulation count. Second, apply and then reset the accumulator on the same boundary, so each optimizer step consumes exactly N micro-batches. The example below shows the manual-loop form with TensorFlow's tape; folding it into a train_step() with the accumulator as a model attribute is the production version.