Skip to content

[PyTorch] Deferred backward quantization state update (removes first-FP8-module ownership) - #34

Draft
pggPL wants to merge 8 commits into
mainfrom
bwd_state_update_refactor
Draft

[PyTorch] Deferred backward quantization state update (removes first-FP8-module ownership)#34
pggPL wants to merge 8 commits into
mainfrom
bwd_state_update_refactor

Conversation

@pggPL

@pggPL pggPL commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Description

Refactor of how the backward delayed-scaling amax reduction is triggered.

Problem. reduce_and_update_fp8_tensors(forward=False) was fired from the backward of the first FP8 module in an autocast, on the assumption that its backward runs last. This breaks in two ways:

  1. Activation recompute (Fix duplicate FP8 state updates during activation checkpoint recomputation NVIDIA/TransformerEngine#3213): the recompute re-runs forwards and re-enters autocasts, advancing delayed-scaling state multiple times per step (duplicate forward update at recompute-autocast exit + per-module ownership re-acquisition).
  2. Non-linear autograd graphs: with branches (or a checkpointed branch whose output never receives a gradient), the owning module's backward is not guaranteed to run last — or at all — so the reduction can fire early or be lost entirely.

Fix. Module backwards no longer trigger the update. They only request it (FP8GlobalStateManager.request_backward_quantization_update(), a bool flag). The update is flushed exactly once at the next top-level autocast entry — at that point the previous backward is provably complete, whatever the graph topology or checkpointing scheme. Guards ensure the flush never runs during the recompute phase, inside a running backward (torch._C._current_graph_task_id()), under CUDA graph capture, or under torch.compile tracing. The forward update at autocast exit is skipped during the recompute phase (fixes the duplicate forward update; the recompute-phase flag now lives on the quantization state and also covers Megatron-Core's checkpoint, which wraps its recompute in TE's activation_recompute_forward).

This removes the is_first_fp8_module consumption from all eager modules and the save/restore bookkeeping in checkpoint contexts (_is_first_fp8_module FIFO, per-module restore hacks). The CUDA-graphs path (graph.py) keeps its own mechanism and is unchanged. Timing note: the backward update now runs at the start of the next step instead of the end of the current backward — in between, nothing reads the affected state, so scale trajectories are unchanged (asserted exactly in the new tests).

Naming: reduce_and_update_fp8_tensorsreduce_and_update_quantization_state (old name kept as an alias since Megatron-Core calls it), since delayed update methods will be used beyond FP8 (e.g. RHT in NVFP4).

Supersedes the approach in NVIDIA#3213: instead of scoping ownership to checkpoint frames, ownership is removed.

New tests (in tests/pytorch/test_recipe.py, already enumerated by the L0 QA suite)

Type of change

  • Bug fix + refactor

🤖 Generated with Claude Code

pggPL and others added 8 commits August 17, 2026 12:31
…ization state update

The backward amax reduction for delayed scaling was triggered from the
backward of the 'first FP8 module' of an autocast, assuming its backward
runs last. That assumption breaks with activation recompute (duplicate
forward/backward updates per step) and with branched autograd graphs
(ownership can land in a frame whose backward never runs).

Instead, module backwards now only request the update
(schedule_backward_quantization_update); it is flushed exactly once at
the next top-level autocast entry, after the backward pass is complete.
Forward updates at autocast exit are skipped during the recompute phase.

Removes the is_first_fp8_module save/restore bookkeeping from checkpoint
contexts and modules; renames reduce_and_update_fp8_tensors to
reduce_and_update_quantization_state (old name kept as alias).

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
test_recipe and test_fusible_ops asserted backward scales immediately
after backward(); with the deferred update they flush explicitly.
Drop raw torch.utils.checkpoint mode from new tests (unsupported with
FP8 weight caching independently of this change) and relax exact amax
history comparison for nested checkpoints (inner checkpoint re-records
amaxes during outer recompute; scales unaffected).

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…factor

# Conflicts:
#	transformer_engine/pytorch/distributed.py
#	transformer_engine/pytorch/ops/fuser.py
…tization_update

The method only idempotently marks the update as pending, so 'request'
is more precise. Context booleans renamed to
should_request_backward_quantization_update accordingly.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Keeps them in a file already enumerated by the L0 QA suite instead of
adding a new one.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The activation-checkpoint duplicate-update problem and its regression
test scenarios were first diagnosed and addressed in NVIDIA#3213; this PR
supersedes that approach by removing first-module ownership entirely.

Co-authored-by: AlbertYang514 <201034045+AlbertYang514@users.noreply.github.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
FP8GlobalStateManager now only orchestrates when updates run; how a
recipe updates its process-global state is a RecipeState classmethod
(reduce_and_update_global_state), no-op by default and overridden by
DelayedScalingRecipeState with the amax reduction + scale recompute.
The recipe-to-state-class mapping is extracted from RecipeState.create
into class_for_recipe and reused for dispatch.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The reduction no longer runs in module backwards, so the old per-module
NVTX markers had nothing left to wrap; mark it at its new single
execution site in flush_backward_quantization_update instead.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant