3636 update_joint_with_descriptors ,
3737)
3838from .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)
4545from .module_construction import make_parallel_module
4646from .optimize_sharding import ShardingOptimizer
@@ -92,7 +92,7 @@ def _suppress_wait_tensor_side_effect():
9292class JointGraphResult :
9393 gm : torch .fx .GraphModule
9494 joint_with_descriptors : Any
95- traced_inputs : list [ Any ]
95+ traced_inputs : "ForwardInputs"
9696
9797
9898def _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 ,
0 commit comments