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
onnxscript/ir/serde.py ADDED
@@ -0,0 +1,1620 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Serialize and deserialize the intermediate representation to/from ONNX protos."""
4
+
5
+ # NOTES for developers:
6
+ # NOTE: Do not import pathlib in the IR. It is slow. Use os.path methods instead.
7
+ #
8
+ # NOTE: Protobuf serialization
9
+ # Initializing a protobuf message with initialized protobuf messages incurs
10
+ # a copy and is slow. Instead, use proto.add() to add to a repeated field.
11
+ # or initialize the message first and then set the fields if the fields are
12
+ # plain Python objects.
13
+
14
+ from __future__ import annotations
15
+
16
+ import functools
17
+ import typing
18
+
19
+ __all__ = [
20
+ # Tensors
21
+ "TensorProtoTensor",
22
+ # Deserialization
23
+ "from_proto",
24
+ "deserialize_attribute",
25
+ "deserialize_dimension",
26
+ "deserialize_function",
27
+ "deserialize_graph",
28
+ "deserialize_metadata_props",
29
+ "deserialize_model",
30
+ "deserialize_node",
31
+ "deserialize_opset_import",
32
+ "deserialize_tensor",
33
+ "deserialize_tensor_shape",
34
+ "deserialize_type_proto_for_shape",
35
+ "deserialize_type_proto_for_type",
36
+ "deserialize_value_info_proto",
37
+ # Serialization
38
+ "to_proto",
39
+ "serialize_attribute_into",
40
+ "serialize_attribute",
41
+ "serialize_dimension_into",
42
+ "serialize_function_into",
43
+ "serialize_function",
44
+ "serialize_graph_into",
45
+ "serialize_graph",
46
+ "serialize_model_into",
47
+ "serialize_model",
48
+ "serialize_node_into",
49
+ "serialize_node",
50
+ "serialize_shape_into",
51
+ "serialize_reference_attribute_into",
52
+ "serialize_tensor_into",
53
+ "serialize_tensor",
54
+ "serialize_type_into",
55
+ "serialize_type",
56
+ "serialize_value_into",
57
+ "serialize_value",
58
+ "SerdeError",
59
+ ]
60
+
61
+ import collections
62
+ import logging
63
+ import os
64
+ from typing import Any, Callable, List, Mapping, Sequence
65
+
66
+ import numpy as np
67
+ import onnx
68
+ import onnx.external_data_helper
69
+
70
+ from onnxscript.ir import _core, _enums, _metadata, _protocols, _type_casting
71
+
72
+ if typing.TYPE_CHECKING:
73
+ import google.protobuf.internal.containers as proto_containers
74
+ import numpy.typing as npt
75
+
76
+ logger = logging.getLogger(__name__)
77
+
78
+ _PLEASE_CONTRIBUTE = (
79
+ "Please contribute by creating a PR at https://github.com/microsoft/onnxscript."
80
+ )
81
+ _FUNCTION_VALUE_INFO_SUPPORTED_VERSION = (
82
+ 10 # ONNX IR version where value info in functions was introduced
83
+ )
84
+ _T = typing.TypeVar("_T", bound=Callable[..., Any])
85
+
86
+
87
+ class SerdeError(RuntimeError):
88
+ """Error during serialization or deserialization."""
89
+
90
+
91
+ def _capture_errors(arg_capturer: Callable[..., str]) -> Callable[[_T], _T]:
92
+ """Decorator to capture errors and display the stack."""
93
+
94
+ def decorator(func: _T) -> _T:
95
+ @functools.wraps(func)
96
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
97
+ try:
98
+ return func(*args, **kwargs)
99
+ except Exception as e:
100
+ raise SerdeError(
101
+ f"Error calling {func.__name__} with: {arg_capturer(*args, **kwargs)}"
102
+ ) from e
103
+
104
+ return wrapper # type: ignore
105
+
106
+ return decorator
107
+
108
+
109
+ def _little_endian_dtype(dtype) -> np.dtype:
110
+ """Create a small endian dtype on all platforms.
111
+
112
+ This is useful because ONNX always stores raw_data in small endian. On big
113
+ endian platforms, we still need to interpret the raw_data in small endian.
114
+ """
115
+ return np.dtype(dtype).newbyteorder("<")
116
+
117
+
118
+ def _unflatten_complex(
119
+ array: npt.NDArray[np.float32 | np.float64],
120
+ ) -> npt.NDArray[np.complex64 | np.complex128]:
121
+ """Convert the real representation of a complex dtype to the complex dtype."""
122
+ return array[::2] + 1j * array[1::2]
123
+
124
+
125
+ @typing.overload
126
+ def from_proto(proto: onnx.ModelProto) -> _core.Model: ... # type: ignore[overload-overlap]
127
+ @typing.overload
128
+ def from_proto(proto: onnx.GraphProto) -> _core.Graph: ... # type: ignore[overload-overlap]
129
+ @typing.overload
130
+ def from_proto(proto: onnx.NodeProto) -> _core.Node: ... # type: ignore[overload-overlap]
131
+ @typing.overload
132
+ def from_proto(proto: onnx.TensorProto) -> _protocols.TensorProtocol: ... # type: ignore[overload-overlap]
133
+ @typing.overload
134
+ def from_proto(proto: onnx.AttributeProto) -> _core.Attr: ... # type: ignore[overload-overlap]
135
+ @typing.overload
136
+ def from_proto(proto: onnx.ValueInfoProto) -> _core.Value: ... # type: ignore[overload-overlap]
137
+ @typing.overload
138
+ def from_proto(proto: onnx.TypeProto) -> _core.TypeAndShape: ... # type: ignore[overload-overlap]
139
+ @typing.overload
140
+ def from_proto(proto: onnx.FunctionProto) -> _core.Function: ... # type: ignore[overload-overlap]
141
+ @typing.overload
142
+ def from_proto(proto: onnx.TensorShapeProto) -> _core.Shape: ... # type: ignore[overload-overlap]
143
+ @typing.overload
144
+ def from_proto( # type: ignore[overload-overlap]
145
+ proto: onnx.TensorShapeProto.Dimension,
146
+ ) -> tuple[int | _core.SymbolicDim, str | None]: ...
147
+ @typing.overload
148
+ def from_proto(proto: Sequence[onnx.OperatorSetIdProto]) -> dict[str, int]: ... # type: ignore[overload-overlap]
149
+ @typing.overload
150
+ def from_proto(proto: Sequence[onnx.StringStringEntryProto]) -> dict[str, str]: ... # type: ignore[overload-overlap]
151
+
152
+
153
+ def from_proto(proto: object) -> object:
154
+ """Deserialize an ONNX proto message to an IR object."""
155
+ if isinstance(proto, onnx.ModelProto):
156
+ return deserialize_model(proto)
157
+ if isinstance(proto, onnx.GraphProto):
158
+ return deserialize_graph(proto)
159
+ if isinstance(proto, onnx.NodeProto):
160
+ return deserialize_node(proto)
161
+ if isinstance(proto, onnx.TensorProto):
162
+ return deserialize_tensor(proto)
163
+ if isinstance(proto, onnx.AttributeProto):
164
+ return deserialize_attribute(proto)
165
+ if isinstance(proto, onnx.ValueInfoProto):
166
+ return deserialize_value_info_proto(proto, None)
167
+ if isinstance(proto, onnx.TypeProto):
168
+ return _core.TypeAndShape(
169
+ deserialize_type_proto_for_type(proto),
170
+ deserialize_type_proto_for_shape(proto),
171
+ )
172
+ if isinstance(proto, onnx.FunctionProto):
173
+ return deserialize_function(proto)
174
+ if isinstance(proto, onnx.TensorShapeProto):
175
+ return deserialize_tensor_shape(proto)
176
+ if isinstance(proto, onnx.TensorShapeProto.Dimension):
177
+ return deserialize_dimension(proto)
178
+ if isinstance(proto, Sequence) and all(
179
+ isinstance(p, onnx.OperatorSetIdProto) for p in proto
180
+ ):
181
+ return deserialize_opset_import(proto)
182
+ if isinstance(proto, Sequence) and all(
183
+ isinstance(p, onnx.StringStringEntryProto) for p in proto
184
+ ):
185
+ return deserialize_metadata_props(proto)
186
+ raise NotImplementedError(
187
+ f"Deserialization of {type(proto)} in from_proto is not implemented. "
188
+ "Use a specific ir.serde.deserialize* function instead."
189
+ )
190
+
191
+
192
+ @typing.overload
193
+ def to_proto(ir_object: _protocols.ModelProtocol) -> onnx.ModelProto: ... # type: ignore[overload-overlap]
194
+ @typing.overload
195
+ def to_proto(ir_object: _protocols.GraphProtocol) -> onnx.GraphProto: ... # type: ignore[overload-overlap]
196
+ @typing.overload
197
+ def to_proto(ir_object: _protocols.NodeProtocol) -> onnx.NodeProto: ... # type: ignore[overload-overlap]
198
+ @typing.overload
199
+ def to_proto(ir_object: _protocols.TensorProtocol) -> onnx.TensorProto: ... # type: ignore[overload-overlap]
200
+ @typing.overload
201
+ def to_proto(ir_object: _protocols.AttributeProtocol) -> onnx.AttributeProto: ... # type: ignore[overload-overlap]
202
+ @typing.overload
203
+ def to_proto(ir_object: _protocols.ReferenceAttributeProtocol) -> onnx.AttributeProto: ... # type: ignore[overload-overlap]
204
+ @typing.overload
205
+ def to_proto(ir_object: _protocols.ValueProtocol) -> onnx.ValueInfoProto: ... # type: ignore[overload-overlap]
206
+ @typing.overload
207
+ def to_proto(ir_object: _protocols.TypeProtocol) -> onnx.TypeProto: ... # type: ignore[overload-overlap]
208
+ @typing.overload
209
+ def to_proto(ir_object: _protocols.FunctionProtocol) -> onnx.FunctionProto: ... # type: ignore[overload-overlap]
210
+ @typing.overload
211
+ def to_proto(ir_object: _protocols.GraphViewProtocol) -> onnx.GraphProto: ... # type: ignore[overload-overlap]
212
+
213
+
214
+ def to_proto(ir_object: object) -> object:
215
+ """Serialize an IR object to a proto."""
216
+ if isinstance(ir_object, _protocols.ModelProtocol):
217
+ return serialize_model(ir_object)
218
+ if isinstance(ir_object, _protocols.GraphProtocol):
219
+ return serialize_graph(ir_object)
220
+ if isinstance(ir_object, _protocols.NodeProtocol):
221
+ return serialize_node(ir_object)
222
+ if isinstance(ir_object, _protocols.TensorProtocol):
223
+ return serialize_tensor(ir_object)
224
+ if isinstance(ir_object, _protocols.ValueProtocol):
225
+ return serialize_value(ir_object)
226
+ if isinstance(ir_object, _protocols.AttributeProtocol):
227
+ return serialize_attribute(ir_object)
228
+ if isinstance(ir_object, _protocols.ReferenceAttributeProtocol):
229
+ return serialize_reference_attribute_into(onnx.AttributeProto(), ir_object)
230
+ if isinstance(ir_object, _protocols.TypeProtocol):
231
+ return serialize_type_into(onnx.TypeProto(), ir_object)
232
+ if isinstance(ir_object, _protocols.GraphViewProtocol):
233
+ return serialize_graph(ir_object)
234
+ if isinstance(ir_object, _protocols.FunctionProtocol):
235
+ return serialize_function(ir_object)
236
+ raise NotImplementedError(
237
+ f"Serialization of {type(ir_object)} in to_proto is not implemented. "
238
+ "Use a specific ir.serde.serialize* function instead."
239
+ )
240
+
241
+
242
+ class TensorProtoTensor(_core.TensorBase): # pylint: disable=too-many-ancestors
243
+ """A tensor initialized from a tensor proto."""
244
+
245
+ def __init__(self, proto: onnx.TensorProto) -> None:
246
+ self._proto = proto
247
+ self._metadata_props: dict[str, str] | None = deserialize_metadata_props(
248
+ proto.metadata_props
249
+ )
250
+ self._metadata: _metadata.MetadataStore | None = None
251
+
252
+ @property
253
+ def name(self) -> str:
254
+ return self._proto.name
255
+
256
+ @name.setter
257
+ def name(self, value: str | None) -> None:
258
+ if value is None:
259
+ self._proto.ClearField("name")
260
+ else:
261
+ self._proto.name = value
262
+
263
+ @property
264
+ def shape(self) -> _core.Shape:
265
+ return _core.Shape(self._proto.dims, frozen=True)
266
+
267
+ @property
268
+ def dtype(self) -> _enums.DataType:
269
+ return _enums.DataType(self._proto.data_type)
270
+
271
+ @property
272
+ def doc_string(self) -> str:
273
+ return self._proto.doc_string
274
+
275
+ @property
276
+ def raw(self) -> onnx.TensorProto:
277
+ return self._proto
278
+
279
+ def __repr__(self) -> str:
280
+ # It is a little hard to display the content when there can be types
281
+ # unsupported by numpy
282
+ # Preferably we should display some content when the tensor is small
283
+ return f"{self._repr_base()}(name={self.name!r})"
284
+
285
+ def __array__(self, dtype: Any = None) -> np.ndarray:
286
+ """Return the tensor as a numpy array, compatible with np.array."""
287
+ return self.numpy().__array__(dtype)
288
+
289
+ def __dlpack__(self, *, stream: Any = None) -> Any:
290
+ return self.numpy().__dlpack__(stream=stream)
291
+
292
+ def __dlpack_device__(self) -> tuple[int, int]:
293
+ return self.numpy().__dlpack_device__()
294
+
295
+ def numpy(self) -> np.ndarray:
296
+ """Return the tensor as a numpy array.
297
+
298
+ This is an improved version of onnx.numpy_helper.to_array.
299
+ It first reads the data using the dtype corresponding to the tensor
300
+ proto data field, then converts it to the correct dtype and shape.
301
+ Special cases are bfloat16, complex and int4 where we need to
302
+ reinterpret the data. Other types can simply be casted.
303
+
304
+ When the data type is not supported by numpy, the dtypes from the ``ml_dtype``
305
+ package are used. The values can be reinterpreted as bit representations
306
+ using the ``.view()`` method.
307
+
308
+ When the data type is a string, this method returns a numpy array
309
+ of bytes instead of a numpy array of strings, to follow the ONNX
310
+ specification.
311
+
312
+ External tensors are not supported by this class. Use
313
+ :class:`onnxscript.ir.ExternalTensor` instead.
314
+
315
+ Raises:
316
+ ValueError: If the data type is UNDEFINED.
317
+ """
318
+ dtype = self.dtype
319
+ if dtype == _enums.DataType.UNDEFINED:
320
+ raise ValueError("Cannot convert UNDEFINED tensor to numpy array.")
321
+ if self._proto.data_location == onnx.TensorProto.EXTERNAL:
322
+ raise ValueError(
323
+ "Cannot convert external tensor to numpy array. Use ir.ExternalTensor instead."
324
+ )
325
+
326
+ if self._proto.HasField("raw_data"):
327
+ array = np.frombuffer(self._proto.raw_data, dtype=dtype.numpy().newbyteorder("<"))
328
+ # Cannot return now, because we may need to unpack 4bit tensors
329
+ elif dtype == _enums.DataType.STRING:
330
+ return np.array(self._proto.string_data).reshape(self._proto.dims)
331
+ elif self._proto.int32_data:
332
+ array = np.array(self._proto.int32_data, dtype=_little_endian_dtype(np.int32))
333
+ if dtype in {_enums.DataType.FLOAT16, _enums.DataType.BFLOAT16}:
334
+ # Reinterpret the int32 as float16 or bfloat16
335
+ array = array.astype(np.uint16).view(dtype.numpy())
336
+ elif dtype in {
337
+ _enums.DataType.FLOAT8E4M3FN,
338
+ _enums.DataType.FLOAT8E4M3FNUZ,
339
+ _enums.DataType.FLOAT8E5M2,
340
+ _enums.DataType.FLOAT8E5M2FNUZ,
341
+ }:
342
+ array = array.astype(np.uint8).view(dtype.numpy())
343
+ elif self._proto.int64_data:
344
+ array = np.array(self._proto.int64_data, dtype=_little_endian_dtype(np.int64))
345
+ elif self._proto.uint64_data:
346
+ array = np.array(self._proto.uint64_data, dtype=_little_endian_dtype(np.uint64))
347
+ elif self._proto.float_data:
348
+ array = np.array(self._proto.float_data, dtype=_little_endian_dtype(np.float32))
349
+ if dtype == _enums.DataType.COMPLEX64:
350
+ array = _unflatten_complex(array)
351
+ elif self._proto.double_data:
352
+ array = np.array(self._proto.double_data, dtype=_little_endian_dtype(np.float64))
353
+ if dtype == _enums.DataType.COMPLEX128:
354
+ array = _unflatten_complex(array)
355
+ else:
356
+ # Empty tensor
357
+ if not self._proto.dims:
358
+ # When dims not precent and there is no data, we return an empty array
359
+ return np.array([], dtype=dtype.numpy())
360
+ else:
361
+ # Otherwise we return a size 0 array with the correct shape
362
+ return np.zeros(self._proto.dims, dtype=dtype.numpy())
363
+
364
+ if dtype == _enums.DataType.INT4:
365
+ return _type_casting.unpack_int4(array.astype(np.uint8), self._proto.dims)
366
+ elif dtype == _enums.DataType.UINT4:
367
+ return _type_casting.unpack_uint4(array.astype(np.uint8), self._proto.dims)
368
+ elif dtype == _enums.DataType.FLOAT4E2M1:
369
+ return _type_casting.unpack_float4e2m1(array.astype(np.uint8), self._proto.dims)
370
+ else:
371
+ # Otherwise convert to the correct dtype and reshape
372
+ # Note we cannot use view() here because the storage dtype may not be the same size as the target
373
+ return array.astype(dtype.numpy()).reshape(self._proto.dims)
374
+
375
+ def tobytes(self) -> bytes:
376
+ """Return the tensor as a byte string conformed to the ONNX specification, in little endian.
377
+
378
+ Raises:
379
+ ValueError: If the tensor is a string tensor or an external tensor.
380
+ ValueError: If the tensor is of UNDEFINED data type.
381
+ """
382
+ if self._proto.data_location == onnx.TensorProto.EXTERNAL:
383
+ raise ValueError(
384
+ "Cannot convert external tensor to bytes. Use ir.ExternalTensor instead."
385
+ )
386
+ if self.dtype == _enums.DataType.STRING:
387
+ raise ValueError("Cannot convert string tensor to bytes.")
388
+ if self.dtype == _enums.DataType.UNDEFINED:
389
+ raise ValueError("Cannot convert UNDEFINED tensor to bytes.")
390
+
391
+ if self._proto.HasField("raw_data"):
392
+ return self._proto.raw_data
393
+ if self._proto.float_data:
394
+ return np.array(
395
+ self._proto.float_data, dtype=_little_endian_dtype(np.float32)
396
+ ).tobytes()
397
+ if self._proto.int32_data:
398
+ array = np.array(self._proto.int32_data, dtype=np.int32)
399
+ if self.dtype in {
400
+ _enums.DataType.INT16,
401
+ _enums.DataType.UINT16,
402
+ _enums.DataType.FLOAT16,
403
+ _enums.DataType.BFLOAT16,
404
+ }:
405
+ return array.astype(_little_endian_dtype(np.uint16)).tobytes()
406
+ if self.dtype in {
407
+ _enums.DataType.INT8,
408
+ _enums.DataType.UINT8,
409
+ _enums.DataType.BOOL,
410
+ _enums.DataType.FLOAT8E4M3FN,
411
+ _enums.DataType.FLOAT8E4M3FNUZ,
412
+ _enums.DataType.FLOAT8E5M2,
413
+ _enums.DataType.FLOAT8E5M2FNUZ,
414
+ _enums.DataType.INT4,
415
+ _enums.DataType.UINT4,
416
+ _enums.DataType.FLOAT4E2M1,
417
+ }:
418
+ # uint4 and int4 values are already packed, even when stored as int32
419
+ # so we don't need to pack them again
420
+ return array.astype(_little_endian_dtype(np.uint8)).tobytes()
421
+ assert self.dtype == _enums.DataType.INT32
422
+ return array.tobytes()
423
+ if self._proto.int64_data:
424
+ return np.array(
425
+ self._proto.int64_data, dtype=_little_endian_dtype(np.int64)
426
+ ).tobytes()
427
+ if self._proto.double_data:
428
+ return np.array(
429
+ self._proto.double_data, dtype=_little_endian_dtype(np.float64)
430
+ ).tobytes()
431
+ if self._proto.uint64_data:
432
+ array = np.array(self._proto.uint64_data, dtype=_little_endian_dtype(np.uint64))
433
+ if self.dtype == _enums.DataType.UINT32:
434
+ return array.astype(_little_endian_dtype(np.uint32)).tobytes()
435
+ assert self.dtype == _enums.DataType.UINT64
436
+ return array.tobytes()
437
+ # The repeating fields can be empty and still valid.
438
+ # For example, int32_data can be empty and still be a valid tensor.
439
+ return b""
440
+
441
+ @property
442
+ def meta(self) -> _metadata.MetadataStore:
443
+ """The metadata store for intermediate analysis.
444
+
445
+ Write to the :attr:`metadata_props` if you would like the metadata to be serialized
446
+ to the ONNX proto.
447
+ """
448
+ if self._metadata is None:
449
+ self._metadata = _metadata.MetadataStore()
450
+ return self._metadata
451
+
452
+ @property
453
+ def metadata_props(self) -> dict[str, str]:
454
+ if self._metadata_props is None:
455
+ self._metadata_props = {}
456
+ return self._metadata_props
457
+
458
+
459
+ def _get_field(proto: Any, field: str) -> Any:
460
+ if proto.HasField(field):
461
+ return getattr(proto, field)
462
+ return None
463
+
464
+
465
+ # Deserialization
466
+
467
+
468
+ def deserialize_opset_import(
469
+ protos: Sequence[onnx.OperatorSetIdProto],
470
+ ) -> dict[str, int]:
471
+ return {opset.domain: opset.version for opset in protos}
472
+
473
+
474
+ def _parse_experimental_function_value_info_name(
475
+ name: str,
476
+ ) -> tuple[str, str, str] | None:
477
+ """Get the function domain, name and value name if the value info is for a function.
478
+
479
+ The experimental format is:
480
+ {function_domain}::{function_name}/{value_name}
481
+
482
+ Args:
483
+ name: The name stored in the value info.
484
+
485
+ Returns:
486
+ A tuple of the function domain, function name and value name if the value info is for a function.
487
+ None otherwise.
488
+ """
489
+ parts = name.split("/")
490
+ expected_parts = 2
491
+ if len(parts) != expected_parts:
492
+ return None
493
+ function, value_name = parts
494
+ parts = function.split("::")
495
+ if len(parts) != expected_parts:
496
+ return None
497
+ # NOTE: There will not be overload because overloads are introduced in ONNX IR v10, which also
498
+ # introduces the ValueInfoProto for functions
499
+ function_domain, function_name = parts
500
+ return function_domain, function_name, value_name
501
+
502
+
503
+ def deserialize_model(proto: onnx.ModelProto) -> _core.Model:
504
+ graph = _deserialize_graph(proto.graph, [])
505
+ graph.opset_imports.update(deserialize_opset_import(proto.opset_import))
506
+
507
+ functions = []
508
+ for func in proto.functions:
509
+ functions.append(deserialize_function(func))
510
+
511
+ model = _core.Model(
512
+ graph,
513
+ ir_version=proto.ir_version,
514
+ producer_name=_get_field(proto, "producer_name"),
515
+ producer_version=_get_field(proto, "producer_version"),
516
+ domain=_get_field(proto, "domain"),
517
+ model_version=_get_field(proto, "model_version"),
518
+ doc_string=_get_field(proto, "doc_string"),
519
+ functions=functions,
520
+ meta_data_props=deserialize_metadata_props(proto.metadata_props),
521
+ )
522
+
523
+ # Handle experimental value info for functions created by the dynamo exporter in IR version 9
524
+ if model.ir_version < _FUNCTION_VALUE_INFO_SUPPORTED_VERSION:
525
+ _deserialized_experimental_value_info_for_function_ir9(
526
+ model.functions, proto.graph.value_info
527
+ )
528
+
529
+ return model
530
+
531
+
532
+ def _deserialized_experimental_value_info_for_function_ir9(
533
+ functions: Mapping[_protocols.OperatorIdentifier, _core.Function],
534
+ value_info_protos: Sequence[onnx.ValueInfoProto],
535
+ ) -> None:
536
+ """Deserialize value info for functions when they are stored in an experimental format.
537
+
538
+ The experimental format is:
539
+ {function_domain}::{function_name}/{value_name}
540
+ """
541
+ # Parse value info for functions from the main graph
542
+ function_value_value_info_mapping: collections.defaultdict[
543
+ _protocols.OperatorIdentifier,
544
+ dict[str, onnx.ValueInfoProto],
545
+ ] = collections.defaultdict(dict)
546
+ for value_info_proto in value_info_protos:
547
+ if (
548
+ parsed := _parse_experimental_function_value_info_name(value_info_proto.name)
549
+ ) is None:
550
+ continue
551
+ function_domain, function_name, value_name = parsed
552
+ function_overload = ""
553
+ # TODO(justinchuby): Create a constructor for OperatorIdentifier so we don't create tuples manually
554
+ function_id = (function_domain, function_name, function_overload)
555
+ function = functions.get(function_id)
556
+ if function is None:
557
+ # Function not found
558
+ logger.debug(
559
+ "Function with ID '%s' not found in model functions. Value info '%s' will be ignored.",
560
+ function_id,
561
+ value_info_proto.name,
562
+ )
563
+ continue
564
+ function_value_value_info_mapping[function_id][value_name] = value_info_proto
565
+ for function_id, function in functions.items():
566
+ for input in function.inputs:
567
+ if input.name in function_value_value_info_mapping[function_id]:
568
+ deserialize_value_info_proto(
569
+ function_value_value_info_mapping[function_id][input.name], input
570
+ )
571
+ for node in function:
572
+ for output in node.outputs:
573
+ if output.name in function_value_value_info_mapping[function_id]:
574
+ deserialize_value_info_proto(
575
+ function_value_value_info_mapping[function_id][output.name],
576
+ output,
577
+ )
578
+ # The function outputs are handled as well because they are also node outputs
579
+
580
+
581
+ def deserialize_graph(proto: onnx.GraphProto) -> _core.Graph:
582
+ """Deserialize a graph proto, recursively if needed.
583
+
584
+ Args:
585
+ proto: The graph proto to deserialize.
586
+
587
+ Returns:
588
+ IR Graph.
589
+ """
590
+ return _deserialize_graph(proto, [])
591
+
592
+
593
+ @_capture_errors(lambda proto, scoped_values: proto.name)
594
+ def _deserialize_graph(
595
+ proto: onnx.GraphProto, scoped_values: list[dict[str, _core.Value]]
596
+ ) -> _core.Graph:
597
+ """Deserialize a graph proto, recursively if needed.
598
+
599
+ Args:
600
+ proto: The graph proto to deserialize.
601
+ scoped_values: A list of dictionaries mapping value names to their corresponding Value objects.
602
+ Every time we enter a new graph, a new scope is created and appended to this list to include
603
+ all values defined in the scope.
604
+ scoped_value_info: A list of dictionaries mapping value names to their corresponding ValueInfoProto.
605
+
606
+ Returns:
607
+ IR Graph.
608
+ """
609
+ # Create values for initializers and inputs
610
+ initializer_tensors = [deserialize_tensor(tensor) for tensor in proto.initializer]
611
+ inputs = [_core.Input(info.name) for info in proto.input]
612
+ for info, value in zip(proto.input, inputs):
613
+ deserialize_value_info_proto(info, value)
614
+
615
+ # Initialize the values dictionary for this graph scope with the inputs and initializers
616
+ values: dict[str, _core.Value] = {v.name: v for v in inputs} # type: ignore[misc]
617
+ scoped_values.append(values)
618
+ initializer_values = []
619
+ for tensor in initializer_tensors:
620
+ if tensor.name in values:
621
+ # The initializer is for an input
622
+ initializer_value = values[tensor.name]
623
+ initializer_value.const_value = tensor
624
+ else:
625
+ # The initializer is for some other value. Create this value first
626
+ initializer_value = _core.Value(
627
+ None,
628
+ index=None,
629
+ name=tensor.name,
630
+ # TODO(justinchuby): Fix type hinting for shape and dtype
631
+ shape=tensor.shape, # type: ignore
632
+ type=_core.TensorType(tensor.dtype),
633
+ const_value=tensor,
634
+ )
635
+ values[tensor.name] = initializer_value # type: ignore[index]
636
+ initializer_values.append(initializer_value)
637
+
638
+ # Add ValueInfos for this graph scope
639
+ value_info = {info.name: info for info in proto.value_info}
640
+
641
+ # Deserialize nodes with all known values
642
+ nodes = [_deserialize_node(node, scoped_values, value_info) for node in proto.node]
643
+
644
+ # Fill in values for graph outputs
645
+ outputs = [deserialize_value_info_proto(info, values[info.name]) for info in proto.output]
646
+ scoped_values.pop()
647
+ return _core.Graph(
648
+ inputs,
649
+ outputs,
650
+ nodes=nodes,
651
+ initializers=initializer_values,
652
+ doc_string=_get_field(proto, "doc_string"),
653
+ name=_get_field(proto, "name"),
654
+ metadata_props=deserialize_metadata_props(proto.metadata_props),
655
+ )
656
+
657
+
658
+ @_capture_errors(lambda proto: proto.name)
659
+ def deserialize_function(proto: onnx.FunctionProto) -> _core.Function:
660
+ inputs = [_core.Input(name) for name in proto.input]
661
+ values: dict[str, _core.Value] = {v.name: v for v in inputs} # type: ignore[misc]
662
+ value_info = {info.name: info for info in getattr(proto, "value_info", [])}
663
+
664
+ # TODO(justinchuby): Handle unsorted nodes
665
+ nodes = [_deserialize_node(node, [values], value_info=value_info) for node in proto.node]
666
+ outputs = [values[name] for name in proto.output]
667
+ graph = _core.Graph(
668
+ inputs,
669
+ outputs,
670
+ nodes=nodes,
671
+ initializers=(),
672
+ doc_string=_get_field(proto, "doc_string"),
673
+ opset_imports=deserialize_opset_import(proto.opset_import),
674
+ name=(
675
+ f"{proto.name}_{proto.domain}" + f"__{proto.overload}"
676
+ if hasattr(proto, "overload") and proto.overload
677
+ else ""
678
+ ),
679
+ )
680
+ attributes = [_deserialize_attribute(attr, []) for attr in proto.attribute_proto]
681
+ # Attributes without defaults
682
+ attributes += [
683
+ _core.Attr(name, _enums.AttributeType.UNDEFINED, None) for name in proto.attribute
684
+ ]
685
+ return _core.Function(
686
+ domain=proto.domain,
687
+ name=proto.name,
688
+ overload=getattr(proto, "overload", ""),
689
+ graph=graph,
690
+ attributes=typing.cast(List[_core.Attr], attributes),
691
+ metadata_props=deserialize_metadata_props(proto.metadata_props),
692
+ )
693
+
694
+
695
+ @_capture_errors(lambda proto, value: str(proto))
696
+ def deserialize_value_info_proto(
697
+ proto: onnx.ValueInfoProto, value: _core.Value | None
698
+ ) -> _core.Value:
699
+ if value is None:
700
+ value = _core.Value(name=proto.name)
701
+ value.shape = deserialize_type_proto_for_shape(proto.type)
702
+ value.type = deserialize_type_proto_for_type(proto.type)
703
+ metadata_props = deserialize_metadata_props(proto.metadata_props)
704
+ if metadata_props is not None:
705
+ value.metadata_props.update(metadata_props)
706
+ value.doc_string = _get_field(proto, "doc_string")
707
+ return value
708
+
709
+
710
+ @_capture_errors(str)
711
+ def deserialize_tensor_shape(proto: onnx.TensorShapeProto) -> _core.Shape:
712
+ # This logic handles when the shape is [] as well
713
+ dim_protos = proto.dim
714
+ deserialized_dim_denotations = [
715
+ deserialize_dimension(dim_proto) for dim_proto in dim_protos
716
+ ]
717
+ dims = [dim for dim, _ in deserialized_dim_denotations]
718
+ denotations = [denotation for _, denotation in deserialized_dim_denotations]
719
+ return _core.Shape(dims, denotations=denotations, frozen=True)
720
+
721
+
722
+ @_capture_errors(str)
723
+ def deserialize_type_proto_for_shape(proto: onnx.TypeProto) -> _core.Shape | None:
724
+ if proto.HasField("tensor_type"):
725
+ if (shape_proto := _get_field(proto.tensor_type, "shape")) is None:
726
+ return None
727
+ return deserialize_tensor_shape(shape_proto)
728
+ if proto.HasField("sparse_tensor_type"):
729
+ if (shape_proto := _get_field(proto.sparse_tensor_type, "shape")) is None:
730
+ return None
731
+ return deserialize_tensor_shape(shape_proto)
732
+ if proto.HasField("sequence_type"):
733
+ if (elem_type := _get_field(proto.sequence_type, "elem_type")) is None:
734
+ return None
735
+ return deserialize_type_proto_for_shape(elem_type)
736
+ if proto.HasField("optional_type"):
737
+ if (elem_type := _get_field(proto.optional_type, "elem_type")) is None:
738
+ return None
739
+ return deserialize_type_proto_for_shape(elem_type)
740
+ if proto.HasField("map_type"):
741
+ # TODO(justinchuby): Do we need to support map types?
742
+ raise NotImplementedError(f"Map types are not supported yet. {_PLEASE_CONTRIBUTE}")
743
+
744
+ return None
745
+
746
+
747
+ @_capture_errors(str)
748
+ def deserialize_type_proto_for_type(
749
+ proto: onnx.TypeProto,
750
+ ) -> _protocols.TypeProtocol | None:
751
+ denotation = _get_field(proto, "denotation")
752
+ if proto.HasField("tensor_type"):
753
+ if (elem_type := _get_field(proto.tensor_type, "elem_type")) is None:
754
+ return None
755
+ return _core.TensorType(_enums.DataType(elem_type), denotation=denotation)
756
+ if proto.HasField("sparse_tensor_type"):
757
+ if (elem_type := _get_field(proto.sparse_tensor_type, "elem_type")) is None:
758
+ return None
759
+ return _core.SparseTensorType(_enums.DataType(elem_type), denotation=denotation)
760
+ if proto.HasField("sequence_type"):
761
+ # FIXME(justinchuby): Allow nested types being None
762
+ if (elem_type := _get_field(proto.sequence_type, "elem_type")) is None:
763
+ raise ValueError(f"SequenceTypeProto must have elem_type set: {proto}")
764
+ nested_type = deserialize_type_proto_for_type(elem_type)
765
+ if nested_type is None:
766
+ raise ValueError(f"SequenceType must have elem_type set: {proto}")
767
+ return _core.SequenceType(nested_type, denotation=denotation)
768
+ if proto.HasField("optional_type"):
769
+ # FIXME(justinchuby): Allow nested types being None
770
+ if (elem_type := _get_field(proto.optional_type, "elem_type")) is None:
771
+ raise ValueError(f"SequenceTypeProto must have elem_type set: {proto}")
772
+ nested_type = deserialize_type_proto_for_type(elem_type)
773
+ if nested_type is None:
774
+ raise ValueError(f"SequenceType must have elem_type set: {proto}")
775
+ return _core.OptionalType(nested_type, denotation=denotation)
776
+ if proto.HasField("map_type"):
777
+ # TODO(justinchuby): Do we need to support map types?
778
+ raise NotImplementedError(f"Map types are not supported yet. {_PLEASE_CONTRIBUTE}")
779
+
780
+ return None
781
+
782
+
783
+ @_capture_errors(str)
784
+ def deserialize_dimension(
785
+ proto: onnx.TensorShapeProto.Dimension,
786
+ ) -> tuple[int | _core.SymbolicDim, str | None]:
787
+ """Deserialize a dimension proto into (dimension, denotation).
788
+
789
+ Args:
790
+ proto: The dimension proto to deserialize.
791
+
792
+ Returns:
793
+ A tuple of the dimension and its denotation.
794
+ """
795
+ value_field = proto.WhichOneof("value")
796
+ denotation = _get_field(proto, "denotation")
797
+ if value_field is not None:
798
+ value = getattr(proto, value_field)
799
+ if value_field == "dim_value":
800
+ return value, denotation
801
+ if value_field == "dim_param":
802
+ return _core.SymbolicDim(value), denotation
803
+ return _core.SymbolicDim(None), denotation
804
+
805
+
806
+ @_capture_errors(lambda proto, base_path: proto.name)
807
+ def deserialize_tensor(
808
+ proto: onnx.TensorProto, base_path: str | os.PathLike = ""
809
+ ) -> _protocols.TensorProtocol:
810
+ # TODO: Sanitize base_path
811
+ if proto.data_location == onnx.TensorProto.EXTERNAL:
812
+ external_info = onnx.external_data_helper.ExternalDataInfo(proto)
813
+ return _core.ExternalTensor(
814
+ external_info.location,
815
+ offset=external_info.offset,
816
+ length=external_info.length,
817
+ dtype=_enums.DataType(proto.data_type),
818
+ base_dir=base_path,
819
+ name=_get_field(proto, "name"),
820
+ shape=_core.Shape(proto.dims),
821
+ doc_string=_get_field(proto, "doc_string"),
822
+ metadata_props=deserialize_metadata_props(proto.metadata_props),
823
+ )
824
+ if proto.data_type == _enums.DataType.STRING:
825
+ name = _get_field(proto, "name")
826
+ doc_string = _get_field(proto, "doc_string")
827
+ metadata_props = deserialize_metadata_props(proto.metadata_props)
828
+ return _core.StringTensor(
829
+ proto.string_data,
830
+ shape=_core.Shape(proto.dims),
831
+ name=name,
832
+ doc_string=doc_string,
833
+ metadata_props=metadata_props,
834
+ )
835
+ return TensorProtoTensor(proto)
836
+
837
+
838
+ def deserialize_metadata_props(
839
+ proto: Sequence[onnx.StringStringEntryProto],
840
+ ) -> dict[str, str] | None:
841
+ if len(proto) == 0:
842
+ # Avoid creating an empty dictionary to save memory
843
+ return None
844
+ return {entry.key: entry.value for entry in proto}
845
+
846
+
847
+ def deserialize_attribute(proto: onnx.AttributeProto) -> _core.Attr | _core.RefAttr:
848
+ return _deserialize_attribute(proto, [])
849
+
850
+
851
+ @_capture_errors(lambda proto, scoped_values: str(proto))
852
+ def _deserialize_attribute(
853
+ proto: onnx.AttributeProto, scoped_values: list[dict[str, _core.Value]]
854
+ ) -> _core.Attr | _core.RefAttr:
855
+ name = proto.name
856
+ doc_string = _get_field(proto, "doc_string")
857
+ type_ = _enums.AttributeType(proto.type)
858
+ ref_attr_name = _get_field(proto, "ref_attr_name")
859
+ if ref_attr_name:
860
+ return _core.RefAttr(name, ref_attr_name, type_, doc_string=doc_string)
861
+
862
+ if type_ == _enums.AttributeType.INT:
863
+ return _core.AttrInt64(name, proto.i, doc_string=doc_string)
864
+ if type_ == _enums.AttributeType.FLOAT:
865
+ return _core.AttrFloat32(name, proto.f, doc_string=doc_string)
866
+ if type_ == _enums.AttributeType.STRING:
867
+ return _core.AttrString(name, proto.s.decode("utf-8"), doc_string=doc_string)
868
+ if type_ == _enums.AttributeType.INTS:
869
+ return _core.AttrInt64s(name, proto.ints, doc_string=doc_string)
870
+ if type_ == _enums.AttributeType.FLOATS:
871
+ return _core.AttrFloat32s(name, proto.floats, doc_string=doc_string)
872
+ if type_ == _enums.AttributeType.STRINGS:
873
+ return _core.AttrStrings(
874
+ name, [s.decode("utf-8") for s in proto.strings], doc_string=doc_string
875
+ )
876
+ if type_ == _enums.AttributeType.TENSOR:
877
+ return _core.AttrTensor(name, deserialize_tensor(proto.t), doc_string=doc_string)
878
+ if type_ == _enums.AttributeType.GRAPH:
879
+ return _core.AttrGraph(
880
+ name, _deserialize_graph(proto.g, scoped_values), doc_string=doc_string
881
+ )
882
+ if type_ == _enums.AttributeType.TENSORS:
883
+ return _core.AttrTensors(
884
+ name,
885
+ [deserialize_tensor(t) for t in proto.tensors],
886
+ doc_string=doc_string,
887
+ )
888
+ if type_ == _enums.AttributeType.GRAPHS:
889
+ return _core.AttrGraphs(
890
+ name,
891
+ [_deserialize_graph(g, scoped_values) for g in proto.graphs],
892
+ doc_string=doc_string,
893
+ )
894
+ if type_ == _enums.AttributeType.SPARSE_TENSOR:
895
+ raise NotImplementedError(
896
+ f"Sparse tensors are not supported yet. {_PLEASE_CONTRIBUTE}"
897
+ )
898
+ if type_ == _enums.AttributeType.SPARSE_TENSORS:
899
+ raise NotImplementedError(
900
+ f"Sparse tensors are not supported yet. {_PLEASE_CONTRIBUTE}"
901
+ )
902
+ if type_ == _enums.AttributeType.TYPE_PROTO:
903
+ ir_type = deserialize_type_proto_for_type(proto.tp)
904
+ shape = deserialize_type_proto_for_shape(proto.tp)
905
+ return _core.AttrTypeProto(
906
+ name, _core.TypeAndShape(ir_type, shape), doc_string=doc_string
907
+ )
908
+ if type_ == _enums.AttributeType.TYPE_PROTOS:
909
+ type_and_shapes = []
910
+ for type_proto in proto.type_protos:
911
+ ir_type = deserialize_type_proto_for_type(type_proto)
912
+ shape = deserialize_type_proto_for_shape(type_proto)
913
+ type_and_shapes.append(_core.TypeAndShape(ir_type, shape))
914
+ return _core.AttrTypeProtos(name, type_and_shapes, doc_string=doc_string)
915
+ if type_ == _enums.AttributeType.UNDEFINED:
916
+ return _core.Attr(name, type_, None, doc_string=doc_string)
917
+ raise ValueError(f"Unsupported attribute type: '{type_}'")
918
+
919
+
920
+ def deserialize_node(proto: onnx.NodeProto) -> _core.Node:
921
+ return _deserialize_node(proto, scoped_values=[], value_info={})
922
+
923
+
924
+ @_capture_errors(lambda proto, scoped_values, value_info: str(proto))
925
+ def _deserialize_node(
926
+ proto: onnx.NodeProto,
927
+ scoped_values: list[dict[str, _core.Value]],
928
+ value_info: dict[str, onnx.ValueInfoProto],
929
+ ) -> _core.Node:
930
+ node_inputs: list[_core.Value | None] = []
931
+ for input_name in proto.input:
932
+ if input_name == "":
933
+ # Empty input
934
+ node_inputs.append(None)
935
+ continue
936
+
937
+ # Find the input in all value scopes
938
+ found = False
939
+ for values in reversed(scoped_values):
940
+ if input_name not in values:
941
+ continue
942
+ node_inputs.append(values[input_name])
943
+ found = True
944
+ del values # Remove the reference so it is not used by mistake
945
+ break
946
+ if not found:
947
+ # If the input is not found, we know the graph may be unsorted and
948
+ # the input may be a supposed-to-be initializer or an output of a node that comes later.
949
+ # Here we create the value with the name and add it to the current scope.
950
+ # Nodes need to check the value pool for potentially initialized outputs
951
+ logger.warning(
952
+ "Input '%s' of node '%s(%s::%s:%s)' not found in any scope. "
953
+ "The graph may be unsorted. Creating a new input (current depth: %s) .",
954
+ input_name,
955
+ proto.name,
956
+ proto.domain,
957
+ proto.op_type,
958
+ getattr(proto, "overload", ""),
959
+ len(scoped_values),
960
+ )
961
+ if len(scoped_values) > 1:
962
+ logger.warning(
963
+ "Caveat: The value is created in the subgraph. If "
964
+ "the node is referencing a value that is not in the current graph, "
965
+ "it is impossible to create it in the correct scope.",
966
+ )
967
+ value = _core.Value(name=input_name)
968
+ # Fill in shape/type information if they exist
969
+ if input_name in value_info:
970
+ deserialize_value_info_proto(value_info[input_name], value)
971
+ node_inputs.append(value)
972
+ # We can only create the value in the current scope. If the subgraph is
973
+ # referencing a value that is not in the current scope, it is impossible
974
+ # to create it in the correct scope.
975
+ scoped_values[-1][input_name] = value
976
+
977
+ # Build the output values for the node.
978
+ node_outputs: list[_core.Value] = []
979
+ for output_name in proto.output:
980
+ if output_name == "":
981
+ # Empty output
982
+ node_outputs.append(_core.Value(name=""))
983
+ continue
984
+
985
+ # 1. When the graph is unsorted, we may be able to find the output already created
986
+ # as an input to some other nodes in the current scope.
987
+ # Note that a value is always owned by the producing node. Even though a value
988
+ # can be created when parsing inputs of other nodes, the new node created here
989
+ # that produces the value will assume ownership. It is then impossible to transfer
990
+ # the ownership to any other node.
991
+
992
+ # The output can only be found in the current scope. It is impossible for
993
+ # a node to produce an output that is not in its own scope.
994
+ current_scope = scoped_values[-1]
995
+ if output_name in current_scope:
996
+ value = current_scope[output_name]
997
+ else:
998
+ # 2. Common scenario: the graph is sorted and this is the first time we see the output.
999
+ # Create the value and add it to the current scope.
1000
+ value = _core.Value(name=output_name)
1001
+ current_scope[output_name] = value
1002
+ # Fill in shape/type information if they exist
1003
+ if output_name in value_info:
1004
+ deserialize_value_info_proto(value_info[output_name], value)
1005
+ else:
1006
+ logger.debug(
1007
+ "ValueInfoProto not found for output '%s' in node '%s' of type '%s'",
1008
+ output_name,
1009
+ proto.name,
1010
+ proto.op_type,
1011
+ )
1012
+ node_outputs.append(value)
1013
+ return _core.Node(
1014
+ proto.domain,
1015
+ proto.op_type,
1016
+ node_inputs,
1017
+ [_deserialize_attribute(a, scoped_values) for a in proto.attribute],
1018
+ overload=getattr(proto, "overload", ""),
1019
+ outputs=node_outputs,
1020
+ name=proto.name,
1021
+ doc_string=_get_field(proto, "doc_string"),
1022
+ metadata_props=deserialize_metadata_props(proto.metadata_props),
1023
+ )
1024
+
1025
+
1026
+ # Serialization
1027
+
1028
+
1029
+ def serialize_model(model: _protocols.ModelProtocol) -> onnx.ModelProto:
1030
+ return serialize_model_into(onnx.ModelProto(), from_=model)
1031
+
1032
+
1033
+ @_capture_errors(
1034
+ lambda model_proto, from_: (
1035
+ f"ir_version={from_.ir_version}, producer_name={from_.producer_name}, "
1036
+ f"producer_version={from_.producer_version}, domain={from_.domain}, "
1037
+ )
1038
+ )
1039
+ def serialize_model_into(
1040
+ model_proto: onnx.ModelProto, from_: _protocols.ModelProtocol
1041
+ ) -> onnx.ModelProto:
1042
+ """Serialize an IR model to an ONNX model proto."""
1043
+ model_proto.ir_version = from_.ir_version
1044
+ if from_.producer_name:
1045
+ model_proto.producer_name = from_.producer_name
1046
+ if from_.producer_version:
1047
+ model_proto.producer_version = from_.producer_version
1048
+ if from_.domain:
1049
+ model_proto.domain = from_.domain
1050
+ if from_.model_version:
1051
+ model_proto.model_version = from_.model_version
1052
+ if from_.doc_string:
1053
+ model_proto.doc_string = from_.doc_string
1054
+ # Sort names for deterministic serialization
1055
+ _serialize_opset_imports_into(model_proto.opset_import, from_.opset_imports)
1056
+ if from_.metadata_props:
1057
+ _serialize_metadata_props_into(model_proto.metadata_props, from_.metadata_props)
1058
+ serialize_graph_into(model_proto.graph, from_.graph)
1059
+
1060
+ create_value_info_in_functions = from_.ir_version >= _FUNCTION_VALUE_INFO_SUPPORTED_VERSION
1061
+ for func in from_.functions.values():
1062
+ serialize_function_into(
1063
+ model_proto.functions.add(),
1064
+ from_=func,
1065
+ create_value_info=create_value_info_in_functions,
1066
+ )
1067
+ if not create_value_info_in_functions:
1068
+ # Create them in the main graph instead
1069
+ _serialize_experimental_value_info_for_function_ir9_into(model_proto.graph, func)
1070
+ return model_proto
1071
+
1072
+
1073
+ def _should_create_value_info_for_value(value: _protocols.ValueProtocol) -> bool:
1074
+ """Check if value info should be created for a value.
1075
+
1076
+ Args:
1077
+ value: The value to check.
1078
+
1079
+ Returns:
1080
+ True if value info should be created for the value.
1081
+ """
1082
+ # No need to serialize value info if it is not set
1083
+ if value.shape is None and value.type is None:
1084
+ return False
1085
+ if not value.name:
1086
+ logger.debug("Did not serialize '%s' because its name is empty", value)
1087
+ return False
1088
+ return True
1089
+
1090
+
1091
+ def _serialize_experimental_value_info_for_function_ir9_into(
1092
+ graph_proto: onnx.GraphProto, function: _protocols.FunctionProtocol
1093
+ ) -> None:
1094
+ """Serialize value info for functions in an experimental format for IR version 9.
1095
+
1096
+ Because IRv9 and older does not have ValueInfoProto for functions, we give the value info
1097
+ special names and store them in the main graph instead.
1098
+
1099
+ The experimental format is:
1100
+ {function_domain}::{function_name}/{value_name}
1101
+
1102
+ Args:
1103
+ graph_proto: The graph proto to create ValueInfoProto in.
1104
+ function: The function to serialize.
1105
+ """
1106
+ # TODO(justinchuby): In the future, we can decide if it is a good idea to simply iterate over
1107
+ # all values in the function and call serialize_value_into instead.
1108
+ function_qualified_name = f"{function.domain}::{function.name}"
1109
+
1110
+ def format_name(value_name: str) -> str:
1111
+ return f"{function_qualified_name}/{value_name}"
1112
+
1113
+ for input in function.inputs:
1114
+ if not input.name:
1115
+ logger.warning(
1116
+ "Function '%s': Value name not set for function input: %s",
1117
+ function_qualified_name,
1118
+ input,
1119
+ )
1120
+ continue
1121
+ if not _should_create_value_info_for_value(input):
1122
+ # No need to serialize value info if it is not set
1123
+ continue
1124
+ serialize_value_into(graph_proto.value_info.add(), input, name=format_name(input.name))
1125
+ for node in function:
1126
+ for node_output in node.outputs:
1127
+ if not node_output.name:
1128
+ logger.warning(
1129
+ "Function '%s': Value name not set for node output: %s",
1130
+ function_qualified_name,
1131
+ node_output,
1132
+ )
1133
+ continue
1134
+ if not _should_create_value_info_for_value(node_output):
1135
+ # No need to serialize value info if it is not set
1136
+ continue
1137
+ serialize_value_into(
1138
+ graph_proto.value_info.add(),
1139
+ node_output,
1140
+ name=format_name(node_output.name),
1141
+ )
1142
+
1143
+
1144
+ def _serialize_opset_imports_into(
1145
+ opset_ids: proto_containers.RepeatedCompositeFieldContainer[onnx.OperatorSetIdProto],
1146
+ from_: Mapping[str, int],
1147
+ ) -> None:
1148
+ """Serialize opset imports into a repeated field of OperatorSetId protos.
1149
+
1150
+ Args:
1151
+ opset_ids: The repeated field to serialize into.
1152
+ from_: The mapping of opset domains to versions to serialize.
1153
+ """
1154
+ # Sort names for deterministic serialization
1155
+ for domain, version in from_.items():
1156
+ opset_ids.add(domain=domain, version=version)
1157
+
1158
+
1159
+ def _serialize_metadata_props_into(
1160
+ string_string_entries: proto_containers.RepeatedCompositeFieldContainer[
1161
+ onnx.StringStringEntryProto
1162
+ ],
1163
+ from_: Mapping[str, str],
1164
+ ) -> None:
1165
+ """Serialize metadata properties into a repeated field of string-string entries.
1166
+
1167
+ Args:
1168
+ string_string_entries: The repeated field to serialize into.
1169
+ from_: The mapping of metadata properties to serialize.
1170
+ """
1171
+ # Sort names for deterministic serialization
1172
+ for key in sorted(from_):
1173
+ string_string_entries.add(key=key, value=from_[key])
1174
+
1175
+
1176
+ def serialize_graph(
1177
+ graph: _protocols.GraphProtocol | _protocols.GraphViewProtocol,
1178
+ ) -> onnx.GraphProto:
1179
+ """Serializes the given graph into an :class:`onnx.GraphProto`.
1180
+
1181
+ When the graph initializers do not have `const_value` set, they will be skipped.
1182
+
1183
+ Args:
1184
+ graph: The graph to be serialized.
1185
+
1186
+ Returns:
1187
+ The serialized ONNX GraphProto object.
1188
+ """
1189
+ graph_proto = onnx.GraphProto()
1190
+ serialize_graph_into(graph_proto, from_=graph)
1191
+ return graph_proto
1192
+
1193
+
1194
+ @_capture_errors(
1195
+ lambda graph_proto, from_: (
1196
+ f"name={from_.name}, doc_string={from_.doc_string}, "
1197
+ f"len(inputs)={len(from_.inputs)}, len(initializers)={len(from_.initializers)}, "
1198
+ f"len(nodes)={len(from_)}, len(outputs)={len(from_.outputs)}, metadata_props={from_.metadata_props}"
1199
+ )
1200
+ )
1201
+ def serialize_graph_into(
1202
+ graph_proto: onnx.GraphProto,
1203
+ from_: _protocols.GraphProtocol | _protocols.GraphViewProtocol,
1204
+ ) -> None:
1205
+ if from_.name:
1206
+ graph_proto.name = from_.name
1207
+ if from_.doc_string:
1208
+ graph_proto.doc_string = from_.doc_string
1209
+ for input_ in from_.inputs:
1210
+ serialize_value_into(graph_proto.input.add(), input_)
1211
+ # TODO(justinchuby): Support sparse_initializer
1212
+ for initializer in from_.initializers.values():
1213
+ if initializer.const_value is None:
1214
+ # Skip initializers without constant values
1215
+ logger.warning(
1216
+ "Initializer '%s' does not have a constant value set.", initializer.name
1217
+ )
1218
+ continue
1219
+ # Make sure the tensor's name is the same as the value's name
1220
+ initializer.const_value.name = initializer.name
1221
+ serialize_tensor_into(graph_proto.initializer.add(), from_=initializer.const_value)
1222
+ for node in from_:
1223
+ serialize_node_into(graph_proto.node.add(), from_=node)
1224
+ for node_output in node.outputs:
1225
+ if not _should_create_value_info_for_value(node_output):
1226
+ # No need to serialize value info if it is not set
1227
+ continue
1228
+ if node_output.is_graph_output():
1229
+ # No need to serialize value info for these outputs because they are also graph outputs
1230
+ continue
1231
+ serialize_value_into(graph_proto.value_info.add(), node_output)
1232
+ for output in from_.outputs:
1233
+ serialize_value_into(graph_proto.output.add(), from_=output)
1234
+ if from_.metadata_props:
1235
+ _serialize_metadata_props_into(graph_proto.metadata_props, from_.metadata_props)
1236
+
1237
+
1238
+ def serialize_function(
1239
+ function: _protocols.FunctionProtocol, *, create_value_info: bool = True
1240
+ ) -> onnx.FunctionProto:
1241
+ """Serialize an IR function as a FunctionProto.
1242
+
1243
+ Args:
1244
+ function: The function to serialize.
1245
+ create_value_info: Whether to create ValueInfoProto for nodes in the function. This is supported
1246
+ starting from ONNX IR version 10.
1247
+ """
1248
+ function_proto = onnx.FunctionProto()
1249
+ serialize_function_into(
1250
+ function_proto, from_=function, create_value_info=create_value_info
1251
+ )
1252
+ return function_proto
1253
+
1254
+
1255
+ @_capture_errors(lambda function_proto, from_, create_value_info: repr(from_))
1256
+ def serialize_function_into(
1257
+ function_proto: onnx.FunctionProto,
1258
+ from_: _protocols.FunctionProtocol,
1259
+ *,
1260
+ create_value_info: bool = True,
1261
+ ) -> None:
1262
+ """Serialize an IR function into a FunctionProto.
1263
+
1264
+ Args:
1265
+ function_proto: The proto to serialize into.
1266
+ from_: The function to serialize.
1267
+ create_value_info: Whether to create ValueInfoProto for nodes in the function. This is supported
1268
+ starting from ONNX IR version 10.
1269
+ """
1270
+ if from_.domain:
1271
+ function_proto.domain = from_.domain
1272
+ if from_.name:
1273
+ function_proto.name = from_.name
1274
+ if from_.overload:
1275
+ function_proto.overload = from_.overload
1276
+ if from_.doc_string:
1277
+ function_proto.doc_string = from_.doc_string
1278
+ if from_.opset_imports:
1279
+ # A valid ONNX graph should have at least one opset import, that is
1280
+ # the default ONNX opset.
1281
+ # Here we check for emptiness before serializing to keep the logic consistent
1282
+ _serialize_opset_imports_into(function_proto.opset_import, from_.opset_imports)
1283
+ if from_.metadata_props:
1284
+ _serialize_metadata_props_into(function_proto.metadata_props, from_.metadata_props)
1285
+ for input_ in from_.inputs:
1286
+ function_proto.input.append(input_.name)
1287
+ if not _should_create_value_info_for_value(input_):
1288
+ # No need to serialize value info if it is not set
1289
+ continue
1290
+ if not create_value_info:
1291
+ continue
1292
+ serialize_value_into(function_proto.value_info.add(), input_)
1293
+ for attr in from_.attributes.values():
1294
+ if attr.value is not None:
1295
+ serialize_attribute_into(function_proto.attribute_proto.add(), from_=attr)
1296
+ else:
1297
+ # ONNX does not record type information if the attribute does not have a default
1298
+ function_proto.attribute.append(attr.name)
1299
+ for func_output in from_.outputs:
1300
+ function_proto.output.append(func_output.name)
1301
+ # No need to serialize value info for function outputs because they are
1302
+ # also node outputs
1303
+ for node in from_:
1304
+ serialize_node_into(function_proto.node.add(), from_=node)
1305
+ # Record value info for outputs
1306
+ for node_output in node.outputs:
1307
+ if not _should_create_value_info_for_value(node_output):
1308
+ # No need to serialize value info if it is not set
1309
+ continue
1310
+ if not create_value_info:
1311
+ continue
1312
+ serialize_value_into(function_proto.value_info.add(), node_output)
1313
+
1314
+
1315
+ def serialize_node(node: _protocols.NodeProtocol) -> onnx.NodeProto:
1316
+ node_proto = onnx.NodeProto()
1317
+ serialize_node_into(node_proto, from_=node)
1318
+ return node_proto
1319
+
1320
+
1321
+ def _remove_trailing_outputs(
1322
+ outputs: Sequence[_protocols.ValueProtocol],
1323
+ ) -> Sequence[_protocols.ValueProtocol]:
1324
+ """Remove trailing outputs that have empty names.
1325
+
1326
+ Args:
1327
+ outputs: The outputs to remove trailing outputs from.
1328
+
1329
+ Returns:
1330
+ The outputs with trailing outputs removed.
1331
+ """
1332
+ for i, output in enumerate(reversed(outputs)):
1333
+ if output.name:
1334
+ return outputs[: len(outputs) - i]
1335
+ return []
1336
+
1337
+
1338
+ @_capture_errors(lambda node_proto, from_: repr(from_))
1339
+ def serialize_node_into(node_proto: onnx.NodeProto, from_: _protocols.NodeProtocol) -> None:
1340
+ node_proto.op_type = from_.op_type
1341
+ if from_.domain:
1342
+ # If the domain is "", we can assume the default domain and not set it
1343
+ node_proto.domain = from_.domain
1344
+ if from_.name:
1345
+ node_proto.name = from_.name
1346
+ if from_.overload:
1347
+ node_proto.overload = from_.overload
1348
+ if from_.doc_string:
1349
+ node_proto.doc_string = from_.doc_string
1350
+ if from_.metadata_props:
1351
+ _serialize_metadata_props_into(node_proto.metadata_props, from_.metadata_props)
1352
+ for input_ in from_.inputs:
1353
+ if input_ is None:
1354
+ node_proto.input.append("")
1355
+ else:
1356
+ node_proto.input.append(input_.name)
1357
+
1358
+ # Do not include the trailing outputs that have empty names
1359
+ for output in _remove_trailing_outputs(from_.outputs):
1360
+ node_proto.output.append(output.name)
1361
+
1362
+ for attr in from_.attributes.values():
1363
+ if isinstance(attr, _core.Attr):
1364
+ serialize_attribute_into(node_proto.attribute.add(), from_=attr)
1365
+ elif isinstance(attr, _core.RefAttr):
1366
+ serialize_reference_attribute_into(node_proto.attribute.add(), from_=attr)
1367
+ # Handle protocol attributes for completeness. We do not check them first because
1368
+ # calling isinstance on a protocol can be slow.
1369
+ # Most of the time, we will have Attr or RefAttr so the two branches below
1370
+ # will not be taken.
1371
+ elif isinstance(attr, _protocols.AttributeProtocol):
1372
+ serialize_attribute_into(node_proto.attribute.add(), from_=attr)
1373
+ elif isinstance(attr, _protocols.ReferenceAttributeProtocol):
1374
+ serialize_reference_attribute_into(node_proto.attribute.add(), from_=attr)
1375
+ else:
1376
+ raise TypeError(f"Unsupported attribute type: {type(attr)}")
1377
+
1378
+
1379
+ def serialize_tensor(tensor: _protocols.TensorProtocol) -> onnx.TensorProto:
1380
+ tensor_proto = onnx.TensorProto()
1381
+ serialize_tensor_into(tensor_proto, from_=tensor)
1382
+ return tensor_proto
1383
+
1384
+
1385
+ @_capture_errors(lambda tensor_proto, from_: repr(from_))
1386
+ def serialize_tensor_into(
1387
+ tensor_proto: onnx.TensorProto, from_: _protocols.TensorProtocol
1388
+ ) -> None:
1389
+ if isinstance(from_, TensorProtoTensor):
1390
+ # Directly copy from the tensor proto if it is available
1391
+ tensor_proto.CopyFrom(from_.raw)
1392
+ if from_.metadata_props:
1393
+ _serialize_metadata_props_into(tensor_proto.metadata_props, from_.metadata_props)
1394
+ return
1395
+
1396
+ if from_.name:
1397
+ tensor_proto.name = from_.name
1398
+ if from_.doc_string:
1399
+ tensor_proto.doc_string = from_.doc_string
1400
+ tensor_proto.data_type = from_.dtype.value
1401
+ tensor_proto.dims.extend(from_.shape.numpy())
1402
+ if isinstance(from_, _core.ExternalTensor):
1403
+ # Store external tensors as is
1404
+ tensor_proto.data_location = onnx.TensorProto.EXTERNAL
1405
+ for k, v in {
1406
+ "location": os.fspath(from_.location),
1407
+ "offset": from_.offset,
1408
+ "length": from_.length,
1409
+ }.items():
1410
+ if v is not None:
1411
+ entry = tensor_proto.external_data.add()
1412
+ entry.key = k
1413
+ entry.value = str(v)
1414
+ elif isinstance(from_, _core.StringTensor):
1415
+ tensor_proto.string_data.extend(from_.string_data())
1416
+ else:
1417
+ tensor_proto.raw_data = from_.tobytes()
1418
+ _serialize_metadata_props_into(tensor_proto.metadata_props, from_.metadata_props)
1419
+
1420
+
1421
+ def serialize_attribute(attribute: _protocols.AttributeProtocol) -> onnx.AttributeProto:
1422
+ attribute_proto = onnx.AttributeProto()
1423
+ serialize_attribute_into(attribute_proto, from_=attribute)
1424
+ return attribute_proto
1425
+
1426
+
1427
+ @_capture_errors(lambda attribute_proto, from_: repr(from_))
1428
+ def serialize_attribute_into(
1429
+ attribute_proto: onnx.AttributeProto, from_: _protocols.AttributeProtocol
1430
+ ) -> None:
1431
+ attribute_proto.name = from_.name
1432
+ if from_.doc_string:
1433
+ attribute_proto.doc_string = from_.doc_string
1434
+ _fill_in_value_for_attribute(attribute_proto, from_.type, from_.value)
1435
+
1436
+
1437
+ def _fill_in_value_for_attribute(
1438
+ attribute_proto: onnx.AttributeProto, type_: _enums.AttributeType, value: Any
1439
+ ) -> None:
1440
+ if type_ == _enums.AttributeType.INT:
1441
+ # value: int
1442
+ attribute_proto.i = value
1443
+ attribute_proto.type = onnx.AttributeProto.INT
1444
+ elif type_ == _enums.AttributeType.FLOAT:
1445
+ # value: float
1446
+ attribute_proto.f = value
1447
+ attribute_proto.type = onnx.AttributeProto.FLOAT
1448
+ elif type_ == _enums.AttributeType.STRING:
1449
+ # value: str
1450
+ attribute_proto.s = value.encode("utf-8")
1451
+ attribute_proto.type = onnx.AttributeProto.STRING
1452
+ elif type_ == _enums.AttributeType.INTS:
1453
+ # value: Sequence[int]
1454
+ attribute_proto.ints.extend(value)
1455
+ attribute_proto.type = onnx.AttributeProto.INTS
1456
+ elif type_ == _enums.AttributeType.FLOATS:
1457
+ # value: Sequence[float]
1458
+ attribute_proto.floats.extend(value)
1459
+ attribute_proto.type = onnx.AttributeProto.FLOATS
1460
+ elif type_ == _enums.AttributeType.STRINGS:
1461
+ # value: Sequence[str]
1462
+ attribute_proto.strings.extend([s.encode("utf-8") for s in value])
1463
+ attribute_proto.type = onnx.AttributeProto.STRINGS
1464
+ elif type_ == _enums.AttributeType.TENSOR:
1465
+ # value: _protocols.TensorProtocol
1466
+ serialize_tensor_into(attribute_proto.t, value)
1467
+ attribute_proto.type = onnx.AttributeProto.TENSOR
1468
+ elif type_ == _enums.AttributeType.GRAPH:
1469
+ # value: _protocols.GraphProtocol
1470
+ serialize_graph_into(attribute_proto.g, value)
1471
+ attribute_proto.type = onnx.AttributeProto.GRAPH
1472
+ elif type_ == _enums.AttributeType.TENSORS:
1473
+ # value: Sequence[_protocols.TensorProtocol]
1474
+ for tensor in value:
1475
+ serialize_tensor_into(attribute_proto.tensors.add(), tensor)
1476
+ attribute_proto.type = onnx.AttributeProto.TENSORS
1477
+ elif type_ == _enums.AttributeType.GRAPHS:
1478
+ # value: Sequence[_protocols.GraphProtocol]
1479
+ for graph in value:
1480
+ serialize_graph_into(attribute_proto.graphs.add(), graph)
1481
+ attribute_proto.type = onnx.AttributeProto.GRAPHS
1482
+ elif type_ == _enums.AttributeType.SPARSE_TENSOR:
1483
+ raise NotImplementedError(
1484
+ f"Sparse tensors are not supported yet. {_PLEASE_CONTRIBUTE}"
1485
+ )
1486
+ elif type_ == _enums.AttributeType.SPARSE_TENSORS:
1487
+ raise NotImplementedError(
1488
+ f"Sparse tensors are not supported yet. {_PLEASE_CONTRIBUTE}"
1489
+ )
1490
+ elif type_ == _enums.AttributeType.TYPE_PROTO:
1491
+ # value: _core.TypeAndShape
1492
+ if value.type is not None:
1493
+ serialize_type_into(attribute_proto.tp, value.type)
1494
+ # Need to create the type _before_ writing the shape
1495
+ if value.shape is not None:
1496
+ serialize_shape_into(attribute_proto.tp, value.shape)
1497
+ attribute_proto.type = onnx.AttributeProto.TYPE_PROTO
1498
+ elif type_ == _enums.AttributeType.TYPE_PROTOS:
1499
+ for ir_type in value:
1500
+ # ir_type: _core.TypeAndShape
1501
+ type_proto = attribute_proto.type_protos.add()
1502
+ if ir_type.type is not None:
1503
+ serialize_type_into(type_proto, ir_type.type)
1504
+ # Need to create the type _before_ writing the shape so that the shape can be written to the leaf type proto
1505
+ if ir_type.shape is not None:
1506
+ serialize_shape_into(type_proto, ir_type.shape)
1507
+ attribute_proto.type = onnx.AttributeProto.TYPE_PROTOS
1508
+ else:
1509
+ raise TypeError(f"Unsupported attribute type: {type_}")
1510
+
1511
+
1512
+ @_capture_errors(lambda attribute_proto, from_: repr(from_))
1513
+ def serialize_reference_attribute_into(
1514
+ attribute_proto: onnx.AttributeProto, from_: _protocols.ReferenceAttributeProtocol
1515
+ ) -> None:
1516
+ attribute_proto.name = from_.name
1517
+ attribute_proto.ref_attr_name = from_.ref_attr_name
1518
+ if from_.doc_string:
1519
+ attribute_proto.doc_string = from_.doc_string
1520
+ attribute_proto.type = typing.cast(onnx.AttributeProto.AttributeType, from_.type.value)
1521
+
1522
+
1523
+ def serialize_value(value: _protocols.ValueProtocol, *, name: str = "") -> onnx.ValueInfoProto:
1524
+ """Serialize a value into a ValueInfoProto.
1525
+
1526
+ Args:
1527
+ value: The proto to serialize into.
1528
+ from_: The value to serialize.
1529
+ name: A custom name to set for the value info. If not provided, the name from the value will be used.
1530
+ """
1531
+ value_info_proto = onnx.ValueInfoProto()
1532
+ serialize_value_into(value_info_proto, value, name=name)
1533
+ return value_info_proto
1534
+
1535
+
1536
+ @_capture_errors(lambda value_info_proto, from_: repr(from_))
1537
+ def serialize_value_into(
1538
+ value_info_proto: onnx.ValueInfoProto,
1539
+ from_: _protocols.ValueProtocol,
1540
+ *,
1541
+ name: str = "",
1542
+ ) -> None:
1543
+ """Serialize a value into a ValueInfoProto.
1544
+
1545
+ Args:
1546
+ value_info_proto: The proto to serialize into.
1547
+ from_: The value to serialize.
1548
+ name: A custom name to set for the value info. If not provided, the name from the value will be used.
1549
+ """
1550
+ if name:
1551
+ value_info_proto.name = name
1552
+ else:
1553
+ value_info_proto.name = from_.name
1554
+ if from_.metadata_props:
1555
+ _serialize_metadata_props_into(value_info_proto.metadata_props, from_.metadata_props)
1556
+ if from_.type is not None:
1557
+ serialize_type_into(value_info_proto.type, from_.type)
1558
+ # Need to create the type _before_ writing the shape so that the shape can be written to the leaf type proto
1559
+ if from_.shape is not None:
1560
+ serialize_shape_into(value_info_proto.type, from_.shape)
1561
+ if from_.doc_string:
1562
+ value_info_proto.doc_string = from_.doc_string
1563
+
1564
+
1565
+ @_capture_errors(lambda type_proto, from_: repr(from_))
1566
+ def serialize_type_into(type_proto: onnx.TypeProto, from_: _protocols.TypeProtocol) -> None:
1567
+ if from_.denotation:
1568
+ type_proto.denotation = from_.denotation
1569
+ if isinstance(from_, _core.TensorType):
1570
+ tensor_type_proto = type_proto.tensor_type
1571
+ tensor_type_proto.elem_type = from_.dtype.value
1572
+ elif isinstance(from_, _core.SparseTensorType):
1573
+ sparse_tensor_type_proto = type_proto.sparse_tensor_type
1574
+ sparse_tensor_type_proto.elem_type = from_.dtype.value
1575
+ elif isinstance(from_, _core.SequenceType):
1576
+ sequence_type_proto = type_proto.sequence_type
1577
+ serialize_type_into(sequence_type_proto.elem_type, from_.elem_type)
1578
+ elif isinstance(from_, _core.OptionalType):
1579
+ optional_type_proto = type_proto.optional_type
1580
+ serialize_type_into(optional_type_proto.elem_type, from_.elem_type)
1581
+ else:
1582
+ raise TypeError(f"Unsupported type: {from_}")
1583
+
1584
+
1585
+ def serialize_type(type_protocol: _protocols.TypeProtocol) -> onnx.TypeProto:
1586
+ type_proto = onnx.TypeProto()
1587
+ serialize_type_into(type_proto, from_=type_protocol)
1588
+ return type_proto
1589
+
1590
+
1591
+ @_capture_errors(lambda type_proto, from_: repr(from_))
1592
+ def serialize_shape_into(type_proto: onnx.TypeProto, from_: _protocols.ShapeProtocol) -> None:
1593
+ value_field = type_proto.WhichOneof("value")
1594
+ tensor_type = getattr(type_proto, value_field)
1595
+ while not isinstance(tensor_type.elem_type, int):
1596
+ # Find the leaf type that has the shape field
1597
+ type_proto = tensor_type.elem_type
1598
+ value_field = type_proto.WhichOneof("value")
1599
+ tensor_type = getattr(type_proto, value_field)
1600
+ # When from is empty, we still need to set the shape field to an empty list by touching it
1601
+ tensor_type.shape.ClearField("dim")
1602
+ for i, dim in enumerate(from_):
1603
+ denotation = from_.get_denotation(i)
1604
+ serialize_dimension_into(tensor_type.shape.dim.add(), dim, denotation)
1605
+
1606
+
1607
+ @_capture_errors(lambda dim_proto, dim, denotation: repr(dim_proto))
1608
+ def serialize_dimension_into(
1609
+ dim_proto: onnx.TensorShapeProto.Dimension,
1610
+ dim: int | _protocols.SymbolicDimProtocol,
1611
+ denotation: str | None = None,
1612
+ ) -> None:
1613
+ if denotation:
1614
+ dim_proto.denotation = denotation
1615
+ if isinstance(dim, int):
1616
+ dim_proto.dim_value = dim
1617
+ elif isinstance(dim, (_core.SymbolicDim, _protocols.SymbolicDimProtocol)):
1618
+ if dim.value is not None:
1619
+ # TODO(justinchuby): None is probably not a valid value for dim_param
1620
+ dim_proto.dim_param = str(dim.value)