fix: guard backward_dw() bias-grad backfill against a legitimately-empty bgrad - #3400
Open
nvegesna-netizen wants to merge 1 commit into
Open
Conversation
Contributor
Greptile SummaryThe PR prevents delayed weight-gradient computation from dereferencing a legitimately absent FP8 bias gradient and adds regression coverage for frameworks that consume and clear
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Sequence DiagramsequenceDiagram
participant Autograd
participant Linear
participant Hook as Gradient hook
participant Store as WeightGradStore
Autograd->>Linear: backward()
Linear-->>Autograd: eager FP8 bias gradient
Autograd->>Hook: accumulate bias.grad
Hook->>Hook: fold into main_grad and clear bias.grad
Linear->>Store: backward_dw()
Store-->>Linear: weight gradient, empty bgrad
Linear->>Linear: skip empty bias backfill
Reviews (2): Last reviewed commit: "fix: guard backward_dw() bias-grad backf..." | Re-trigger Greptile |
…pty bgrad TransformerEngineBaseModule.backward_dw() unconditionally assumes the bgrad popped from wgrad_store is a real gradient tensor whenever use_bias is True, and crashes with AttributeError: 'NoneType' object has no attribute 'to' if it isn't. In FP8 mode, grad_output_preprocess() always computes the bias gradient eagerly (via bgrad_quantize or an unfused sum), regardless of delay_wgrad_compute. That value flows through the module's ordinary backward() return into normal autograd, which sets bias.grad. If the training framework's own gradient-accumulation hook then consumes bias.grad and resets it to None before backward_dw() runs (a common pattern for frameworks that manage their own master-gradient buffers, e.g. to fold .grad into a separate main_grad and free it for reuse), backward_dw()'s `if bias_tensor.grad is None:` check is satisfied even though there is nothing new to backfill -- the real gradient was already correctly handled by the eager path. Meanwhile, in linear.py's wgrad-GEMM closure, `"bias": (bias if (grad_bias is None and not bwd_args.fp8) else None)` deliberately skips computing bias grad in the wgrad step whenever grad_bias was already set, which in FP8 mode it always is -- so bgrad popped from wgrad_store is legitimately empty in this case, and backward_dw() crashes trying to use it anyway. Guard on bgrad actually holding data before assigning, matching the existing (grad_bias is not None and grad_bias.numel() != 0) pattern already used for the equivalent case in GroupedLinear.backward_dw(). Skipping is correct here rather than computing bgrad in the wgrad step too, since the latter would double-count a gradient that was already folded into the framework's own gradient buffer by the eager path. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
nvegesna-netizen
force-pushed
the
nvegesna/fix-backward-dw-bgrad-none
branch
from
August 22, 2026 18:11
1c27f05 to
e813158
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TransformerEngineBaseModule.backward_dw()can crash withAttributeError: 'NoneType' object has no attribute 'to'when delayed weight-gradientcomputation is used with FP8 and a biased
LinearorLayerNormLinear. The failureoccurs if an external gradient consumer moves the eagerly computed bias gradient into
its own accumulation buffer and clears
bias.gradbeforebackward_dw().Root cause
In FP8 mode,
grad_output_preprocess()computes the bias gradient eagerly. Themodule's ordinary backward returns that gradient to autograd.
The delayed wgrad closure does not request another bias gradient in FP8 mode. Its
GEMM therefore returns
bgrad=None, which is valid because the eager path alreadyproduced the real bias gradient.
If an external hook consumes the eager gradient and clears
bias.grad, the oldbackward_dw()implementation enters its bias backfill branch and callsbgrad.to(...)even though the deferred GEMM did not produce a bias gradient.Current framework integrations that honor TE's
skip_backward_post_hookcontract maydefer their gradient hook until
backward_dw()and avoid this ordering. The failingstate is still valid for an unconditional external gradient consumer, and it has been
observed in an integration where that deferral was not preserved.
Fix
Only backfill
bias.gradwhen the delayed GEMM returned a nonempty bias gradient.This matches the existing guard in
GroupedLinear.backward_dw().The non-FP8 path is unchanged. In that path the deferred GEMM returns a real bias
gradient, so the existing backfill still runs. In the FP8 failure path, skipping the
empty value preserves the eager gradient already consumed by the caller and avoids
double counting.
Testing
Added a focused GPU regression in
tests/pytorch/test_numerics.py. It uses delayedscaling FP8 and delayed wgrad computation, then installs a post-accumulate parameter
hook that moves the eager bias gradient into
main_gradand clearsbias.grad. Thetest verifies that:
backward_dw()completes without recreating or double counting the bias gradientValidation on one H100 with the pinned NeMo rc8 Transformer Engine build produced the
expected red and green result. The unpatched build failed at
bgrad.to(...)with thereported
NoneTypeexception. The same test passed after applying this one-line fix.All 24 existing H100 cases selected by
test_linear_accuracy_delay_wgrad_computealso passed after the patch. Those casescover the unaffected non-FP8 delayed-wgrad path across supported dtypes, batch sizes,
bias settings, and fused wgrad accumulation settings.