polygraphy 0.50.3__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.
- polygraphy/__init__.py +3 -0
- polygraphy/backend/__init__.py +0 -0
- polygraphy/backend/base/__init__.py +2 -0
- polygraphy/backend/base/loader.py +40 -0
- polygraphy/backend/base/runner.py +258 -0
- polygraphy/backend/base/util.py +53 -0
- polygraphy/backend/common/__init__.py +1 -0
- polygraphy/backend/common/loader.py +105 -0
- polygraphy/backend/onnx/__init__.py +1 -0
- polygraphy/backend/onnx/loader.py +997 -0
- polygraphy/backend/onnx/util.py +388 -0
- polygraphy/backend/onnxrt/__init__.py +2 -0
- polygraphy/backend/onnxrt/loader.py +91 -0
- polygraphy/backend/onnxrt/runner.py +90 -0
- polygraphy/backend/pluginref/__init__.py +17 -0
- polygraphy/backend/pluginref/references.py +102 -0
- polygraphy/backend/pluginref/runner.py +83 -0
- polygraphy/backend/pyt/__init__.py +1 -0
- polygraphy/backend/pyt/runner.py +81 -0
- polygraphy/backend/tensorrt_rtx/__init__.py +1 -0
- polygraphy/backend/tensorrt_rtx/config.py +124 -0
- polygraphy/backend/tf/__init__.py +39 -0
- polygraphy/backend/tf/loader.py +496 -0
- polygraphy/backend/tf/runner.py +107 -0
- polygraphy/backend/tf/util.py +204 -0
- polygraphy/backend/trt/__init__.py +9 -0
- polygraphy/backend/trt/algorithm_selector.py +423 -0
- polygraphy/backend/trt/calibrator.py +295 -0
- polygraphy/backend/trt/config.py +716 -0
- polygraphy/backend/trt/file_reader.py +95 -0
- polygraphy/backend/trt/loader.py +1219 -0
- polygraphy/backend/trt/profile.py +215 -0
- polygraphy/backend/trt/runner.py +761 -0
- polygraphy/backend/trt/util.py +951 -0
- polygraphy/common/__init__.py +2 -0
- polygraphy/common/interface.py +192 -0
- polygraphy/common/struct.py +203 -0
- polygraphy/comparator/__init__.py +5 -0
- polygraphy/comparator/comparator.py +483 -0
- polygraphy/comparator/compare.py +2016 -0
- polygraphy/comparator/data_loader.py +531 -0
- polygraphy/comparator/postprocess.py +64 -0
- polygraphy/comparator/struct.py +409 -0
- polygraphy/comparator/util.py +405 -0
- polygraphy/config.py +65 -0
- polygraphy/constants.py +32 -0
- polygraphy/cuda/__init__.py +1 -0
- polygraphy/cuda/cuda.py +521 -0
- polygraphy/datatype/__init__.py +6 -0
- polygraphy/datatype/datatype.py +274 -0
- polygraphy/datatype/numpy.py +77 -0
- polygraphy/datatype/onnx.py +86 -0
- polygraphy/datatype/onnxrt.py +72 -0
- polygraphy/datatype/tensorrt.py +75 -0
- polygraphy/datatype/torch.py +68 -0
- polygraphy/exception/__init__.py +1 -0
- polygraphy/exception/exception.py +52 -0
- polygraphy/func/__init__.py +1 -0
- polygraphy/func/func.py +199 -0
- polygraphy/json/__init__.py +1 -0
- polygraphy/json/serde.py +442 -0
- polygraphy/logger/__init__.py +1 -0
- polygraphy/logger/logger.py +686 -0
- polygraphy/mod/__init__.py +3 -0
- polygraphy/mod/exporter.py +362 -0
- polygraphy/mod/importer.py +345 -0
- polygraphy/mod/trt_importer.py +41 -0
- polygraphy/mod/util.py +41 -0
- polygraphy/tools/__init__.py +3 -0
- polygraphy/tools/_main.py +88 -0
- polygraphy/tools/args/__init__.py +19 -0
- polygraphy/tools/args/backend/__init__.py +21 -0
- polygraphy/tools/args/backend/onnx/__init__.py +16 -0
- polygraphy/tools/args/backend/onnx/loader.py +770 -0
- polygraphy/tools/args/backend/onnxrt/__init__.py +17 -0
- polygraphy/tools/args/backend/onnxrt/loader.py +85 -0
- polygraphy/tools/args/backend/onnxrt/runner.py +42 -0
- polygraphy/tools/args/backend/pluginref/__init__.py +16 -0
- polygraphy/tools/args/backend/pluginref/runner.py +47 -0
- polygraphy/tools/args/backend/runner_select.py +107 -0
- polygraphy/tools/args/backend/tf/__init__.py +18 -0
- polygraphy/tools/args/backend/tf/config.py +77 -0
- polygraphy/tools/args/backend/tf/loader.py +266 -0
- polygraphy/tools/args/backend/tf/runner.py +70 -0
- polygraphy/tools/args/backend/trt/__init__.py +18 -0
- polygraphy/tools/args/backend/trt/config.py +993 -0
- polygraphy/tools/args/backend/trt/helper.py +34 -0
- polygraphy/tools/args/backend/trt/loader.py +894 -0
- polygraphy/tools/args/backend/trt/runner.py +107 -0
- polygraphy/tools/args/base.py +212 -0
- polygraphy/tools/args/comparator/__init__.py +19 -0
- polygraphy/tools/args/comparator/comparator.py +340 -0
- polygraphy/tools/args/comparator/compare.py +407 -0
- polygraphy/tools/args/comparator/data_loader.py +269 -0
- polygraphy/tools/args/comparator/postprocess.py +143 -0
- polygraphy/tools/args/logger/__init__.py +16 -0
- polygraphy/tools/args/logger/logger.py +172 -0
- polygraphy/tools/args/model.py +227 -0
- polygraphy/tools/args/util/__init__.py +1 -0
- polygraphy/tools/args/util/util.py +428 -0
- polygraphy/tools/base/__init__.py +1 -0
- polygraphy/tools/base/tool.py +212 -0
- polygraphy/tools/check/__init__.py +1 -0
- polygraphy/tools/check/check.py +32 -0
- polygraphy/tools/check/subtool/__init__.py +1 -0
- polygraphy/tools/check/subtool/lint.py +1000 -0
- polygraphy/tools/convert/__init__.py +1 -0
- polygraphy/tools/convert/convert.py +115 -0
- polygraphy/tools/data/__init__.py +1 -0
- polygraphy/tools/data/data.py +33 -0
- polygraphy/tools/data/subtool/__init__.py +2 -0
- polygraphy/tools/data/subtool/concat.py +115 -0
- polygraphy/tools/data/subtool/merge.py +89 -0
- polygraphy/tools/debug/__init__.py +1 -0
- polygraphy/tools/debug/debug.py +72 -0
- polygraphy/tools/debug/subtool/__init__.py +4 -0
- polygraphy/tools/debug/subtool/base.py +159 -0
- polygraphy/tools/debug/subtool/build.py +46 -0
- polygraphy/tools/debug/subtool/iterative_debug_args.py +761 -0
- polygraphy/tools/debug/subtool/precision.py +281 -0
- polygraphy/tools/debug/subtool/reduce.py +594 -0
- polygraphy/tools/debug/subtool/repeat.py +46 -0
- polygraphy/tools/inspect/__init__.py +1 -0
- polygraphy/tools/inspect/inspect.py +44 -0
- polygraphy/tools/inspect/subtool/__init__.py +6 -0
- polygraphy/tools/inspect/subtool/capability.py +429 -0
- polygraphy/tools/inspect/subtool/data.py +146 -0
- polygraphy/tools/inspect/subtool/diff_tactics.py +128 -0
- polygraphy/tools/inspect/subtool/model/__init__.py +1 -0
- polygraphy/tools/inspect/subtool/model/args.py +62 -0
- polygraphy/tools/inspect/subtool/model/extractors/__init__.py +27 -0
- polygraphy/tools/inspect/subtool/model/extractors/_helpers.py +60 -0
- polygraphy/tools/inspect/subtool/model/extractors/_onnx.py +237 -0
- polygraphy/tools/inspect/subtool/model/extractors/_trt.py +332 -0
- polygraphy/tools/inspect/subtool/model/graph_data.py +108 -0
- polygraphy/tools/inspect/subtool/model/model.py +217 -0
- polygraphy/tools/inspect/subtool/model/text.py +298 -0
- polygraphy/tools/inspect/subtool/model/viewer/__init__.py +3 -0
- polygraphy/tools/inspect/subtool/model/viewer/_templates.py +1435 -0
- polygraphy/tools/inspect/subtool/model/viewer/_viewer.py +192 -0
- polygraphy/tools/inspect/subtool/model/viewer/colors.py +284 -0
- polygraphy/tools/inspect/subtool/model/viewer/elements.py +276 -0
- polygraphy/tools/inspect/subtool/model/viewer/layout.py +255 -0
- polygraphy/tools/inspect/subtool/model/viewer/node_info.py +228 -0
- polygraphy/tools/inspect/subtool/model/viewer/page.py +287 -0
- polygraphy/tools/inspect/subtool/sparsity.py +50 -0
- polygraphy/tools/inspect/subtool/tactics.py +38 -0
- polygraphy/tools/multi_device/__init__.py +9 -0
- polygraphy/tools/multi_device/multi_device.py +314 -0
- polygraphy/tools/multi_device/subtool/__init__.py +1 -0
- polygraphy/tools/multi_device/subtool/shard.py +960 -0
- polygraphy/tools/plugin/__init__.py +1 -0
- polygraphy/tools/plugin/plugin.py +35 -0
- polygraphy/tools/plugin/subtool/__init__.py +5 -0
- polygraphy/tools/plugin/subtool/autotune.py +261 -0
- polygraphy/tools/plugin/subtool/autotuner/__init__.py +6 -0
- polygraphy/tools/plugin/subtool/autotuner/auto_tuner.py +250 -0
- polygraphy/tools/plugin/subtool/autotuner/optimization_strategy.py +350 -0
- polygraphy/tools/plugin/subtool/autotuner/performance_analyzer.py +280 -0
- polygraphy/tools/plugin/subtool/autotuner/replacement_engine.py +181 -0
- polygraphy/tools/plugin/subtool/list_plugins.py +34 -0
- polygraphy/tools/plugin/subtool/match.py +40 -0
- polygraphy/tools/plugin/subtool/plugin_base.py +172 -0
- polygraphy/tools/plugin/subtool/replace.py +179 -0
- polygraphy/tools/registry.py +68 -0
- polygraphy/tools/run/__init__.py +1 -0
- polygraphy/tools/run/run.py +241 -0
- polygraphy/tools/script.py +490 -0
- polygraphy/tools/sparse.py +388 -0
- polygraphy/tools/surgeon/__init__.py +1 -0
- polygraphy/tools/surgeon/subtool/__init__.py +6 -0
- polygraphy/tools/surgeon/subtool/base.py +83 -0
- polygraphy/tools/surgeon/subtool/extract.py +215 -0
- polygraphy/tools/surgeon/subtool/insert.py +170 -0
- polygraphy/tools/surgeon/subtool/prune.py +58 -0
- polygraphy/tools/surgeon/subtool/sanitize.py +259 -0
- polygraphy/tools/surgeon/subtool/weight_reconstruct.py +111 -0
- polygraphy/tools/surgeon/subtool/weight_strip.py +467 -0
- polygraphy/tools/surgeon/surgeon.py +46 -0
- polygraphy/tools/template/__init__.py +1 -0
- polygraphy/tools/template/subtool/__init__.py +4 -0
- polygraphy/tools/template/subtool/base.py +30 -0
- polygraphy/tools/template/subtool/onnx_gs.py +89 -0
- polygraphy/tools/template/subtool/shard_hint.py +419 -0
- polygraphy/tools/template/subtool/trt_config.py +62 -0
- polygraphy/tools/template/subtool/trt_network.py +76 -0
- polygraphy/tools/template/template.py +30 -0
- polygraphy/tools/util.py +50 -0
- polygraphy/util/__init__.py +2 -0
- polygraphy/util/array.py +1522 -0
- polygraphy/util/util.py +1138 -0
- polygraphy-0.50.3.dist-info/METADATA +180 -0
- polygraphy-0.50.3.dist-info/RECORD +197 -0
- polygraphy-0.50.3.dist-info/WHEEL +5 -0
- polygraphy-0.50.3.dist-info/entry_points.txt +2 -0
- polygraphy-0.50.3.dist-info/top_level.txt +1 -0
- polygraphy-0.50.3.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,997 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
import copy
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
|
|
22
|
+
from polygraphy import constants, mod, util
|
|
23
|
+
from polygraphy.backend.base import BaseLoader
|
|
24
|
+
from polygraphy.backend.onnx import util as onnx_util
|
|
25
|
+
from polygraphy.datatype import DataType
|
|
26
|
+
from polygraphy.logger import G_LOGGER, LogMode
|
|
27
|
+
|
|
28
|
+
np = mod.lazy_import("numpy")
|
|
29
|
+
onnx = mod.lazy_import("onnx>=1.8.1")
|
|
30
|
+
onnxrt = mod.lazy_import("onnxruntime>=1.10.0")
|
|
31
|
+
onnxconverter_common = mod.lazy_import("onnxconverter_common>=1.12.2")
|
|
32
|
+
tf = mod.lazy_import("tensorflow<2.0")
|
|
33
|
+
tf2onnx = mod.lazy_import("tf2onnx")
|
|
34
|
+
tf_util = mod.lazy_import("polygraphy.backend.tf.util", log=False)
|
|
35
|
+
gs = mod.lazy_import("onnx_graphsurgeon>=0.3.27")
|
|
36
|
+
# ONNX-RT's shape inference also requires "sympy", but it is not reported as a dependency,
|
|
37
|
+
# so we work around it by checking for it manually.
|
|
38
|
+
onnxrt_symbolic_shape_inference = mod.lazy_import(
|
|
39
|
+
"onnxruntime.tools.symbolic_shape_infer>=1.10.0", requires=["sympy"]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
LARGE_MODEL_THRESHOLD = 512 << 20 # 512 MiB
|
|
43
|
+
PROTOBUF_THRESHOLD = 2e9
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BaseLoadOnnxCopy(BaseLoader):
|
|
47
|
+
"""
|
|
48
|
+
Abstract base class for loaders that require loading an ONNX model and potentially
|
|
49
|
+
making a copy.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, model, copy=None):
|
|
53
|
+
"""
|
|
54
|
+
Args:
|
|
55
|
+
model (Union[onnx.ModelProto, Callable() -> onnx.ModelProto]):
|
|
56
|
+
An ONNX model or a callable that returns one.
|
|
57
|
+
|
|
58
|
+
copy (bool): Whether to create a copy of the model first. Defaults to False.
|
|
59
|
+
"""
|
|
60
|
+
self._model = model
|
|
61
|
+
self.copy = util.default(copy, False)
|
|
62
|
+
|
|
63
|
+
def load(self):
|
|
64
|
+
model, _ = util.invoke_if_callable(self._model)
|
|
65
|
+
if self.copy:
|
|
66
|
+
model = copy.copy(model)
|
|
67
|
+
return model
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class _GSGraphManager:
|
|
71
|
+
"""
|
|
72
|
+
Imports an ONNX-GraphSurgeon graph.
|
|
73
|
+
|
|
74
|
+
If the provided model is already a graph, the graph is not
|
|
75
|
+
exported to ONNX.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, model):
|
|
79
|
+
self._model = model
|
|
80
|
+
|
|
81
|
+
def __enter__(self):
|
|
82
|
+
model, _ = util.invoke_if_callable(self._model)
|
|
83
|
+
self.USE_GS_GRAPH = isinstance(model, gs.Graph)
|
|
84
|
+
if self.USE_GS_GRAPH:
|
|
85
|
+
self.graph = model.copy()
|
|
86
|
+
else:
|
|
87
|
+
self.graph = gs_from_onnx(model)
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
91
|
+
if self.USE_GS_GRAPH:
|
|
92
|
+
self.retval = self.graph
|
|
93
|
+
else:
|
|
94
|
+
self.retval = gs.export_onnx(self.graph, do_type_check=False)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@mod.export(funcify=True)
|
|
98
|
+
class GsFromOnnx(BaseLoader):
|
|
99
|
+
"""
|
|
100
|
+
Functor that creates an ONNX-GraphSurgeon graph from an ONNX ModelProto.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(self, model):
|
|
104
|
+
"""
|
|
105
|
+
Creates an ONNX-GraphSurgeon graph from an ONNX ModelProto.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
model (Union[onnx.ModelProto, Callable() -> onnx.ModelProto]):
|
|
109
|
+
An ONNX model or a callable that returns one.
|
|
110
|
+
"""
|
|
111
|
+
self._model = model
|
|
112
|
+
|
|
113
|
+
@util.check_called_by("__call__")
|
|
114
|
+
def call_impl(self):
|
|
115
|
+
"""
|
|
116
|
+
Returns:
|
|
117
|
+
onnx_graphsurgeon.Graph: The ONNX-GraphSurgeon representation of the ONNX model
|
|
118
|
+
"""
|
|
119
|
+
model, _ = util.invoke_if_callable(self._model)
|
|
120
|
+
return gs.import_onnx(model)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@mod.export(funcify=True)
|
|
124
|
+
class OnnxFromPath(BaseLoader):
|
|
125
|
+
"""
|
|
126
|
+
Functor that loads an ONNX model from a file.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
def __init__(self, path, external_data_dir=None, ignore_external_data=None):
|
|
130
|
+
"""
|
|
131
|
+
Loads an ONNX model from a file.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
path (str): The path from which to load the model.
|
|
135
|
+
|
|
136
|
+
external_data_dir (str): The directory where external data for the model is stored.
|
|
137
|
+
ignore_external_data (bool):
|
|
138
|
+
Whether to ignore any external data and just load the model structure without any weights.
|
|
139
|
+
The model will be usable only for purposes that don't require weights, such as extracting
|
|
140
|
+
subgraphs or inspecting model structure.
|
|
141
|
+
This can be useful in cases where external data is not available.
|
|
142
|
+
Defaults to False.
|
|
143
|
+
"""
|
|
144
|
+
self.path = path
|
|
145
|
+
self.external_data_dir = external_data_dir
|
|
146
|
+
self.ignore_external_data = util.default(ignore_external_data, False)
|
|
147
|
+
|
|
148
|
+
@util.check_called_by("__call__")
|
|
149
|
+
def call_impl(self):
|
|
150
|
+
"""
|
|
151
|
+
Returns:
|
|
152
|
+
onnx.ModelProto: The ONNX model
|
|
153
|
+
"""
|
|
154
|
+
G_LOGGER.info(f"Loading model: {self.path}")
|
|
155
|
+
# If external_data_dir is not None, we'll load external data ourselves
|
|
156
|
+
auto_load_ext_data = (
|
|
157
|
+
self.external_data_dir is None and not self.ignore_external_data
|
|
158
|
+
)
|
|
159
|
+
try:
|
|
160
|
+
model = onnx.load(self.path, load_external_data=auto_load_ext_data)
|
|
161
|
+
except FileNotFoundError:
|
|
162
|
+
if auto_load_ext_data:
|
|
163
|
+
G_LOGGER.warning(
|
|
164
|
+
"Failed to load model. This could be because external data could not be loaded.\n"
|
|
165
|
+
"Hint: If you don't need the model weights, try ignoring external data by setting `ignore_external_data=True` "
|
|
166
|
+
"or using the `--ignore-external-data` command-line option."
|
|
167
|
+
)
|
|
168
|
+
raise
|
|
169
|
+
|
|
170
|
+
if self.external_data_dir is not None:
|
|
171
|
+
G_LOGGER.verbose(f"Loading external data from: {self.external_data_dir}")
|
|
172
|
+
onnx.external_data_helper.load_external_data_for_model(
|
|
173
|
+
model, self.external_data_dir
|
|
174
|
+
)
|
|
175
|
+
return model
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@mod.export(funcify=True)
|
|
179
|
+
class OnnxFromTfGraph(BaseLoader):
|
|
180
|
+
"""
|
|
181
|
+
Functor that loads a TensorFlow graph and converts it to ONNX using the tf2onnx converter.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def __init__(self, graph, opset=None, optimize=None):
|
|
185
|
+
"""
|
|
186
|
+
Converts a TensorFlow model into ONNX.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
graph (Union[Tuple[tf.Graph, Sequence[str]], Callable() -> Tuple[tf.Graph, Sequence[str]]]):
|
|
190
|
+
A tuple containing a TensorFlow graph and output names or a callable that returns one.
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
opset (int): The ONNX opset to use during conversion.
|
|
194
|
+
optimize (bool): Whether to use tf2onnx's graph optimization pass.
|
|
195
|
+
"""
|
|
196
|
+
self._graph = graph
|
|
197
|
+
self.opset = util.default(opset, 11)
|
|
198
|
+
self.optimize = util.default(optimize, True)
|
|
199
|
+
|
|
200
|
+
@util.check_called_by("__call__")
|
|
201
|
+
def call_impl(self):
|
|
202
|
+
"""
|
|
203
|
+
Returns:
|
|
204
|
+
onnx.ModelProto: The ONNX model.
|
|
205
|
+
"""
|
|
206
|
+
(graph, output_names), _ = util.invoke_if_callable(self._graph)
|
|
207
|
+
input_names = list(tf_util.get_input_metadata(graph).keys())
|
|
208
|
+
|
|
209
|
+
graphdef = graph.as_graph_def()
|
|
210
|
+
if self.optimize:
|
|
211
|
+
graphdef = tf2onnx.tfonnx.tf_optimize(
|
|
212
|
+
input_names, output_names, graph.as_graph_def()
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
with tf.Graph().as_default() as graph, tf.compat.v1.Session(
|
|
216
|
+
graph=graph
|
|
217
|
+
) as sess:
|
|
218
|
+
tf.import_graph_def(graphdef, name="")
|
|
219
|
+
|
|
220
|
+
onnx_graph = tf2onnx.tfonnx.process_tf_graph(
|
|
221
|
+
graph,
|
|
222
|
+
input_names=input_names,
|
|
223
|
+
output_names=output_names,
|
|
224
|
+
opset=self.opset,
|
|
225
|
+
)
|
|
226
|
+
if self.optimize:
|
|
227
|
+
onnx_graph = tf2onnx.optimizer.optimize_graph(onnx_graph)
|
|
228
|
+
return onnx_graph.make_model("model")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@mod.export(funcify=True)
|
|
232
|
+
class ModifyOutputs(BaseLoadOnnxCopy):
|
|
233
|
+
"""
|
|
234
|
+
Functor that modifies the outputs of an ONNX model.
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
def __init__(
|
|
238
|
+
self, model, outputs=None, exclude_outputs=None, copy=None, mark_types=None
|
|
239
|
+
):
|
|
240
|
+
"""
|
|
241
|
+
Modifies outputs of an ONNX model.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
model (Union[onnx.ModelProto, Callable() -> onnx.ModelProto]):
|
|
245
|
+
An ONNX model or a callable that returns one.
|
|
246
|
+
|
|
247
|
+
outputs (Sequence[str]):
|
|
248
|
+
Names of tensors to mark as outputs. If provided, this will override the
|
|
249
|
+
existing model outputs. Supports fnmatch wildcard patterns (e.g. ``"conv_*"``).
|
|
250
|
+
If a value of `constants.MARK_ALL` is used instead of a list, all tensors in the network are marked.
|
|
251
|
+
exclude_outputs (Sequence[str]):
|
|
252
|
+
Names of tensors to exclude as outputs. Supports fnmatch wildcard patterns (e.g. ``"conv_*"``).
|
|
253
|
+
This can be useful in conjunction with ``outputs=constants.MARK_ALL`` to omit outputs.
|
|
254
|
+
copy (bool): Whether to create a copy of the model first. Defaults to False.
|
|
255
|
+
mark_types (Sequence[str]):
|
|
256
|
+
Op type names (case-insensitive) whose output tensors should be marked as outputs.
|
|
257
|
+
For example, ``mark_types=["Conv", "Add"]`` marks the outputs of all Conv and Add nodes.
|
|
258
|
+
If a requested type is not found in the model, a non-critical error is logged with
|
|
259
|
+
suggestions for similar types.
|
|
260
|
+
"""
|
|
261
|
+
super().__init__(model, copy)
|
|
262
|
+
self.outputs = outputs
|
|
263
|
+
self.exclude_outputs = exclude_outputs
|
|
264
|
+
self.mark_types = mark_types
|
|
265
|
+
|
|
266
|
+
@util.check_called_by("__call__")
|
|
267
|
+
def call_impl(self):
|
|
268
|
+
"""
|
|
269
|
+
Returns:
|
|
270
|
+
onnx.ModelProto: The ONNX model with modified outputs.
|
|
271
|
+
"""
|
|
272
|
+
model = self.load()
|
|
273
|
+
|
|
274
|
+
if self.outputs == constants.MARK_ALL:
|
|
275
|
+
G_LOGGER.verbose("Marking all ONNX tensors as outputs")
|
|
276
|
+
model = onnx_util.mark_layerwise(model)
|
|
277
|
+
elif self.outputs is not None:
|
|
278
|
+
model = onnx_util.mark_outputs(model, self.outputs)
|
|
279
|
+
|
|
280
|
+
if self.mark_types is not None:
|
|
281
|
+
model = onnx_util.mark_by_op_type(model, self.mark_types)
|
|
282
|
+
|
|
283
|
+
if self.exclude_outputs is not None:
|
|
284
|
+
model = onnx_util.unmark_outputs(model, self.exclude_outputs)
|
|
285
|
+
|
|
286
|
+
return model
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@mod.export(funcify=True)
|
|
290
|
+
class ConvertToFp16(BaseLoadOnnxCopy):
|
|
291
|
+
"""
|
|
292
|
+
Functor that converts all floating point tensors in the model to 16-bit precision.
|
|
293
|
+
This is *not* needed in order to use TensorRT's fp16 precision, but may be useful for other backends.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
def __init__(self, model, copy=None):
|
|
297
|
+
"""
|
|
298
|
+
Converts all floating point tensors in the model to 16-bit precision.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
model (Union[onnx.ModelProto, Callable() -> onnx.ModelProto]):
|
|
302
|
+
An ONNX model or a callable that returns one.
|
|
303
|
+
copy (bool): Whether to create a copy of the model first. Defaults to False.
|
|
304
|
+
"""
|
|
305
|
+
super().__init__(model, copy)
|
|
306
|
+
|
|
307
|
+
@util.check_called_by("__call__")
|
|
308
|
+
def call_impl(self):
|
|
309
|
+
"""
|
|
310
|
+
Returns:
|
|
311
|
+
onnx.ModelProto: The modified ONNX model.
|
|
312
|
+
"""
|
|
313
|
+
model = self.load()
|
|
314
|
+
|
|
315
|
+
G_LOGGER.info("Converting float tensors to float16")
|
|
316
|
+
model = onnxconverter_common.float16.convert_float_to_float16(
|
|
317
|
+
model, keep_io_types=True, disable_shape_infer=True
|
|
318
|
+
)
|
|
319
|
+
return model
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@mod.export(funcify=True)
|
|
323
|
+
class FoldConstants(BaseLoadOnnxCopy):
|
|
324
|
+
"""
|
|
325
|
+
Functor that folds constants in an ONNX model.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
def __init__(
|
|
329
|
+
self,
|
|
330
|
+
model,
|
|
331
|
+
num_passes=None,
|
|
332
|
+
do_shape_inference=None,
|
|
333
|
+
partitioning=None,
|
|
334
|
+
fold_shapes=None,
|
|
335
|
+
copy=None,
|
|
336
|
+
error_ok=None,
|
|
337
|
+
size_threshold=None,
|
|
338
|
+
allow_onnxruntime_shape_inference=None,
|
|
339
|
+
):
|
|
340
|
+
"""
|
|
341
|
+
Fold constants in an ONNX model.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
model (Union[onnx.ModelProto, Callable() -> onnx.ModelProto]):
|
|
345
|
+
An ONNX model or a callable that returns one.
|
|
346
|
+
|
|
347
|
+
num_passes (int):
|
|
348
|
+
The number of constant folding passes to run.
|
|
349
|
+
Sometimes, subgraphs that compute tensor shapes may not be foldable in a single pass.
|
|
350
|
+
By default, Polygraphy will automatically determine the number of passes required.
|
|
351
|
+
do_shape_inference (bool):
|
|
352
|
+
Whether to run shape inference in the model between passes.
|
|
353
|
+
This enables the loader to fold `Shape` nodes.
|
|
354
|
+
Only effective if `fold_shapes` is True.
|
|
355
|
+
Defaults to True.
|
|
356
|
+
partitioning (Union[str, None]):
|
|
357
|
+
Whether/How to partition the graph so that errors in folding one
|
|
358
|
+
part of a model do not affect other parts. Available modes are:
|
|
359
|
+
|
|
360
|
+
- None: Do not partition the graph. If inference fails, no constants are folded.
|
|
361
|
+
- 'basic': Partition the graph. If inference fails in one partition, other partitions will remain unaffected.
|
|
362
|
+
- 'recursive': Parition the graph recursively. If inference fails in a partition, the partition will be further partitioned.
|
|
363
|
+
|
|
364
|
+
Defaults to None.
|
|
365
|
+
fold_shapes (bool):
|
|
366
|
+
Whether to fold `Shape` nodes in the graph.
|
|
367
|
+
This requires shapes to be inferred in the graph, and can only fold
|
|
368
|
+
static shapes.
|
|
369
|
+
Defaults to True.
|
|
370
|
+
copy (bool):
|
|
371
|
+
Whether to create a copy of the model first.
|
|
372
|
+
Defaults to False.
|
|
373
|
+
error_ok (bool):
|
|
374
|
+
Whether to suppress errors during constant folding.
|
|
375
|
+
If this is set to ``False``, errors will be re-raised.
|
|
376
|
+
Defaults to True.
|
|
377
|
+
size_threshold (int):
|
|
378
|
+
The maximum size threshold, in bytes, for which to fold constants.
|
|
379
|
+
Any tensors larger than this value will not be folded.
|
|
380
|
+
Set to ``None`` to disable the size threshold and always fold constants.
|
|
381
|
+
For example, some models may apply ops like `Tile` or `Expand` to constants, which can
|
|
382
|
+
result in very large tensors. Rather than pre-computing those constants and bloating
|
|
383
|
+
the model size, it may be desirable to skip folding them and allow them to be computed
|
|
384
|
+
at runtime.
|
|
385
|
+
Defaults to None.
|
|
386
|
+
allow_onnxruntime_shape_inference (bool):
|
|
387
|
+
Allow ONNX-Runtime's shape inference to be used if available instead of ONNX's
|
|
388
|
+
shape inference utilities. The former may provide performance or memory usage benefits.
|
|
389
|
+
Has no effect if ``do_shape_inference`` is False.
|
|
390
|
+
Defaults to True.
|
|
391
|
+
"""
|
|
392
|
+
super().__init__(model, copy)
|
|
393
|
+
self.num_passes = num_passes
|
|
394
|
+
self.do_shape_inference = util.default(do_shape_inference, True)
|
|
395
|
+
self.partitioning = partitioning
|
|
396
|
+
self.fold_shapes = util.default(fold_shapes, True)
|
|
397
|
+
self.error_ok = util.default(error_ok, True)
|
|
398
|
+
self.size_threshold = size_threshold
|
|
399
|
+
self.allow_onnxruntime_shape_inference = allow_onnxruntime_shape_inference
|
|
400
|
+
|
|
401
|
+
@util.check_called_by("__call__")
|
|
402
|
+
def call_impl(self):
|
|
403
|
+
"""
|
|
404
|
+
Returns:
|
|
405
|
+
onnx.ModelProto: The new ONNX model with constants folded.
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
def run_const_fold_pass(model):
|
|
409
|
+
graph = gs_from_onnx(model)
|
|
410
|
+
del model
|
|
411
|
+
|
|
412
|
+
graph.fold_constants(
|
|
413
|
+
fold_shapes=self.fold_shapes,
|
|
414
|
+
partitioning=self.partitioning,
|
|
415
|
+
size_threshold=self.size_threshold,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
model = gs.export_onnx(graph.cleanup(), do_type_check=False)
|
|
419
|
+
del graph
|
|
420
|
+
|
|
421
|
+
if self.fold_shapes and self.do_shape_inference:
|
|
422
|
+
model = infer_shapes(
|
|
423
|
+
model, allow_onnxruntime=self.allow_onnxruntime_shape_inference
|
|
424
|
+
)
|
|
425
|
+
return model
|
|
426
|
+
|
|
427
|
+
# Need to manually trigger the autoinstall this since it's used by ONNX-GS, which does not have an autoinstall mechanism.
|
|
428
|
+
mod.autoinstall(onnxrt)
|
|
429
|
+
if not onnxrt.is_installed() or not onnxrt.is_importable():
|
|
430
|
+
G_LOGGER.error(
|
|
431
|
+
f"ONNX-Runtime is not installed, so constant folding may be suboptimal or not work at all.\n"
|
|
432
|
+
f"Consider installing ONNX-Runtime: {sys.executable} -m pip install onnxruntime"
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
model = self.load()
|
|
436
|
+
|
|
437
|
+
prefold_num_nodes = len(model.graph.node)
|
|
438
|
+
postfold_num_nodes = -1
|
|
439
|
+
index = 0
|
|
440
|
+
|
|
441
|
+
while (prefold_num_nodes != postfold_num_nodes) and (
|
|
442
|
+
self.num_passes is None or index < self.num_passes
|
|
443
|
+
):
|
|
444
|
+
prefold_num_nodes = onnx_util.get_num_nodes(model)
|
|
445
|
+
|
|
446
|
+
G_LOGGER.start(f"Folding Constants | Pass {index + 1}")
|
|
447
|
+
try:
|
|
448
|
+
model = run_const_fold_pass(model)
|
|
449
|
+
except Exception as err:
|
|
450
|
+
if not self.error_ok:
|
|
451
|
+
raise
|
|
452
|
+
G_LOGGER.warning(
|
|
453
|
+
f"Constant folding pass failed. Skipping subsequent passes.\nNote: Error was:\n{err}"
|
|
454
|
+
)
|
|
455
|
+
break
|
|
456
|
+
else:
|
|
457
|
+
postfold_num_nodes = onnx_util.get_num_nodes(model)
|
|
458
|
+
index += 1
|
|
459
|
+
|
|
460
|
+
G_LOGGER.finish(
|
|
461
|
+
f"{constants.TAB}Total Nodes | Original: {prefold_num_nodes:5}, "
|
|
462
|
+
f"After Folding: {postfold_num_nodes:5} | {prefold_num_nodes - postfold_num_nodes:5} Nodes Folded"
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
return model
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@mod.export(funcify=True)
|
|
469
|
+
class SetUpperBound(BaseLoadOnnxCopy):
|
|
470
|
+
"""
|
|
471
|
+
Functor that sets upper bounds for tensors with unbounded DDS in an ONNX model.
|
|
472
|
+
|
|
473
|
+
Requires that the model has been constant folded and has shapes inferred.
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
def __init__(
|
|
477
|
+
self,
|
|
478
|
+
model,
|
|
479
|
+
upper_bounds,
|
|
480
|
+
copy=None,
|
|
481
|
+
):
|
|
482
|
+
"""
|
|
483
|
+
Set upper bounds for tensors with unbounded DDS in an ONNX model.
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
model (Union[onnx.ModelProto, Callable() -> onnx.ModelProto]):
|
|
487
|
+
An ONNX model or a callable that returns one.
|
|
488
|
+
|
|
489
|
+
upper_bounds (Union[int, Dict[str, int]]):
|
|
490
|
+
The upper bounds for tensors with unbounded DDS.
|
|
491
|
+
If a single integer is provided, it will be used as the default upper bound for all tensors with unbounded DDS.
|
|
492
|
+
This can also be provided on a per-tensor basis using a dictionary. In that case, use an empty string ("") as the
|
|
493
|
+
key to specify default upper bound for tensors not explicitly listed.
|
|
494
|
+
copy (bool):
|
|
495
|
+
Whether to create a copy of the model first.
|
|
496
|
+
Defaults to False.
|
|
497
|
+
"""
|
|
498
|
+
super().__init__(model, copy)
|
|
499
|
+
self.upper_bounds = upper_bounds
|
|
500
|
+
|
|
501
|
+
def call_impl(self):
|
|
502
|
+
"""
|
|
503
|
+
Returns:
|
|
504
|
+
onnx.ModelProto: The new ONNX model.
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
# Set upper bounds for tensors with unbounded DDS in the onnx model.
|
|
508
|
+
def set_upper_bound(graph, target_tensor_list):
|
|
509
|
+
applied_bounds = {}
|
|
510
|
+
for tensor in target_tensor_list:
|
|
511
|
+
upper_bound = util.value_or_from_dict(self.upper_bounds, tensor.name)
|
|
512
|
+
if upper_bound is None:
|
|
513
|
+
continue
|
|
514
|
+
# Insert a min operator to set the upper bound for the target tensor.
|
|
515
|
+
# A target tensor should always be produced from a single node.
|
|
516
|
+
assert len(tensor.inputs) == 1
|
|
517
|
+
producer = tensor.inputs[0]
|
|
518
|
+
producer_idx = producer.outputs.index(tensor)
|
|
519
|
+
tensor_copy = gs.Variable(
|
|
520
|
+
tensor.name + "_copy", dtype=tensor.dtype, shape=tensor.shape
|
|
521
|
+
)
|
|
522
|
+
upper_bound_values = np.array(upper_bound)
|
|
523
|
+
if tensor.shape is not None and len(tensor.shape) > 0:
|
|
524
|
+
upper_bound_values = np.array([upper_bound] * len(tensor.shape))
|
|
525
|
+
tensor_upper_bound = gs.Constant(
|
|
526
|
+
tensor.name + "_upper_bound", values=upper_bound_values
|
|
527
|
+
)
|
|
528
|
+
min_node = gs.Node(
|
|
529
|
+
op="Min", inputs=[tensor_copy, tensor_upper_bound], outputs=[tensor]
|
|
530
|
+
)
|
|
531
|
+
producer.outputs[producer_idx] = tensor_copy
|
|
532
|
+
tensor.inputs = [min_node]
|
|
533
|
+
graph.nodes.append(min_node)
|
|
534
|
+
applied_bounds[tensor.name] = upper_bound
|
|
535
|
+
G_LOGGER.info(f"Set tensor upper bounds: {applied_bounds}")
|
|
536
|
+
return graph
|
|
537
|
+
|
|
538
|
+
model = self.load()
|
|
539
|
+
graph = gs_from_onnx(model)
|
|
540
|
+
|
|
541
|
+
target_tensor_list = onnx_util.get_unbounded_dds_tensors(graph)
|
|
542
|
+
|
|
543
|
+
tensor_map = graph.tensors()
|
|
544
|
+
target_names = {tensor.name for tensor in target_tensor_list}
|
|
545
|
+
if isinstance(self.upper_bounds, dict):
|
|
546
|
+
input_names = set(self.upper_bounds.keys()) - {""}
|
|
547
|
+
# Report error when input tensor name is not in the graph.
|
|
548
|
+
util.check_sequence_contains(
|
|
549
|
+
set(tensor_map.keys()),
|
|
550
|
+
input_names,
|
|
551
|
+
name="the upper bounds dictionary",
|
|
552
|
+
items_name="tensors",
|
|
553
|
+
check_extra=False,
|
|
554
|
+
)
|
|
555
|
+
# Report warning when input tensor is not a unbounded DDS tensor.
|
|
556
|
+
util.check_sequence_contains(
|
|
557
|
+
set(target_names),
|
|
558
|
+
input_names,
|
|
559
|
+
name="the upper bounds dictionary",
|
|
560
|
+
items_name="tensors",
|
|
561
|
+
log_func=G_LOGGER.warning,
|
|
562
|
+
check_extra=False,
|
|
563
|
+
)
|
|
564
|
+
# Still set upper bound for input tensors with bounded shapes.
|
|
565
|
+
target_names.update(input_names)
|
|
566
|
+
graph = set_upper_bound(graph, [tensor_map[name] for name in target_names])
|
|
567
|
+
model = gs.export_onnx(graph.cleanup(), do_type_check=False)
|
|
568
|
+
|
|
569
|
+
return model
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
@mod.export(funcify=True)
|
|
573
|
+
class InferShapes(BaseLoader):
|
|
574
|
+
"""
|
|
575
|
+
Functor that runs shape inference on an ONNX model.
|
|
576
|
+
"""
|
|
577
|
+
|
|
578
|
+
def __init__(
|
|
579
|
+
self,
|
|
580
|
+
model,
|
|
581
|
+
error_ok=None,
|
|
582
|
+
external_data_dir=None,
|
|
583
|
+
save_to_disk_threshold_bytes=None,
|
|
584
|
+
allow_onnxruntime=None,
|
|
585
|
+
):
|
|
586
|
+
"""
|
|
587
|
+
Run shape inference on an ONNX model.
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
model (Union[onnx.ModelProto, Callable() -> onnx.ModelProto, str, Callable() -> str]):
|
|
591
|
+
An ONNX model or a callable that returns one, or a path to a model.
|
|
592
|
+
Supports models larger than the 2 GB protobuf limit.
|
|
593
|
+
|
|
594
|
+
error_ok (bool):
|
|
595
|
+
Whether errors during shape inference should be suppressed.
|
|
596
|
+
Defaults to True.
|
|
597
|
+
external_data_dir (str):
|
|
598
|
+
The directory where external data for the model is stored.
|
|
599
|
+
Only used if the model is provided via a path rather than a loader.
|
|
600
|
+
save_to_disk_threshold_bytes (int):
|
|
601
|
+
The size in bytes above which a ModelProto will be serialized to the disk
|
|
602
|
+
before running shape inference.
|
|
603
|
+
This can be used to work around the 2 GB protobuf limitation.
|
|
604
|
+
Defaults to 2 GB.
|
|
605
|
+
allow_onnxruntime (bool):
|
|
606
|
+
Allow ONNX-Runtime's shape inference to be used if available instead of ONNX's
|
|
607
|
+
shape inference utilities. The former may provide performance or memory usage benefits.
|
|
608
|
+
Defaults to True.
|
|
609
|
+
"""
|
|
610
|
+
self._model = model
|
|
611
|
+
self.error_ok = util.default(error_ok, True)
|
|
612
|
+
self.external_data_dir = external_data_dir
|
|
613
|
+
# Subtract a little so we're below the real threshold
|
|
614
|
+
self.save_to_disk_threshold_bytes = util.default(
|
|
615
|
+
save_to_disk_threshold_bytes, PROTOBUF_THRESHOLD
|
|
616
|
+
)
|
|
617
|
+
self.allow_onnxruntime = util.default(allow_onnxruntime, True)
|
|
618
|
+
|
|
619
|
+
def _run_onnx_shape_inference(self, model, external_data_dir):
|
|
620
|
+
if isinstance(model, onnx.ModelProto):
|
|
621
|
+
MODEL_SIZE = model.ByteSize()
|
|
622
|
+
if MODEL_SIZE > LARGE_MODEL_THRESHOLD:
|
|
623
|
+
G_LOGGER.warning(
|
|
624
|
+
f"Attempting to run shape inference on a large model ({MODEL_SIZE // 1024.0 ** 2} MiB). "
|
|
625
|
+
"This may require a large amount of memory.\nIf memory consumption becomes too high, "
|
|
626
|
+
"the process may be killed. You may want to try disabling shape inference in that case. ",
|
|
627
|
+
mode=LogMode.ONCE,
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
if MODEL_SIZE >= self.save_to_disk_threshold_bytes:
|
|
631
|
+
G_LOGGER.warning(
|
|
632
|
+
f"Model size ({MODEL_SIZE / 1024.0 ** 2} MiB) exceeds the in-memory size threshold: "
|
|
633
|
+
f"{self.save_to_disk_threshold_bytes / 1024.0 ** 2} MiB.\n"
|
|
634
|
+
f"The model will be saved to a temporary file before shape inference is run.",
|
|
635
|
+
mode=LogMode.ONCE,
|
|
636
|
+
)
|
|
637
|
+
outdir = tempfile.TemporaryDirectory()
|
|
638
|
+
outpath = os.path.join(outdir.name, "tmp_model.onnx")
|
|
639
|
+
save_onnx(model, outpath, external_data_path="ext.data")
|
|
640
|
+
model = outpath
|
|
641
|
+
external_data_dir = outdir.name
|
|
642
|
+
|
|
643
|
+
if isinstance(model, onnx.ModelProto):
|
|
644
|
+
model = onnx.shape_inference.infer_shapes(model)
|
|
645
|
+
else:
|
|
646
|
+
tmp_path = util.NamedTemporaryFile(
|
|
647
|
+
prefix="tmp_polygraphy_", suffix=".onnx"
|
|
648
|
+
).name
|
|
649
|
+
G_LOGGER.verbose(f"Writing shape-inferred model to: {tmp_path}")
|
|
650
|
+
onnx.shape_inference.infer_shapes_path(model, tmp_path)
|
|
651
|
+
# In cases where the original model had external data stored in the same directory,
|
|
652
|
+
# the external data directory may not be explicitly specified.
|
|
653
|
+
# In such cases, we need to use the model's directory as the external data path
|
|
654
|
+
# for the newly generated model.
|
|
655
|
+
model = onnx_from_path(
|
|
656
|
+
tmp_path,
|
|
657
|
+
external_data_dir=util.default(
|
|
658
|
+
external_data_dir, os.path.dirname(model) or None
|
|
659
|
+
),
|
|
660
|
+
)
|
|
661
|
+
return model
|
|
662
|
+
|
|
663
|
+
def _run_onnxruntime_shape_inference(self, model, external_data_dir):
|
|
664
|
+
if not isinstance(model, onnx.ModelProto):
|
|
665
|
+
model = onnx_from_path(model, external_data_dir=external_data_dir)
|
|
666
|
+
return onnxrt_symbolic_shape_inference.SymbolicShapeInference.infer_shapes(
|
|
667
|
+
model, auto_merge=True
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
@util.check_called_by("__call__")
|
|
671
|
+
def call_impl(self):
|
|
672
|
+
"""
|
|
673
|
+
Returns:
|
|
674
|
+
onnx.ModelProto: The new ONNX model with shapes inferred.
|
|
675
|
+
"""
|
|
676
|
+
model, _ = util.invoke_if_callable(self._model)
|
|
677
|
+
external_data_dir = self.external_data_dir
|
|
678
|
+
|
|
679
|
+
G_LOGGER.verbose("Starting shape inference")
|
|
680
|
+
|
|
681
|
+
try:
|
|
682
|
+
use_onnx_shape_inference = not self.allow_onnxruntime
|
|
683
|
+
if self.allow_onnxruntime:
|
|
684
|
+
try:
|
|
685
|
+
model = self._run_onnxruntime_shape_inference(
|
|
686
|
+
model, external_data_dir
|
|
687
|
+
)
|
|
688
|
+
G_LOGGER.verbose(
|
|
689
|
+
"Inferred shapes in the model with `onnxruntime.tools.symbolic_shape_infer`.\n"
|
|
690
|
+
"Note: To force Polygraphy to use `onnx.shape_inference` instead, set `allow_onnxruntime=False` or "
|
|
691
|
+
"use the `--no-onnxruntime-shape-inference` command-line option.",
|
|
692
|
+
mode=LogMode.ONCE,
|
|
693
|
+
)
|
|
694
|
+
except Exception as err:
|
|
695
|
+
use_onnx_shape_inference = True
|
|
696
|
+
G_LOGGER.extra_verbose(
|
|
697
|
+
f"Error while running `onnxruntime.tools.symbolic_shape_infer`:\n{err}"
|
|
698
|
+
)
|
|
699
|
+
G_LOGGER.warning(
|
|
700
|
+
"Falling back to `onnx.shape_inference` because `onnxruntime.tools.symbolic_shape_infer` either could not be loaded "
|
|
701
|
+
"or did not run successfully.\n"
|
|
702
|
+
"Note that using ONNX-Runtime for shape inference may be faster and require less memory.\n"
|
|
703
|
+
"Consider installing ONNX-Runtime or setting POLYGRAPHY_AUTOINSTALL_DEPS=1 in your environment "
|
|
704
|
+
"variables to allow Polygraphy to do so automatically.",
|
|
705
|
+
mode=LogMode.ONCE,
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
if use_onnx_shape_inference:
|
|
709
|
+
model = self._run_onnx_shape_inference(model, external_data_dir)
|
|
710
|
+
except Exception as err:
|
|
711
|
+
if not self.error_ok:
|
|
712
|
+
raise
|
|
713
|
+
G_LOGGER.warning(f"ONNX shape inference exited with an error:\n{err}")
|
|
714
|
+
G_LOGGER.internal_error(
|
|
715
|
+
f"ONNX shape inference exited with an error:\n{err}"
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
if not isinstance(model, onnx.ModelProto):
|
|
719
|
+
model = onnx_from_path(model, external_data_dir=external_data_dir)
|
|
720
|
+
else:
|
|
721
|
+
G_LOGGER.verbose("Shape inference completed successfully")
|
|
722
|
+
|
|
723
|
+
return model
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
@mod.export(funcify=True)
|
|
727
|
+
class ExtractSubgraph(BaseLoader):
|
|
728
|
+
"""
|
|
729
|
+
Functor that extracts a subgraph from an ONNX model.
|
|
730
|
+
"""
|
|
731
|
+
|
|
732
|
+
def __init__(
|
|
733
|
+
self, model, input_metadata=None, output_metadata=None, check_meta=None
|
|
734
|
+
):
|
|
735
|
+
"""
|
|
736
|
+
Extracts a subgraph from an ONNX model.
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
model (Union[Union[onnx.ModelProto, onnx_graphsurgeon.Graph], Callable() -> Union[onnx.ModelProto, onnx_graphsurgeon.Graph]]):
|
|
740
|
+
An ONNX model or ONNX-GraphSurgeon Graph or a callable that returns one.
|
|
741
|
+
|
|
742
|
+
input_metadata (TensorMetadata):
|
|
743
|
+
Metadata for the inputs of the subgraph.
|
|
744
|
+
Name, shape, and data type are required.
|
|
745
|
+
If not provided, the graph outputs are not modified.
|
|
746
|
+
output_metadata (TensorMetadata):
|
|
747
|
+
Metadata for the outputs of the subgraph.
|
|
748
|
+
Name and data type are required.
|
|
749
|
+
If not provided, the graph outputs are not modified.
|
|
750
|
+
check_meta (bool):
|
|
751
|
+
Whether to check that the provided input and output metadata include
|
|
752
|
+
all the expected fields.
|
|
753
|
+
Defaults to True.
|
|
754
|
+
"""
|
|
755
|
+
self._model = model
|
|
756
|
+
self.input_metadata = input_metadata
|
|
757
|
+
self.output_metadata = output_metadata
|
|
758
|
+
self.check_meta = util.default(check_meta, True)
|
|
759
|
+
|
|
760
|
+
@util.check_called_by("__call__")
|
|
761
|
+
def call_impl(self):
|
|
762
|
+
"""
|
|
763
|
+
Returns:
|
|
764
|
+
Union[onnx.ModelProto, onnx_graphsurgeon.Graph]:
|
|
765
|
+
The new ONNX model or ONNX-GraphSurgeon Graph.
|
|
766
|
+
"""
|
|
767
|
+
with _GSGraphManager(self._model) as manager:
|
|
768
|
+
graph = manager.graph
|
|
769
|
+
TENSOR_MAP = graph.tensors()
|
|
770
|
+
|
|
771
|
+
def get_tensor(name):
|
|
772
|
+
if name not in TENSOR_MAP:
|
|
773
|
+
G_LOGGER.critical(f"Tensor: {name} does not exist in the model.")
|
|
774
|
+
return TENSOR_MAP[name]
|
|
775
|
+
|
|
776
|
+
def update_tensor(name, dtype, shape):
|
|
777
|
+
tensor = get_tensor(name)
|
|
778
|
+
# No need to update constants
|
|
779
|
+
if isinstance(tensor, gs.Variable):
|
|
780
|
+
tensor.dtype, tensor.shape = (
|
|
781
|
+
DataType.to_dtype(DataType.from_dtype(dtype), "onnx")
|
|
782
|
+
if dtype is not None
|
|
783
|
+
else None
|
|
784
|
+
) or tensor.dtype, shape or tensor.shape
|
|
785
|
+
return tensor
|
|
786
|
+
|
|
787
|
+
def check_meta(name, dtype, shape, meta_type, needs_shape=True):
|
|
788
|
+
if not self.check_meta:
|
|
789
|
+
return
|
|
790
|
+
if needs_shape and shape is None:
|
|
791
|
+
G_LOGGER.warning(
|
|
792
|
+
f"{meta_type} metadata should include shape, but no shape was provided for tensor: {name}"
|
|
793
|
+
)
|
|
794
|
+
if dtype is None:
|
|
795
|
+
G_LOGGER.warning(
|
|
796
|
+
f"{meta_type} metadata should include data type, but no data type was provided for tensor: {name}"
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
if self.input_metadata is not None:
|
|
800
|
+
graph.inputs.clear()
|
|
801
|
+
for name, (dtype, shape) in self.input_metadata.items():
|
|
802
|
+
tensor = update_tensor(name, dtype, shape)
|
|
803
|
+
check_meta(name, tensor.dtype, tensor.shape, "Input")
|
|
804
|
+
tensor.inputs.clear()
|
|
805
|
+
graph.inputs.append(tensor)
|
|
806
|
+
|
|
807
|
+
if self.output_metadata is not None:
|
|
808
|
+
graph.outputs.clear()
|
|
809
|
+
for name, (dtype, shape) in self.output_metadata.items():
|
|
810
|
+
tensor = update_tensor(name, dtype, shape)
|
|
811
|
+
check_meta(
|
|
812
|
+
name, tensor.dtype, tensor.shape, "Output", needs_shape=False
|
|
813
|
+
)
|
|
814
|
+
graph.outputs.append(tensor)
|
|
815
|
+
|
|
816
|
+
graph.cleanup()
|
|
817
|
+
|
|
818
|
+
tensor_map = graph.tensors()
|
|
819
|
+
for tensor in tensor_map.values():
|
|
820
|
+
if (
|
|
821
|
+
isinstance(tensor, gs.Variable)
|
|
822
|
+
and not tensor.inputs
|
|
823
|
+
and tensor not in graph.inputs
|
|
824
|
+
):
|
|
825
|
+
consumer_nodes = [
|
|
826
|
+
f"Node: '{node.name}' (Op: {node.op})"
|
|
827
|
+
for node in tensor.outputs
|
|
828
|
+
]
|
|
829
|
+
G_LOGGER.error(
|
|
830
|
+
f"Tensor: '{tensor.name}' is a variable tensor consumed by: {consumer_nodes}, "
|
|
831
|
+
"but is not produced by a node or marked as a graph input."
|
|
832
|
+
f"\nDid you forget to mark a tensor as a graph input? Hint: Try inspecting the resulting model. "
|
|
833
|
+
f"\nNote: The resulting model will not be valid!"
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
return manager.retval
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
@mod.export(funcify=True)
|
|
840
|
+
class SaveOnnx(BaseLoader):
|
|
841
|
+
"""
|
|
842
|
+
Functor that saves an ONNX model to the specified path.
|
|
843
|
+
"""
|
|
844
|
+
|
|
845
|
+
def __init__(
|
|
846
|
+
self,
|
|
847
|
+
model,
|
|
848
|
+
path,
|
|
849
|
+
external_data_path=None,
|
|
850
|
+
size_threshold=None,
|
|
851
|
+
all_tensors_to_one_file=None,
|
|
852
|
+
):
|
|
853
|
+
"""
|
|
854
|
+
Saves an ONNX model to the specified path.
|
|
855
|
+
|
|
856
|
+
Args:
|
|
857
|
+
model (Union[onnx.ModelProto, Callable() -> onnx.ModelProto]):
|
|
858
|
+
An ONNX model or a callable that returns one.
|
|
859
|
+
path (str): Path at which to write the ONNX model.
|
|
860
|
+
external_data_path (str):
|
|
861
|
+
Path to save external data.
|
|
862
|
+
This is always a relative path; external data is always written to the same
|
|
863
|
+
directory as the model.
|
|
864
|
+
Set to an empty string to use the default path.
|
|
865
|
+
Set to None to disable.
|
|
866
|
+
Defaults to None if the model is within the protobuf size threshold and an empty string otherwise.
|
|
867
|
+
size_threshold (int):
|
|
868
|
+
Tensor size threshold, in bytes, above which tensor data will be
|
|
869
|
+
stored in the external file.
|
|
870
|
+
Tensors smaller that this threshold will remain in the ONNX file.
|
|
871
|
+
Has no effect if external_data_path is not set.
|
|
872
|
+
Defaults to 1024.
|
|
873
|
+
all_tensors_to_one_file (bool):
|
|
874
|
+
Whether to write all tensors to one file when saving external data.
|
|
875
|
+
Has no effect if external_data_path is not set.
|
|
876
|
+
Defaults to True.
|
|
877
|
+
"""
|
|
878
|
+
self._model = model
|
|
879
|
+
self.path = path
|
|
880
|
+
self.external_data_path = external_data_path
|
|
881
|
+
self.size_threshold = size_threshold
|
|
882
|
+
self.all_tensors_to_one_file = all_tensors_to_one_file
|
|
883
|
+
|
|
884
|
+
@util.check_called_by("__call__")
|
|
885
|
+
def call_impl(self):
|
|
886
|
+
"""
|
|
887
|
+
Returns:
|
|
888
|
+
onnx.ModelProto: The model, after saving it.
|
|
889
|
+
"""
|
|
890
|
+
model, _ = util.invoke_if_callable(self._model)
|
|
891
|
+
G_LOGGER.info(f"Saving ONNX model to: {self.path}")
|
|
892
|
+
|
|
893
|
+
model_size = model.ByteSize()
|
|
894
|
+
if self.external_data_path is None and model_size >= PROTOBUF_THRESHOLD:
|
|
895
|
+
external_data_path = ""
|
|
896
|
+
G_LOGGER.warning(
|
|
897
|
+
f"Model size ({model_size // 1024.0 ** 2} MiB) exceeds protobuf size threshold ({PROTOBUF_THRESHOLD // 1024 ** 2} MiB). "
|
|
898
|
+
f"Will save weight data to an external file.\n"
|
|
899
|
+
f"To control the location of this file, use the `external_data_path` parameter or the `--external-data-path` command-line option. "
|
|
900
|
+
)
|
|
901
|
+
else:
|
|
902
|
+
external_data_path = self.external_data_path
|
|
903
|
+
|
|
904
|
+
if external_data_path is not None:
|
|
905
|
+
G_LOGGER.verbose(
|
|
906
|
+
f"Saving external data for ONNX model to: {external_data_path}"
|
|
907
|
+
)
|
|
908
|
+
try:
|
|
909
|
+
onnx.external_data_helper.convert_model_to_external_data(
|
|
910
|
+
model,
|
|
911
|
+
location=external_data_path,
|
|
912
|
+
all_tensors_to_one_file=util.default(
|
|
913
|
+
self.all_tensors_to_one_file, True
|
|
914
|
+
),
|
|
915
|
+
size_threshold=util.default(self.size_threshold, 1024),
|
|
916
|
+
)
|
|
917
|
+
except TypeError:
|
|
918
|
+
if self.size_threshold is not None:
|
|
919
|
+
G_LOGGER.warning(
|
|
920
|
+
"This version of onnx does not support size_threshold in convert_model_to_external_data"
|
|
921
|
+
)
|
|
922
|
+
onnx.external_data_helper.convert_model_to_external_data(
|
|
923
|
+
model,
|
|
924
|
+
location=external_data_path,
|
|
925
|
+
all_tensors_to_one_file=util.default(
|
|
926
|
+
self.all_tensors_to_one_file, True
|
|
927
|
+
),
|
|
928
|
+
)
|
|
929
|
+
else:
|
|
930
|
+
if self.size_threshold is not None:
|
|
931
|
+
G_LOGGER.warning(
|
|
932
|
+
"size_threshold is set, but external data path has not been set. "
|
|
933
|
+
"No external data will be written."
|
|
934
|
+
)
|
|
935
|
+
if self.all_tensors_to_one_file is not None:
|
|
936
|
+
G_LOGGER.warning(
|
|
937
|
+
"all_tensors_to_one_file is set, but external data path has not been set. "
|
|
938
|
+
"No external data will be written."
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
util.makedirs(self.path)
|
|
942
|
+
onnx.save(model, self.path)
|
|
943
|
+
return model
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
@mod.export(funcify=True)
|
|
947
|
+
class BytesFromOnnx(BaseLoader):
|
|
948
|
+
"""
|
|
949
|
+
Functor that serializes an ONNX model.
|
|
950
|
+
"""
|
|
951
|
+
|
|
952
|
+
def __init__(self, model):
|
|
953
|
+
"""
|
|
954
|
+
Serializes an ONNX model.
|
|
955
|
+
|
|
956
|
+
Args:
|
|
957
|
+
model (Union[onnx.ModelProto, Callable() -> onnx.ModelProto]):
|
|
958
|
+
An ONNX model or a callable that returns one.
|
|
959
|
+
"""
|
|
960
|
+
self._model = model
|
|
961
|
+
|
|
962
|
+
@util.check_called_by("__call__")
|
|
963
|
+
def call_impl(self):
|
|
964
|
+
"""
|
|
965
|
+
Returns:
|
|
966
|
+
bytes: The serialized model.
|
|
967
|
+
"""
|
|
968
|
+
model, _ = util.invoke_if_callable(self._model)
|
|
969
|
+
return model.SerializeToString()
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
@mod.export(funcify=True)
|
|
973
|
+
class OnnxFromBytes(BaseLoader):
|
|
974
|
+
"""
|
|
975
|
+
Functor that deserializes an ONNX model.
|
|
976
|
+
"""
|
|
977
|
+
|
|
978
|
+
def __init__(self, serialized_onnx):
|
|
979
|
+
"""
|
|
980
|
+
Deserializes an ONNX model.
|
|
981
|
+
|
|
982
|
+
Args:
|
|
983
|
+
serialized_onnx (Union[bytes, Callable() -> bytes]):
|
|
984
|
+
A serialized ONNX model or a callable that returns one.
|
|
985
|
+
"""
|
|
986
|
+
self._serialized_onnx = serialized_onnx
|
|
987
|
+
|
|
988
|
+
@util.check_called_by("__call__")
|
|
989
|
+
def call_impl(self):
|
|
990
|
+
"""
|
|
991
|
+
Returns:
|
|
992
|
+
onnx.ModelProto: The ONNX model.
|
|
993
|
+
"""
|
|
994
|
+
serialized_onnx, _ = util.invoke_if_callable(self._serialized_onnx)
|
|
995
|
+
model = onnx.ModelProto()
|
|
996
|
+
model.ParseFromString(serialized_onnx)
|
|
997
|
+
return model
|