onnxscript 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. onnxscript/__init__.py +131 -0
  2. onnxscript/_framework_apis/__init__.py +3 -0
  3. onnxscript/_framework_apis/torch_2_5.py +117 -0
  4. onnxscript/_framework_apis/torch_2_6.py +45 -0
  5. onnxscript/_internal/__init__.py +0 -0
  6. onnxscript/_internal/analysis.py +229 -0
  7. onnxscript/_internal/ast_utils.py +64 -0
  8. onnxscript/_internal/autocast.py +250 -0
  9. onnxscript/_internal/deprecation.py +78 -0
  10. onnxscript/_internal/param_manipulation.py +148 -0
  11. onnxscript/_internal/runtime_typing.py +43 -0
  12. onnxscript/_internal/utils.py +99 -0
  13. onnxscript/_internal/version_utils.py +118 -0
  14. onnxscript/_legacy_ir/__init__.py +341 -0
  15. onnxscript/_legacy_ir/visitor.py +937 -0
  16. onnxscript/_thirdparty/asciichartpy.py +313 -0
  17. onnxscript/backend/__init__.py +2 -0
  18. onnxscript/backend/onnx_backend.py +303 -0
  19. onnxscript/backend/onnx_export.py +885 -0
  20. onnxscript/converter.py +1470 -0
  21. onnxscript/evaluator.py +619 -0
  22. onnxscript/function_libs/tools/torch_lib/deduce_type_constraints.py +403 -0
  23. onnxscript/function_libs/tools/torch_lib/generate_aten_signatures.py +333 -0
  24. onnxscript/function_libs/tools/torch_lib/generate_prims_signatures.py +331 -0
  25. onnxscript/function_libs/torch_lib/__init__.py +12 -0
  26. onnxscript/function_libs/torch_lib/_constants.py +5 -0
  27. onnxscript/function_libs/torch_lib/_flags.py +58 -0
  28. onnxscript/function_libs/torch_lib/graph_building/__init__.py +56 -0
  29. onnxscript/function_libs/torch_lib/graph_building/_graph_building_ir.py +723 -0
  30. onnxscript/function_libs/torch_lib/graph_building/_graph_building_torch.py +1125 -0
  31. onnxscript/function_libs/torch_lib/ops/__init__.py +27 -0
  32. onnxscript/function_libs/torch_lib/ops/common.py +80 -0
  33. onnxscript/function_libs/torch_lib/ops/core.py +8935 -0
  34. onnxscript/function_libs/torch_lib/ops/fft.py +385 -0
  35. onnxscript/function_libs/torch_lib/ops/linalg.py +399 -0
  36. onnxscript/function_libs/torch_lib/ops/nested.py +25 -0
  37. onnxscript/function_libs/torch_lib/ops/nn.py +2713 -0
  38. onnxscript/function_libs/torch_lib/ops/prims.py +850 -0
  39. onnxscript/function_libs/torch_lib/ops/quantized_decomposed.py +63 -0
  40. onnxscript/function_libs/torch_lib/ops/sparse.py +23 -0
  41. onnxscript/function_libs/torch_lib/ops/special.py +387 -0
  42. onnxscript/function_libs/torch_lib/ops/vision.py +25 -0
  43. onnxscript/function_libs/torch_lib/registration.py +151 -0
  44. onnxscript/function_libs/torch_lib/tensor_typing.py +74 -0
  45. onnxscript/ir/__init__.py +153 -0
  46. onnxscript/ir/_convenience.py +447 -0
  47. onnxscript/ir/_core.py +3119 -0
  48. onnxscript/ir/_display.py +49 -0
  49. onnxscript/ir/_enums.py +163 -0
  50. onnxscript/ir/_graph_comparison.py +23 -0
  51. onnxscript/ir/_io.py +97 -0
  52. onnxscript/ir/_linked_list.py +276 -0
  53. onnxscript/ir/_metadata.py +44 -0
  54. onnxscript/ir/_name_authority.py +72 -0
  55. onnxscript/ir/_polyfill.py +25 -0
  56. onnxscript/ir/_protocols.py +598 -0
  57. onnxscript/ir/_schemas.py +548 -0
  58. onnxscript/ir/_tape.py +120 -0
  59. onnxscript/ir/_type_casting.py +106 -0
  60. onnxscript/ir/convenience.py +32 -0
  61. onnxscript/ir/external_data.py +396 -0
  62. onnxscript/ir/passes/__init__.py +33 -0
  63. onnxscript/ir/passes/_pass_infra.py +172 -0
  64. onnxscript/ir/serde.py +1620 -0
  65. onnxscript/ir/tensor_adapters.py +122 -0
  66. onnxscript/ir/traversal.py +82 -0
  67. onnxscript/irbuilder.py +542 -0
  68. onnxscript/main.py +167 -0
  69. onnxscript/onnx_opset/__init__.py +232 -0
  70. onnxscript/onnx_opset/_impl/opset1.py +4100 -0
  71. onnxscript/onnx_opset/_impl/opset10.py +1227 -0
  72. onnxscript/onnx_opset/_impl/opset11.py +4013 -0
  73. onnxscript/onnx_opset/_impl/opset12.py +1078 -0
  74. onnxscript/onnx_opset/_impl/opset13.py +3924 -0
  75. onnxscript/onnx_opset/_impl/opset14.py +999 -0
  76. onnxscript/onnx_opset/_impl/opset15.py +604 -0
  77. onnxscript/onnx_opset/_impl/opset16.py +1255 -0
  78. onnxscript/onnx_opset/_impl/opset17.py +561 -0
  79. onnxscript/onnx_opset/_impl/opset18.py +1803 -0
  80. onnxscript/onnx_opset/_impl/opset19.py +1942 -0
  81. onnxscript/onnx_opset/_impl/opset2.py +218 -0
  82. onnxscript/onnx_opset/_impl/opset20.py +675 -0
  83. onnxscript/onnx_opset/_impl/opset21.py +1976 -0
  84. onnxscript/onnx_opset/_impl/opset22.py +2588 -0
  85. onnxscript/onnx_opset/_impl/opset3.py +199 -0
  86. onnxscript/onnx_opset/_impl/opset4.py +77 -0
  87. onnxscript/onnx_opset/_impl/opset5.py +84 -0
  88. onnxscript/onnx_opset/_impl/opset6.py +944 -0
  89. onnxscript/onnx_opset/_impl/opset7.py +1243 -0
  90. onnxscript/onnx_opset/_impl/opset8.py +444 -0
  91. onnxscript/onnx_opset/_impl/opset9.py +1485 -0
  92. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml1.py +974 -0
  93. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml2.py +112 -0
  94. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml3.py +308 -0
  95. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml4.py +129 -0
  96. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml5.py +158 -0
  97. onnxscript/onnx_opset/_impl/opset_ai_onnx_preview_training1.py +577 -0
  98. onnxscript/onnx_types.py +229 -0
  99. onnxscript/optimizer/__init__.py +39 -0
  100. onnxscript/optimizer/_constant_folding.py +1083 -0
  101. onnxscript/optimizer/_inliner.py +312 -0
  102. onnxscript/optimizer/_legacy/_optimizer.py +98 -0
  103. onnxscript/optimizer/_legacy/_remove_unused_proto.py +144 -0
  104. onnxscript/optimizer/_legacy/_simple_function_folding.py +243 -0
  105. onnxscript/optimizer/_legacy/constant_folding.py +293 -0
  106. onnxscript/optimizer/_legacy/evaluator.py +439 -0
  107. onnxscript/optimizer/_optimizer.py +61 -0
  108. onnxscript/optimizer/_remove_unused.py +106 -0
  109. onnxscript/optimizer/_remove_unused_function.py +72 -0
  110. onnxscript/py.typed +1 -0
  111. onnxscript/rewriter/__init__.py +56 -0
  112. onnxscript/rewriter/_ir_utils.py +111 -0
  113. onnxscript/rewriter/broadcast_to_matmul.py +180 -0
  114. onnxscript/rewriter/cast_constant_of_shape.py +50 -0
  115. onnxscript/rewriter/collapse_slices.py +141 -0
  116. onnxscript/rewriter/erfgelu.py +27 -0
  117. onnxscript/rewriter/function_rule.py +232 -0
  118. onnxscript/rewriter/gemm_to_matmul_add.py +21 -0
  119. onnxscript/rewriter/generic_pattern.py +700 -0
  120. onnxscript/rewriter/llama_rule_sets.py +287 -0
  121. onnxscript/rewriter/no_op.py +56 -0
  122. onnxscript/rewriter/onnxruntime/__init__.py +50 -0
  123. onnxscript/rewriter/onnxruntime/bfloat16_utils/bfloat16_converter.py +99 -0
  124. onnxscript/rewriter/onnxruntime/fused_matmul_rule_sets.py +177 -0
  125. onnxscript/rewriter/onnxruntime/group_normalization_merge_silu.py +64 -0
  126. onnxscript/rewriter/onnxruntime/instance_to_group_normalization.py +155 -0
  127. onnxscript/rewriter/onnxruntime/softmax.py +63 -0
  128. onnxscript/rewriter/onnxruntime/transformers/__init__.py +21 -0
  129. onnxscript/rewriter/onnxruntime/transformers/biassplitgelu.py +31 -0
  130. onnxscript/rewriter/onnxruntime/transformers/fastgelu.py +29 -0
  131. onnxscript/rewriter/onnxruntime/transformers/layernorm.py +47 -0
  132. onnxscript/rewriter/onnxruntime/transformers/multihead_attention.py +715 -0
  133. onnxscript/rewriter/ort_fusions/__init__.py +9 -0
  134. onnxscript/rewriter/ort_fusions/_core.py +28 -0
  135. onnxscript/rewriter/ort_fusions/_smollm_1.py +253 -0
  136. onnxscript/rewriter/ort_fusions/_smollm_2.py +467 -0
  137. onnxscript/rewriter/ort_fusions/_test_models.py +122 -0
  138. onnxscript/rewriter/ort_fusions/_test_utils.py +42 -0
  139. onnxscript/rewriter/ort_fusions/cos_sin_cache.py +154 -0
  140. onnxscript/rewriter/ort_fusions/gqa.py +156 -0
  141. onnxscript/rewriter/ort_fusions/mha.py +198 -0
  142. onnxscript/rewriter/ort_fusions/rms_normalization.py +95 -0
  143. onnxscript/rewriter/ort_fusions/rotary_embedding.py +64 -0
  144. onnxscript/rewriter/ort_fusions/sdpa.py +75 -0
  145. onnxscript/rewriter/ort_fusions/skip_normalization.py +46 -0
  146. onnxscript/rewriter/pattern.py +1714 -0
  147. onnxscript/rewriter/testing.py +77 -0
  148. onnxscript/sourceinfo.py +59 -0
  149. onnxscript/tensor.py +227 -0
  150. onnxscript/testing/__init__.py +482 -0
  151. onnxscript/tools/__init__.py +4 -0
  152. onnxscript/tools/benchmark/__init__.py +23 -0
  153. onnxscript/tools/benchmark/benchmark_helpers.py +783 -0
  154. onnxscript/tools/benchmark/benchmark_run.py +140 -0
  155. onnxscript/tools/benchmark/export_model.py +207 -0
  156. onnxscript/tools/benchmark/export_model_batch.py +146 -0
  157. onnxscript/tools/memory_peak.py +244 -0
  158. onnxscript/tools/training_helper.py +50 -0
  159. onnxscript/tools/transformers_models/__init__.py +190 -0
  160. onnxscript/tools/transformers_models/llama.py +168 -0
  161. onnxscript/tools/transformers_models/mistral.py +238 -0
  162. onnxscript/tools/transformers_models/phi.py +248 -0
  163. onnxscript/tools/transformers_models/phi3.py +259 -0
  164. onnxscript/type_annotation.py +281 -0
  165. onnxscript/utils/__init__.py +0 -0
  166. onnxscript/utils/evaluation_utils.py +56 -0
  167. onnxscript/utils/timing_utils.py +33 -0
  168. onnxscript/utils/utils.py +84 -0
  169. onnxscript/values.py +790 -0
  170. onnxscript/version_converter/__init__.py +21 -0
  171. onnxscript/version_converter/_version_converter.py +314 -0
  172. onnxscript-0.1.0.dist-info/LICENSE +21 -0
  173. onnxscript-0.1.0.dist-info/METADATA +370 -0
  174. onnxscript-0.1.0.dist-info/RECORD +176 -0
  175. onnxscript-0.1.0.dist-info/WHEEL +5 -0
  176. onnxscript-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,937 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ from __future__ import annotations
4
+
5
+ import dataclasses
6
+ import logging
7
+ from typing import Any, Sequence
8
+
9
+ import numpy as np
10
+ import onnx
11
+
12
+ import onnxscript._legacy_ir as ir
13
+ from onnxscript.utils.utils import (
14
+ get_initializer_type,
15
+ is_control_flow_op,
16
+ normalize_domain,
17
+ )
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def _override_inferred_value_type_with_symbolic_value_type(
23
+ symbolic_value: ir.Value | None,
24
+ inferred_value: ir.Value | None,
25
+ ) -> ir.Value | None:
26
+ if inferred_value is not None and symbolic_value is not None:
27
+ inferred_value.type = symbolic_value.type
28
+ if inferred_value is None:
29
+ inferred_value = symbolic_value
30
+ return inferred_value
31
+
32
+
33
+ def is_local_function_node(
34
+ node: onnx.NodeProto, functions: dict[ir.FunctionId, onnx.FunctionProto]
35
+ ) -> bool:
36
+ return ir.get_function_id_from_node(node) in functions
37
+
38
+
39
+ class FunctionShapeEnv:
40
+ def __init__(self):
41
+ # Mapping from (domain, function_name, overload) to {value_name: ir_value}
42
+ self._function_values: dict[ir.FunctionId, dict[str, ir.Value]] = {}
43
+
44
+ def load_from_model_proto(self, model_proto: onnx.ModelProto) -> None:
45
+ for value_info in model_proto.graph.value_info:
46
+ self.load_from_value_info(value_info)
47
+
48
+ def save_to_model_proto(self, model_proto: onnx.ModelProto) -> None:
49
+ for (
50
+ domain,
51
+ function_name,
52
+ overload,
53
+ ), named_ir_values in self._function_values.items():
54
+ for ir_value in named_ir_values.values():
55
+ if (
56
+ value_info := self.save_to_value_info(
57
+ ir_value, domain, function_name, overload
58
+ )
59
+ ) is not None:
60
+ model_proto.graph.value_info.append(value_info)
61
+
62
+ def load_from_value_info(self, value_info: onnx.ValueInfoProto) -> None:
63
+ function_id, ir_value = self.process_value_info(value_info)
64
+ if function_id is not None:
65
+ logger.debug(
66
+ "Loads torch symbolic value info '%s'.",
67
+ value_info.name,
68
+ )
69
+ self._function_values.setdefault(function_id, {})[ir_value.name] = ir_value
70
+
71
+ def process_value_info(
72
+ self, value_info: onnx.ValueInfoProto
73
+ ) -> tuple[ir.FunctionId | None, ir.Value]:
74
+ name = value_info.name
75
+ if len(splits := name.split("/")) == 2:
76
+ # Experimental function value info format.
77
+ # To be deprecated after ONNX 1.16, where value_info is introduced in FunctionProto.
78
+ function_id, value_name = splits
79
+ splits = function_id.split("::")
80
+ domain, function_name = splits[0], splits[1]
81
+ # 'overload' is introduced in ONNX 1.16, consider it as empty string prior to that.
82
+ # The code is for future proof, in case overload is encoded in this format.
83
+ overload = ""
84
+ if len(splits) == 3:
85
+ overload = splits[2]
86
+ function_id = (domain, function_name, overload)
87
+ else:
88
+ # Standard main graph value info format.
89
+ function_id = None
90
+ value_name = name
91
+ return function_id, ir.Value(name=value_name, type=value_info.type)
92
+
93
+ def save_to_value_info(
94
+ self, value: ir.Value, domain: str, function_name: str, overload: str
95
+ ) -> onnx.ValueInfoProto | None:
96
+ if overload != "":
97
+ raise NotImplementedError("Overload is not supported yet.")
98
+ function_id = f"{domain}::{function_name}"
99
+
100
+ if value.type is not None:
101
+ return onnx.helper.make_value_info(f"{function_id}/{value.name}", value.type)
102
+ return None
103
+
104
+ def lookup(self, function: onnx.FunctionProto, value_name: str) -> ir.Value | None:
105
+ """Lookup ir value of 'value_name' inside 'function'."""
106
+ function_id = ir.get_function_id(function)
107
+ function_values = self._function_values.get(function_id)
108
+ if function_values is None or (ir_value := function_values.get(value_name)) is None:
109
+ logger.debug(
110
+ "Lookup Missed %s torch symbolic value info in function %s::%s.",
111
+ value_name,
112
+ function.domain,
113
+ function.name,
114
+ )
115
+ return None
116
+ logger.debug(
117
+ "Lookup found %s torch symbolic value info in function %s::%s.",
118
+ value_name,
119
+ function.domain,
120
+ function.name,
121
+ )
122
+ return ir_value
123
+
124
+ def bind(self, value: ir.Value, domain: str, function_name: str, overload: str) -> None:
125
+ """Bind ir value 'value' to 'value_name' inside 'function'."""
126
+ function_id = (domain, function_name, overload)
127
+ self._function_values.setdefault(function_id, {})[value.name] = value
128
+
129
+ def get_ir_values(self, function: onnx.FunctionProto) -> dict[str, ir.Value]:
130
+ """Get all ir values inside 'function'."""
131
+ function_id = ir.get_function_id(function)
132
+ return self._function_values.get(function_id, {})
133
+
134
+
135
+ class SubScope:
136
+ values: dict[str, ir.Value]
137
+ ref_attributes: dict[str, onnx.AttributeProto]
138
+ owner: onnx.GraphProto | onnx.FunctionProto
139
+
140
+ def __init__(self, owner: onnx.GraphProto | onnx.FunctionProto):
141
+ self.values = {}
142
+ self.ref_attributes = {}
143
+ self.owner = owner
144
+
145
+ def lookup(self, name: str) -> ir.Value | None:
146
+ return self.values.get(name)
147
+
148
+ def bind(self, name: str, value: ir.Value) -> None:
149
+ self.values[name] = value
150
+
151
+ def lookup_ref_attribute(self, ref_attr_name: str) -> onnx.AttributeProto | None:
152
+ return self.ref_attributes.get(ref_attr_name)
153
+
154
+ def bind_ref_attribute(self, ref_attr_name: str, attr: onnx.AttributeProto) -> None:
155
+ self.ref_attributes[ref_attr_name] = attr
156
+
157
+ def readable_strs(self, indent: int = 0) -> list[str]:
158
+ indent_str = " " * indent
159
+ strs = []
160
+ if isinstance(self.owner, onnx.GraphProto):
161
+ strs.append(f"Graph {self.owner.name}:")
162
+ else:
163
+ strs.append(f"Function {self.owner.name}:")
164
+ strs.append(" ir.Values:")
165
+ for name, value in self.values.items():
166
+ strs.append(f" {name}: {value}")
167
+ strs.append(" RefAttributes:")
168
+ for name, attr in self.ref_attributes.items():
169
+ strs.append(f" {name}: {attr}")
170
+
171
+ return [f"{indent_str}{s}" for s in strs]
172
+
173
+ def __str__(self) -> str:
174
+ return "\n".join(self.readable_strs())
175
+
176
+
177
+ @dataclasses.dataclass
178
+ class Scope:
179
+ _sub_scopes: list[SubScope] = dataclasses.field(default_factory=list)
180
+
181
+ def lookup(self, name: str) -> ir.Value | None:
182
+ """Lookup value by name from all SubScopes."""
183
+ for sub_scope in reversed(self._sub_scopes):
184
+ if (result := sub_scope.lookup(name)) is not None:
185
+ return result
186
+ return None
187
+
188
+ def bind(self, name: str, value: ir.Value) -> None:
189
+ """Bind value to name in the most recent SubScope."""
190
+ if name == "":
191
+ raise ValueError("Cannot bind to empty name.")
192
+ if value is None:
193
+ raise ValueError(f"Cannot bind None to value {name}.")
194
+ self._sub_scopes[-1].bind(name, value)
195
+
196
+ def lookup_or_create(self, name: str) -> ir.Value:
197
+ """Lookup value by name from all SubScopes. If not found, create a new one in most recent SubScope."""
198
+ if name == "":
199
+ raise ValueError("Cannot lookup or create empty name.")
200
+ for sub_scope in reversed(self._sub_scopes):
201
+ if (result := sub_scope.lookup(name)) is not None:
202
+ return result
203
+ value = ir.Value(name=name)
204
+ self.bind(name, value)
205
+ return value
206
+
207
+ def lookup_ref_attribute(self, ref_attr_name: str) -> onnx.AttributeProto | None:
208
+ for sub_scope in reversed(self._sub_scopes):
209
+ if (result := sub_scope.lookup_ref_attribute(ref_attr_name)) is not None:
210
+ return result
211
+ return None
212
+
213
+ def bind_ref_attribute(self, ref_attr_name: str, attr: onnx.AttributeProto) -> None:
214
+ self._sub_scopes[-1].bind_ref_attribute(ref_attr_name, attr)
215
+
216
+ def enter_sub_scope(self, owner: onnx.GraphProto) -> None:
217
+ self._sub_scopes.append(SubScope(owner))
218
+
219
+ def exit_sub_scope(self) -> SubScope:
220
+ return self._sub_scopes.pop()
221
+
222
+ def current_function_scope(self) -> SubScope | None:
223
+ if len(self._sub_scopes) == 0:
224
+ return None
225
+ if isinstance(self._sub_scopes[0].owner, onnx.FunctionProto):
226
+ return self._sub_scopes[0]
227
+ return None
228
+
229
+ def current_function(self) -> onnx.FunctionProto | None:
230
+ current_function_scope = self.current_function_scope()
231
+ if current_function_scope is not None:
232
+ return current_function_scope.owner
233
+ return None
234
+
235
+ def current_graph(self) -> onnx.GraphProto | None:
236
+ for sub_scope in reversed(self._sub_scopes):
237
+ if isinstance(sub_scope.owner, onnx.GraphProto):
238
+ return sub_scope.owner
239
+ return None
240
+
241
+ def readable_strs(self, indent: int = 0) -> list[str]:
242
+ indent_str = " " * indent
243
+ strs = []
244
+ for i, sub_scope in enumerate(self._sub_scopes):
245
+ strs.append(f"SubScope {i}:")
246
+ strs.extend(sub_scope.readable_strs(indent=indent + 2))
247
+ return [f"{indent_str}{s}" for s in strs]
248
+
249
+ def __str__(self) -> str:
250
+ return "\n".join(self.readable_strs())
251
+
252
+
253
+ @dataclasses.dataclass
254
+ class ScopeStack:
255
+ """Stack of scopes.
256
+
257
+ Each Scope represents statically-nested SubScopes (where inner SubScopes can access names defined in outer SubScopes)
258
+ produced by subgraphs (occurring as attribute values), except for the first SubScope which could be produced by a function.
259
+ With a ScopeStack, there is no such possibility of referencing variables defined higher up in the stack by name.
260
+ Instead, it is meant to represent a sequence of (nested) function-calls. Each entry in the stack (except the outermost)
261
+ represents a call to a function.
262
+
263
+ Thus, we would use a ScopeStack for a context-sensitive analysis (where we recursively process a called function).
264
+ For a context-insensitive analysis, we would only need a Scope (where we recursively process subgraphs).
265
+
266
+ To debug, `print(scope_stack)` will print the scope structure as well as the info stored
267
+ in each scope.
268
+ """
269
+
270
+ _scopes: list[Scope] = dataclasses.field(default_factory=lambda: [Scope()])
271
+
272
+ def current_scope(self) -> Scope:
273
+ return self._scopes[-1]
274
+
275
+ def lookup(self, name: str) -> ir.Value | None:
276
+ """Lookup value by name from the current Scope."""
277
+ return self.current_scope().lookup(name)
278
+
279
+ def bind(self, name: str, value: ir.Value) -> None:
280
+ """Bind value to name in the current Scope."""
281
+ self.current_scope().bind(name, value)
282
+
283
+ def lookup_or_create(self, name: str) -> ir.Value:
284
+ """Lookup value by name from the current Scope. If not found, create a new one."""
285
+ return self.current_scope().lookup_or_create(name)
286
+
287
+ def lookup_ref_attribute(self, ref_attr_name: str) -> onnx.AttributeProto | None:
288
+ return self.current_scope().lookup_ref_attribute(ref_attr_name)
289
+
290
+ def bind_ref_attribute(self, ref_attr_name: str, attr: onnx.AttributeProto) -> None:
291
+ self.current_scope().bind_ref_attribute(ref_attr_name, attr)
292
+
293
+ def enter_graph_scope(self, graph: onnx.GraphProto) -> None:
294
+ self.current_scope().enter_sub_scope(graph)
295
+
296
+ def exit_graph_scope(self) -> SubScope:
297
+ sub_scope = self.current_scope().exit_sub_scope()
298
+ assert isinstance(sub_scope.owner, onnx.GraphProto), "Expected graph scope."
299
+ return sub_scope
300
+
301
+ def enter_function_scope(self, function: onnx.FunctionProto) -> None:
302
+ self._scopes.append(Scope())
303
+ self.current_scope().enter_sub_scope(function)
304
+
305
+ def exit_function_scope(self) -> SubScope:
306
+ sub_scope = self.current_scope().exit_sub_scope()
307
+ assert isinstance(sub_scope.owner, onnx.FunctionProto), "Expected function scope."
308
+ self._scopes.pop()
309
+ return sub_scope
310
+
311
+ def current_function(self) -> onnx.FunctionProto | None:
312
+ return self.current_scope().current_function()
313
+
314
+ def current_graph(self) -> onnx.GraphProto | None:
315
+ return self.current_scope().current_graph()
316
+
317
+ def __str__(self) -> str:
318
+ strs = ["ScopeStach:"]
319
+ for i, scope in enumerate(self._scopes):
320
+ strs.append(f" Scope {i}:")
321
+ strs.extend(scope.readable_strs(indent=2))
322
+ return "\n".join(strs)
323
+
324
+
325
+ class ProtoVisitorCore:
326
+ def visit_model(self, model: onnx.ModelProto):
327
+ self.process_model(model)
328
+ for opset in model.opset_import:
329
+ self.process_opset_import(opset)
330
+ self.visit_graph(model.graph)
331
+ for function in model.functions:
332
+ self.visit_function(function)
333
+
334
+ def process_model(self, model: onnx.ModelProto):
335
+ pass
336
+
337
+ def process_opset_import(self, opset: onnx.OperatorSetIdProto):
338
+ pass
339
+
340
+ def visit_graph(self, graph: onnx.GraphProto):
341
+ self.enter_scope(graph)
342
+ self.process_graph(graph)
343
+ for input in graph.input:
344
+ self.process_graph_input(input)
345
+ for init in graph.initializer:
346
+ self.process_initializer(init)
347
+ for value_info in graph.value_info:
348
+ self.process_value_info(value_info)
349
+ for node in graph.node:
350
+ self.visit_node(node)
351
+ for output in graph.output:
352
+ self.process_graph_output(output)
353
+ self.exit_scope(graph)
354
+
355
+ def visit_function(self, function: onnx.FunctionProto):
356
+ self.enter_function_scope(function)
357
+ self.process_function(function)
358
+ for input in function.input:
359
+ self.process_function_input(input)
360
+ for node in function.node:
361
+ self.visit_node(node)
362
+ for output in function.output:
363
+ self.process_function_output(output)
364
+ self.exit_function_scope(function)
365
+
366
+ def process_function_input(self, input: str):
367
+ pass
368
+
369
+ def process_function_output(self, output: str):
370
+ pass
371
+
372
+ def process_function(self, function: onnx.FunctionProto):
373
+ pass
374
+
375
+ def enter_function_scope(self, function: onnx.FunctionProto):
376
+ pass
377
+
378
+ def exit_function_scope(self, function: onnx.FunctionProto) -> SubScope:
379
+ pass
380
+
381
+ def enter_scope(self, graph: onnx.GraphProto):
382
+ pass
383
+
384
+ def process_graph(self, graph: onnx.GraphProto):
385
+ pass
386
+
387
+ def exit_scope(self, graph: onnx.GraphProto) -> SubScope:
388
+ pass
389
+
390
+ def process_graph_input(self, input: onnx.ValueInfoProto):
391
+ pass
392
+
393
+ def process_initializer(self, init: onnx.TensorProto):
394
+ pass
395
+
396
+ def process_value_info(self, value_info: onnx.ValueInfoProto):
397
+ pass
398
+
399
+ def visit_node(self, node: onnx.NodeProto):
400
+ self.process_node(node)
401
+ for attr in node.attribute:
402
+ self.visit_attribute(attr)
403
+
404
+ def process_node(self, node: onnx.NodeProto) -> Sequence[onnx.NodeProto] | None:
405
+ pass
406
+
407
+ def process_graph_output(self, output: onnx.ValueInfoProto):
408
+ pass
409
+
410
+ def visit_attribute(self, attr: onnx.AttributeProto):
411
+ self.process_attribute(attr)
412
+ if attr.HasField("g"):
413
+ self.visit_graph(attr.g)
414
+ elif len(attr.graphs) > 0:
415
+ for graph in attr.graphs:
416
+ self.visit_graph(graph)
417
+
418
+ def process_attribute(self, attr: onnx.AttributeProto):
419
+ pass
420
+
421
+
422
+ class ProtoVisitor(ProtoVisitorCore):
423
+ def __init__(
424
+ self, external_data_folder: str = "", *, do_shape_inference: bool = False
425
+ ) -> None:
426
+ super().__init__()
427
+ self.scopes = ScopeStack()
428
+ self.function_shape_env = FunctionShapeEnv()
429
+ self.version_map = {} # Map from domain to version
430
+ self.do_shape_inference = do_shape_inference
431
+ self.external_data_folder = external_data_folder
432
+ self.modified = False
433
+
434
+ def process_opset_import(self, opset: onnx.OperatorSetIdProto):
435
+ domain = normalize_domain(opset.domain)
436
+ self.version_map[domain] = opset.version
437
+
438
+ def lookup_version(self, domain: str) -> int:
439
+ domain = normalize_domain(domain)
440
+ return self.version_map.get(domain, 1) # TODO: handle missing domain
441
+
442
+ def lookup(self, name: str) -> ir.Value | None:
443
+ if name == "":
444
+ return None
445
+ if (result := self.scopes.lookup(name)) is None:
446
+ logger.debug("Lookup value %s unfound.", name)
447
+ raise ValueError(
448
+ f"Undefined variable {name}.\n"
449
+ f"Available variables: {self.scopes.current_scope()}"
450
+ )
451
+ logger.debug("Lookup value %s. Shape %s", name, result.tensor_shape_proto())
452
+ return result
453
+
454
+ def bind(self, name: str, value: ir.Value) -> None:
455
+ logger.debug("Binding value %s. Shape %s", name, value.tensor_shape_proto())
456
+ self.scopes.bind(name, value)
457
+
458
+ def lookup_or_create(self, name: str) -> ir.Value:
459
+ return self.scopes.lookup_or_create(name)
460
+
461
+ def has_input(self, node: onnx.NodeProto, index: int) -> bool:
462
+ return index < len(node.input) and node.input[index] != ""
463
+
464
+ # TODO: Cleanup handling of undefined variables. May fail in some of methods below.
465
+
466
+ def get_input(self, node: onnx.NodeProto, index: int) -> ir.Value | None:
467
+ if index < len(node.input):
468
+ return self.lookup(node.input[index])
469
+ return None
470
+
471
+ def input_type(self, node: onnx.NodeProto, index: int) -> onnx.TypeProto | None:
472
+ info = self.get_input(node, index)
473
+ return info.type if info is not None else None
474
+
475
+ def input_element_type(self, node: onnx.NodeProto, index: int) -> int | None:
476
+ info = self.get_input(node, index)
477
+ return info.element_type if info is not None else None
478
+
479
+ def input_shape(self, node: onnx.NodeProto, index: int) -> onnx.TensorShapeProto | None:
480
+ info = self.get_input(node, index)
481
+ return info.tensor_shape_proto() if info is not None else None
482
+
483
+ def input_const_value(self, node: onnx.NodeProto, index: int) -> Any:
484
+ if not self.has_input(node, index):
485
+ return None # This is treated as a known constant value "None"
486
+ info = self.get_input(node, index)
487
+ return info.value
488
+
489
+ def has_output(self, node: onnx.NodeProto, index: int) -> bool:
490
+ return index < len(node.output) and node.output[index] != ""
491
+
492
+ def get_output(self, node: onnx.NodeProto, index: int) -> ir.Value | None:
493
+ if index < len(node.output):
494
+ return self.lookup(node.output[index])
495
+ return None
496
+
497
+ def get_input_value(
498
+ self, node: onnx.NodeProto, index: int, default: Any | None = None
499
+ ) -> Any | None:
500
+ info = self.get_input(node, index)
501
+ if info is not None:
502
+ return info.value
503
+ return default
504
+
505
+ def get_input_type(
506
+ self, node: onnx.NodeProto, index: int, default: onnx.TypeProto | None = None
507
+ ) -> onnx.TypeProto | None:
508
+ info = self.get_input(node, index)
509
+ if info is not None:
510
+ return info.type
511
+ return default
512
+
513
+ def enter_scope(self, graph: onnx.GraphProto):
514
+ logger.debug("enter_scope: graph %s", graph.name)
515
+ self.scopes.enter_graph_scope(graph)
516
+
517
+ def exit_scope(self, graph: onnx.GraphProto) -> SubScope:
518
+ logger.debug("exit_scope: graph %s", graph.name)
519
+ return self.scopes.exit_graph_scope()
520
+
521
+ def enter_function_scope(self, function: onnx.FunctionProto):
522
+ logger.debug("enter_function_scope: function %s", function.name)
523
+ self.scopes.enter_function_scope(function)
524
+ ir_values = self.function_shape_env.get_ir_values(function)
525
+ for name, ir_value in ir_values.items():
526
+ inferred_ir_value = self.lookup_or_create(name)
527
+ updated_ir_value = _override_inferred_value_type_with_symbolic_value_type(
528
+ ir_value, inferred_ir_value
529
+ )
530
+ self.bind(name, updated_ir_value)
531
+
532
+ def exit_function_scope(self, function: onnx.FunctionProto) -> SubScope:
533
+ logger.debug("exit_function_scope: function %s", function.name)
534
+ # Sync ir value back to function_shape_env
535
+ function_scope = self.scopes.exit_function_scope()
536
+ for ir_value in function_scope.values.values():
537
+ self.function_shape_env.bind(ir_value, *ir.get_function_id(function))
538
+ return function_scope
539
+
540
+ def process_initializer(self, init: onnx.TensorProto):
541
+ array = onnx.numpy_helper.to_array(init, self.external_data_folder)
542
+ self.bind(
543
+ init.name,
544
+ ir.Value(name=init.name, value=array, type=get_initializer_type(init)),
545
+ )
546
+
547
+ def process_graph_input(self, input: onnx.ValueInfoProto):
548
+ self.bind(input.name, ir.Value(name=input.name, type=input.type))
549
+
550
+ def process_value_info(self, value_info: onnx.ValueInfoProto):
551
+ logger.debug("process_value_info: %s", value_info)
552
+ value = self.lookup_or_create(value_info.name)
553
+ value.type = value_info.type
554
+ # Populate function shape environment
555
+ self.function_shape_env.load_from_value_info(value_info)
556
+
557
+ def process_node(self, node: onnx.NodeProto) -> Sequence[onnx.NodeProto] | None:
558
+ output_types = {}
559
+ if self.do_shape_inference and not is_control_flow_op(node):
560
+ # Control-flow ops are more complicated. Not supported here yet.
561
+ # TODO: handle optional inputs
562
+ def get_constant_value(i: int) -> onnx.TensorProto | None:
563
+ value = self.input_const_value(node, i)
564
+ if isinstance(value, np.ndarray) and value.size < 20:
565
+ return onnx.numpy_helper.from_array(value, node.input[i])
566
+ return None
567
+
568
+ input_types = {x: self.input_type(node, i) for i, x in enumerate(node.input)}
569
+ input_data = {x: get_constant_value(i) for i, x in enumerate(node.input)}
570
+ input_data = {k: v for k, v in input_data.items() if v is not None}
571
+ if any(t is None for t in input_types.values()):
572
+ logger.debug(
573
+ "Skipping shape inference for node %s due to missing input type.",
574
+ node.name,
575
+ )
576
+ else:
577
+ # TODO: pass in constant values, ir_version
578
+ try:
579
+ schema = onnx.defs.get_schema(
580
+ node.op_type, self.lookup_version(node.domain), node.domain
581
+ )
582
+ output_types = onnx.shape_inference.infer_node_outputs(
583
+ schema, node, input_types, input_data
584
+ )
585
+ except Exception as e:
586
+ logger.debug(
587
+ "Skipping shape inference for node %s due to exception: %s",
588
+ node.name,
589
+ e,
590
+ )
591
+
592
+ for output in node.output:
593
+ if output == "":
594
+ continue
595
+ info = self.lookup_or_create(output)
596
+ if output in output_types:
597
+ if info.type is not None:
598
+ if (
599
+ info.type.tensor_type.elem_type
600
+ != output_types[output].tensor_type.elem_type
601
+ ):
602
+ logger.warning(
603
+ "Overriding existing type %s with inferred type %s for %s",
604
+ info.type,
605
+ output_types[output],
606
+ output,
607
+ )
608
+ # TODO: merge types
609
+ info.type = output_types[output]
610
+
611
+
612
+ class ProtoTransformer(ProtoVisitor):
613
+ # TODO(lowpri) Practically this is useless.
614
+ # Subgraph only exist in 'if' nodes. 'if' nodes only exist in torchlib functions.
615
+ # There is no pre-existing value_info in torchlib functions.
616
+ # def exit_scope(self, graph: onnx.GraphProto) -> SubScope:
617
+ # # Also sync updated ir values back to value_info in graph.
618
+ # sub_scope = super().exit_scope(graph)
619
+
620
+ def visit_node(self, node: onnx.NodeProto) -> list[onnx.NodeProto] | None:
621
+ replacement = self.process_node(node)
622
+ logger.debug(
623
+ "visit_node: %s::%s %s replacement %s",
624
+ node.domain,
625
+ node.op_type,
626
+ node.name,
627
+ "found" if replacement is not None else "missed",
628
+ )
629
+ if replacement is None:
630
+ # No change. Process attributes.
631
+ for attr in node.attribute:
632
+ self.visit_attribute(attr)
633
+ return None
634
+ else:
635
+ self.modified = True
636
+ # We recursively visit the replacement nodes.
637
+ result = []
638
+ for newnode in replacement:
639
+ n = self.visit_node(newnode)
640
+ if n is not None:
641
+ result.extend(n)
642
+ else:
643
+ result.append(newnode)
644
+ return result
645
+
646
+ def visit_graph(self, graph: onnx.GraphProto) -> dict[str, ir.Value]:
647
+ self.enter_scope(graph)
648
+ self.process_graph(graph)
649
+ for input in graph.input:
650
+ self.process_graph_input(input)
651
+ for init in graph.initializer:
652
+ self.process_initializer(init)
653
+ for value_info in graph.value_info:
654
+ self.process_value_info(value_info)
655
+ updates = []
656
+ nodes = graph.node
657
+ for i, node in enumerate(nodes):
658
+ replacement = self.visit_node(node)
659
+ if replacement is not None:
660
+ updates.append((i, replacement))
661
+ for i, replacement in reversed(updates):
662
+ old_node_name = nodes[i].name
663
+ del nodes[i]
664
+ for newnode in reversed(replacement):
665
+ logger.debug(
666
+ "Replacement node %s for %s. Size %s",
667
+ newnode.name,
668
+ old_node_name,
669
+ newnode.ByteSize(),
670
+ )
671
+ nodes.insert(i, newnode)
672
+ for output in graph.output:
673
+ self.process_graph_output(output)
674
+ return self.exit_scope(graph)
675
+
676
+
677
+ class FunctionCallsiteAnalysis(ProtoVisitor):
678
+ """Collects the callsites of each function."""
679
+
680
+ def __init__(self):
681
+ super().__init__()
682
+ self.functions: dict[ir.FunctionId, onnx.FunctionProto] = {}
683
+ self.function_calls: dict[ir.FunctionId, list[onnx.NodeProto]] = {}
684
+
685
+ def visit_function(self, function: onnx.FunctionProto):
686
+ # Do not visit function via model.functions.
687
+ # Only visit function at callsites.
688
+ # The purpose of this analysis is to collect the callsites of each function.
689
+ pass
690
+
691
+ def visit_node(self, node: onnx.NodeProto) -> None:
692
+ if is_local_function_node(node, self.functions):
693
+ function_id = ir.get_function_id_from_node(node)
694
+ self.function_calls.setdefault(function_id, []).append(node)
695
+ for subnode in self.functions[function_id].node:
696
+ self.visit_node(subnode)
697
+
698
+ def visit_model(self, model: onnx.ModelProto) -> None:
699
+ for function in model.functions:
700
+ self.functions[ir.get_function_id(function)] = function
701
+
702
+ super().visit_model(model)
703
+
704
+
705
+ class FunctionRenamer:
706
+ _POSTFIX_FORMAT = "{name}|{postfix}_{count}"
707
+
708
+ def __init__(self, postfix="folded"):
709
+ self._function_key_to_instance_count = {}
710
+ self._postfix = postfix
711
+
712
+ def rename(self, function: onnx.FunctionProto) -> None:
713
+ domain = function.domain
714
+ name = function.name
715
+ key = (domain, name)
716
+ self._function_key_to_instance_count.setdefault(key, 0)
717
+ function.name = self._POSTFIX_FORMAT.format(
718
+ name=name,
719
+ postfix=self._postfix,
720
+ count=self._function_key_to_instance_count[key],
721
+ )
722
+ self._function_key_to_instance_count[key] += 1
723
+
724
+
725
+ class FunctionCallsiteProtoTransformer(ProtoTransformer):
726
+ """Unlike other base visitors, this is a special visitor that visits functions at their callsite.
727
+
728
+ This allows transforming and constructing specialized functions based on callsite context.
729
+ """
730
+
731
+ _functions: dict[ir.FunctionId, onnx.FunctionProto]
732
+ _function_callsites: dict[ir.FunctionId, list[onnx.NodeProto]]
733
+ _new_functions: list[onnx.FunctionProto]
734
+ _function_renamer: FunctionRenamer
735
+
736
+ def _gather_function_metadata(self, model: onnx.ModelProto):
737
+ analysis = FunctionCallsiteAnalysis()
738
+ analysis.visit_model(model)
739
+ self._functions = analysis.functions
740
+ self._function_callsites = analysis.function_calls
741
+ self._new_functions = []
742
+ self._function_renamer = FunctionRenamer()
743
+
744
+ def process_function_outputs(self, function: onnx.FunctionProto) -> bool:
745
+ """Process function outputs.
746
+
747
+ This method is called when a function is visited at its callsite.
748
+
749
+ Returns:
750
+ True if the function outputs are modified.
751
+ """
752
+ del function # Unused
753
+ return False
754
+
755
+ def process_function_node_outputs(
756
+ self,
757
+ node: onnx.NodeProto,
758
+ function_scope: SubScope,
759
+ ) -> None:
760
+ """Fetch value infos of function output to re-bind them for function node output."""
761
+ function = function_scope.owner
762
+ output_values = [function_scope.lookup(output) for output in function.output]
763
+ for actual_name, formal_value in zip(node.output, output_values):
764
+ if formal_value is None:
765
+ raise RuntimeError(
766
+ "Missing output %s in function-call to %s",
767
+ actual_name,
768
+ node.op_type,
769
+ )
770
+ actual_value = self.lookup_or_create(actual_name)
771
+ actual_value.identity_merge_from(formal_value)
772
+ if logger.level <= logging.INFO:
773
+ logger.info(
774
+ "Binding outputs for function %s. %s => %s",
775
+ function.name,
776
+ actual_value,
777
+ node.output,
778
+ )
779
+
780
+ def lookup_ref_attribute(self, ref_attr_name: str) -> onnx.AttributeProto | None:
781
+ return self.scopes.lookup_ref_attribute(ref_attr_name)
782
+
783
+ def bind_ref_attribute(self, ref_attr_name: str, attr: onnx.AttributeProto) -> None:
784
+ self.scopes.bind_ref_attribute(ref_attr_name, attr)
785
+
786
+ def visit_model(self, model: onnx.ModelProto):
787
+ self._gather_function_metadata(model)
788
+
789
+ self.process_model(model)
790
+ for opset in model.opset_import:
791
+ self.process_opset_import(opset)
792
+ self.visit_graph(model.graph)
793
+
794
+ for new_function in self._new_functions:
795
+ model.functions.append(new_function)
796
+
797
+ self.function_shape_env.save_to_model_proto(model)
798
+
799
+ def visit_node(self, node: onnx.NodeProto) -> list[onnx.NodeProto] | None:
800
+ if is_local_function_node(node, self._functions):
801
+ function_id = ir.get_function_id_from_node(node)
802
+ if function_id not in self._functions:
803
+ # Do not recursively visit new functions.
804
+ return None
805
+ replacement, _ = self.process_function_node(node)
806
+ else:
807
+ replacement = self.process_node(node)
808
+ logger.debug(
809
+ "visit_node: %s::%s %s replacement %s",
810
+ node.domain,
811
+ node.op_type,
812
+ node.name,
813
+ "found" if replacement is not None else "missed",
814
+ )
815
+ if replacement is None:
816
+ # No change. Process attributes.
817
+ for attr in node.attribute:
818
+ self.visit_attribute(attr)
819
+ return None
820
+ else:
821
+ self.modified = True
822
+ # We recursively visit the replacement nodes.
823
+ result = []
824
+ for newnode in replacement:
825
+ n = self.visit_node(newnode)
826
+ if n is not None:
827
+ result.extend(n)
828
+ else:
829
+ result.append(newnode)
830
+ return result
831
+
832
+ def process_function_node(
833
+ self, node: onnx.NodeProto
834
+ ) -> tuple[list[onnx.NodeProto] | None, onnx.FunctionProto | None]:
835
+ function_id = ir.get_function_id_from_node(node)
836
+ function = self._functions[function_id]
837
+
838
+ is_unique_callsite = len(self._function_callsites[function_id]) == 1
839
+ if not is_unique_callsite:
840
+ mutable_function = onnx.FunctionProto()
841
+ mutable_function.CopyFrom(function)
842
+ else:
843
+ mutable_function = function
844
+
845
+ logger.info("Visit function %s node %s", function_id, node.name)
846
+ actual_input_value_infos = [self.lookup(input) for input in node.input]
847
+ # Handle omitted inputs, these are considered optional inputs of the function.
848
+ actual_input_value_infos.extend(
849
+ [None] * (len(function.input) - len(actual_input_value_infos))
850
+ )
851
+ ref_attributes = {
852
+ attr_proto.name: self.lookup_ref_attribute(attr_proto.ref_attr_name)
853
+ for attr_proto in node.attribute
854
+ if attr_proto.ref_attr_name
855
+ }
856
+
857
+ self.enter_function_scope(mutable_function)
858
+ if logger.level <= logging.INFO:
859
+ printable_actual_input_value_infos = [str(x) for x in actual_input_value_infos]
860
+ logger.info(
861
+ "Actual input value infos: %s",
862
+ printable_actual_input_value_infos,
863
+ )
864
+ logger.info("Enter function scope: %s", self.scopes.current_scope())
865
+
866
+ logger.debug("Binding inputs for function %s", function.name)
867
+ for actual_input_value_info, formal_input in zip(
868
+ actual_input_value_infos, function.input
869
+ ):
870
+ formal_info = ir.Value(formal_input)
871
+ if actual_input_value_info is not None:
872
+ formal_info.identity_merge_from(actual_input_value_info)
873
+ self.bind(formal_input, formal_info)
874
+
875
+ for attr_proto in function.attribute_proto:
876
+ # Default value of function attributes.
877
+ self.bind_ref_attribute(attr_proto.name, attr_proto)
878
+
879
+ for attr_proto in node.attribute:
880
+ if attr_proto.ref_attr_name:
881
+ concrete_attribute = ref_attributes.get(attr_proto.name)
882
+ if concrete_attribute is None:
883
+ continue
884
+ self.bind_ref_attribute(attr_proto.name, concrete_attribute)
885
+ else:
886
+ self.bind_ref_attribute(attr_proto.name, attr_proto)
887
+
888
+ # Visit inner function nodes.
889
+ node_updates: list[tuple[int, list[onnx.NodeProto]]] = []
890
+ nodes = mutable_function.node
891
+ for i, inner_node in enumerate(nodes):
892
+ replacement = self.visit_node(inner_node)
893
+ if replacement is not None:
894
+ node_updates.append((i, replacement))
895
+ for i, replacement in reversed(node_updates):
896
+ old_node_name = nodes[i].name
897
+ old_node_op_type = nodes[i].op_type
898
+ del nodes[i]
899
+ for newnode in reversed(replacement):
900
+ logger.debug(
901
+ "Replacement node inside function %s: %s for %s %s. Size %s",
902
+ node.name,
903
+ newnode.output,
904
+ old_node_name,
905
+ old_node_op_type,
906
+ newnode.ByteSize(),
907
+ )
908
+ nodes.insert(i, newnode)
909
+ added_domains = set()
910
+ del mutable_function.opset_import[:]
911
+ for inner_node in nodes:
912
+ # Update opset_import if needed.
913
+ if inner_node.domain not in added_domains:
914
+ version = self.lookup_version(inner_node.domain)
915
+ mutable_function.opset_import.append(
916
+ onnx.OperatorSetIdProto(domain=inner_node.domain, version=version)
917
+ )
918
+ added_domains.add(inner_node.domain)
919
+
920
+ output_updates = self.process_function_outputs(mutable_function)
921
+
922
+ is_new_function = not is_unique_callsite and (node_updates or output_updates)
923
+ if is_new_function:
924
+ self._new_functions.append(mutable_function)
925
+ self._function_renamer.rename(mutable_function)
926
+ node.op_type = mutable_function.name
927
+
928
+ function_scope = self.exit_function_scope(mutable_function)
929
+
930
+ self.process_function_node_outputs(node, function_scope)
931
+
932
+ logger.info("Exit function scope: %s", function_scope)
933
+ logger.info("Exit function %s node %s", function_id, node.name)
934
+
935
+ if is_new_function:
936
+ return [node], mutable_function
937
+ return None, None