|
7 | 7 | # pyre-unsafe |
8 | 8 |
|
9 | 9 | import torch |
| 10 | +from executorch.exir.dialects._ops import ops as exir_ops |
10 | 11 | from torch.export.exported_program import ( |
11 | 12 | ConstantArgument, |
12 | 13 | ExportGraphSignature, |
13 | 14 | InputSpec, |
14 | 15 | OutputSpec, |
15 | 16 | ) |
| 17 | +from torch.export.graph_signature import OutputKind |
| 18 | +from torch.fx.traceback import NodeSource, NodeSourceAction |
16 | 19 |
|
17 | 20 |
|
18 | 21 | def _get_updated_range_constraints(gm, old_range_constraints=None): |
@@ -54,9 +57,12 @@ def get_shape_env(gm): |
54 | 57 | def _get_updated_graph_signature( |
55 | 58 | old_signature: ExportGraphSignature, |
56 | 59 | new_gm: torch.fx.GraphModule, |
| 60 | + *, |
| 61 | + use_edge_ops: bool = False, |
| 62 | + use_dim_order: bool = False, |
57 | 63 | ) -> ExportGraphSignature: |
58 | 64 | """ |
59 | | - Update the graph signature's user_input/user_outputs. |
| 65 | + Update the positional signature, disambiguating mutation writes in-place. |
60 | 66 | """ |
61 | 67 | new_input_specs = [] |
62 | 68 | i = 0 |
@@ -87,6 +93,51 @@ def _get_updated_graph_signature( |
87 | 93 | output_node = new_gm.graph.output_node() |
88 | 94 | assert output_node.op == "output" |
89 | 95 |
|
| 96 | + output_args = output_node.args[0] |
| 97 | + assert isinstance(output_args, (tuple, list)) |
| 98 | + assert len(output_args) == len( |
| 99 | + old_signature.output_specs |
| 100 | + ), "Number of outputs changed after transformation" |
| 101 | + seen_mutations: set[tuple[OutputKind, torch.fx.Node]] = set() |
| 102 | + new_output_args = list(output_args) |
| 103 | + for i, (node, spec) in enumerate(zip(output_args, old_signature.output_specs)): |
| 104 | + if spec.kind not in ( |
| 105 | + OutputKind.BUFFER_MUTATION, |
| 106 | + OutputKind.USER_INPUT_MUTATION, |
| 107 | + ): |
| 108 | + continue |
| 109 | + assert isinstance(node, torch.fx.Node) |
| 110 | + mutation = (spec.kind, node) |
| 111 | + if mutation in seen_mutations: |
| 112 | + # Mutation dictionaries are keyed by node name, while user outputs |
| 113 | + # are positional and may share a node. Copy only colliding writes. |
| 114 | + with new_gm.graph.inserting_before(output_node): |
| 115 | + clone = new_gm.graph.call_function( |
| 116 | + ( |
| 117 | + ( |
| 118 | + exir_ops.edge.dim_order_ops._clone_dim_order.default |
| 119 | + if use_dim_order |
| 120 | + else exir_ops.edge.aten.clone.default |
| 121 | + ) |
| 122 | + if use_edge_ops |
| 123 | + else torch.ops.aten.clone.default |
| 124 | + ), |
| 125 | + args=(node,), |
| 126 | + ) |
| 127 | + clone.meta = { |
| 128 | + key: node.meta[key] |
| 129 | + for key in ("val", "stack_trace", "nn_module_stack", "debug_handle") |
| 130 | + if key in node.meta |
| 131 | + } |
| 132 | + clone.meta["from_node"] = [ |
| 133 | + NodeSource(node, "_get_updated_graph_signature", NodeSourceAction.CREATE) |
| 134 | + ] |
| 135 | + new_output_args[i] = clone |
| 136 | + seen_mutations.add(mutation) |
| 137 | + if new_output_args != list(output_args): |
| 138 | + output_node.args = (tuple(new_output_args),) + output_node.args[1:] |
| 139 | + new_gm.recompile() |
| 140 | + |
90 | 141 | new_output_specs = [] |
91 | 142 | for i, node in enumerate(output_node.args[0]): |
92 | 143 | assert i < len( |
|
0 commit comments