Skip to content

Commit 3362a5e

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 3362a5e

6 files changed

Lines changed: 104 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: 19 additions & 54 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
@@ -297,6 +248,9 @@ class TracedResult:
297248
num_flat_outputs: Number of flat graph outputs before subclass rewrapping.
298249
output_subclass_layouts: Subclass unwrap/rewrap metadata for outputs.
299250
output_spec: Original output pytree spec used during reconstruction.
251+
model: The original nn.Module, cached for downstream passes that need
252+
module structure (e.g. transformer block bucketing). Only set when
253+
traced via :func:`trace_train_step`.
300254
"""
301255

302256
gm: torch.fx.GraphModule
@@ -307,6 +261,7 @@ class TracedResult:
307261
num_flat_outputs: int
308262
output_subclass_layouts: dict[int, SubclassLayout]
309263
output_spec: pytree.TreeSpec
264+
model: nn.Module | None = None
310265

311266
@property
312267
def num_static_inputs(self) -> int:
@@ -402,17 +357,25 @@ def fn_with_subclass_handling(*plain_args: Any) -> list:
402357
state_for_fn = dict(zip(state_fqns, state_wrapped, strict=True))
403358
user_list = pytree.tree_unflatten(list(user_flat), user_args_spec)
404359

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

408362
flat_outs, output_spec = pytree.tree_flatten(result)
409363
num_flat_outputs = len(flat_outs)
410364
unwrapped_outs, output_layouts = _unwrap_subclasses(flat_outs)
411365
return unwrapped_outs
412366

413367
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():
368+
# preserve_node_meta propagates fx.traceback.annotate metadata to traced nodes.
369+
# _non_strict_tracing_context + _patch_autograd_grad install autograd
370+
# hooks so backward nodes get seq_nr and autograd_backward metadata.
371+
with (
372+
fake_mode,
373+
tracing(ctx),
374+
preserve_node_meta(),
375+
_skip_nested_compile(),
376+
torch.compiler._non_strict_tracing_context(),
377+
torch.compiler._patch_autograd_grad(),
378+
):
416379
traced = make_fx(
417380
fn_with_subclass_handling,
418381
record_stack_traces=True,
@@ -489,7 +452,9 @@ def _stateless_fn(state: dict[str, torch.Tensor], *user_args: Any) -> Any:
489452
with stateless._reparametrize_module(module, state):
490453
return fn(module, *user_args)
491454

492-
return minimal_fx_tracer(_stateless_fn)(extract_module_state(module), *args)
455+
result = minimal_fx_tracer(_stateless_fn)(extract_module_state(module), *args)
456+
result.model = module
457+
return result
493458

494459
return _trace_with_module
495460

torchtitan/experiments/graph_trainer/passes.py

Lines changed: 64 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 (
@@ -65,13 +65,20 @@ def construct_default_graph_passes(
6565
Returns:
6666
An ordered list of graph passes ready to apply.
6767
"""
68+
from torchtitan.experiments.graph_trainer.common_utils import (
69+
get_transformer_block_buckets,
70+
)
6871
from torchtitan.models.common.attention import FlexAttention
6972

7073
passes: list[Callable] = [
7174
functools.partial(tlparse_log_graph_pass, graph_name="make_fx_graph_traced"),
7275
remove_detach_pass,
7376
remove_identity_view_pass,
7477
remove_identity_slice_pass,
78+
functools.partial(
79+
joint_transformer_block_bucketing_reordering_pass,
80+
fsdp_manual_buckets=get_transformer_block_buckets(traced_result.model),
81+
),
7582
# FlexAttention HOPs must be compiled (via regional_inductor) to
7683
# produce bitwise identical results to the eager Trainer path.
7784
# When left uncompiled, flex_attention still runs correctly but
@@ -157,6 +164,62 @@ def transformer_block_bucketing_reordering_pass(
157164
return gm
158165

159166

167+
def joint_transformer_block_bucketing_reordering_pass(
168+
gm: torch.fx.GraphModule,
169+
example_inputs: tuple | None = None,
170+
*,
171+
fsdp_manual_buckets,
172+
) -> torch.fx.GraphModule:
173+
"""Apply manual bucketing and reordering on a joint fwd+bwd graph.
174+
175+
The joint graph is processed in two passes to ensure each direction's
176+
collectives are bucketed independently and reordering uses the correct
177+
execution order (forward order for fwd, reversed order for bwd).
178+
179+
Used by the aot_fx_trace mode where forward and backward are in a
180+
single graph.
181+
"""
182+
183+
def _make_module_fqn_stack_fn(
184+
*, bwd: bool
185+
) -> Callable[[torch.fx.Node], list[tuple[str, type]]]:
186+
"""Create a module_stack_fn that filters nodes by forward/backward direction."""
187+
188+
def _stack_fn(node: torch.fx.Node) -> list[tuple[str, type]]:
189+
is_bwd = node.meta.get("autograd_backward", False)
190+
if is_bwd != bwd:
191+
return []
192+
fqn = node.meta.get("custom", {}).get(_MODULE_FQN)
193+
if not fqn:
194+
return []
195+
return [(fqn, torch.nn.Module)]
196+
197+
return _stack_fn
198+
199+
# Forward pass: bucket and reorder in forward execution order
200+
manual_overlap_bucketing(
201+
gm,
202+
module_bucket_plans=fsdp_manual_buckets,
203+
insert_overlap_deps=False,
204+
module_stack_fn=_make_module_fqn_stack_fn(bwd=False),
205+
)
206+
207+
# keep it until https://github.com/pytorch/pytorch/pull/180579 lands
208+
for node in gm.graph.nodes:
209+
node.meta.pop("manual_bucket_node_type", None)
210+
211+
# Backward pass: bucket and reorder backward collectives separately
212+
manual_overlap_bucketing(
213+
gm,
214+
module_bucket_plans=fsdp_manual_buckets,
215+
insert_overlap_deps=False,
216+
module_stack_fn=_make_module_fqn_stack_fn(bwd=True),
217+
)
218+
219+
gm.recompile()
220+
return gm
221+
222+
160223
def _ops_filter_with_distributed(name: str) -> bool:
161224
"""Ops filter that allows distributed collective ops for serialization.
162225

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

0 commit comments

Comments
 (0)