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,313 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ #
4
+ # Copyright © 2016 Igor Kroitor
5
+ #
6
+ # MIT License
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files (the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be included in all
16
+ # copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ # SOFTWARE.
25
+
26
+ """Module to generate ascii charts.
27
+
28
+ This module provides a single function `plot` that can be used to generate an
29
+ ascii chart from a series of numbers. The chart can be configured via several
30
+ options to tune the output.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from math import ceil, floor, isnan
36
+ from typing import Mapping
37
+
38
+ black = "\033[30m"
39
+ red = "\033[31m"
40
+ green = "\033[32m"
41
+ yellow = "\033[33m"
42
+ blue = "\033[34m"
43
+ magenta = "\033[35m"
44
+ cyan = "\033[36m"
45
+ lightgray = "\033[37m"
46
+ default = "\033[39m"
47
+ darkgray = "\033[90m"
48
+ lightred = "\033[91m"
49
+ lightgreen = "\033[92m"
50
+ lightyellow = "\033[93m"
51
+ lightblue = "\033[94m"
52
+ lightmagenta = "\033[95m"
53
+ lightcyan = "\033[96m"
54
+ white = "\033[97m"
55
+ reset = "\033[0m"
56
+
57
+
58
+ __all__ = [
59
+ "plot",
60
+ "black",
61
+ "red",
62
+ "green",
63
+ "yellow",
64
+ "blue",
65
+ "magenta",
66
+ "cyan",
67
+ "lightgray",
68
+ "default",
69
+ "darkgray",
70
+ "lightred",
71
+ "lightgreen",
72
+ "lightyellow",
73
+ "lightblue",
74
+ "lightmagenta",
75
+ "lightcyan",
76
+ "white",
77
+ "reset",
78
+ ]
79
+
80
+
81
+ # Python 3.2 has math.isfinite, which could have been used, but to support older
82
+ # versions, this little helper is shorter than having to keep doing not isnan(),
83
+ # plus the double-negative of "not is not a number" is confusing, so this should
84
+ # help with readability.
85
+ def _isnum(n):
86
+ return not isnan(n)
87
+
88
+
89
+ def colored(char, color):
90
+ if not color:
91
+ return char
92
+ else:
93
+ return color + char + reset
94
+
95
+
96
+ _DEFAULT_SYMBOLS = ("┼", "┤", "╶", "╴", "─", "╰", "╭", "╮", "╯", "│")
97
+
98
+
99
+ def plot(series, *, bin_edges=None, cfg=None):
100
+ """Generate an ascii chart for a series of numbers.
101
+
102
+ `series` should be a list of ints or floats. Missing data values in the
103
+ series can be specified as a NaN. In Python versions less than 3.5, use
104
+ float("nan") to specify an NaN. With 3.5 onwards, use math.nan to specify a
105
+ NaN.
106
+
107
+ >>> series = [1,2,3,4,float("nan"),4,3,2,1]
108
+ >>> print(plot(series))
109
+ 4.00 ┤ ╭╴╶╮
110
+ 3.00 ┤ ╭╯ ╰╮
111
+ 2.00 ┤╭╯ ╰╮
112
+ 1.00 ┼╯ ╰
113
+
114
+ `series` can also be a list of lists to support multiple data series.
115
+
116
+ >>> series = [[10,20,30,40,30,20,10], [40,30,20,10,20,30,40]]
117
+ >>> print(plot(series, cfg={'height': 3}))
118
+ 40.00 ┤╮ ╭╮ ╭
119
+ 30.00 ┤╰╮╯╰╭╯
120
+ 20.00 ┤╭╰╮╭╯╮
121
+ 10.00 ┼╯ ╰╯ ╰
122
+
123
+ `bin_edges` is an optional list of bin edges to display on the x-axis. If
124
+ provided, the x-axis will be labeled with the bin edges. If there are too
125
+ many bin edges to fit on the x-axis, some labels will be dropped and they
126
+ will be spaced out evenly to fit the width of the chart.
127
+ The labels will be formatted using the `x_format` option in `cfg`.
128
+
129
+ `cfg` is an optional dictionary of various parameters to tune the appearance
130
+ of the chart. `min` and `max` will clamp the y-axis and all values:
131
+
132
+ >>> series = [1,2,3,4,float("nan"),4,3,2,1]
133
+ >>> print(plot(series, cfg={'min': 0}))
134
+ 4.00 ┼ ╭╴╶╮
135
+ 3.00 ┤ ╭╯ ╰╮
136
+ 2.00 ┤╭╯ ╰╮
137
+ 1.00 ┼╯ ╰
138
+ 0.00 ┤
139
+
140
+ >>> print(plot(series, cfg={'min': 2}))
141
+ 4.00 ┤ ╭╴╶╮
142
+ 3.00 ┤ ╭╯ ╰╮
143
+ 2.00 ┼─╯ ╰─
144
+
145
+ >>> print(plot(series, cfg={'min': 2, 'max': 3}))
146
+ 3.00 ┤ ╭─╴╶─╮
147
+ 2.00 ┼─╯ ╰─
148
+
149
+ `height` specifies the number of rows the graph should occupy. It can be
150
+ used to scale down a graph with large data values:
151
+
152
+ >>> series = [10,20,30,40,50,40,30,20,10]
153
+ >>> print(plot(series, cfg={'height': 4}))
154
+ 50.00 ┤ ╭╮
155
+ 40.00 ┤ ╭╯╰╮
156
+ 30.00 ┤ ╭╯ ╰╮
157
+ 20.00 ┤╭╯ ╰╮
158
+ 10.00 ┼╯ ╰
159
+
160
+ `format` specifies a Python format string used to format the labels on the
161
+ y-axis. The default value is "{:8.2f} ". This can be used to remove the
162
+ decimal point:
163
+
164
+ >>> series = [10,20,30,40,50,40,30,20,10]
165
+ >>> print(plot(series, cfg={'height': 4, 'format':'{:8.0f}'}))
166
+ 50 ┤ ╭╮
167
+ 40 ┤ ╭╯╰╮
168
+ 30 ┤ ╭╯ ╰╮
169
+ 20 ┤╭╯ ╰╮
170
+ 10 ┼╯ ╰
171
+ """
172
+ if len(series) == 0:
173
+ return ""
174
+
175
+ if not isinstance(series[0], list):
176
+ if all(isnan(n) for n in series):
177
+ return ""
178
+ else:
179
+ series = [series]
180
+
181
+ if cfg is not None and not isinstance(cfg, Mapping):
182
+ raise TypeError("cfg must be a dictionary or None")
183
+
184
+ cfg = cfg or {}
185
+
186
+ colors = cfg.get("colors", [None])
187
+
188
+ minimum = cfg.get("min", min(filter(_isnum, [j for i in series for j in i])))
189
+ maximum = cfg.get("max", max(filter(_isnum, [j for i in series for j in i])))
190
+
191
+ symbols = cfg.get("symbols", _DEFAULT_SYMBOLS)
192
+
193
+ if minimum > maximum:
194
+ raise ValueError("The min value cannot exceed the max value.")
195
+
196
+ interval = maximum - minimum
197
+ offset = cfg.get("offset", 3)
198
+ height = cfg.get("height", interval)
199
+ ratio = height / interval if interval > 0 else 1
200
+
201
+ min2 = floor(minimum * ratio)
202
+ max2 = ceil(maximum * ratio)
203
+
204
+ def clamp(n):
205
+ return min(max(n, minimum), maximum)
206
+
207
+ def scaled(y):
208
+ return int(round(clamp(y) * ratio) - min2)
209
+
210
+ rows = max2 - min2
211
+
212
+ width = 0
213
+ for series_i in series:
214
+ width = max(width, len(series_i))
215
+ width += offset
216
+
217
+ placeholder = cfg.get("format", "{:8.2f} ")
218
+ x_placeholder = cfg.get("x_format", "{:4.4f}")
219
+
220
+ result = [[" "] * width for i in range(rows + 1)]
221
+
222
+ # axis and labels
223
+ for y in range(min2, max2 + 1):
224
+ label = placeholder.format(maximum - ((y - min2) * interval / (rows if rows else 1)))
225
+ result[y - min2][max(offset - len(label), 0)] = label
226
+ result[y - min2][offset - 1] = symbols[0] if y == 0 else symbols[1] # zero tick mark
227
+
228
+ # first value is a tick mark across the y-axis
229
+ d0 = series[0][0]
230
+ if _isnum(d0):
231
+ result[rows - scaled(d0)][offset - 1] = symbols[0]
232
+
233
+ for i, series_i in enumerate(series):
234
+ color = colors[i % len(colors)]
235
+
236
+ # plot the line
237
+ for x in range(len(series_i) - 1):
238
+ d0 = series_i[x + 0]
239
+ d1 = series_i[x + 1]
240
+
241
+ if isnan(d0) and isnan(d1):
242
+ continue
243
+
244
+ if isnan(d0) and _isnum(d1):
245
+ result[rows - scaled(d1)][x + offset] = colored(symbols[2], color)
246
+ continue
247
+
248
+ if _isnum(d0) and isnan(d1):
249
+ result[rows - scaled(d0)][x + offset] = colored(symbols[3], color)
250
+ continue
251
+
252
+ y0 = scaled(d0)
253
+ y1 = scaled(d1)
254
+ if y0 == y1:
255
+ result[rows - y0][x + offset] = colored(symbols[4], color)
256
+ continue
257
+
258
+ result[rows - y1][x + offset] = (
259
+ colored(symbols[5], color) if y0 > y1 else colored(symbols[6], color)
260
+ )
261
+ result[rows - y0][x + offset] = (
262
+ colored(symbols[7], color) if y0 > y1 else colored(symbols[8], color)
263
+ )
264
+
265
+ start = min(y0, y1) + 1
266
+ end = max(y0, y1)
267
+ for y in range(start, end):
268
+ result[rows - y][x + offset] = colored(symbols[9], color)
269
+
270
+ the_plot = "\n".join(["".join(row).rstrip() for row in result])
271
+
272
+ if bin_edges is None or len(bin_edges) == 0:
273
+ return the_plot
274
+
275
+ # Plot x axis labels
276
+ current_location = 0
277
+ # Compute the amount of leading space for the first x-label using the old label size
278
+ leading_space = offset + len(label)
279
+ # Obtain the first x-label to compute its size
280
+ x_label = x_placeholder.format(bin_edges[0])
281
+ # Initialize the x-label text with the leading space. We allow the first label to
282
+ # recess so that the center of it is aligned with the first tick mark.
283
+ x_label_size = len(x_label)
284
+ x_leading_space = max(0, leading_space - x_label_size)
285
+
286
+ x_labels = []
287
+ # This is the amount of space we have to fit the x-labels. It can overflow the width
288
+ # by half of the x-label size
289
+ workable_width = width + x_label_size // 2
290
+ # Compute the spacing between x-labels
291
+ # If we fit labels and space them by 2 characters, we can fit this many labels:
292
+ min_spacing = 2
293
+ num_labels_can_fit = width // (x_label_size + min_spacing)
294
+ labels_count = len(bin_edges)
295
+ # Find out the actual number of labels we need to display
296
+ num_labels_to_display = min(labels_count, num_labels_can_fit)
297
+ num_spaces = num_labels_to_display - 1
298
+ spacing = max(
299
+ min_spacing,
300
+ (workable_width - num_labels_to_display * x_label_size) // num_spaces,
301
+ )
302
+ # Now start placing labels
303
+ while current_location < workable_width:
304
+ # Find the current label that would be suitable for the current location
305
+ bin_index = int((current_location / workable_width) * labels_count)
306
+ x_label = x_placeholder.format(bin_edges[bin_index])
307
+ x_labels.append(x_label)
308
+ # Move to the next location
309
+ current_location += len(x_label) + spacing
310
+ # Create the x-label row
311
+ x_labels_text = " " * x_leading_space + (" " * spacing).join(x_labels)
312
+
313
+ return the_plot + "\n" + x_labels_text
@@ -0,0 +1,2 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
@@ -0,0 +1,303 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+
5
+ import os
6
+ import textwrap
7
+ from typing import Iterator
8
+
9
+ import numpy as np
10
+ import onnx
11
+ import onnx.numpy_helper
12
+ from onnx.backend.test import __file__ as backend_folder
13
+
14
+ from onnxscript.backend import onnx_export
15
+
16
+
17
+ def assert_almost_equal_string(expected, value):
18
+ """Compares two arrays knowing they contain strings.
19
+ Raises an exception if the test fails.
20
+
21
+ Args:
22
+ expected: expected array
23
+ value: value
24
+ """
25
+
26
+ def is_float(x): # pylint: disable=unused-argument
27
+ try:
28
+ return True
29
+ except ValueError: # pragma: no cover
30
+ return False
31
+
32
+ if all(map(is_float, expected.ravel())):
33
+ expected_float = expected.astype(np.float32)
34
+ value_float = value.astype(np.float32)
35
+ np.testing.assert_almost_equal(expected_float, value_float)
36
+ else:
37
+ np.testing.assert_almost_equal(expected, value)
38
+
39
+
40
+ class OnnxBackendTest:
41
+ """Definition of a backend test. It starts with a folder,
42
+ in this folder, one onnx file must be there, then a subfolder
43
+ for each test to run with this model.
44
+
45
+ Args:
46
+ folder: test folder
47
+ onnx_path: onnx file
48
+ onnx_model: loaded onnx file
49
+ tests: list of test
50
+ """
51
+
52
+ @staticmethod
53
+ def _sort(filenames):
54
+ temp = []
55
+ for f in filenames:
56
+ name = os.path.splitext(f)[0]
57
+ i = name.split("_")[-1]
58
+ temp.append((int(i), f))
59
+ temp.sort()
60
+ return [_[1] for _ in temp]
61
+
62
+ @staticmethod
63
+ def _read_proto_from_file(full):
64
+ if not os.path.exists(full):
65
+ raise FileNotFoundError(f"File not found: {full!r}.") # pragma: no cover
66
+ with open(full, "rb") as f:
67
+ serialized = f.read()
68
+ try:
69
+ loaded = onnx.numpy_helper.to_array(onnx.load_tensor_from_string(serialized))
70
+ except Exception as e: # pylint: disable=W0703
71
+ seq = onnx.SequenceProto()
72
+ try:
73
+ seq.ParseFromString(serialized)
74
+ loaded = onnx.numpy_helper.to_list(seq) # type: ignore[assignment]
75
+ except Exception: # pylint: disable=W0703
76
+ try:
77
+ loaded = onnx.load_model_from_string(serialized) # type: ignore[assignment]
78
+ except Exception:
79
+ raise RuntimeError(
80
+ f"Unable to read {full!r}, error is {e}, "
81
+ f"content is {serialized[:100]!r}."
82
+ ) from e
83
+ return loaded
84
+
85
+ @staticmethod
86
+ def _load(folder, names):
87
+ res = []
88
+ for name in names:
89
+ full = os.path.join(folder, name)
90
+ new_tensor = OnnxBackendTest._read_proto_from_file(full)
91
+ if isinstance(new_tensor, (np.ndarray, onnx.ModelProto, list)):
92
+ t = new_tensor
93
+ elif isinstance(new_tensor, onnx.TensorProto):
94
+ t = onnx.numpy_helper.to_array(new_tensor)
95
+ else:
96
+ raise RuntimeError( # noqa: TRY004
97
+ f"Unexpected type {type(new_tensor)!r} for {full!r}."
98
+ )
99
+ res.append(t)
100
+ return res
101
+
102
+ def __repr__(self):
103
+ """Usual"""
104
+ return f"{self.__class__.__name__}({self.folder!r})"
105
+
106
+ def __init__(self, folder):
107
+ if not os.path.exists(folder):
108
+ raise FileNotFoundError(f"Unable to find folder {folder!r}.") # pragma: no cover
109
+ content = os.listdir(folder)
110
+ onx = [c for c in content if os.path.splitext(c)[-1] in {".onnx"}]
111
+ if len(onx) != 1:
112
+ raise ValueError( # pragma: no cover
113
+ f"There is more than one onnx file in {folder!r} ({onx!r})."
114
+ )
115
+ self.folder = folder
116
+ self.onnx_path = os.path.join(folder, onx[0])
117
+ self.onnx_model = onnx.load(self.onnx_path)
118
+
119
+ self.tests = []
120
+ for sub in content:
121
+ full = os.path.join(folder, sub)
122
+ if os.path.isdir(full):
123
+ pb = [c for c in os.listdir(full) if os.path.splitext(c)[-1] in {".pb"}]
124
+ inputs = OnnxBackendTest._sort(c for c in pb if c.startswith("input_"))
125
+ outputs = OnnxBackendTest._sort(c for c in pb if c.startswith("output_"))
126
+
127
+ t = dict(
128
+ inputs=OnnxBackendTest._load(full, inputs),
129
+ outputs=OnnxBackendTest._load(full, outputs),
130
+ )
131
+ self.tests.append(t)
132
+
133
+ @property
134
+ def name(self):
135
+ """Returns the test name."""
136
+ return os.path.split(self.folder)[-1]
137
+
138
+ def __len__(self):
139
+ """Returns the number of tests."""
140
+ return len(self.tests)
141
+
142
+ def _compare_results(self, index, i, e, o, decimal=None):
143
+ """Compares the expected output and the output produced
144
+ by the runtime. Raises an exception if not equal.
145
+
146
+ Args:
147
+ index: test index
148
+ i: output index
149
+ e: expected output
150
+ o: output
151
+ decimal: precision
152
+ """
153
+ if isinstance(e, np.ndarray):
154
+ if isinstance(o, np.ndarray):
155
+ if decimal is None:
156
+ if e.dtype == np.float32:
157
+ deci = 6
158
+ elif e.dtype == np.float64:
159
+ deci = 12
160
+ else:
161
+ deci = 7
162
+ else:
163
+ deci = decimal
164
+ if e.dtype == np.object_:
165
+ try:
166
+ assert_almost_equal_string(e, o)
167
+ except AssertionError as ex:
168
+ raise AssertionError( # pragma: no cover
169
+ f"Output {i} of test {index} in folder {self.folder} failed."
170
+ ) from ex
171
+ else:
172
+ try:
173
+ np.testing.assert_almost_equal(e, o, decimal=deci)
174
+ except AssertionError as ex:
175
+ raise AssertionError(
176
+ f"Output {i} of test {index} in folder {self.folder} failed."
177
+ ) from ex
178
+ elif hasattr(o, "is_compatible"):
179
+ # A shape
180
+ if e.dtype != o.dtype:
181
+ raise AssertionError(
182
+ f"Output {i} of test {index} in folder "
183
+ f"{self.folder} failed (e.dtype={e.dtype}, o={o})."
184
+ )
185
+ if not o.is_compatible(e.shape):
186
+ raise AssertionError( # pragma: no cover
187
+ f"Output {i} of test {index} in folder "
188
+ f"{self.folder} failed (e.shape={e.shape}, o={o})."
189
+ )
190
+ else:
191
+ raise NotImplementedError(f"Comparison not implemented for type {type(e)!r}.")
192
+
193
+ def is_random(self):
194
+ """Returns whether the test is random."""
195
+ return "bernoulli" in self.folder
196
+
197
+ def run(self, load_fct, run_fct, index=None, decimal=None):
198
+ """Executes a tests or all tests if index is None.
199
+ The function crashes if the tests fails.
200
+
201
+ Args:
202
+ load_fct: loading function, takes a loaded onnx graph, and
203
+ returns an object
204
+ run_fct: running function, takes the result of previous
205
+ function, the inputs, and returns the outputs
206
+ index: index of the test to run or all.
207
+ decimal: requested precision to compare results
208
+ """
209
+ if index is None:
210
+ for i in range(len(self)):
211
+ self.run(load_fct, run_fct, index=i, decimal=decimal)
212
+ return
213
+
214
+ obj = load_fct(self.onnx_model)
215
+
216
+ got = run_fct(obj, *self.tests[index]["inputs"])
217
+ expected = self.tests[index]["outputs"]
218
+ if len(got) != len(expected):
219
+ raise AssertionError( # pragma: no cover
220
+ f"Unexpected number of output (test {index}, folder {self.folder}), "
221
+ f"got {len(got)}, expected {len(expected)}."
222
+ )
223
+ for i, (e, o) in enumerate(zip(expected, got)):
224
+ if self.is_random():
225
+ if e.dtype != o.dtype:
226
+ raise AssertionError(
227
+ f"Output {i} of test {index} in folder "
228
+ f"{self.folder} failed (type mismatch {e.dtype} != {o.dtype})."
229
+ )
230
+ if e.shape != o.shape:
231
+ raise AssertionError(
232
+ f"Output {i} of test {index} in folder "
233
+ f"{self.folder} failed (shape mismatch {e.shape} != {o.shape})."
234
+ )
235
+ else:
236
+ self._compare_results(index, i, e, o, decimal=decimal)
237
+
238
+ def to_python(self):
239
+ """Returns a python code equivalent to the ONNX test.
240
+
241
+ Returns:
242
+ code
243
+ """
244
+ rows = []
245
+ code = onnx_export.export2onnx(self.onnx_model) # type: ignore[attr-defined]
246
+ lines = code.split("\n")
247
+ lines = [
248
+ line
249
+ for line in lines
250
+ if not line.strip().startswith("print") and not line.strip().startswith("# ")
251
+ ]
252
+ rows.append(textwrap.dedent("\n".join(lines)))
253
+ rows.append("oinf = OnnxInference(onnx_model)")
254
+ for test in self.tests:
255
+ rows.append("xs = [")
256
+ for inp in test["inputs"]:
257
+ rows.append(textwrap.indent(f"{inp!r},", " " * 2))
258
+ rows.append("]")
259
+ rows.append("ys = [")
260
+ for out in test["outputs"]:
261
+ rows.append(textwrap.indent(f"{out!r},", " " * 2))
262
+ rows.append("]")
263
+ rows.append("feeds = {n: x for n, x in zip(oinf.input_names, xs)}")
264
+ rows.append("got = oinf.run(feeds)")
265
+ rows.append("goty = [got[k] for k in oinf.output_names]")
266
+ rows.append("for y, gy in zip(ys, goty):")
267
+ rows.append(" self.assertEqualArray(y, gy)")
268
+ rows.append("")
269
+ code = "\n".join(rows)
270
+ final = "\n".join([f"def {self.name}(self):", textwrap.indent(code, " ")])
271
+ return final
272
+
273
+
274
+ def enumerate_onnx_tests(series, fct_filter=None) -> Iterator[OnnxBackendTest]:
275
+ """Collects test from a sub folder of `onnx/backend/test`.
276
+ Works as an enumerator to start processing them
277
+ without waiting or storing too much of them.
278
+
279
+ Args:
280
+ series: which subfolder to load, possible values: (`'node'`,
281
+ ...)
282
+ fct_filter: function `lambda testname: boolean` to load or skip
283
+ the test, None for all
284
+
285
+ Yields:
286
+ list of @see cl OnnxBackendTest
287
+ """
288
+ root = os.path.dirname(backend_folder)
289
+ sub = os.path.join(root, "data", series)
290
+ if not os.path.exists(sub):
291
+ raise FileNotFoundError(
292
+ f"Unable to find series of tests in {root!r}, subfolders:\n"
293
+ + "\n".join(os.listdir(root))
294
+ )
295
+ tests = os.listdir(sub)
296
+ for t in tests:
297
+ if fct_filter is not None and not fct_filter(t):
298
+ continue
299
+ folder = os.path.join(sub, t)
300
+ content = os.listdir(folder)
301
+ onx = [c for c in content if os.path.splitext(c)[-1] in {".onnx"}]
302
+ if len(onx) == 1:
303
+ yield OnnxBackendTest(folder)