Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion megatron/core/pipeline_parallel/combined_1f1b.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

import contextlib
from contextlib import nullcontext
Expand All @@ -21,6 +21,17 @@
Shape = Union[List[int], torch.Size]


def _release_tensor_storage(tensors):
"""Release tensor storage after all backward users are done."""
if tensors is None:
return

for tensor in tensors:
if isinstance(tensor, torch.Tensor) and tensor.is_cuda:
tensor.record_stream(torch.cuda.current_stream())
tensor.untyped_storage().resize_(0)
Comment on lines +26 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: record_stream() will raise on CPU tensors. Adding a .is_cuda guard is cheap insurance:

Suggested change
if tensors is None:
return
for tensor in tensors:
if isinstance(tensor, torch.Tensor):
tensor.record_stream(torch.cuda.current_stream())
tensor.untyped_storage().resize_(0)
def _release_tensor_storage(tensors):
"""Release tensor storage after all backward users are done."""
if tensors is None:
return
for tensor in tensors:
if isinstance(tensor, torch.Tensor) and tensor.is_cuda:



Comment on lines 23 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: the record_stream + untyped_storage().resize_(0) two-liner already exists in ScheduleNode._forward (utils.py:229-231) and in fine_grained_activation_offload.py:1062-1064. Consider promoting this helper to megatron/core/pipeline_parallel/utils.py and importing it at all three sites to avoid drift between copies.

def combined_1f1b_schedule_for_no_pipelining(
forward_step_func,
data_iterator,
Expand Down Expand Up @@ -405,6 +416,7 @@ def forward_backward_step():
# backward preprocess, the same as the backward_step()
unwrap_input_tensor_grad = False
b_schedule_plan = None
loss_node_inputs_to_release = None
if b_model is not None:
# Retain the grad on the input_tensor.
if not isinstance(b_input_tensor, list):
Expand Down Expand Up @@ -432,6 +444,8 @@ def forward_backward_step():
# Backward pass for loss function
torch.autograd.backward(b_output_tensor[0], grad_tensors=b_output_tensor_grad[0])
b_output_tensor_grad[0] = loss_node.get_grad()
loss_node_inputs_to_release = loss_node.inputs
loss_node._release_state()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the reason why the input is saved additionally is to release loss_node as early as possible, but do we really need it, considering the loss tensor is small?


# If fp8_recipe is delayed, wrap the entire pass with get_fp8_context(),
# otherwise do nothing extra at the outer level
Expand All @@ -454,6 +468,7 @@ def forward_backward_step():
post_forward=post_forward,
post_backward=post_backward,
)
_release_tensor_storage(loss_node_inputs_to_release)

# forward post process
num_tokens = None
Expand Down
Loading