Skip to content

Commit 5f4069f

Browse files
committed
Merge branch 'main' of github.com:meta-pytorch/autoparallel into fmassa/better_bucketing
2 parents bebfbfb + 9fa2781 commit 5f4069f

17 files changed

Lines changed: 752 additions & 1129 deletions

.github/workflows/test_cuda.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,5 @@ jobs:
7878
pip uninstall -y torch
7979
pip install --no-input --quiet --pre torch --index-url https://download.pytorch.org/whl/nightly/cu126
8080
pip install --quiet .
81-
python examples/example_dcp.py
81+
python -m pytest tests/test_dcp_roundtrip.py -v
8282
torchrun --standalone --nproc-per-node 4 examples/example_ds3_local_map.py

autoparallel/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
from autoparallel.api import AutoParallel, auto_parallel
77
from autoparallel.collectives import with_sharding_constraint
88
from autoparallel.compile import autoparallel_backend
9+
from autoparallel.input_validation import ForwardInputs
910

1011
__all__ = [
1112
"auto_parallel",
1213
"AutoParallel",
1314
"autoparallel_backend",
15+
"ForwardInputs",
1416
"with_sharding_constraint",
1517
]

autoparallel/api.py

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@
3636
update_joint_with_descriptors,
3737
)
3838
from .input_validation import (
39+
ForwardInputs,
40+
_build_input_fn_from_sample,
3941
_check_forward_args,
40-
_compute_expected_inputs,
41-
_extract_input_info,
4242
_flatten_out_shardings,
43-
_make_input_fn,
43+
flatten_and_convert_inputs_to_local_shapes,
4444
)
4545
from .module_construction import make_parallel_module
4646
from .optimize_sharding import ShardingOptimizer
@@ -92,7 +92,7 @@ def _suppress_wait_tensor_side_effect():
9292
class JointGraphResult:
9393
gm: torch.fx.GraphModule
9494
joint_with_descriptors: Any
95-
traced_inputs: list[Any]
95+
traced_inputs: "ForwardInputs"
9696

9797

9898
def _make_inputs_dynamic(
@@ -135,27 +135,38 @@ def build_joint_graph(
135135
with fake_mode:
136136
raw_inputs = input_fn()
137137

138-
formatted_inputs = raw_inputs if isinstance(raw_inputs, tuple) else (raw_inputs,)
138+
if isinstance(raw_inputs, ForwardInputs):
139+
traced_inputs = ForwardInputs(
140+
args=tuple(raw_inputs.args), kwargs=dict(raw_inputs.kwargs)
141+
)
142+
elif isinstance(raw_inputs, tuple):
143+
traced_inputs = ForwardInputs(args=raw_inputs)
144+
else:
145+
traced_inputs = ForwardInputs(args=(raw_inputs,))
139146

140147
if fake_mode.shape_env is not None:
141-
formatted_inputs = _make_inputs_dynamic(formatted_inputs, fake_mode)
142-
143-
traced_inputs = list(formatted_inputs)
148+
args, kwargs = _make_inputs_dynamic(
149+
(traced_inputs.args, traced_inputs.kwargs), fake_mode
150+
)
151+
traced_inputs = ForwardInputs(args=args, kwargs=kwargs)
144152

145153
with (
146154
set_dtype_cast(True),
147155
enable_local_map_wrapping(),
148156
torch._dynamo.utils._disable_saved_tensors_hooks_during_tracing(),
149157
):
150-
torch_ir_with_fqn = _dynamo_graph_capture_for_export(model)(*formatted_inputs)
158+
torch_ir_with_fqn = _dynamo_graph_capture_for_export(model)(
159+
*traced_inputs.args, **traced_inputs.kwargs
160+
)
151161
_restore_state_dict(model, torch_ir_with_fqn)
152162
_add_unused_params_and_buffers(model, torch_ir_with_fqn)
153163
# TODO Can't use fake mode here because it clashes with the user level
154164
# fake mode. Ideally dynamo should reuse the user level fake mode.
155165
joint_with_descriptors = aot_export_joint_with_descriptors(
156166
stack,
157167
torch_ir_with_fqn,
158-
formatted_inputs,
168+
traced_inputs.args,
169+
traced_inputs.kwargs or None,
159170
decompositions=decomp_table,
160171
)
161172
gm = joint_with_descriptors.graph_module
@@ -543,16 +554,34 @@ def _inference_fn(args):
543554
strategy = sharding_placement[node]
544555
solved_input_placements.append(tuple(strategy.output_specs.placements))
545556

546-
expected_inputs, dynamic_dims = _compute_expected_inputs(
547-
self._traced_inputs, solved_input_placements, self.mesh
557+
expected_inputs, dynamic_dims = flatten_and_convert_inputs_to_local_shapes(
558+
self._traced_inputs,
559+
solved_input_placements,
560+
self.mesh,
548561
)
549562

563+
# Spec of (args, kwargs) at trace time. Used at runtime to reorder
564+
# caller-supplied kwargs back into the canonical leaf order that the
565+
# compiled graph expects (matches Dynamo+AOT placeholder order).
566+
from torch.export._tree_utils import reorder_kwargs
567+
568+
trace_in_spec = torch.utils._pytree.tree_flatten(
569+
(tuple(self._traced_inputs.args), self._traced_inputs.kwargs)
570+
)[1]
571+
has_traced_kwargs = bool(self._traced_inputs.kwargs)
572+
550573
def forward(self, *args, **kwargs):
551-
# Flatten pytree args (e.g. dicts, nested structures) to tensor
552-
# leaves, matching how Dynamo flattened the inputs during tracing.
553-
flat_args, _ = torch.utils._pytree.tree_flatten(args)
554-
if len(flat_args) != len(expected_inputs):
574+
if has_traced_kwargs or kwargs:
575+
if kwargs:
576+
kwargs = reorder_kwargs(kwargs, trace_in_spec)
555577
flat_args, _ = torch.utils._pytree.tree_flatten((args, kwargs))
578+
else:
579+
# Positional-only fast path. Flatten args directly so that a
580+
# single pytree positional (e.g. a dict batch) keeps working
581+
# exactly as before.
582+
flat_args, _ = torch.utils._pytree.tree_flatten(args)
583+
if len(flat_args) != len(expected_inputs):
584+
flat_args, _ = torch.utils._pytree.tree_flatten((args, kwargs))
556585
_check_forward_args(flat_args, expected_inputs, dynamic_dims)
557586
# NB: don't close over the parameters/buffers, as the user may
558587
# reassign the module!
@@ -561,13 +590,22 @@ def forward(self, *args, **kwargs):
561590
params = [
562591
self.get_parameter(fqn).to_local() for fqn in graph_param_fqns
563592
] + [self.get_buffer(fqn).to_local() for fqn in graph_buffer_fqns]
564-
boxed_args = [*params, *flat_args]
565-
del params
566593
if torch.is_grad_enabled():
567-
# NB: don't do self.parallel_model_fn work around Dynamo bug
568-
out = parallel_model_fn(boxed_args)
594+
# parallel_model_fn is AOT's unflattened wrapper; it calls
595+
# `reorder_kwargs(kwargs, in_spec)` and re-flattens
596+
# `(args, kwargs)`. We must hand it kwargs by name when the
597+
# traced graph had them, otherwise the keyword-mismatch check
598+
# rejects the call. For the no-kwargs case keep the historical
599+
# single-boxed-list shape so behavior is bit-identical.
600+
if has_traced_kwargs:
601+
out = parallel_model_fn(*params, *args, **kwargs)
602+
else:
603+
boxed_args = [*params, *flat_args]
604+
out = parallel_model_fn(boxed_args)
569605
else:
606+
boxed_args = [*params, *flat_args]
570607
out = _inference_fn(boxed_args)
608+
del params
571609
return out
572610

573611
self.parallel_model = make_parallel_module(
@@ -651,25 +689,26 @@ def auto_parallel(
651689
... "attention_mask": DTensor.from_local(mask, mesh, [Shard(0)]),
652690
... }
653691
>>> parallel_model = auto_parallel(model, mesh, sample_inputs, out_shardings=...)
692+
693+
Example with keyword-only forward args:
694+
>>> from autoparallel import ForwardInputs
695+
>>> sample_inputs = ForwardInputs(
696+
... args=(tokens,),
697+
... kwargs={"attention_masks": mask, "positions": pos},
698+
... )
699+
>>> parallel_model = auto_parallel(model, mesh, sample_inputs, out_shardings=...)
654700
"""
655701
# Handle callable sample_inputs
656702
if callable(sample_inputs):
657703
raw_inputs = sample_inputs()
658704
else:
659705
raw_inputs = sample_inputs
660706

661-
# Extract metadata and placements (does not materialize tensors)
662-
shapes, dtypes, input_placements, treespec, devices = _extract_input_info(
663-
raw_inputs, mesh
664-
)
707+
input_fn, input_placements = _build_input_fn_from_sample(raw_inputs, mesh)
665708

666709
# Flatten out_shardings to list
667710
output_placements = _flatten_out_shardings(out_shardings)
668711

669-
# Create input_fn that will be called inside FakeTensorMode
670-
# It creates fresh tensors (which become fake tensors inside FakeTensorMode)
671-
input_fn = _make_input_fn(shapes, dtypes, treespec, devices=devices)
672-
673712
# Use AutoParallel context manager
674713
with AutoParallel(
675714
model,

autoparallel/compile.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,15 @@ def autoparallel_backend(
5151
overlap_scheduling: Enable comm/compute overlap scheduling.
5252
"""
5353
functorch_patches = {}
54-
inductor_patches = _INDUCTOR_OVERLAP_PATCHES if overlap_scheduling else {}
54+
inductor_patches = _INDUCTOR_OVERLAP_PATCHES if overlap_scheduling else None
5555

5656
if enable_ac:
5757
functorch_patches["joint_custom_pass"] = _make_ac_joint_pass(
5858
ac_stage_size_in_GiB
5959
)
6060

6161
def backend(gm, example_inputs):
62-
with (
63-
torch._functorch.config.patch(functorch_patches),
64-
torch._inductor.config.patch(inductor_patches),
65-
):
66-
return compile_fx(gm, example_inputs)
62+
with torch._functorch.config.patch(functorch_patches):
63+
return compile_fx(gm, example_inputs, config_patches=inductor_patches)
6764

6865
return backend

0 commit comments

Comments
 (0)