Repository navigation
Fix cross-iteration gradient retention in CheckpointFunction.backward #6339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -632,6 +632,15 @@ def backward(ctx, *args): | |
| torch.autograd.backward(outputs, args) | ||
| grads = tuple(inp.grad if isinstance(inp, torch.Tensor) else inp for inp in detached_inputs) | ||
|
|
||
| # Release the recomputation-leaf gradients: they were harvested into | ||
| # `grads` above, but the detached leaves can outlive this call (e.g. | ||
| # kept alive by saved-variable references from the recomputation | ||
| # subgraph), retaining a stale copy of the segment's input gradients | ||
| # across iterations. | ||
| for inp in detached_inputs: | ||
| if isinstance(inp, torch.Tensor): | ||
| inp.grad = None | ||
|
Comment on lines
+640
to
+642
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [IMPORTANT Correctness] Please confirm this is safe when What: Before this change, the detached leaf's Why it matters: I believe the standard Megatron cudagraph path is unaffected, because Suggested fix: either confirm via a run of the cudagraph + from megatron.core.transformer.cuda_graphs import is_graph_capturing
# (already imported at the top of this method)
# Release the recomputation-leaf gradients ... (comment)
# Skipped during graph capture: the returned `grads` are the only remaining
# owner, and freeing a capture-mempool block lets a later capture alias it.
if not is_graph_capturing():
for inp in detached_inputs:
if isinstance(inp, torch.Tensor):
inp.grad = NoneA one-line note in the PR description recording which configuration you validated would also be useful, since the reported measurement is from a non-cudagraph run. |
||
|
|
||
| _unset_checkpointing() | ||
| return (None, None) + grads | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[SUGGESTION Other] The comment says the stale
.gradis retained "across iterations", but the mechanism it describes — saved-variable references from the recomputation subgraph keeping the detached leaves alive — does not by itself explain an iteration-length lifetime.The recomputed subgraph built at line 623 is dropped as soon as
torch.autograd.backwardon line 632 finishes (it is not retained), andctxis torn down when the engine releases the node. What actually extends the leaves' lifetime past that point is whatever external reference the measurement found, e.g. an outer autograd node or a Python-side container still holding the recomputed tensors. Without naming it, a future reader cannot tell whether the release is still necessary or has become dead code, and cannot reproduce the finding.The PR description says weakref instrumentation identified the retaining path. Naming that path here — the specific holder, not just "saved-variable references" — would make the comment verifiable:
Also worth noting inline: this loop is safe only because no caller reads
detached_inputs[i].gradafterbackwardreturns. I checked and none does —CheckpointWithoutOutputFunction.backward(line 766) readsinp.gradfrom its ownctx.inputs, andScheduleNode.get_grad(pipeline_parallel/utils.py:290) reads from its own detachedself.inputs, both distinct tensor sets. Stating that invariant in the comment protects it against a future change.