Skip to content

Commit 6d7b17a

Browse files
zhxchen17pytorchmergebot
authored andcommitted
[export] Use bytecode to flatten graph inputs. (pytorch#171110)
Summary: Previously for dynamo_graph_capture_for_export, we use make_fx() to trace through the dynamo bytecode so that the GraphModule looks like the following: ``` tree_leaves = pytree_flatten(*args) graph_inputs = self.in_shuffle_graph(*tree_leaves) graph_outputs = graph(*graph_inputs) tree_outs = outputs = self.out_shuffle_graph(*graph_outputs) return pytree.tree_unflatten(tree_outs) ``` After a bunch of experiments, we think the make_fx step is not necessaery, and to further close the gap between strict export and dynamo, we propose the new impl to do the following using torch.fx codegen: ``` graph_inputs = self._dynamo_bytecode_flatten(*args) graph_outs = graph(*graph_inputs) return self._dynamo_bytecode_unflatten(*graph_outs) ``` The `_dynamo_bytecode_flatten` and `_dynamo_bytecode_unflatten` wrappers are constructed in the way that the behavior will be 100% identical to directly running bytecode. This is important for also making precompile reusing the same piece of logic that torch.export uses. Test Plan: test:test_export -- -r _strict_export_v2 Differential Revision: D89673950 Pull Request resolved: pytorch#171110 Approved by: https://github.com/tugsbayasgalan
1 parent 3bfdf85 commit 6d7b17a

5 files changed

Lines changed: 356 additions & 127 deletions

File tree

‎test/export/test_experimental.py‎

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -781,22 +781,16 @@ def make_inputs():
781781
trace_inputs = make_inputs()
782782
gm = dynamo_graph_capture_for_export(foo)(*trace_inputs)
783783
test_inputs = make_inputs()
784-
self.assertExpectedInline(
785-
gm._in_shuffle_graph.code.strip("\r\n "),
786-
"""\
787-
def forward(self, arg0_1, arg1_1, arg2_1):
788-
return (arg1_1, arg2_1)""",
789-
)
790784
self.assertExpectedInline(
791785
gm.code.strip("\r\n "),
792786
"""\
793787
def forward(self, args_0):
794-
_tree_leaf_0, _tree_leaf_1, _tree_leaf_2, = pytree.tree_leaves((self, args_0,))
795-
L_bar_x , L_bar_y , = self._in_shuffle_graph(_tree_leaf_0, _tree_leaf_1, _tree_leaf_2)
788+
_fn_args = (args_0, )
789+
L_bar_x , L_bar_y , = self._dynamo_bytecode_flatten(*_fn_args)
796790
l_bar_x = L_bar_x
797791
l_bar_y = L_bar_y
798792
add = l_bar_x + l_bar_y; l_bar_x = l_bar_y = None
799-
return pytree.tree_unflatten(self._out_shuffle_graph(_tree_leaf_0, _tree_leaf_1, _tree_leaf_2, add), self._out_spec)""",
793+
return self._dynamo_bytecode_unflatten((add,), _fn_args)""",
800794
)
801795
self.assertEqual(gm(*test_inputs), foo(*test_inputs))
802796
finally:
@@ -818,26 +812,19 @@ def forward(self, x):
818812

819813
inps = (torch.randn(10, 32),)
820814
ep = dynamo_graph_capture_for_export(MyModel())(*inps)
821-
self.assertExpectedInline(
822-
ep._in_shuffle_graph.code.strip("\r\n "),
823-
"""\
824-
def forward(self, arg0_1, arg1_1):
825-
_tensor_constant0 = self._tensor_constant0
826-
return (arg1_1, _tensor_constant0)""",
827-
)
828815
self.assertExpectedInline(
829816
ep.code.strip("\r\n "),
830817
"""\
831818
def forward(self, args_0):
832-
_tree_leaf_0, _tree_leaf_1, = pytree.tree_leaves((self, args_0,))
833-
L_x_ , L_outer_ , = self._in_shuffle_graph(_tree_leaf_0, _tree_leaf_1)
819+
_fn_args = (args_0, )
820+
L_x_ , L_outer_ , = self._dynamo_bytecode_flatten(*_fn_args)
834821
l_x_ = L_x_
835822
l_outer_ = L_outer_
836823
z = l_x_ + l_outer_; l_x_ = l_outer_ = None
837824
y = z[(slice(None, -1, None), slice(None, None, None))]; z = None
838825
stacked = torch.stack([y, y, y], dim = 0); y = None
839826
reshaped = stacked.reshape(-1, 3, 32); stacked = None
840-
return pytree.tree_unflatten(self._out_shuffle_graph(_tree_leaf_0, _tree_leaf_1, reshaped), self._out_spec)""",
827+
return self._dynamo_bytecode_unflatten((reshaped,), _fn_args)""",
841828
)
842829
self.assertEqual(ep(*inps), MyModel()(*inps))
843830

@@ -916,12 +903,12 @@ def make_inputs():
916903
gm.code.strip("\r\n "),
917904
"""\
918905
def forward(self, args_0):
919-
_tree_leaf_0, _tree_leaf_1, = pytree.tree_leaves((self, args_0,))
920-
L_args_0_ , = self._in_shuffle_graph(_tree_leaf_0, _tree_leaf_1)
906+
_fn_args = (args_0, )
907+
L_args_0_ , = self._dynamo_bytecode_flatten(*_fn_args)
921908
l_args_0_ = L_args_0_
922909
add = l_args_0_ + 1
923910
mul = l_args_0_ * 2; l_args_0_ = None
924-
return pytree.tree_unflatten(self._out_shuffle_graph(_tree_leaf_0, _tree_leaf_1, mul, add), self._out_spec)""",
911+
return self._dynamo_bytecode_unflatten((mul, add,), _fn_args)""",
925912
)
926913
self.assertEqual(gm(*test_inputs), foo(*test_inputs))
927914

‎test/export/test_export.py‎

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9778,7 +9778,6 @@ def forward(self, x):
97789778
self.assertTrue(torch.allclose(ep.module()(xs), module_out))
97799779

97809780
@requires_cuda_and_triton
9781-
@testing.expectedFailureStrictV2
97829781
def test_export_associative_scan_lifted_buffers(self):
97839782
if "cpp_runtime_nonstrict" in self.id():
97849783
self.skipTest("TODO Unexpected success in OSS but not in fbcode.")
@@ -10023,7 +10022,6 @@ def forward(self, x):
1002310022
self.assertEqual(len(ep.graph_signature.input_specs), 4)
1002410023
self.assertTrue(torch.allclose(ep.module()(*inp), transform.module()(*inp)))
1002510024

10026-
@testing.expectedFailureStrictV2
1002710025
def test_tensor_attribute_zero_args(self):
1002810026
class Foo(torch.nn.Module):
1002910027
def __init__(self, value):
@@ -10343,7 +10341,6 @@ def forward(self, a, b):
1034310341
# expected 4, but got 7
1034410342
ep_v2.module()(*test_inp)
1034510343

10346-
@testing.expectedFailureStrictV2
1034710344
def test_constant_output(self):
1034810345
class ModuleConstant(torch.nn.Module):
1034910346
def __init__(self) -> None:
@@ -16487,13 +16484,10 @@ def forward(self, x):
1648716484
submod_6 = self.submod_1
1648816485
to = torch.ops.higher_order.wrap_with_set_grad_enabled(False, submod_6, mod_embedding_weight); submod_6 = mod_embedding_weight = None
1648916486
getitem = to[0]; to = None
16490-
submod_7 = self.submod_3
16491-
wrap_with_set_grad_enabled = torch.ops.higher_order.wrap_with_set_grad_enabled(False, submod_7); submod_7 = wrap_with_set_grad_enabled = None
16492-
submod_8 = self.submod_4
16493-
view_as = torch.ops.higher_order.wrap_with_set_grad_enabled(False, submod_8, detach, getitem); submod_8 = detach = getitem = None
16494-
getitem_1 = view_as[0]; view_as = None
16487+
set_ = torch.ops.aten.set_.source_Tensor(detach, getitem); detach = getitem = None
16488+
view_as = torch.ops.aten.view_as.default(set_, set_); set_ = None
1649516489
ones = torch.ops.aten.ones.default([4], dtype = torch.int64, device = device(type='cuda', index=0), pin_memory = False)
16496-
embedding = torch.ops.aten.embedding.default(getitem_1, ones); getitem_1 = ones = None
16490+
embedding = torch.ops.aten.embedding.default(view_as, ones); view_as = ones = None
1649716491
sum_1 = torch.ops.aten.sum.default(embedding); embedding = None
1649816492
sum_2 = torch.ops.aten.sum.default(args_0); args_0 = None
1649916493
sum_3 = torch.ops.aten.sum.default(sum_1); sum_1 = None

‎torch/_dynamo/convert_frame.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,11 @@ def get_traced_fn(mod: Any) -> tuple[FunctionType, Optional[object]]:
11031103
# pyrefly: ignore [missing-attribute]
11041104
resolved_forward = resolved_forward.__func__
11051105

1106+
resolved_call = mod.__call__
1107+
if hasattr(resolved_call, "__self__"):
1108+
# pyrefly: ignore [missing-attribute]
1109+
resolved_call = resolved_call.__func__
1110+
11061111
# Mirrored from NNModuleVariable.call_function:
11071112
# https://github.com/pytorch/pytorch/blob/main/torch/_dynamo/variables/nn_module.py#L1035
11081113
if (
@@ -1114,7 +1119,8 @@ def get_traced_fn(mod: Any) -> tuple[FunctionType, Optional[object]]:
11141119
and len(mod._backward_hooks) == 0
11151120
and len(torch.nn.modules.module._global_backward_pre_hooks) == 0
11161121
and len(torch.nn.modules.module._global_backward_hooks) == 0
1117-
and resolved_forward != torch.nn.Module.forward
1122+
and resolved_forward != torch.nn.Module.forward # has forward impl
1123+
and resolved_call == torch.nn.Module.__call__ # no custom __call__ impl
11181124
):
11191125
# We cannot trace __call__ by default because it will break
11201126
# the legacy dynamo export. If we want to revisit this,

0 commit comments

Comments
 (0)