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,783 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ # pylint: disable=import-outside-toplevel, no-else-raise, consider-using-with, consider-using-enumerate
4
+
5
+ from __future__ import annotations
6
+
7
+ import argparse
8
+ import itertools
9
+ import multiprocessing
10
+ import os
11
+ import platform
12
+ import re
13
+ import subprocess
14
+ import sys
15
+ import time
16
+ from typing import Any, Sequence
17
+
18
+ import numpy as np
19
+ import onnx
20
+ import onnx.inliner
21
+
22
+ import onnxscript.optimizer
23
+ import onnxscript.rewriter
24
+ import onnxscript.rewriter.llama_rule_sets as rules
25
+ import onnxscript.rewriter.onnxruntime as ort_rules
26
+ import onnxscript.rewriter.pattern as orp
27
+ from onnxscript import ir
28
+ from onnxscript.optimizer._remove_unused import remove_unused_nodes
29
+
30
+
31
+ def get_parsed_args(
32
+ name: str,
33
+ description: str | None = None,
34
+ epilog: str | None = None,
35
+ new_args: list[str] | None = None,
36
+ **kwargs: tuple[Any, str],
37
+ ) -> dict[str, Any]:
38
+ """
39
+ Returns parsed arguments for examples in this package.
40
+
41
+ Args:
42
+ name: script name
43
+ scenarios: list of available scenarios
44
+ description: parser description
45
+ epilog: text at the end of the parser
46
+ number: default value for number parameter
47
+ repeat: default value for repeat parameter
48
+ warmup: default value for warmup parameter
49
+ sleep: default value for sleep parameter
50
+ expose: if empty, keeps all the parameters,
51
+ if not None, only publish kwargs contains, otherwise the list
52
+ of parameters to publish separated by a comma
53
+ new_args: args to consider or None to take `sys.args`
54
+ kwargs: additional parameters,
55
+ example: `n_trees=(10, "number of trees to train")`
56
+
57
+ Returns:
58
+ interpreted parameters in a dictionary
59
+ """
60
+ parser = argparse.ArgumentParser(
61
+ prog=name,
62
+ description=description or f"Available options for {name}.py.",
63
+ epilog=epilog or "",
64
+ )
65
+ for k, v in kwargs.items():
66
+ parser.add_argument(
67
+ f"--{k}",
68
+ help=f"{v[1]}, default is {v[0]}",
69
+ type=type(v[0]),
70
+ default=v[0],
71
+ )
72
+
73
+ parsed = parser.parse_args(args=new_args)
74
+ return {k: getattr(parsed, k) for k in kwargs}
75
+
76
+
77
+ class BenchmarkError(RuntimeError):
78
+ pass
79
+
80
+
81
+ def get_machine() -> dict[str, Any]:
82
+ """Returns the machine specification."""
83
+ cpu: dict[str, Any] = dict(
84
+ machine=str(platform.machine()),
85
+ processor=str(platform.processor()),
86
+ version=str(sys.version),
87
+ cpu=int(multiprocessing.cpu_count()),
88
+ executable=str(sys.executable),
89
+ )
90
+ try:
91
+ import torch.cuda
92
+ except ImportError:
93
+ return cpu
94
+
95
+ cpu["has_cuda"] = bool(torch.cuda.is_available())
96
+ if cpu["has_cuda"]:
97
+ cpu["capability"] = torch.cuda.get_device_capability(0)
98
+ cpu["device_name"] = str(torch.cuda.get_device_name(0))
99
+ return cpu
100
+
101
+
102
+ def _cmd_line(script_name: str, **kwargs: dict[str, Any]) -> list[str]:
103
+ args = [sys.executable, "-m", script_name]
104
+ for k, v in kwargs.items():
105
+ args.append(f"--{k}")
106
+ args.append(str(v))
107
+ return args
108
+
109
+
110
+ def _extract_metrics(text: str) -> dict[str, str]:
111
+ reg = re.compile(r":(.*?),(.*.?);")
112
+ res = reg.findall(text)
113
+ if len(res) == 0:
114
+ return {}
115
+ return dict(res)
116
+
117
+
118
+ def _make_prefix(script_name: str, index: int) -> str:
119
+ name = os.path.splitext(script_name)[0]
120
+ return f"{name}_dort_c{index}_"
121
+
122
+
123
+ def run_benchmark(
124
+ script_name: str,
125
+ configs: list[dict[str, Any]],
126
+ verbose: int = 0,
127
+ stop_if_exception: bool = True,
128
+ dump: bool = False,
129
+ ) -> list[dict[str, Any]]:
130
+ """
131
+ Runs a script multiple times and extract information from the output
132
+ following the pattern ``:<metric>,<value>;``.
133
+
134
+ Args:
135
+ script_name: python script to run
136
+ configs: list of execution to do
137
+ stop_if_exception: stop if one experiment failed, otherwise continue
138
+ verbose: use tqdm to follow the progress
139
+ dump: dump onnx file
140
+
141
+ Returns:
142
+ values
143
+ """
144
+ if verbose:
145
+ from tqdm import tqdm
146
+
147
+ loop = tqdm(configs)
148
+ else:
149
+ loop = configs
150
+
151
+ data: list[dict[str, Any]] = []
152
+ for i, config in enumerate(loop):
153
+ cmd = _cmd_line(script_name, **config)
154
+
155
+ if dump:
156
+ os.environ["ONNXRT_DUMP_PATH"] = _make_prefix(script_name, i)
157
+ else:
158
+ os.environ["ONNXRT_DUMP_PATH"] = ""
159
+ if verbose > 3:
160
+ print(f"[run_benchmark] cmd={cmd if isinstance(cmd, str) else ' '.join(cmd)}")
161
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
162
+ res = p.communicate()
163
+ out, err = res
164
+ sout = out.decode("utf-8", errors="ignore")
165
+ serr = err.decode("utf-8", errors="ignore")
166
+
167
+ if "ONNXRuntimeError" in serr or "ONNXRuntimeError" in sout:
168
+ if stop_if_exception:
169
+ raise RuntimeError(
170
+ f"Unable to continue with config {config} due to the "
171
+ f"following error\n{serr}"
172
+ f"\n----OUTPUT--\n{sout}"
173
+ )
174
+
175
+ metrics = _extract_metrics(sout)
176
+ if len(metrics) == 0:
177
+ if stop_if_exception:
178
+ raise BenchmarkError(
179
+ f"Unable (2) to continue with config {config}, no metric was "
180
+ f"collected.\n--ERROR--\n{serr}\n--OUTPUT--\n{sout}"
181
+ )
182
+ else:
183
+ metrics = {}
184
+ metrics.update(config)
185
+ metrics["ERROR"] = serr
186
+ metrics["OUTPUT"] = sout
187
+ metrics["CMD"] = f"[{' '.join(cmd)}]"
188
+ data.append(metrics)
189
+ if verbose > 5:
190
+ print("--------------- ERROR")
191
+ print(serr)
192
+ if verbose >= 10:
193
+ print("--------------- OUTPUT")
194
+ print(sout)
195
+
196
+ return data
197
+
198
+
199
+ def measure_discrepancies(
200
+ expected: list[tuple[Any, ...]],
201
+ outputs: list[tuple[Any, ...]],
202
+ ) -> tuple[float, float]:
203
+ """
204
+ Computes the discrepancies.
205
+
206
+ Args:
207
+ expected: list of outputs coming from a torch model
208
+ outputs: list of outputs coming from an onnx model
209
+
210
+ Returns:
211
+ max absolute errors, max relative errors
212
+ """
213
+
214
+ def _flatten(outputs):
215
+ flat = []
216
+ for tensor in outputs:
217
+ if isinstance(tensor, tuple):
218
+ flat.extend(_flatten(tensor))
219
+ else:
220
+ flat.append(tensor)
221
+ return tuple(flat)
222
+
223
+ abs_errs = []
224
+ rel_errs = []
225
+ for torch_outputs_mixed_types, onnx_outputs in zip(expected, outputs):
226
+ torch_outputs = _flatten(torch_outputs_mixed_types)
227
+ assert len(torch_outputs) == len(onnx_outputs), (
228
+ f"Length mismatch {len(torch_outputs)} != {len(onnx_outputs)}"
229
+ )
230
+ for torch_tensor, onnx_tensor in zip(torch_outputs, onnx_outputs):
231
+ assert torch_tensor.dtype == onnx_tensor.dtype, (
232
+ f"Type mismatch {torch_tensor.dtype} != {onnx_tensor.dtype}"
233
+ )
234
+ assert torch_tensor.shape == onnx_tensor.shape, (
235
+ f"Type mismatch {torch_tensor.shape} != {onnx_tensor.shape}"
236
+ )
237
+ diff = torch_tensor - onnx_tensor
238
+ abs_err = float(diff.abs().max())
239
+ rel_err = float((diff.abs() / torch_tensor).max())
240
+ abs_errs.append(abs_err)
241
+ rel_errs.append(rel_err)
242
+ return max(abs_errs), max(rel_errs)
243
+
244
+
245
+ def common_export(
246
+ model: Any,
247
+ inputs: Sequence[Any],
248
+ exporter: str = "dynamo",
249
+ target_opset: int = 18,
250
+ folder: str = "",
251
+ filename: str = "model.onnx",
252
+ dynamic_shapes: Any | None = None,
253
+ verbose: int = 0,
254
+ optimization: str | None = None,
255
+ stats: dict[str, Any] | None = None,
256
+ ):
257
+ """
258
+ Exports a model into a folder.
259
+
260
+ Args:
261
+ model: model
262
+ exporter: script, dynamo
263
+ folder: folder to export into
264
+ filename: onnx filename
265
+ inputs: inputs
266
+ dynamic_shapes: dynamic shapes
267
+ target_opset: target opset
268
+ optimization: optimization scenario, '/' separated values
269
+ verbose: verbosity
270
+ stats: if not None, populates this
271
+ dictionary with statistics about time
272
+
273
+ Returns:
274
+ onnx proto
275
+
276
+ """
277
+ import torch.onnx
278
+
279
+ if folder:
280
+ if not os.path.exists(folder):
281
+ os.mkdir(folder)
282
+ filename = os.path.join(folder, filename)
283
+
284
+ if verbose:
285
+ print(f"[common_export] start exporting with {exporter!r} in {filename!r}")
286
+ begin = time.perf_counter()
287
+ if exporter == "script":
288
+ torch.onnx.export(
289
+ model,
290
+ inputs, # type: ignore[arg-type]
291
+ filename,
292
+ do_constant_folding=False,
293
+ input_names=[f"input{i}" for i in range(len(inputs))],
294
+ opset_version=target_opset,
295
+ dynamic_axes=dynamic_shapes,
296
+ )
297
+ elif exporter == "dynamo":
298
+ assert dynamic_shapes is None, (
299
+ f"dynamic_shapes={dynamic_shapes} is not implemented yet"
300
+ )
301
+ with torch.no_grad():
302
+ prog = torch.onnx.dynamo_export(model, *inputs)
303
+ onnx.save(prog.model_proto, filename)
304
+ else:
305
+ raise ValueError(f"Unknown exporter {exporter!r}")
306
+
307
+ if stats is not None:
308
+ stats["export_time"] = time.perf_counter() - begin
309
+ stats["filesize"] = os.stat(filename).st_size
310
+
311
+ if verbose:
312
+ print(f"[common_export] exporter done in {time.perf_counter() - begin}s")
313
+ print(f"[common_export] size of the export: {os.stat(filename).st_size / 2**20} Mb")
314
+
315
+ with open(filename, "rb") as f:
316
+ onx = onnx.load(f)
317
+
318
+ if optimization:
319
+ if verbose:
320
+ print(f"[common_export] start optimization with {optimization!r}")
321
+ begin = time.perf_counter()
322
+ optimized_model = optimize_model_proto(onx, optimization, verbose=verbose, stats=stats)
323
+ end = time.perf_counter() - begin
324
+ if stats is not None:
325
+ stats["optimization_time"] = end
326
+ if verbose:
327
+ print(f"[common_export] optimization done in {end}")
328
+ print(f"[common_export] saves the model in {filename!r}")
329
+ begin = time.perf_counter()
330
+
331
+ onnx.save(optimized_model, filename)
332
+ if verbose:
333
+ print(f"[common_export] done saving in {time.perf_counter() - begin}")
334
+
335
+ return onx
336
+
337
+
338
+ def apply_rule_sets(
339
+ model_proto: onnx.ModelProto,
340
+ rule_sets: list[str],
341
+ stats: dict[str, Any] | None = None,
342
+ verbose: int = 0,
343
+ ):
344
+ """
345
+ Applies set of patterns on a model to optimizes.
346
+
347
+ Args:
348
+ model_proto: model
349
+ rule_sets: sets ot apply
350
+ stats: add statistics if not empty
351
+ verbose: verbosity
352
+
353
+ Returns:
354
+ optimized model
355
+ """
356
+ assert rule_sets, "No need to call apply_rule_sets for an empty set."
357
+ if verbose:
358
+ print(f"[apply_rule_sets] deserialize model before {rule_sets}")
359
+ begin = time.perf_counter()
360
+ ir_model = ir.serde.deserialize_model(model_proto)
361
+ end = time.perf_counter() - begin
362
+ if stats is not None:
363
+ stats["deserialize_time"] = end
364
+ if verbose:
365
+ print(f"[apply_rule_sets] deserialize done in {end}")
366
+
367
+ for rule_set_name in rule_sets:
368
+ if verbose:
369
+ print(f"[apply_rule_sets] applies {rule_set_name!r}")
370
+
371
+ if rule_set_name == "llama0":
372
+ rule_set = rules.llama_p0_rule_set()
373
+ elif rule_set_name == "onnxruntime":
374
+ rule_set = orp.RewriteRuleSet(ort_rules.ORT_PATTERN_REWRITE_RULES)
375
+ else:
376
+ raise ValueError(f"Unexpected rule_set name {rule_set_name!r}")
377
+
378
+ begin = time.perf_counter()
379
+ rule_set.apply_to_model(ir_model)
380
+ remove_unused_nodes(ir_model)
381
+ end = time.perf_counter() - begin
382
+ if stats is not None:
383
+ stats[f"opt_rule_{rule_set_name}_time"] = end
384
+ if verbose:
385
+ print(f"[apply_rule_sets] {rule_set_name} done in {end}")
386
+
387
+ if verbose:
388
+ print("[apply_rule_sets] serialize model")
389
+ begin = time.perf_counter()
390
+ rewritten_model = ir.serde.serialize_model(ir_model)
391
+ end = time.perf_counter() - begin
392
+ if stats is not None:
393
+ stats["serialize_time"] = end
394
+ if verbose:
395
+ print(f"[apply_rule_sets] serialize done in {end}")
396
+
397
+ if verbose:
398
+ print("[apply_rule_sets] remove unused")
399
+ begin = time.perf_counter()
400
+
401
+ remove_unused_nodes(rewritten_model)
402
+
403
+ end = time.perf_counter() - begin
404
+ if stats is not None:
405
+ stats["opt_remove_unused_time"] = end
406
+ if verbose:
407
+ print(f"[apply_rule_sets] remove unused done in {end}")
408
+
409
+ return rewritten_model
410
+
411
+
412
+ def optimize_model_proto(
413
+ model_proto: onnx.ModelProto,
414
+ optimization: str | None = None,
415
+ verbose: int = 0,
416
+ stats: dict[str, Any] | None = None,
417
+ ):
418
+ """
419
+ Optimizes a model given some scenarios.
420
+
421
+ Args:
422
+ model_proto: ModelProto
423
+ optimization: '/' separated value
424
+ verbose: verbosity
425
+ stats: if not None, populates this dictionary with statistics
426
+
427
+ Returns:
428
+ optmized model
429
+ """
430
+ if not optimization:
431
+ return model_proto
432
+
433
+ known_rule_sets = {"llama0", "onnxruntime"}
434
+
435
+ rule_sets: list[str] = []
436
+ for value in optimization.split("/"):
437
+ if value in known_rule_sets:
438
+ rule_sets.append(value)
439
+ continue
440
+ if value not in known_rule_sets and rule_sets:
441
+ model_proto = apply_rule_sets(model_proto, rule_sets, stats=stats, verbose=verbose)
442
+ del rule_sets[:]
443
+ continue
444
+
445
+ if verbose:
446
+ print(f"[optimize_model_proto] start {value}")
447
+
448
+ n_nodes = len(model_proto.graph.node)
449
+ n_functions = len(model_proto.functions)
450
+ begin = time.perf_counter()
451
+
452
+ if value == "optimize":
453
+ model_proto = onnxscript.optimizer.optimize(
454
+ model_proto,
455
+ num_iterations=2,
456
+ onnx_shape_inference=False,
457
+ )
458
+
459
+ elif value == "rewrite":
460
+ model_proto = onnxscript.rewriter.rewrite(model_proto)
461
+
462
+ elif value == "inline":
463
+ model_proto = onnx.inliner.inline_local_functions(model_proto)
464
+
465
+ else:
466
+ raise AssertionError(
467
+ f"Optimization step {value!r} is not implemented in {optimization!r}"
468
+ )
469
+
470
+ end = time.perf_counter() - begin
471
+ delta = len(model_proto.graph.node) - n_nodes
472
+ deltaf = len(model_proto.functions) - n_functions
473
+ if stats:
474
+ stats[f"opt_{value}_time"] = end
475
+ stats[f"opt_{value}_dnodes"] = delta
476
+ stats[f"opt_{value}_dfunctions"] = deltaf
477
+ if verbose:
478
+ print(
479
+ f"[optimize_model_proto] {value} done in {end} "
480
+ f"with +/- {delta} nodes, +/- {deltaf} functions"
481
+ )
482
+ if rule_sets:
483
+ model_proto = apply_rule_sets(model_proto, rule_sets, stats=stats, verbose=verbose)
484
+
485
+ return model_proto
486
+
487
+
488
+ def run_inference(
489
+ model: Any,
490
+ example_inputs: Sequence[Any],
491
+ warmup: int = 5,
492
+ repeat: int = 5,
493
+ verbose: int = 0,
494
+ ) -> dict[str, Any]:
495
+ """
496
+ Runs multiple times the same inference.
497
+
498
+ Args:
499
+ model: torch model to run
500
+ example_inputs: dummy inputs
501
+ warmup: number of iterations to warmup
502
+ repeat: number of iterations to repeat
503
+ verbose: verbosity
504
+
505
+ Returns:
506
+ statistcs
507
+ """
508
+ if verbose:
509
+ print(f"[run_inference] start {warmup} warmup iterations")
510
+
511
+ stats: dict[str, Any] = {}
512
+ iterations: list[float] = []
513
+ begin = time.perf_counter()
514
+ for i in range(warmup):
515
+ t0 = time.perf_counter()
516
+ model(*example_inputs[i % len(example_inputs)])
517
+ iterations.append(time.perf_counter() - t0)
518
+ end = time.perf_counter() - begin
519
+ stats["warmup"] = warmup
520
+ stats["warmup_time"] = end
521
+ stats["warmup_iter"] = iterations
522
+
523
+ if verbose:
524
+ print(f"[run_inference] warmup done in {time.perf_counter() - begin}")
525
+ print(f"[run_inference] start {repeat} iterations")
526
+
527
+ iterations = []
528
+ begin = time.perf_counter()
529
+ for i in range(warmup):
530
+ t0 = time.perf_counter()
531
+ model(*example_inputs[i % len(example_inputs)])
532
+ iterations.append(time.perf_counter() - t0)
533
+ end = time.perf_counter() - begin
534
+ stats["repeat"] = repeat
535
+ stats["repeat_time"] = end
536
+ stats["repeat_iter"] = iterations
537
+
538
+ if verbose:
539
+ print(f"[run_inference] measure done in {time.perf_counter() - begin}")
540
+
541
+ return stats
542
+
543
+
544
+ class WrapInferenceSessionForTorch:
545
+ def __init__(self, sess: Any):
546
+ # onnxruntime is importing when needed as it takes a couple of seconds if it contains CUDA EP.
547
+ import onnxruntime
548
+ import torch
549
+ from onnxruntime.capi import _pybind_state as ORTC # noqa: N812
550
+
551
+ self.sess = sess
552
+ self.input_names = [i.name for i in sess.get_inputs()]
553
+ self.output_names = [i.name for i in sess.get_outputs()]
554
+ self.bind = onnxruntime.SessionIOBinding(sess._sess)
555
+ self.OrtValue = ORTC.OrtValue
556
+ self.ORTC = ORTC
557
+ self.torch = torch
558
+ self.run_options = onnxruntime.RunOptions()
559
+
560
+ self.TORCH_DTYPE_TO_NUMPY_DTYPE = {
561
+ torch.float16: np.float16,
562
+ torch.float32: np.float32,
563
+ torch.float64: np.float64,
564
+ torch.uint8: np.uint8,
565
+ torch.int8: np.int8,
566
+ torch.int16: np.int16,
567
+ torch.int32: np.int32,
568
+ torch.int64: np.int64,
569
+ torch.bool: np.bool_,
570
+ }
571
+
572
+ DEVICES = {
573
+ -1: ORTC.OrtDevice(ORTC.OrtDevice.cpu(), ORTC.OrtDevice.default_memory(), 0)
574
+ }
575
+
576
+ if torch.cuda.is_available():
577
+ for i in range(torch.cuda.device_count()):
578
+ DEVICES[i] = ORTC.OrtDevice(
579
+ ORTC.OrtDevice.cuda(), ORTC.OrtDevice.default_memory(), i
580
+ )
581
+
582
+ self.DEVICES = DEVICES
583
+
584
+ def _get_ortvalues_from_torch_tensors(
585
+ self,
586
+ tensors: tuple[Any, ...], # tuple["torch.Tensor", ...],
587
+ n_outputs: int,
588
+ ) -> tuple[Any, Any]: # tuple[tuple["torch.Tensor", ...], tuple["OrtDevice", ...]]:
589
+ ortvalues = self.ORTC.OrtValueVector()
590
+ ortvalues.reserve(len(tensors))
591
+ dtypes = []
592
+ shapes = []
593
+ data_ptrs = []
594
+ devices = []
595
+
596
+ max_device = -1
597
+ assert isinstance(max_device, int), f"unexpected type for device={max_device!r}"
598
+ assert tensors is not None, "tensors cannot be None"
599
+ new_tensors = []
600
+ for tensor in tensors:
601
+ assert isinstance(tensor, self.torch.Tensor), f"Unexpected type {type(tensor)}"
602
+ dtypes.append(self.TORCH_DTYPE_TO_NUMPY_DTYPE[tensor.dtype])
603
+ shapes.append(tensor.size())
604
+ data_ptrs.append(tensor.data_ptr())
605
+ d = tensor.get_device()
606
+ devices.append(self.DEVICES[d])
607
+ new_tensors.append(tensor)
608
+ max_device = max(max_device, tensor.get_device())
609
+
610
+ ortvalues.push_back_batch(new_tensors, data_ptrs, dtypes, shapes, devices)
611
+ output_devices = []
612
+ for _ in range(n_outputs):
613
+ dev = self.DEVICES[max_device]
614
+ output_devices.append(dev)
615
+
616
+ return ortvalues, output_devices
617
+
618
+ def _ortvalues_to_torch_tensor(
619
+ self,
620
+ ortvalues: Any, # "onnxruntime.OrtValueVector",
621
+ ) -> tuple[Any, ...]: # tuple["torch.Tensor", ...]:
622
+ if len(ortvalues) == 0:
623
+ return tuple()
624
+
625
+ from torch._C import _from_dlpack
626
+
627
+ if all(map(lambda i: ortvalues[i].has_value(), range(len(ortvalues)))): # noqa: C417
628
+ res = ortvalues.to_dlpacks(_from_dlpack)
629
+ else:
630
+ res = []
631
+ for i in range(len(ortvalues)):
632
+ res.append(
633
+ _from_dlpack(ortvalues[i].to_dlpack())
634
+ if ortvalues[i].has_value()
635
+ else None
636
+ )
637
+ return tuple(res)
638
+
639
+ def run(self, output_names, feeds):
640
+ inputs = [feeds[i] for i in self.input_names]
641
+ return self.run_dlpack(*inputs, output_names=output_names)
642
+
643
+ def run_dlpack(self, *inputs, output_names=None):
644
+ if output_names is None:
645
+ output_names = self.output_names
646
+ ortvalues, output_devices = self._get_ortvalues_from_torch_tensors(
647
+ inputs, len(output_names)
648
+ )
649
+
650
+ ort_outputs = self.ORTC.OrtValueVector()
651
+ self.sess.run_with_ortvaluevector(
652
+ self.run_options,
653
+ self.input_names,
654
+ ortvalues,
655
+ output_names,
656
+ ort_outputs,
657
+ output_devices,
658
+ )
659
+ pth_outputs = self._ortvalues_to_torch_tensor(ort_outputs)
660
+ return pth_outputs
661
+
662
+
663
+ def run_onnx_inference(
664
+ model: onnx.ModelProto,
665
+ example_inputs: Sequence[Any],
666
+ warmup: int = 5,
667
+ repeat: int = 5,
668
+ verbose: int = 0,
669
+ ort_optimize: bool = True,
670
+ torch_model: Any | None = None,
671
+ ) -> dict[str, Any]:
672
+ """
673
+ Runs multiple times the same inference with onnxruntime.
674
+
675
+ Args:
676
+ model: torch model to run
677
+ example_inputs: dummy inputs
678
+ warmup: number of iterations to warmup
679
+ repeat: number of iterations to repeat
680
+ verbose: verbosity
681
+ ort_optimize: enable, disable onnxruntime optimizations
682
+ torch_model: if not empty, measure the discrepancies
683
+
684
+ Returns:
685
+ statistcs
686
+ """
687
+ stats: dict[str, Any] = {}
688
+ device = example_inputs[0][0].get_device()
689
+ providers = (
690
+ ["CUDAExecutionProvider", "CPUExecutionProvider"]
691
+ if device >= 0
692
+ else ["CPUExecutionProvider"]
693
+ )
694
+ stats["providers"] = ",".join(providers)
695
+ if verbose:
696
+ print(f"[run_inference] create session with providers {providers!r}")
697
+
698
+ begin = time.perf_counter()
699
+ # onnxruntime is importing when needed as it takes a couple of seconds if it contains CUDA EP.
700
+ import onnxruntime
701
+
702
+ so = onnxruntime.SessionOptions()
703
+ if ort_optimize:
704
+ so.add_session_config_entry("session.disable_aot_function_inlining", "0")
705
+ so.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
706
+ else:
707
+ so.add_session_config_entry("session.disable_aot_function_inlining", "1")
708
+ so.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_DISABLE_ALL
709
+
710
+ sess = onnxruntime.InferenceSession(model.SerializeToString(), so, providers)
711
+ wrapped_session = WrapInferenceSessionForTorch(sess)
712
+
713
+ end = time.perf_counter() - begin
714
+ stats["ort_session_create_time"] = end
715
+ if verbose:
716
+ print(f"[run_inference] created session in {end}")
717
+ print(f"[run_inference] start {warmup} warmup iterations")
718
+
719
+ if torch_model:
720
+ expected = [
721
+ torch_model(*example_inputs[i % len(example_inputs)]) for i in range(warmup)
722
+ ]
723
+
724
+ got = []
725
+ iterations = []
726
+ begin = time.perf_counter()
727
+ for i in range(warmup):
728
+ t0 = time.perf_counter()
729
+ got.append(wrapped_session.run_dlpack(*example_inputs[i % len(example_inputs)]))
730
+ iterations.append(time.perf_counter() - t0)
731
+ end = time.perf_counter() - begin
732
+ stats["warmup"] = warmup
733
+ stats["warmup_time"] = end / warmup
734
+ stats["warmup_iter"] = iterations
735
+ if torch_model:
736
+ abs_err, rel_err = measure_discrepancies(expected, got)
737
+ stats["discrepancies_abs"] = abs_err
738
+ stats["discrepancies_rel"] = rel_err
739
+
740
+ if verbose:
741
+ print(f"[run_inference] warmup done in {time.perf_counter() - begin}")
742
+ print(f"[run_inference] start {repeat} iterations")
743
+
744
+ iterations = []
745
+ begin = time.perf_counter()
746
+ for i in range(repeat):
747
+ t0 = time.perf_counter()
748
+ wrapped_session.run_dlpack(*example_inputs[i % len(example_inputs)])
749
+ iterations.append(time.perf_counter() - t0)
750
+ end = time.perf_counter() - begin
751
+ stats["repeat"] = repeat
752
+ stats["repeat_time"] = end / repeat
753
+ stats["repeat_iter"] = iterations
754
+
755
+ if verbose:
756
+ print(f"[run_inference] measure done in {time.perf_counter() - begin}")
757
+
758
+ return stats
759
+
760
+
761
+ def multi_run(kwargs: dict[str, Any]) -> bool:
762
+ """Checks if multiple values were sent for one argument."""
763
+ return any(isinstance(v, str) and "," in v for v in kwargs.values())
764
+
765
+
766
+ def make_configs(kwargs: dict[str, Any]) -> list[dict[str, Any]]:
767
+ """Creates all the configurations based on the command line arguments."""
768
+ print(kwargs)
769
+ args = []
770
+ for k, v in kwargs.items():
771
+ if isinstance(v, str):
772
+ args.append([(k, s) for s in v.split(",")])
773
+ else:
774
+ args.append([(k, v)])
775
+ configs = list(itertools.product(*args))
776
+ return [dict(c) for c in configs]
777
+
778
+
779
+ def make_dataframe_from_benchmark_data(data: list[dict]) -> Any:
780
+ """Creates a dataframe from the received data."""
781
+ import pandas
782
+
783
+ return pandas.DataFrame(data)