@@ -39,6 +39,15 @@ def _dump_gm(dump_folder: str | None, gm: torch.fx.GraphModule, name: str) -> No
3939def export_joint (
4040 model , args , kwargs = None , dump_folder : str | None = None
4141) -> tuple [JointWithDescriptors , TracingContext ]:
42+ """
43+ Export joint forward-backward graph with AOT Autograd.
44+
45+ Args:
46+ model: The model to export
47+ args: Tuple of input arguments
48+ kwargs: Dict of keyword arguments for the model
49+ dump_folder: Optional folder to dump the graph to
50+ """
4251 if kwargs is None :
4352 kwargs = {}
4453 assert isinstance (args , tuple )
@@ -68,6 +77,14 @@ def export_joint(
6877
6978
7079def aot_export_joint_with_descriptors_alone (model , args , kwargs = None ):
80+ """
81+ Export joint forward-backward graph with AOT Autograd.
82+
83+ Args:
84+ model: The model to export
85+ args: Tuple of input arguments
86+ kwargs: Dict of keyword arguments for the model
87+ """
7188 if kwargs is None :
7289 kwargs = {}
7390 assert isinstance (args , tuple )
@@ -79,6 +96,7 @@ def aot_export_joint_with_descriptors_alone(model, args, kwargs=None):
7996 args ,
8097 kwargs ,
8198 )
99+
82100 return joint_with_descriptors
83101
84102
@@ -90,6 +108,7 @@ def joint_graph_builder(
90108 bw_compiler : Optional [Callable ] = None ,
91109 joint_custom_passes : Optional [List [Callable ]] = None ,
92110 dump_folder : str | None = None ,
111+ job_config : Optional ["JobConfig" ] = None ,
93112):
94113 """
95114 Build a joint forward-backward graph for the model with optional custom compilers.
@@ -102,16 +121,41 @@ def joint_graph_builder(
102121 bw_compiler: Optional custom backward compiler function
103122 joint_custom_passes: list of custom passes to run on the joint graph
104123 dump_folder: Optional folder to dump the graph to
124+ job_config: Job configuration
105125 """
106126 assert isinstance (model_args , tuple )
107127 for idx , arg in enumerate (model_args ):
108128 assert isinstance (arg , DTensor ), f"Argument { idx } is of type { type (arg )} "
109129
110130 # get joint graph
111- (
112- joint_with_descriptors ,
113- tracing_context ,
114- ) = export_joint (model , model_args , model_kwargs , dump_folder = dump_folder )
131+ (joint_with_descriptors , tracing_context ,) = export_joint (
132+ model ,
133+ model_args ,
134+ model_kwargs ,
135+ dump_folder = dump_folder ,
136+ )
137+
138+ # Check if inductor_decomposition is configured and create the pass with proper context
139+ if job_config is not None :
140+ joint_pass_names = getattr (job_config .compile , "joint_passes" , [])
141+ if "inductor_decomposition" in joint_pass_names :
142+ from torchtitan .experiments .compiler_toolkit .passes import (
143+ inductor_decomposition_pass ,
144+ )
145+
146+ # Create the decomposition pass with context
147+ decomp_pass = functools .partial (
148+ inductor_decomposition_pass ,
149+ model = model ,
150+ joint_with_descriptors = joint_with_descriptors ,
151+ forward_inputs = model_args ,
152+ tracing_context = tracing_context ,
153+ )
154+
155+ # Prepend to joint_custom_passes
156+ if joint_custom_passes is None :
157+ joint_custom_passes = []
158+ joint_custom_passes = [decomp_pass ] + joint_custom_passes
115159
116160 # run custom passes on joint-graph before partitioner
117161 if joint_custom_passes is not None :
@@ -259,28 +303,36 @@ def compiler(
259303 logger .info (f"Applying pass: { pass_name } " )
260304 gm = pass_fn (gm , example_inputs )
261305
262- logger .debug (f"{ name } after compiler:" )
263- logger .debug (
264- gm .print_readable (print_output = False , include_stride = True , include_device = True )
265- )
266- _dump_gm (dump_folder , gm , f"{ name } _after_compiler" )
306+ # Only try to print/dump if gm is still a GraphModule
307+ # (compile_fx_inner returns a CompiledFxGraph which doesn't have print_readable)
308+ if hasattr (gm , "print_readable" ):
309+ logger .debug (f"{ name } after compiler:" )
310+ logger .debug (
311+ gm .print_readable (
312+ print_output = False , include_stride = True , include_device = True
313+ )
314+ )
315+ _dump_gm (dump_folder , gm , f"{ name } _after_compiler" )
316+
267317 return gm
268318
269319
270320def make_compiler_with_passes (
271- passes : List [Callable ] = None , dump_folder : str | None = None
321+ passes : List [Callable ] = None ,
322+ dump_folder : str | None = None ,
272323):
273324 """
274325 Create forward and backward compilers with specified passes.
275326
276327 Args:
277328 passes: List of compiler pass functions to apply. If None, uses DEFAULT_COMPILER_PASSES.
329+ dump_folder: Optional folder to dump graphs
278330
279331 Returns:
280332 Tuple of (fw_compiler, bw_compiler) functions
281333 """
282334
283- def fw_compiler (gm : torch .fx .GraphModule , example_inputs ) -> None :
335+ def fw_compiler (gm : torch .fx .GraphModule , example_inputs ):
284336 return compiler (
285337 "fwd_gm" ,
286338 gm ,
@@ -290,7 +342,7 @@ def fw_compiler(gm: torch.fx.GraphModule, example_inputs) -> None:
290342 is_forward = True ,
291343 )
292344
293- def bw_compiler (gm : torch .fx .GraphModule , example_inputs ) -> None :
345+ def bw_compiler (gm : torch .fx .GraphModule , example_inputs ):
294346 return compiler (
295347 "bwd_gm" ,
296348 gm ,
@@ -303,7 +355,17 @@ def bw_compiler(gm: torch.fx.GraphModule, example_inputs) -> None:
303355 return fw_compiler , bw_compiler
304356
305357
306- def validate_pass_names (pass_names : list [str ]) -> None :
358+ def validate_pass_names (pass_names : list [str ], joint_pass_names : list [str ]) -> None :
359+ """
360+ Validate compiler and joint pass names and their dependencies.
361+
362+ Args:
363+ pass_names: List of compiler pass names
364+ joint_pass_names: List of joint custom pass names
365+
366+ Raises:
367+ ValueError: If pass configuration is invalid
368+ """
307369 if "cudagraph" in pass_names :
308370 assert (
309371 pass_names [- 1 ] == "cudagraph"
@@ -317,13 +379,22 @@ def validate_pass_names(pass_names: list[str]) -> None:
317379 "Cannot apply autobucketing_reordering and transformer_block_bucketing at the same time!"
318380 )
319381
382+ # Validate that full_inductor_compilation requires inductor_decomposition
383+ if "full_inductor_compilation" in pass_names :
384+ if "inductor_decomposition" not in joint_pass_names :
385+ raise ValueError (
386+ "full_inductor_compilation pass requires inductor_decomposition to be "
387+ "specified in joint_passes. Please add --compile.joint_passes inductor_decomposition"
388+ )
389+
320390
321391def get_compiler_passes_from_config (model : torch .nn .Module , job_config : JobConfig ):
322392 """
323393 Extract and validate compiler passes from job config.
324394
325395 Args:
326- job_config: Job configuration containing compile.passes
396+ model: The model being compiled
397+ job_config: Job configuration containing compile.passes and compile.joint_passes
327398
328399 Returns:
329400 List of compiler pass functions
@@ -334,9 +405,18 @@ def get_compiler_passes_from_config(model: torch.nn.Module, job_config: JobConfi
334405 )
335406
336407 pass_names = getattr (job_config .compile , "passes" , [])
337- validate_pass_names (pass_names )
408+ joint_pass_names = getattr (job_config .compile , "joint_passes" , [])
409+
410+ validate_pass_names (pass_names , joint_pass_names )
338411 compiler_passes = []
339412
413+ # Warn if full Inductor compilation is enabled
414+ if "full_inductor_compilation" in pass_names :
415+ logger .warning (
416+ "Full Inductor compilation is enabled. Note that Inductor may change numerics "
417+ "and does not guarantee bitwise equivalent results compared to eager mode."
418+ )
419+
340420 for pass_name in pass_names :
341421 if pass_name not in AVAILABLE_COMPILER_PASSES :
342422 raise ValueError (
@@ -360,25 +440,52 @@ def get_compiler_passes_from_config(model: torch.nn.Module, job_config: JobConfi
360440
361441
362442def get_joint_custom_passes_from_config (
363- parallel_dims : ParallelDims , job_config : JobConfig
443+ parallel_dims : ParallelDims ,
444+ job_config : JobConfig ,
364445):
365446 """
366447 Extract and validate joint custom passes from job config.
367448
449+ Note: The inductor_decomposition pass is handled separately in joint_graph_builder
450+ because it requires context (model, joint_with_descriptors, etc.) that's only
451+ available at graph capture time.
452+
368453 Args:
454+ parallel_dims: Parallelism dimensions
369455 job_config: Job configuration containing parallelism.fsdp_reshard_after_forward
456+ and compile.joint_passes
370457
371458 Returns:
372459 List of joint custom pass functions
373460 """
374461 from torchtitan .experiments .compiler_toolkit .passes import (
462+ AVAILABLE_JOINT_PASSES ,
375463 fsdp_reshard_after_fwd_pass ,
376464 validate_flex_attn_annotation_pass ,
377465 )
378466
379467 joint_custom_passes = []
380468 joint_custom_passes .append (validate_flex_attn_annotation_pass )
381469
470+ # Handle joint passes from config (excluding inductor_decomposition)
471+ joint_pass_names = getattr (job_config .compile , "joint_passes" , [])
472+ for pass_name in joint_pass_names :
473+ if pass_name not in AVAILABLE_JOINT_PASSES :
474+ raise ValueError (
475+ f"Unknown joint pass: { pass_name } . "
476+ f"Available joint passes: { list (AVAILABLE_JOINT_PASSES .keys ())} "
477+ )
478+
479+ # Skip inductor_decomposition - it's handled in joint_graph_builder
480+ if pass_name == "inductor_decomposition" :
481+ continue
482+
483+ joint_custom_passes .append (AVAILABLE_JOINT_PASSES [pass_name ])
484+
485+ if joint_pass_names :
486+ logger .info (f"Using joint passes from config: { joint_pass_names } " )
487+
488+ # Handle FSDP reshard after forward
382489 match job_config .parallelism .fsdp_reshard_after_forward :
383490 case "always" :
384491 fsdp_reshard_after_forward = True
0 commit comments