Skip to content

Commit 9f211ec

Browse files
authored
[Compiler Toolkit] Add option for full inductor. (pytorch#2150)
Being able to compile fw/bw graphs using compile_fx_inner could help with establishing perf rooflines. Full inductor compilation is achieved using `compile_fx_inner`, however, it requires the graph to have been decomposed using Inductor's default decomposition table. We apply this decomposition as a pass on the joint graph. We need to be careful to suitably unwrap the primals/tangents before running this decomposition. Manual testing: NGPU=4 \ CONFIG_FILE=./torchtitan/models/llama3/train_configs/debug_model.toml \ TRAIN_FILE=torchtitan.experiments.compiler_toolkit.train \ ./run_train.sh \ --model.name $MODEL_NAME \ --parallelism.data_parallel_shard_degree=2 \ --parallelism.tensor_parallel_degree=2 \ --job.custom_config_module=torchtitan.experiments.compiler_toolkit.job_config \ --compile.joint_passes inductor_decomposition \ --compile.passes full_inductor_compilation <!-- ps-id: 5d590700-6d1f-44fe-8f70-4d2ea39106f4 -->
1 parent 795a7a0 commit 9f211ec

7 files changed

Lines changed: 326 additions & 22 deletions

File tree

torchtitan/experiments/compiler_toolkit/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,9 @@ NGPU=8 TRAIN_FILE=torchtitan.experiments.compiler_toolkit.train CONFIG_FILE=./to
6161
```shell
6262
NCCL_GRAPH_REGISTER=0 NGPU=8 TRAIN_FILE=torchtitan.experiments.compiler_toolkit.train CONFIG_FILE=./torchtitan/models/llama3/train_configs/debug_model.toml ./run_train.sh --model.name compiler_toolkit.llama3 --parallelism.data_parallel_shard_degree=2 --parallelism.tensor_parallel_degree=4 --job.custom_config_module=torchtitan.experiments.compiler_toolkit.job_config --compile.passes transformer_block_bucketing,regional_inductor,cudagraph --model.flavor=debugmodel_flex_attn
6363
```
64+
65+
**SimpleFSDP + TP + Full Inductor compilation**
66+
67+
```shell
68+
NGPU=8 CONFIG_FILE=./torchtitan/models/llama3/train_configs/debug_model.toml TRAIN_FILE=torchtitan.experiments.compiler_toolkit.train ./run_train.sh --model.name $MODEL_NAME compiler_toolkit.llama3 --parallelism.data_parallel_shard_degree=2 --parallelism.tensor_parallel_degree=4 --job.custom_config_module=torchtitan.experiments.compiler_toolkit.job_config --compile.joint_passes inductor_decomposition --compile.passes full_inductor_compilation
69+
```

torchtitan/experiments/compiler_toolkit/deepseek_v3/parallelize.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def parallelize_deepseekv3(
8282
# Get compiler passes from config
8383
compiler_passes = get_compiler_passes_from_config(model, job_config)
8484

85-
# Create compilers with specified passes (defaults to no passes)
85+
# Create compilers with specified passes
8686
fw_compiler, bw_compiler = make_compiler_with_passes(
8787
compiler_passes, dump_folder=job_config.job.dump_folder
8888
)
@@ -94,6 +94,7 @@ def parallelize_deepseekv3(
9494
bw_compiler=bw_compiler,
9595
joint_custom_passes=joint_custom_passes,
9696
dump_folder=job_config.job.dump_folder,
97+
job_config=job_config,
9798
)
9899

99100
# TODO: CompiledModule should take sample input as well, so that we can

torchtitan/experiments/compiler_toolkit/graph_utils.py

Lines changed: 123 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ def _dump_gm(dump_folder: str | None, gm: torch.fx.GraphModule, name: str) -> No
3939
def 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

7079
def 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

270320
def 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

321391
def 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

362442
def 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

torchtitan/experiments/compiler_toolkit/job_config.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,22 @@
1010
@dataclass
1111
class Compile:
1212
"""
13-
List of compiler pass names to apply in the compiler toolkit workflow.
14-
By default, no passes are applied.
15-
Example: --compile.passes autobucketing_reordering,regional_inductor
13+
Compiler configuration for the compiler toolkit workflow.
14+
15+
- joint_passes: List of joint graph pass names to apply on the joint forward-backward
16+
graph before partitioning.
17+
18+
Example: --compile.joint_passes inductor_decomposition
19+
20+
- passes: List of compiler pass names to apply to the partitioned forward/backward graphs.
21+
22+
Example: --compile.passes full_inductor_compilation
23+
24+
Note: If "full_inductor_compilation" is specified, "inductor_decomposition" must
25+
be included in joint_passes.
1626
"""
1727

28+
joint_passes: list[str] = field(default_factory=list)
1829
passes: list[str] = field(default_factory=list)
1930

2031

torchtitan/experiments/compiler_toolkit/llama3/parallelize.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def parallelize_llama(
6969
# Get compiler passes from config
7070
compiler_passes = get_compiler_passes_from_config(model, job_config)
7171

72-
# Create compilers with specified passes (defaults to no passes)
72+
# Create compilers with specified passes
7373
fw_compiler, bw_compiler = make_compiler_with_passes(
7474
compiler_passes, dump_folder=job_config.job.dump_folder
7575
)
@@ -81,6 +81,7 @@ def parallelize_llama(
8181
bw_compiler=bw_compiler,
8282
joint_custom_passes=joint_custom_passes,
8383
dump_folder=job_config.job.dump_folder,
84+
job_config=job_config,
8485
)
8586

8687
# TODO: CompiledModule should take sample input as well, so that we can

0 commit comments

Comments
 (0)