Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,34 @@ class TrainingState(Enum):
IDLE = auto()


def setup_delayed_wgrad_acc_hook(module, grad_acc_func):
"""Configure delayed wgrad gradient processing for MoE expert parameters.

When ``overlap_dispatch_backward_with_experts_wgrad`` is enabled on a TransformerLayer,
this function:
1. Marks expert parameters so the normal post-accumulate-grad hook is skipped.
2. Registers a callback on the MoE layer that invokes FSDP's gradient
reduce-scatter after the delayed wgrad computation completes.

Args:
module: The module being processed in the forward pre-hook. Only
``TransformerLayer`` instances with the delayed wgrad config flag
enabled are affected; all other modules are no-ops.
process_post_backward_gradients_fn: The FSDP gradient processing function
(``_process_post_backward_gradients``) to be called after the delayed
wgrad computation finishes.
"""
from functools import partial

need_backward_dw = getattr(module, "need_backward_dw", lambda: False)
if not need_backward_dw():
return

for param in module.parameters():
if getattr(param, 'skip_backward_post_hook', False):
param.post_wgrad_grad_acc_hook = partial(grad_acc_func, [param])


class MegatronFSDP(torch.nn.Module):
"""Fully Sharded Data Parallel training.

Expand Down Expand Up @@ -662,6 +690,23 @@ def _process_post_backward_gradients(param_list):
"""
# Filter out shared parameters whose gradients are handled by the root hook.
param_list = [p for p in param_list if not getattr(p, "_is_shared", False)]

# Filter out parameters whose gradient processing is deferred to a delayed
# wgrad accumulation hook (post_wgrad_grad_acc_hook). If skip_backward_post_hook
# is set but the delayed hook was never installed, process the parameter
# immediately as a safety fallback to avoid silently dropping gradients.
param_list = [
p
for p in param_list
if not (
getattr(p, 'skip_backward_post_hook', False)
and hasattr(p, 'post_wgrad_grad_acc_hook')
)
]

if not param_list:
return

for param in param_list:
_grad_acc(param)

Expand Down Expand Up @@ -728,6 +773,7 @@ def _pre_forward_param_unshard(
prefetch=fsdp_forward_prefetch,
prefetch_order=PrefetchOrder.FORWARD_PASS_ORDER,
)

return args, kwargs

@torch.compiler.disable
Expand Down Expand Up @@ -983,6 +1029,8 @@ def _register_pre_backward_param_unshard_hook(module):

fsdp_modules = []
for name, module in root_module.named_modules():
# Set post backward hook for TE grouped gemm if enabled comm overlap
setup_delayed_wgrad_acc_hook(module, _process_post_backward_gradients)
if self.enable_fine_grained_param_gather_hook:
_register_pre_forward_param_unshard_hook(module)
_register_pre_backward_param_unshard_hook(module)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2632,6 +2632,13 @@ def _reset_parameters(self, old_params, new_params):
if getattr(old_param, tp_attr, None) is not None:
setattr(new_param, tp_attr, getattr(old_param, tp_attr))

# For FSDP with delayed_wgrad_compute, `skip_backward_post_hook` needs
# to be reset on new param for correct grad accumulation of wgrad computation.
setattr(
new_param,
'skip_backward_post_hook',
getattr(old_param, 'skip_backward_post_hook', False),
)
for item_id, p in enumerate(self.params):
if p in param_map:
new_p = param_map[p]
Expand Down
10 changes: 7 additions & 3 deletions megatron/core/extensions/transformer_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -1707,10 +1707,14 @@ def __init__(
self.disable_parameter_transpose_cache = self.config.disable_parameter_transpose_cache

extra_kwargs = _get_extra_te_kwargs(config)
self.delay_wgrad_compute = (
self.config.delay_wgrad_compute
or self.config.overlap_dispatch_backward_with_experts_wgrad
)

if self.config.delay_wgrad_compute:
if self.delay_wgrad_compute:
if is_te_min_version("2.3.0"):
extra_kwargs["delay_wgrad_compute"] = self.config.delay_wgrad_compute
extra_kwargs["delay_wgrad_compute"] = True
else:
raise RuntimeError(
"Only TE with version >=2.3.0 supports delay_wgrad_compute now."
Expand Down Expand Up @@ -2040,7 +2044,7 @@ def backward_dw(self):
Compute weight gradients during the backward pass
if delay_wgrad_compute is enabled.
"""
if self.config.delay_wgrad_compute:
if self.delay_wgrad_compute:
super().backward_dw()

class TEColumnParallelGroupedLinear(TEGroupedLinear):
Expand Down
9 changes: 9 additions & 0 deletions megatron/core/model_parallel_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,15 @@ class ModelParallelConfig:
delay_wgrad_compute: bool = False
"""Delay the weight gradient computation to improve batch-level communication overlapping"""

overlap_dispatch_backward_with_experts_wgrad: bool = False
"""Delay the weight gradient computation for TE Grouped GEMM MoE experts.
When enabled with FSDP, the expert weight gradients are computed on a separate
CUDA stream after the data gradients finish, allowing overlap of wgrad compute
with EP A2A communication. The FSDP gradient reduce-scatter for
expert parameters is deferred until the delayed wgrad computation completes.
This requires transformer_engine with GroupedLinear support (TE >= 2.3.0).
"""

ep_overlap_early_attn_memory_release: bool = False
"""Enable early memory release of attention activations during EP overlap.
EP overlap can increase peak memory usage when the overlapped forward module allocates
Expand Down
90 changes: 86 additions & 4 deletions megatron/core/transformer/moe/moe_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ def __init__(
self.cudagraph_tensor_store = MoECudaGraphTensorStore()
self.fwd_execution_map = ["route", "expert_compute", "postprocess"]

# Setup events and streams for delayed wgrad computation.
self.setup_delayed_wgrad_for_dispatch_backward_overlap()

def _setup_inference_mode(self, pg_collection):
"""Set up inference-optimized token dispatcher and state.

Expand All @@ -365,6 +368,16 @@ def _setup_inference_mode(self, pg_collection):
pg_collection=pg_collection,
)

def setup_delayed_wgrad_for_dispatch_backward_overlap(self):
"""Initializes CUDA events and streams for overlapping expert
weight gradient computation with dispatch backward.
"""
self._delayed_wgrad_event: Optional[torch.cuda.Event] = None
self._delayed_wgrad_stream: Optional[torch.cuda.Stream] = None
if self.config.overlap_dispatch_backward_with_experts_wgrad:
self._delayed_wgrad_event = torch.cuda.Event()
self._delayed_wgrad_stream = torch.cuda.Stream(device="cuda")

def set_inference_cuda_graphed_iteration(self):
"""Enable CUDA-graphed iteration mode on this layer, its router, and its experts.

Expand Down Expand Up @@ -435,6 +448,8 @@ def dispatch(self, hidden_states: torch.Tensor, probs: torch.Tensor):
tokens and their associated probabilities to the devices hosting their assigned
experts.
"""
if self.config.overlap_dispatch_backward_with_experts_wgrad:
hidden_states = _RegisterDelayedWgradForExperts.apply(self, hidden_states)
return self.token_dispatcher.token_dispatch(hidden_states, probs)

@maybe_skip_or_early_return_by_cudagraph("shared_experts_compute")
Expand Down Expand Up @@ -473,6 +488,10 @@ def routed_experts_compute(self, hidden_states: torch.Tensor, probs: torch.Tenso
for each expert. It then passes the tokens through the local experts.
The output from the experts is preprocessed for the combine step.
"""
if self.config.overlap_dispatch_backward_with_experts_wgrad:
hidden_states = _RecordExpertDgradCompletion.apply(
self._delayed_wgrad_event, hidden_states
)
dispatched_input, tokens_per_expert, permuted_probs = (
self.token_dispatcher.dispatch_postprocess(hidden_states, probs)
)
Expand Down Expand Up @@ -618,24 +637,24 @@ def custom_forward(hidden_states, intermediate_tensors=None, padding_mask=None):

def backward_dw(self, routed_experts: bool = True, shared_experts: bool = False):
"""Compute weight gradients for experts and shared experts."""
from megatron.core.pipeline_parallel.utils import get_comm_stream

# TODO(Wohox): replace the "routed_experts" and "shared_experts" arguments with better
# naming to better explain that they are actually from different fine-grained callables,
# or use scanning to decide which backward_dw should be called.
if routed_experts:
self.experts.backward_dw()
if self.config.moe_latent_size:
if self.config.moe_latent_size and self.config.overlap_moe_expert_parallel_comm:
# TODO(Wohox): fc2_latent_proj forward and backward are executed in comm stream,
# so we execute its backward_dw in the comm stream too. But this may harm the
# EP overlap performance. Better to check if there is a better way to handle this.
from megatron.core.pipeline_parallel.utils import get_comm_stream

comm_stream = get_comm_stream()
with torch.cuda.stream(comm_stream):
self.fc2_latent_proj.backward_dw()
if shared_experts:
if self.use_shared_expert and not self.shared_expert_overlap:
self.shared_experts.backward_dw()
if self.config.moe_latent_size:
if self.config.moe_latent_size and self.config.overlap_moe_expert_parallel_comm:
self.fc1_latent_proj.backward_dw()

def set_for_recompute_pre_mlp_layernorm(self):
Expand All @@ -646,3 +665,66 @@ def set_for_recompute_pre_mlp_layernorm(self):
from megatron.core.extensions.transformer_engine import set_save_original_input

set_save_original_input(self.shared_experts.linear_fc1)


class _RecordExpertDgradCompletion(torch.autograd.Function):
"""Autograd function that records a CUDA event when expert data gradients finish.

Placed in the forward graph just before the expert computation so that during
the backward pass, when the expert dgrad completes, we record an event. The
subsequent ``_RegisterDelayedWgradForExperts`` waits on this event before
launching the delayed wgrad computation on a separate CUDA stream.
"""

@staticmethod
def forward(ctx, event: torch.cuda.Event, *inputs):
"""Forward pass that stores the event and passes through inputs unchanged."""
ctx.event = event
return inputs[0] if len(inputs) == 1 else inputs

@staticmethod
def backward(ctx, *grad_outputs):
"""Backward pass that records the event when expert dgrad completes."""
ctx.event.record(torch.cuda.current_stream())
ctx.event = None
return (None,) + grad_outputs


class _RegisterDelayedWgradForExperts(torch.autograd.Function):
"""Autograd function that orchestrates delayed wgrad computation for MoE experts.

Placed in the forward graph at the dispatch boundary. During the backward pass,
this function:
1. Records an event on the current (backward) stream to signal the dgrad is done.
2. Executes the delayed wgrad computation on a dedicated CUDA stream.
3. Waits for the wgrad computation to complete.
4. Invokes the registered gradient processing callback (e.g., FSDP reduce-scatter).
"""

@staticmethod
def forward(ctx, module: MoELayer, *inputs):
"""Forward pass that stores the MoE module and passes through inputs unchanged."""
ctx.module = module
return inputs[0] if len(inputs) == 1 else inputs

@staticmethod
def backward(ctx, *grad_outputs):
"""Backward pass that executes delayed wgrad computation on a separate stream."""
module = ctx.module
event = module._delayed_wgrad_event
wgrad_stream = module._delayed_wgrad_stream

wgrad_stream.wait_event(event)
with torch.cuda.stream(wgrad_stream):
Comment thread
Wohox marked this conversation as resolved.
with torch.cuda.nvtx.range("delayed_expert_wgrad"):
module.backward_dw(routed_experts=True, shared_experts=False)
event.record(wgrad_stream)

torch.cuda.current_stream().wait_event(event)

for param in module.parameters():
if getattr(param, "post_wgrad_grad_acc_hook", None) is not None:
param.post_wgrad_grad_acc_hook()

ctx.module = None
return (None,) + grad_outputs
13 changes: 13 additions & 0 deletions megatron/core/transformer/transformer_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2148,6 +2148,19 @@ def __post_init__(self):
'partial cuda graph'
)

if self.overlap_dispatch_backward_with_experts_wgrad:
assert not self.overlap_moe_expert_parallel_comm, (
'overlap_moe_expert_parallel_comm must be disabled when enabling '
'overlap_dispatch_backward_with_experts_wgrad.'
)
assert is_te_min_version(
"2.3.0"
), 'TE version >= 2.3.0 is required for overlap_dispatch_backward_with_experts_wgrad'
assert not self.delay_wgrad_compute, (
'delay_wgrad_compute and overlap_dispatch_backward_with_experts_wgrad '
'are mutually exclusive; use only one'
)

if self.ep_overlap_early_attn_memory_release:
assert self.overlap_moe_expert_parallel_comm, (
'overlap_moe_expert_parallel_comm must be enabled when enabling '
Expand Down
Loading
Loading