Skip to content

Commit 991e351

Browse files
committed
[GraphTrainer] Support transformer_block_bucketing in aot_fx_trace mode
aot_fx_trace mode traces with record_module_stack=False, so nodes lack nn_module_stack metadata that manual_overlap_bucketing uses to match nodes to modules. This change uses the existing annotate_fn mechanism (already used for AC region tagging) to tag module FQNs during tracing, then provides a custom module_stack_fn to manual_overlap_bucketing. Changes: - Add annotate_module_fqns() and get_module_stack_from_annotation() to common_utils.py - Call annotate_module_fqns() in both llama3 and deepseek_v3 annotate functions - Add module_stack_fn parameter to transformer_block_bucketing_reordering_pass - Update construct_default_graph_passes to accept compile_config, model, and parallel_dims, and wire up the bucketing pass with reassign_to_pg - Pass config/model/parallel_dims from GraphTrainer to construct_default_graph_passes - Add numerics tests for both llama3 and deepseek_v3 with aot_fx_trace + transformer_block_bucketing
1 parent be09084 commit 991e351

7 files changed

Lines changed: 114 additions & 55 deletions

File tree

torchtitan/experiments/graph_trainer/common_utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@
1818
from torchtitan.tools.logging import logger
1919

2020
_AC_REGION_ID = "ac_region_id"
21+
_MODULE_FQN = "module_fqn"
22+
23+
24+
def annotate_module_fqns(model: nn.Module) -> None:
25+
"""Annotate all modules' forward with their fully-qualified names.
26+
27+
Every named submodule (excluding the root) gets its forward method wrapped
28+
with annotate_fn so that FX nodes carry the module_fqn metadata.
29+
"""
30+
for fqn, submodule in model.named_modules():
31+
if fqn: # skip root module
32+
submodule.forward = annotate_fn({_MODULE_FQN: fqn})(submodule.forward)
2133

2234

2335
def annotate_ac_regions(model: nn.Module) -> None:

torchtitan/experiments/graph_trainer/deepseek_v3/parallelize.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from torchtitan.distributed.tensor_parallel import maybe_enable_async_tp
2020
from torchtitan.experiments.graph_trainer.common_utils import (
2121
annotate_ac_regions,
22+
annotate_module_fqns,
2223
apply_graph_ac,
2324
)
2425
from torchtitan.experiments.graph_trainer.compile import apply_compile
@@ -55,6 +56,7 @@ def annotate_deepseekv3(model: GraphTrainerDeepSeekV3Model) -> None:
5556
)
5657
MoE.forward = annotate_fn({"EP": "compute"})(MoE.forward)
5758

59+
annotate_module_fqns(model)
5860
annotate_ac_regions(model)
5961

6062

torchtitan/experiments/graph_trainer/llama3/parallelize.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from torchtitan.distributed.tensor_parallel import maybe_enable_async_tp
1717
from torchtitan.experiments.graph_trainer.common_utils import (
1818
annotate_ac_regions,
19+
annotate_module_fqns,
1920
apply_graph_ac,
2021
)
2122
from torchtitan.experiments.graph_trainer.compile import apply_compile
@@ -32,10 +33,14 @@
3233
def annotate_llama(model: GraphTrainerLlama3Model) -> None:
3334
"""Attach annotations to FX graph nodes with ``torch.fx.traceback.annotate_fn``
3435
36+
- Module FQN annotation: Tags each submodule's forward with its
37+
fully-qualified name so that the transformer_block_bucketing pass
38+
can identify which graph nodes belong to which module.
3539
- AC region annotation: Tags each transformer block's forward with a unique
3640
ac_region_id so that apply_sac_pass can assign per-block ac_graph_id
3741
boundaries for the min-cut partitioner.
3842
"""
43+
annotate_module_fqns(model)
3944
annotate_ac_regions(model)
4045

4146

torchtitan/experiments/graph_trainer/make_fx_tracer.py

Lines changed: 12 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
import torch
1313
import torch.nn as nn
1414
import torch.utils._pytree as pytree
15-
from torch._functorch._aot_autograd.logging_utils import (
16-
setup_stacktrace_preservation_hooks,
17-
)
1815
from torch._guards import tracing, TracingContext
1916
from torch._subclasses import FakeTensorMode
2017
from torch.fx.experimental.proxy_tensor import make_fx
@@ -200,52 +197,6 @@ def _remove_cpu_shadow_chains(gm: torch.fx.GraphModule) -> None:
200197
gm.recompile()
201198

202199

203-
@contextmanager
204-
def _patch_engine_run_backward() -> Generator[None, None, None]:
205-
"""Patch _engine_run_backward to install stacktrace preservation hooks.
206-
207-
Why this is needed:
208-
When make_fx traces a function that calls loss.backward(), the backward
209-
pass is decomposed into primitive ATen ops. Normally (in eager autograd),
210-
``setup_stacktrace_preservation_hooks`` is called by the autograd engine
211-
to propagate ``seq_nr`` from forward ops to their corresponding backward
212-
ops. Under make_fx tracing, this hook setup doesn't happen automatically
213-
because the engine path differs, so backward FX nodes end up without
214-
``seq_nr`` metadata. Without ``seq_nr``, we can't correlate backward
215-
nodes back to their forward counterparts (needed by
216-
``_copy_fwd_metadata_to_bw_nodes``).
217-
218-
This context manager patches ``_engine_run_backward`` to call
219-
``setup_stacktrace_preservation_hooks`` before the autograd engine runs,
220-
restoring ``seq_nr`` propagation during tracing.
221-
222-
We must patch the name in both modules since ``torch.autograd.__init__``
223-
imports it via ``from .graph import``.
224-
"""
225-
import torch.autograd
226-
import torch.autograd.graph
227-
228-
_orig_fn = torch.autograd.graph._engine_run_backward
229-
230-
def _patched(t_outputs, *args, **kwargs): # type: ignore[no-untyped-def]
231-
roots = [
232-
t.grad_fn
233-
for t in t_outputs
234-
if isinstance(t, torch.Tensor) and t.grad_fn is not None
235-
]
236-
if roots:
237-
setup_stacktrace_preservation_hooks(roots)
238-
return _orig_fn(t_outputs, *args, **kwargs)
239-
240-
torch.autograd.graph._engine_run_backward = _patched # type: ignore[assignment]
241-
torch.autograd._engine_run_backward = _patched # type: ignore[assignment]
242-
try:
243-
yield
244-
finally:
245-
torch.autograd.graph._engine_run_backward = _orig_fn # type: ignore[assignment]
246-
torch.autograd._engine_run_backward = _orig_fn # type: ignore[assignment]
247-
248-
249200
def _copy_fwd_metadata_to_bw_nodes(fx_g: torch.fx.GraphModule) -> None:
250201
"""Copy forward node metadata (custom) to later nodes sharing the same seq_nr.
251202
@@ -402,17 +353,25 @@ def fn_with_subclass_handling(*plain_args: Any) -> list:
402353
state_for_fn = dict(zip(state_fqns, state_wrapped, strict=True))
403354
user_list = pytree.tree_unflatten(list(user_flat), user_args_spec)
404355

405-
with _patch_engine_run_backward():
406-
result = fn(state_for_fn, *user_list)
356+
result = fn(state_for_fn, *user_list)
407357

408358
flat_outs, output_spec = pytree.tree_flatten(result)
409359
num_flat_outputs = len(flat_outs)
410360
unwrapped_outs, output_layouts = _unwrap_subclasses(flat_outs)
411361
return unwrapped_outs
412362

413363
ctx = TracingContext(fake_mode)
414-
# preserve_node_meta propagates fx.traceback.annotate metadata to traced nodes
415-
with fake_mode, tracing(ctx), preserve_node_meta(), _skip_nested_compile():
364+
# preserve_node_meta propagates fx.traceback.annotate metadata to traced nodes.
365+
# _non_strict_tracing_context + _patch_autograd_grad install autograd
366+
# hooks so backward nodes get seq_nr and autograd_backward metadata.
367+
with (
368+
fake_mode,
369+
tracing(ctx),
370+
preserve_node_meta(),
371+
_skip_nested_compile(),
372+
torch.compiler._non_strict_tracing_context(),
373+
torch.compiler._patch_autograd_grad(),
374+
):
416375
traced = make_fx(
417376
fn_with_subclass_handling,
418377
record_stack_traces=True,

torchtitan/experiments/graph_trainer/passes.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from torch.utils.checkpoint import CheckpointPolicy
3737

3838
from torchtitan.distributed.activation_checkpoint import _get_save_ops
39-
from torchtitan.experiments.graph_trainer.common_utils import _AC_REGION_ID
39+
from torchtitan.experiments.graph_trainer.common_utils import _AC_REGION_ID, _MODULE_FQN
4040
from torchtitan.experiments.graph_trainer.custom_codegen import custom_codegen_pass
4141
from torchtitan.experiments.graph_trainer.make_fx_tracer import TracedResult
4242
from torchtitan.experiments.graph_trainer.remove_noop_passes import (
@@ -52,6 +52,7 @@
5252

5353
def construct_default_graph_passes(
5454
traced_result: "TracedResult",
55+
model: torch.nn.Module,
5556
) -> list[Callable]:
5657
"""Build the default pass list for the aot_fx_trace compile path.
5758
@@ -61,17 +62,25 @@ def construct_default_graph_passes(
6162
6263
Args:
6364
traced_result: The traced graph and metadata from ``trace_train_step``.
65+
model: The model for extracting transformer block buckets.
6466
6567
Returns:
6668
An ordered list of graph passes ready to apply.
6769
"""
70+
from torchtitan.experiments.graph_trainer.common_utils import (
71+
get_transformer_block_buckets,
72+
)
6873
from torchtitan.models.common.attention import FlexAttention
6974

7075
passes: list[Callable] = [
7176
functools.partial(tlparse_log_graph_pass, graph_name="make_fx_graph_traced"),
7277
remove_detach_pass,
7378
remove_identity_view_pass,
7479
remove_identity_slice_pass,
80+
functools.partial(
81+
joint_transformer_block_bucketing_reordering_pass,
82+
fsdp_manual_buckets=get_transformer_block_buckets(model),
83+
),
7584
# FlexAttention HOPs must be compiled (via regional_inductor) to
7685
# produce bitwise identical results to the eager Trainer path.
7786
# When left uncompiled, flex_attention still runs correctly but
@@ -141,6 +150,31 @@ def autobucketing_reordering_pass(
141150
return gm
142151

143152

153+
def _make_module_fqn_stack_fn(
154+
*, bwd: bool
155+
) -> Callable[[torch.fx.Node], list[tuple[str, type]]]:
156+
"""Create a module_stack_fn that filters nodes by forward/backward direction.
157+
158+
Args:
159+
bwd: If True, only return FQNs for backward nodes. If False, only
160+
return FQNs for forward nodes.
161+
162+
Returns:
163+
A callable compatible with make_graph_view's module_stack_fn parameter.
164+
"""
165+
166+
def _stack_fn(node: torch.fx.Node) -> list[tuple[str, type]]:
167+
is_bwd = node.meta.get("autograd_backward", False)
168+
if is_bwd != bwd:
169+
return []
170+
fqn = node.meta.get("custom", {}).get(_MODULE_FQN)
171+
if not fqn:
172+
return []
173+
return [(fqn, torch.nn.Module)]
174+
175+
return _stack_fn
176+
177+
144178
def transformer_block_bucketing_reordering_pass(
145179
gm: torch.fx.GraphModule,
146180
example_inputs: tuple | None = None,
@@ -157,6 +191,48 @@ def transformer_block_bucketing_reordering_pass(
157191
return gm
158192

159193

194+
def joint_transformer_block_bucketing_reordering_pass(
195+
gm: torch.fx.GraphModule,
196+
example_inputs: tuple | None = None,
197+
*,
198+
fsdp_manual_buckets,
199+
) -> torch.fx.GraphModule:
200+
"""Apply manual bucketing and reordering on a joint fwd+bwd graph.
201+
202+
The joint graph is processed in two passes to ensure each direction's
203+
collectives are bucketed independently and reordering uses the correct
204+
execution order (forward order for fwd, reversed order for bwd).
205+
206+
Used by the aot_fx_trace mode where forward and backward are in a
207+
single graph.
208+
"""
209+
# Forward pass: bucket and reorder in forward execution order
210+
manual_overlap_bucketing(
211+
gm,
212+
module_bucket_plans=fsdp_manual_buckets,
213+
insert_overlap_deps=False,
214+
module_stack_fn=_make_module_fqn_stack_fn(bwd=False),
215+
)
216+
217+
# Clear manual_bucket_node_type metadata left by the forward pass.
218+
# The reordering is already applied via topological sort, but stale
219+
# metadata would confuse the second call's _manual_reorder_graph
220+
# which iterates all graph nodes regardless of direction.
221+
for node in gm.graph.nodes:
222+
node.meta.pop("manual_bucket_node_type", None)
223+
224+
# Backward pass: bucket and reorder backward collectives separately
225+
manual_overlap_bucketing(
226+
gm,
227+
module_bucket_plans=fsdp_manual_buckets,
228+
insert_overlap_deps=False,
229+
module_stack_fn=_make_module_fqn_stack_fn(bwd=True),
230+
)
231+
232+
gm.recompile()
233+
return gm
234+
235+
160236
def _ops_filter_with_distributed(name: str) -> bool:
161237
"""Ops filter that allows distributed collective ops for serialization.
162238

torchtitan/experiments/graph_trainer/qwen3/parallelize.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from torchtitan.distributed.tensor_parallel import maybe_enable_async_tp
2020
from torchtitan.experiments.graph_trainer.common_utils import (
2121
annotate_ac_regions,
22+
annotate_module_fqns,
2223
apply_graph_ac,
2324
)
2425
from torchtitan.experiments.graph_trainer.compile import apply_compile
@@ -66,6 +67,7 @@ def annotate_qwen3(model: GraphTrainerQwen3Model) -> None:
6667
FlexAttention.forward
6768
)
6869

70+
annotate_module_fqns(model)
6971
annotate_ac_regions(model)
7072

7173

torchtitan/experiments/graph_trainer/trainer.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,10 @@ def _make_fx_forward_backward_step(
128128
)
129129

130130
if self.config.compile.enable_passes:
131-
passes = construct_default_graph_passes(self._traced_step)
131+
passes = construct_default_graph_passes(
132+
self._traced_step,
133+
model=model,
134+
)
132135
self._traced_step.gm = apply_graph_passes(
133136
self._traced_step.gm,
134137
self._traced_step.example_inputs,

0 commit comments

Comments
 (0)