Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[REQUEST] Unmanaged gradient accumulation: let callers own the optimizer-step boundary #8183

Copy link
Copy link

Description

@sfc-gh-truwase
Issue body actions

Motivation

DeepSpeed owns the gradient-accumulation boundary via an internal micro-step counter:
backward() bumps micro_steps, and step() only reduces gradients + runs the optimizer when

(self.micro_steps + 1) % self.gradient_accumulation_steps() == 0

This enables symmetric forward/backward/step on every micro-batch, which is ideal for the
standard training loop.

It breaks down for callers that want to own the boundary themselves — notably HTTP/RPC-driven RL
backends (e.g. Tinker-protocol servers) where one logical optimizer step arrives as N backward()
calls followed by exactly one step()
, with N client-controlled and not known at config time.
FSDP-based backends handle this natively (they call optimizer.step() when they choose); DeepSpeed's
only lever today is set_gradient_accumulation_boundary(bool), which must be toggled around every
backward, is easy to get wrong, and couples the caller to engine internals.

Proposal

Add a boolean ds_config flag managed_gradient_accumulation (default True, i.e. current behavior).
When False ("unmanaged" mode):

  • micro-step tracking is disabled; there is no counter-driven boundary,
  • is_gradient_accumulation_boundary() is True only while step() executes,
  • backward() accumulates; step() finalizes accumulated grads and applies exactly one optimizer
    update — unconditionally, every call,
  • the caller is responsible for calling step() at the true boundary.

This gives a first-class, config-level API for "backward and step are separate primitives, the client
owns the boundary" without per-backward toggling.

Per-stage semantics

  • ZeRO 0/1 / DDP: backward() accumulates gradients locally (no reduction); step() performs the
    all-reduce then the optimizer update.
  • ZeRO 2/3: keep reducing/partitioning on every backward() (their natural behavior), but defer
    the averaged_gradients finalization + optimizer update to step() via a
    finalize_gradient_accumulation_boundary() hook.
  • ZeRO offload (CPU/NVMe optimizer): supported; the boundary-time grad-norm computation and
    fp32/optimizer-buffer copies move into that finalize hook invoked from step().

Gradient scaling (open question)

DeepSpeed scales loss/grads by the configured gradient_accumulation_steps:

gas_scaled_loss = loss / self.gradient_accumulation_steps() if scale_wrt_gas else loss

In unmanaged mode the number of backward() calls per step may differ from the configured GAS (and
may vary per step). Callers who accumulate a variable count should either set
gradient_accumulation_steps accordingly or use backward(loss, scale_wrt_gas=False) and average
client-side. Open question: should unmanaged mode default to scale_wrt_gas=False, or expose a
per-step "effective accumulation count"?

Non-goals / open questions

  • Pipeline parallelism (PipelineModule.train_batch() owns the whole schedule) is out of scope
    for v1; managed_gradient_accumulation=False would be rejected there.
  • Naming: managed_gradient_accumulation vs external_grad_accum_boundary / manual_optimizer_step.
  • Scaling knob: should there be a first-class control for variable accumulation counts (see above)?
  • Interactions with ZenFlow, eigenvalue computation, and gradient clipping edge cases.

Reference implementation

A working prototype exists covering ZeRO 0-3 including CPU optimizer offload, with unit tests asserting
numerical parity against managed mode (N backwards + 1 step == managed GAS=N). Happy to contribute as
stacked PRs (ZeRO 0/1 -> 2 -> 3 -> offload) if there's interest.

Reactions are currently unavailable

Metadata

Metadata

Labels

enhancementNew feature or requestNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

Morty Proxy This is a proxified and sanitized view of the page, visit original site.