advanced28 min studyLesson 544 in Engineering

Accumulation changes communication, not the intended batch objective

Derive a window-level update and scope DDP no_sync, mixed-precision scaling, clipping, and optimizer boundaries precisely.

On this page

What you’ll understand

  • Derive equal-size and unequal-token accumulation scaling.
  • Place DDP no_sync around the required forward and backward work.
  • Separate one accumulated update from multiple optimizer updates.

Gradient accumulation lets several microbatches contribute to one optimizer update without holding all their activations at once. It does not automatically choose the correct loss denominator, and it does not make several optimizer steps equivalent to one larger-batch step. We assume PyTorch 2.9 DDP with fixed participation, default averaging, and one intended optimizer boundary per accumulation window. Mixed-precision details are scoped to the documented scaling workflow; sharded wrappers and offload modes may have additional memory or support constraints.

Define the window and its single update

With equal token counts in every rank and microbatch, dividing each local mean loss by the number of microbatches gives the intended window mean after default DDP averaging. With unequal valid counts, that shortcut fails: use the global eligible-token denominator across the whole window and the local summed-loss scaling derived earlier. Either way, parameters remain at the same pre-update values while those microbatch gradients accumulate. Clear old gradients at the intended window boundary, not between contributing microbatches, and perform the optimizer update only after the complete synchronized contribution is ready.

Calling the optimizer between microbatches evaluates later gradients at different parameters and advances optimizer state more often. Even stateless SGD on a curved loss need not match the accumulated update; momentum, adaptive moments, weight decay, and step-based schedules create additional differences. A smaller final window also needs its actual denominator. Do not blindly divide by a nominal accumulation count when an epoch tail contains fewer contributing tokens. Make that tail policy explicit and compatible on all ranks instead of letting one rank silently stop early.

gwindow=1WK∑r=0W−1∑k=1Kgr,kg_{\rm window}=\frac{1}{WK}\sum_{r=0}^{W-1}\sum_{k=1}^{K}g_{r,k}
For W positive ranks and K positive microbatches with equal eligible counts, gr,k is each local microbatch mean gradient evaluated at unchanged parameters. This exact-arithmetic formula is replaced by token-sum/global-count weighting when counts differ.↔ Scroll wide equations horizontally.

Suppress intermediate synchronization without losing participation

DDP no_sync suppresses gradient synchronization within its context while allowing local gradient accumulation. The forward pass must be inside the context as well as the corresponding backward work; wrapping only backward is not the documented protocol. A final normal forward/backward outside the context synchronizes accumulated gradients. Every rank must reach compatible synchronization boundaries. Skipping a final synchronized microbatch or clearing the accumulated buffer first defeats the intended update even when earlier local backward calls succeeded.

Communication suppression does not make other collectives disappear. Count reductions, synchronized layers, or application collectives still need matching participation and order. Also distinguish DDP from FSDP: the PyTorch 2.9 FSDP wrapper documents that no_sync can retain full rather than sharded gradients and increase memory. CPU offload introduces further supported-accumulation restrictions. Verify the chosen wrapper and strategy rather than copy a DDP accumulation recipe into a sharded deployment and assume the same memory footprint and synchronization behavior.

L~r,k=WNwindowSr,k,Nwindow=∑r,kNr,k>0\widetilde L_{r,k}=\frac{W}{N_{\rm window}}S_{r,k},\qquad N_{\rm window}=\sum_{r,k}N_{r,k}>0
This local summed-loss construction assumes fixed-world-size default DDP averaging, unchanged parameters within the window, and a shared denominator for every contributing microbatch. There is no extra division by K when this global token count already covers the entire window.↔ Scroll wide equations horizontally.

Keep scaling and nonlinear transforms at the right boundary

For mixed precision, use one consistent loss scale while accumulating the window so that contributions are commensurate. Unscale only after all contributions are present, and apply gradient clipping to the intended synchronized unscaled global gradient before the single optimizer step. Updating the scaler or clipping each microbatch separately can change the algorithm. Ensure all replicas make compatible overflow and skipped-step decisions; otherwise optimizer state, schedules, and parameters can diverge despite a shared nominal step counter. Include those edge cases in a small reference test.

The optimization lab demonstrates why step boundaries matter in a simpler setting. Reset, explicitly set step size 0.1 and initial position two, predict the first two iterates, then Step twice. Compare a fresh Reset with step size 0.2 and initial position two before one Step. The quadratic gives 1.8 then 1.62 versus 1.6. Two sequential updates evaluate a changing gradient. This is not an accumulation implementation, has no microbatches or no_sync, and models no momentum or loss scaler; it only makes the update-boundary distinction concrete.

Four equal microbatch contributions form one mean update

Two ranks each process two equal-sized microbatches at unchanged parameters. Rank A’s scalar mean gradients are one and three; rank B’s are five and seven. The starting parameter is ten, learning rate 0.1, and SGD is stateless.

  1. The desired mean over four equal-sized contributions is (1+3+5+7)/4=4. Each local mean loss is divided by the accumulation count two under the stated equal-size assumptions.

  2. Rank A accumulates 1/2+3/2=2, and rank B accumulates 5/2+7/2=6. The final default DDP average is (2+6)/2=4. Intermediate no_sync avoids redundant reductions but does not replace this normalization.

  3. After completed final synchronization, one optimizer step produces 10−0.1×4=9.6. Clearing gradients between the microbatches or stepping before the final reduction would implement a different computation.

The result

The accumulated window produces gradient four and one next parameter of 9.6 under the equal-size and fixed-parameter assumptions.

Explore the model

Interactive lab

Interactive lab · optimization

Two small steps are a different schedule from one larger step

Run Reset → explicitly set BOTH η=.1 and x₀=2 → predict → Step twice to get 1.8, then 1.62. In a separate Reset run set BOTH η=.2 and x₀=2 before predicting and taking one Step, giving 1.6. This is fixed quadratic descent, not microbatch accumulation.

Two small steps are a different schedule from one larger stepGradient descent on f(x) = x²/2: xₙ = (1 − η)ⁿx₀. Playback advances two discrete iterations per second. Values outside |x| ≤ 4 are explicitly reported. Current readings: Position x0: 2; Objective x²/2: 2; Iteration behavior: Stable step. All parameters are adjustable with the labeled controls below.−4−202402.174.356.528.7Parameter xf(x) = x²/2n = 0
ObjectiveDiscrete iterate
Position x0
2
Objective x²/2
2
Iteration behavior
Stable step
n = 0 · t = 0 s

Gradient descent on f(x) = x²/2: xₙ = (1 − η)ⁿx₀. Playback advances two discrete iterations per second. Values outside |x| ≤ 4 are explicitly reported. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forAn optimizer boundary changes where the next gradient is evaluated; suppressing communication does not define that boundary for you.

Check your understanding

Which DDP no_sync usage matches the documented accumulation contract?

Try it yourself

Two ranks each process three equal-sized microbatches at unchanged parameters. Their local mean gradients are [2,4,6] and [8,10,12]. Calculate each local accumulated contribution after dividing losses by three and the final averaged gradient. How should an unequal-token window differ?

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Use the equal-size formula only for the first calculation, then return to one global valid-token denominator.

Compare with the solution

The first rank accumulates 12/3=4 and the second 30/3=10. Default DDP averages them to seven, matching the mean of all six contributions. For unequal token counts, accumulate local summed losses scaled by world size divided by the global window’s eligible count, with no redundant division by three. Keep parameters unchanged, do not clear intermediate gradients, and synchronize before the single optimizer step. The actual final window size, zero-count handling, clipping, and loss scale must follow the same coordinated policy.

Take it with you

  • Normalize the entire intended update window.
  • Use no_sync with forward/backward scope and a final compatible synchronization.
  • Keep clipping, loss-scale changes, and optimizer updates at their defined boundaries.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.