Skip to content

Commit 9f90da6

Browse files
committed
Change to use annotate_module_components
1 parent e89da8f commit 9f90da6

18 files changed

Lines changed: 515 additions & 90 deletions

File tree

.github/workflows/integration_test_8gpu_graph_trainer.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ jobs:
7171
# Run the graph passes unit tests
7272
torchrun --nproc-per-node=8 -m pytest torchtitan/experiments/graph_trainer/tests/test_passes.py::TestReassignToPgPass -v
7373
pytest torchtitan/experiments/graph_trainer/tests/test_passes.py::TestApplySACPass -v
74+
pytest torchtitan/experiments/graph_trainer/tests/test_passes.py::TestAnnotateModuleFqns -v
75+
76+
# Run kernel annotation profiler tests (skips if cuda-compat < 13.1)
77+
pytest torchtitan/experiments/graph_trainer/tests/profiler_tests.py -v
7478
7579
# Run the make_fx tracer unit tests
7680
pytest torchtitan/experiments/graph_trainer/tests/test_trace_module.py -v -k "TestTraceModule or TestTraceDTensor or TestMetadataPropagation"

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,3 @@ CLAUDE.local.md
4949
/debug_*.py
5050
CLAUDE_CONTEXT/
5151
/.claude/settings.local.json
52-
agent_space/

torchtitan/experiments/graph_trainer/.claude/CLAUDE.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,18 @@ This verifies that the aot_fx_trace path produces bitwise identical losses
109109
and gradients across runs, and matches eager numerics exactly. Any change
110110
that breaks this test must be investigated and fixed before proceeding with
111111
other tests.
112+
113+
### CUDA Graph Kernel Annotations
114+
115+
The `insert_kernel_annotations_pass` labels CUDA graph kernels with their
116+
originating `nn.Module` path in profiler traces. It runs automatically in the
117+
`aot_fx_trace` path (bundled with the cudagraph pass). The profiler
118+
post-processes the trace automatically via `_maybe_annotate_trace` in
119+
`torchtitan/tools/profiling.py` — no manual post-processing is needed.
120+
121+
Requirements: `cuda-python` package and CUDA toolkit/driver >= 13.1
122+
(or `cuda-compat >= 13.1` on `LD_LIBRARY_PATH`). The pass is a no-op when
123+
these are unavailable.
124+
125+
To view annotated traces, open the exported JSON in https://ui.perfetto.dev.
126+
Kernel events will have `module_fqn` fields like `layers.0.attention.wq`.

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/compile.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,6 @@ def apply_compile(
240240
return model
241241

242242
torch._inductor.config.reorder_for_peak_memory = False
243-
torch._inductor.config.triton.cudagraph_kernel_annotations = True
244243
torch._dynamo.config.capture_scalar_outputs = True
245244

246245
fsdp_reshard_after_forward = get_fsdp_reshard_after_forward_policy(

torchtitan/experiments/graph_trainer/cudagraph.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ def __init__(self) -> None:
3131
self._initialized = False
3232
self._cudagraph_wrappers: list["CUDAGraphWrapper"] = []
3333
self._teardown_called = False
34+
# toolsId (graph_id << 32 | node_id) -> list of annotation dicts
35+
# (e.g. [{"module_fqn": "layers.0.attention.wq"}]).
3436
self.all_annotations: dict[int, list] = {}
37+
self.enable_annotations: bool = False
3538

3639
def maybe_initialize(self) -> None:
3740
if self._initialized:
@@ -106,6 +109,11 @@ def get_cudagraph_annotations() -> dict[int, list]:
106109
return _cg_manager.all_annotations
107110

108111

112+
def enable_cudagraph_annotations() -> None:
113+
"""Enable kernel annotation capture on subsequent CUDA graph recordings."""
114+
_cg_manager.enable_annotations = True
115+
116+
109117
class CUDAGraphWrapper:
110118
"""Wraps a callable with cudagraph. It warms up the callable, records cudagraph,
111119
and replays cudagraph during runtime. It also handles static input tensors, which
@@ -212,15 +220,15 @@ def __call__(self, *args):
212220
self._cudagraph,
213221
pool=_cg_manager.graph_pool,
214222
stream=_cg_manager.stream,
215-
enable_annotations=True,
223+
enable_annotations=_cg_manager.enable_annotations,
216224
):
217225
# `output` is managed by pytorch's cudagraph pool
218226
self._output = self._runnable(*args)
219227

220-
# Save kernel annotations for trace post-processing.
221-
from torch.cuda._graph_annotations import get_kernel_annotations
228+
if _cg_manager.enable_annotations:
229+
from torch.cuda._graph_annotations import get_kernel_annotations
222230

223-
_cg_manager.all_annotations.update(get_kernel_annotations())
231+
_cg_manager.all_annotations.update(get_kernel_annotations())
224232

225233
if self._should_check_address:
226234
self._check_static_inputs_address()

torchtitan/experiments/graph_trainer/deepseek_v3/parallelize.py

Lines changed: 5 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
@@ -42,6 +43,9 @@ def annotate_deepseekv3(model: GraphTrainerDeepSeekV3Model) -> None:
4243
- AC region annotation: Tags each transformer block's forward with a unique
4344
ac_region_id so that apply_sac_pass can assign per-block ac_graph_id
4445
boundaries for the min-cut partitioner.
46+
- Module FQN annotation: Tags each submodule's forward with its
47+
fully-qualified name so that insert_kernel_annotations_pass can label
48+
CUDA graph kernels with their originating module.
4549
4650
"""
4751
from torchtitan.distributed.expert_parallel import ExpertParallel
@@ -56,6 +60,7 @@ def annotate_deepseekv3(model: GraphTrainerDeepSeekV3Model) -> None:
5660
MoE.forward = annotate_fn({"EP": "compute"})(MoE.forward)
5761

5862
annotate_ac_regions(model)
63+
annotate_module_fqns(model)
5964

6065

6166
# Adapted from llama4/infra/parallelize.py

torchtitan/experiments/graph_trainer/graph_utils.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -519,11 +519,6 @@ def get_compiler_passes_from_config(
519519
)
520520
)
521521
else:
522-
# Auto-insert annotation pass before cudagraph capture.
523-
if pass_name == "cudagraph":
524-
compiler_passes.append(
525-
AVAILABLE_COMPILER_PASSES["insert_kernel_annotations"]
526-
)
527522
compiler_passes.append(AVAILABLE_COMPILER_PASSES[pass_name])
528523

529524
if pass_names:

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
@@ -35,8 +36,12 @@ def annotate_llama(model: GraphTrainerLlama3Model) -> None:
3536
- AC region annotation: Tags each transformer block's forward with a unique
3637
ac_region_id so that apply_sac_pass can assign per-block ac_graph_id
3738
boundaries for the min-cut partitioner.
39+
- Module FQN annotation: Tags each submodule's forward with its
40+
fully-qualified name so that insert_kernel_annotations_pass can label
41+
CUDA graph kernels with their originating module.
3842
"""
3943
annotate_ac_regions(model)
44+
annotate_module_fqns(model)
4045

4146

4247
def parallelize_llama(

torchtitan/experiments/graph_trainer/passes.py

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,13 @@ def construct_default_graph_passes(
8080
flex_compile_config=FlexAttention.inductor_configs,
8181
),
8282
regional_inductor_pass,
83+
# insert_kernel_annotations must run before cudagraph: it inserts
84+
# mark_kernels enter/exit calls that execute during CUDA graph
85+
# capture. It is a no-op when cuda-python or cuda-compat >= 13.1
86+
# is not available.
87+
insert_kernel_annotations_pass,
8388
]
8489

85-
# Insert kernel annotation markers before cudagraph capture.
86-
passes.append(insert_kernel_annotations_pass)
87-
8890
# cudagraph should be the last pass.
8991
from torchtitan.experiments.graph_trainer.cudagraph import is_cudagraph_compatible
9092

@@ -263,67 +265,77 @@ def _get_fake_mode_from_gm(gm: torch.fx.GraphModule):
263265
return gm
264266

265267

266-
def _mark_kernels_enter(annotation: dict) -> object:
267-
"""Helper called from FX graph to enter a mark_kernels scope."""
268-
from torch.cuda._graph_annotations import mark_kernels
269-
270-
ctx = mark_kernels(annotation)
271-
ctx.__enter__()
272-
return ctx
273-
274-
275-
def _mark_kernels_exit(ctx: object) -> None:
276-
"""Helper called from FX graph to exit a mark_kernels scope."""
277-
ctx.__exit__(None, None, None) # type: ignore[union-attr]
278-
279-
280268
def insert_kernel_annotations_pass(
281269
gm: torch.fx.GraphModule,
282270
example_inputs: tuple | None = None,
283271
) -> torch.fx.GraphModule:
284-
"""Insert mark_kernels() calls at component boundaries in the FX graph.
272+
"""Insert mark_kernels() calls at module boundaries in the FX graph.
285273
286-
Reads ``node.meta["custom"]["component"]`` (set via
287-
``torch.fx.traceback.annotate``) and inserts enter/exit calls so that
274+
Reads ``node.meta["custom"]["module_fqn"]`` (set via
275+
``annotate_module_fqns``) and inserts enter/exit calls so that
288276
CUDA graph capture records the annotations.
277+
278+
Requires ``cuda-python`` package and CUDA toolkit/driver >= 13.1
279+
(or cuda-compat >= 13.1). Returns the graph unchanged when unavailable.
280+
281+
Also enables annotation capture on :class:`CUDAGraphWrapper` so that
282+
``enable_annotations=True`` is passed to ``torch.cuda.graph()``.
289283
"""
284+
from torch.cuda._graph_annotations import _is_tools_id_unavailable
285+
286+
from torchtitan.experiments.graph_trainer.common_utils import _MODULE_FQN
287+
from torchtitan.experiments.graph_trainer.cudagraph import (
288+
enable_cudagraph_annotations,
289+
)
290+
291+
def _enter(annotation: dict) -> object:
292+
from torch.cuda._graph_annotations import mark_kernels
293+
294+
ctx = mark_kernels(annotation)
295+
ctx.__enter__()
296+
return ctx
297+
298+
def _exit(ctx: object) -> None:
299+
ctx.__exit__(None, None, None) # type: ignore[union-attr]
300+
301+
if _is_tools_id_unavailable():
302+
return gm
303+
304+
enable_cudagraph_annotations()
305+
290306
graph = gm.graph
291-
current_component: str | None = None
307+
current_fqn: str | None = None
292308
current_ctx_node = None
293309

294310
for node in list(graph.nodes):
295-
component = (node.meta.get("custom") or {}).get("component")
311+
fqn = (node.meta.get("custom") or {}).get(_MODULE_FQN)
296312

297-
if component != current_component:
313+
if fqn != current_fqn:
298314
# Close previous scope
299315
if current_ctx_node is not None:
300316
with graph.inserting_before(node):
301-
exit_node = graph.call_function(
302-
_mark_kernels_exit, (current_ctx_node,)
303-
)
317+
exit_node = graph.call_function(_exit, (current_ctx_node,))
304318
exit_node.meta["custom"] = {}
305319
current_ctx_node = None
306320

307321
# Open new scope
308-
if component is not None:
322+
if fqn is not None:
309323
with graph.inserting_before(node):
310324
enter_node = graph.call_function(
311-
_mark_kernels_enter,
312-
({"component": component},),
325+
_enter,
326+
({_MODULE_FQN: fqn},),
313327
)
314328
enter_node.meta["custom"] = {}
315329
current_ctx_node = enter_node
316330

317-
current_component = component
331+
current_fqn = fqn
318332

319333
# Close any trailing scope (before output/return)
320334
if current_ctx_node is not None:
321335
output_nodes = [n for n in graph.nodes if n.op == "output"]
322336
if output_nodes:
323337
with graph.inserting_before(output_nodes[0]):
324-
exit_node = graph.call_function(
325-
_mark_kernels_exit, (current_ctx_node,)
326-
)
338+
exit_node = graph.call_function(_exit, (current_ctx_node,))
327339
exit_node.meta["custom"] = {}
328340

329341
graph.lint()
@@ -751,7 +763,6 @@ def tlparse_log_graph_pass(
751763
"auto_bucketing": autobucketing_reordering_pass,
752764
"transformer_block_bucketing": transformer_block_bucketing_reordering_pass,
753765
"regional_inductor": regional_inductor_pass,
754-
"insert_kernel_annotations": insert_kernel_annotations_pass,
755766
"cudagraph": cudagraph_pass,
756767
"full_inductor_compilation": full_inductor_compilation_pass,
757768
}

0 commit comments

Comments
 (0)