@@ -82,13 +82,13 @@ def construct_default_graph_passes(
8282 regional_inductor_pass ,
8383 ]
8484
85- # Insert kernel annotation markers before cudagraph capture.
86- passes .append (insert_kernel_annotations_pass )
87-
88- # cudagraph should be the last pass.
85+ # cudagraph should be the last pass. insert_kernel_annotations runs
86+ # right before it to inject mark_kernels enter/exit calls that execute
87+ # during CUDA graph capture. Both are no-ops when requirements aren't met.
8988 from torchtitan .experiments .graph_trainer .cudagraph import is_cudagraph_compatible
9089
9190 if is_cudagraph_compatible (traced_result .gm ):
91+ passes .append (insert_kernel_annotations_pass )
9292 static_input_indices = list (range (traced_result .num_static_inputs ))
9393 passes .append (
9494 functools .partial (
@@ -263,67 +263,89 @@ def _get_fake_mode_from_gm(gm: torch.fx.GraphModule):
263263 return gm
264264
265265
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-
280266def insert_kernel_annotations_pass (
281267 gm : torch .fx .GraphModule ,
282268 example_inputs : tuple | None = None ,
283269) -> torch .fx .GraphModule :
284- """Insert mark_kernels() calls at component boundaries in the FX graph.
270+ """Insert mark_kernels() calls at module boundaries in the FX graph.
285271
286- Reads ``node.meta["custom"]["component "]`` (set via
287- ``torch.fx.traceback.annotate ``) and inserts enter/exit calls so that
272+ Reads ``node.meta["custom"]["module_fqn "]`` (set via
273+ ``annotate_module_fqns ``) and inserts enter/exit calls so that
288274 CUDA graph capture records the annotations.
275+
276+ Requires ``cuda-python`` package and CUDA toolkit/driver >= 13.1
277+ (or cuda-compat >= 13.1). Returns the graph unchanged when unavailable.
278+
279+ Also enables annotation capture on :class:`CUDAGraphWrapper` so that
280+ ``enable_annotations=True`` is passed to ``torch.cuda.graph()``.
281+
282+ Alternative approaches:
283+
284+ 1. **fx.Interpreter**: During cudagraph capture, run the graph via an
285+ ``fx.Interpreter`` subclass that reads ``module_fqn`` metadata and
286+ calls ``mark_kernels`` enter/exit around each node — avoids mutating
287+ the graph.
288+ 2. **Custom CodeGen**: Use a custom ``torch.fx.graph.CodeGen`` to emit
289+ enter/exit lines (or ``with`` blocks) directly in the generated
290+ Python code.
291+
292+ The current graph-pass approach is the least invasive.
289293 """
294+ from torch .cuda ._graph_annotations import _is_tools_id_unavailable
295+
296+ from torchtitan .experiments .graph_trainer .common_utils import _MODULE_FQN
297+ from torchtitan .experiments .graph_trainer .cudagraph import (
298+ enable_cudagraph_annotations ,
299+ )
300+
301+ def _enter (annotation : dict ) -> object :
302+ from torch .cuda ._graph_annotations import mark_kernels
303+
304+ ctx = mark_kernels (annotation )
305+ ctx .__enter__ ()
306+ return ctx
307+
308+ def _exit (ctx : object ) -> None :
309+ ctx .__exit__ (None , None , None ) # type: ignore[union-attr]
310+
311+ if _is_tools_id_unavailable ():
312+ return gm
313+
314+ enable_cudagraph_annotations ()
315+
290316 graph = gm .graph
291- current_component : str | None = None
317+ current_fqn : str | None = None
292318 current_ctx_node = None
293319
294320 for node in list (graph .nodes ):
295- component = (node .meta .get ("custom" ) or {}).get ("component" )
321+ fqn = (node .meta .get ("custom" ) or {}).get (_MODULE_FQN )
296322
297- if component != current_component :
323+ if fqn != current_fqn :
298324 # Close previous scope
299325 if current_ctx_node is not None :
300326 with graph .inserting_before (node ):
301- exit_node = graph .call_function (
302- _mark_kernels_exit , (current_ctx_node ,)
303- )
327+ exit_node = graph .call_function (_exit , (current_ctx_node ,))
304328 exit_node .meta ["custom" ] = {}
305329 current_ctx_node = None
306330
307331 # Open new scope
308- if component is not None :
332+ if fqn is not None :
309333 with graph .inserting_before (node ):
310334 enter_node = graph .call_function (
311- _mark_kernels_enter ,
312- ({"component" : component },),
335+ _enter ,
336+ ({_MODULE_FQN : fqn },),
313337 )
314338 enter_node .meta ["custom" ] = {}
315339 current_ctx_node = enter_node
316340
317- current_component = component
341+ current_fqn = fqn
318342
319343 # Close any trailing scope (before output/return)
320344 if current_ctx_node is not None :
321345 output_nodes = [n for n in graph .nodes if n .op == "output" ]
322346 if output_nodes :
323347 with graph .inserting_before (output_nodes [0 ]):
324- exit_node = graph .call_function (
325- _mark_kernels_exit , (current_ctx_node ,)
326- )
348+ exit_node = graph .call_function (_exit , (current_ctx_node ,))
327349 exit_node .meta ["custom" ] = {}
328350
329351 graph .lint ()
@@ -751,7 +773,6 @@ def tlparse_log_graph_pass(
751773 "auto_bucketing" : autobucketing_reordering_pass ,
752774 "transformer_block_bucketing" : transformer_block_bucketing_reordering_pass ,
753775 "regional_inductor" : regional_inductor_pass ,
754- "insert_kernel_annotations" : insert_kernel_annotations_pass ,
755776 "cudagraph" : cudagraph_pass ,
756777 "full_inductor_compilation" : full_inductor_compilation_pass ,
757778}
0 commit comments