Skip to content

Commit 1150bb5

Browse files
authored
[ONNX] Update dynamo_export tutorial (#3196)
1 parent f0cea9e commit 1150bb5

12 files changed

+15
-159
lines changed

.jenkins/validate_tutorials_built.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@
5252
"intermediate_source/text_to_speech_with_torchaudio",
5353
"intermediate_source/tensorboard_profiler_tutorial", # reenable after 2.0 release.
5454
"intermediate_source/torch_export_tutorial", # reenable after 2940 is fixed.
55-
"beginner_source/onnx/export_simple_model_to_onnx_tutorial", # enable when 3191 is fixed
56-
"beginner_source/onnx/onnx_registry_tutorial", # enable when 3191 is fixed
5755
"prototype_source/gpu_quantization_torchao_tutorial", # enable when 3194
5856
"advanced_source/pendulum", # enable when 3195 is fixed
5957
"intermediate_source/reinforcement_ppo" # enable when 3195 is fixed
38.1 KB
Loading
Binary file not shown.
-7.37 KB
Binary file not shown.
-15.9 KB
Binary file not shown.
-8.41 KB
Binary file not shown.
-381 Bytes
Loading
Loading
Binary file not shown.

beginner_source/onnx/export_simple_model_to_onnx_tutorial.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Export a PyTorch model to ONNX
88
==============================
99
10-
**Author**: `Thiago Crepaldi <https://github.com/thiagocrepaldi>`_
10+
**Author**: `Ti-Tai Wang <https://github.com/titaiwangms>`_ and `Xavier Dupré <https://github.com/xadupre>`_
1111
1212
.. note::
1313
As of PyTorch 2.1, there are two versions of ONNX Exporter.
@@ -127,7 +127,7 @@ def forward(self, x):
127127
# Once Netron is open, we can drag and drop our ``my_image_classifier.onnx`` file into the browser or select it after
128128
# clicking the **Open model** button.
129129
#
130-
# .. image:: ../../_static/img/onnx/image_clossifier_onnx_modelon_netron_web_ui.png
130+
# .. image:: ../../_static/img/onnx/image_classifier_onnx_model_on_netron_web_ui.png
131131
# :width: 50%
132132
#
133133
#
@@ -155,7 +155,7 @@ def forward(self, x):
155155

156156
import onnxruntime
157157

158-
onnx_input = onnx_program.adapt_torch_inputs_to_onnx(torch_input)
158+
onnx_input = [torch_input]
159159
print(f"Input length: {len(onnx_input)}")
160160
print(f"Sample input: {onnx_input}")
161161

@@ -166,7 +166,8 @@ def to_numpy(tensor):
166166

167167
onnxruntime_input = {k.name: to_numpy(v) for k, v in zip(ort_session.get_inputs(), onnx_input)}
168168

169-
onnxruntime_outputs = ort_session.run(None, onnxruntime_input)
169+
# onnxruntime returns a list of outputs
170+
onnxruntime_outputs = ort_session.run(None, onnxruntime_input)[0]
170171

171172
####################################################################
172173
# 7. Compare the PyTorch results with the ones from the ONNX Runtime
@@ -179,7 +180,6 @@ def to_numpy(tensor):
179180
# Before comparing the results, we need to convert the PyTorch's output to match ONNX's format.
180181

181182
torch_outputs = torch_model(torch_input)
182-
torch_outputs = onnx_program.adapt_torch_outputs_to_onnx(torch_outputs)
183183

184184
assert len(torch_outputs) == len(onnxruntime_outputs)
185185
for torch_output, onnxruntime_output in zip(torch_outputs, onnxruntime_outputs):

beginner_source/onnx/intro_onnx.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
====================
88
99
Authors:
10-
`Thiago Crepaldi <https://github.com/thiagocrepaldi>`_,
10+
`Ti-Tai Wang <https://github.com/titaiwangms>`_ and `Xavier Dupré <https://github.com/xadupre>`_
1111
1212
`Open Neural Network eXchange (ONNX) <https://onnx.ai/>`_ is an open standard
1313
format for representing machine learning models. The ``torch.onnx`` module provides APIs to

beginner_source/onnx/onnx_registry_tutorial.py

Lines changed: 9 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -31,148 +31,15 @@
3131
#
3232
# In this tutorial, we will cover three scenarios that require extending the ONNX registry with custom operators:
3333
#
34-
# * Unsupported ATen operators
3534
# * Custom operators with existing ONNX Runtime support
3635
# * Custom operators without ONNX Runtime support
3736
#
38-
# Unsupported ATen operators
39-
# --------------------------
40-
#
41-
# Although the ONNX exporter team does their best efforts to support all ATen operators, some of them
42-
# might not be supported yet. In this section, we will demonstrate how you can add
43-
# unsupported ATen operators to the ONNX Registry.
44-
#
45-
# .. note::
46-
# The steps to implement unsupported ATen operators are the same to replace the implementation of an existing
47-
# ATen operator with a custom implementation.
48-
# Because we don't actually have an unsupported ATen operator to use in this tutorial, we are going to leverage
49-
# this and replace the implementation of ``aten::add.Tensor`` with a custom implementation the same way we would
50-
# if the operator was not present in the ONNX Registry.
51-
#
52-
# When a model cannot be exported to ONNX due to an unsupported operator, the ONNX exporter will show an error message
53-
# similar to:
54-
#
55-
# .. code-block:: python
56-
#
57-
# RuntimeErrorWithDiagnostic: Unsupported FX nodes: {'call_function': ['aten.add.Tensor']}.
58-
#
59-
# The error message indicates that the fully qualified name of unsupported ATen operator is ``aten::add.Tensor``.
60-
# The fully qualified name of an operator is composed of the namespace, operator name, and overload following
61-
# the format ``namespace::operator_name.overload``.
62-
#
63-
# To add support for an unsupported ATen operator or to replace the implementation for an existing one, we need:
64-
#
65-
# * The fully qualified name of the ATen operator (e.g. ``aten::add.Tensor``).
66-
# This information is always present in the error message as show above.
67-
# * The implementation of the operator using `ONNX Script <https://github.com/microsoft/onnxscript>`__.
68-
# ONNX Script is a prerequisite for this tutorial. Please make sure you have read the
69-
# `ONNX Script tutorial <https://github.com/microsoft/onnxscript/blob/main/docs/tutorial/index.md>`_
70-
# before proceeding.
71-
#
72-
# Because ``aten::add.Tensor`` is already supported by the ONNX Registry, we will demonstrate how to replace it with a
73-
# custom implementation, but keep in mind that the same steps apply to support new unsupported ATen operators.
74-
#
75-
# This is possible because the :class:`OnnxRegistry` allows users to override an operator registration.
76-
# We will override the registration of ``aten::add.Tensor`` with our custom implementation and verify it exists.
77-
#
7837

7938
import torch
8039
import onnxruntime
8140
import onnxscript
8241
from onnxscript import opset18 # opset 18 is the latest (and only) supported version for now
8342

84-
class Model(torch.nn.Module):
85-
def forward(self, input_x, input_y):
86-
return torch.ops.aten.add(input_x, input_y) # generates a aten::add.Tensor node
87-
88-
input_add_x = torch.randn(3, 4)
89-
input_add_y = torch.randn(3, 4)
90-
aten_add_model = Model()
91-
92-
93-
# Now we create a ONNX Script function that implements ``aten::add.Tensor``.
94-
# The function name (e.g. ``custom_aten_add``) is displayed in the ONNX graph, so we recommend to use intuitive names.
95-
custom_aten = onnxscript.values.Opset(domain="custom.aten", version=1)
96-
97-
# NOTE: The function signature must match the signature of the unsupported ATen operator.
98-
# https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/native_functions.yaml
99-
# NOTE: All attributes must be annotated with type hints.
100-
@onnxscript.script(custom_aten)
101-
def custom_aten_add(input_x, input_y, alpha: float = 1.0):
102-
input_y = opset18.Mul(input_y, alpha)
103-
return opset18.Add(input_x, input_y)
104-
105-
106-
# Now we have everything we need to support unsupported ATen operators.
107-
# Let's register the ``custom_aten_add`` function to ONNX registry, and export the model to ONNX again.
108-
onnx_registry = torch.onnx.OnnxRegistry()
109-
onnx_registry.register_op(
110-
namespace="aten", op_name="add", overload="Tensor", function=custom_aten_add
111-
)
112-
print(f"aten::add.Tensor is supported by ONNX registry: \
113-
{onnx_registry.is_registered_op(namespace='aten', op_name='add', overload='Tensor')}"
114-
)
115-
export_options = torch.onnx.ExportOptions(onnx_registry=onnx_registry)
116-
onnx_program = torch.onnx.dynamo_export(
117-
aten_add_model, input_add_x, input_add_y, export_options=export_options
118-
)
119-
120-
######################################################################
121-
# Now let's inspect the model and verify the model has a ``custom_aten_add`` instead of ``aten::add.Tensor``.
122-
# The graph has one graph node for ``custom_aten_add``, and inside of it there are four function nodes, one for each
123-
# operator, and one for constant attribute.
124-
#
125-
126-
# graph node domain is the custom domain we registered
127-
assert onnx_program.model_proto.graph.node[0].domain == "custom.aten"
128-
assert len(onnx_program.model_proto.graph.node) == 1
129-
# graph node name is the function name
130-
assert onnx_program.model_proto.graph.node[0].op_type == "custom_aten_add"
131-
# function node domain is empty because we use standard ONNX operators
132-
assert {node.domain for node in onnx_program.model_proto.functions[0].node} == {""}
133-
# function node name is the standard ONNX operator name
134-
assert {node.op_type for node in onnx_program.model_proto.functions[0].node} == {"Add", "Mul", "Constant"}
135-
136-
137-
######################################################################
138-
# This is how ``custom_aten_add_model`` looks in the ONNX graph using Netron:
139-
#
140-
# .. image:: /_static/img/onnx/custom_aten_add_model.png
141-
# :width: 70%
142-
# :align: center
143-
#
144-
# Inside the ``custom_aten_add`` function, we can see the three ONNX nodes we
145-
# used in the function (``CastLike``, ``Add``, and ``Mul``), and one ``Constant`` attribute:
146-
#
147-
# .. image:: /_static/img/onnx/custom_aten_add_function.png
148-
# :width: 70%
149-
# :align: center
150-
#
151-
# This was all that we needed to register the new ATen operator into the ONNX Registry.
152-
# As an additional step, we can use ONNX Runtime to run the model, and compare the results with PyTorch.
153-
#
154-
155-
156-
# Use ONNX Runtime to run the model, and compare the results with PyTorch
157-
onnx_program.save("./custom_add_model.onnx")
158-
ort_session = onnxruntime.InferenceSession(
159-
"./custom_add_model.onnx", providers=['CPUExecutionProvider']
160-
)
161-
162-
def to_numpy(tensor):
163-
return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
164-
165-
onnx_input = onnx_program.adapt_torch_inputs_to_onnx(input_add_x, input_add_y)
166-
onnxruntime_input = {k.name: to_numpy(v) for k, v in zip(ort_session.get_inputs(), onnx_input)}
167-
onnxruntime_outputs = ort_session.run(None, onnxruntime_input)
168-
169-
torch_outputs = aten_add_model(input_add_x, input_add_y)
170-
torch_outputs = onnx_program.adapt_torch_outputs_to_onnx(torch_outputs)
171-
172-
assert len(torch_outputs) == len(onnxruntime_outputs)
173-
for torch_output, onnxruntime_output in zip(torch_outputs, onnxruntime_outputs):
174-
torch.testing.assert_close(torch_output, torch.tensor(onnxruntime_output))
175-
17643

17744
######################################################################
17845
# Custom operators with existing ONNX Runtime support
@@ -262,12 +129,11 @@ def custom_aten_gelu(input_x, approximate: str = "none"):
262129
def to_numpy(tensor):
263130
return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
264131

265-
onnx_input = onnx_program.adapt_torch_inputs_to_onnx(input_gelu_x)
132+
onnx_input = [input_gelu_x]
266133
onnxruntime_input = {k.name: to_numpy(v) for k, v in zip(ort_session.get_inputs(), onnx_input)}
267-
onnxruntime_outputs = ort_session.run(None, onnxruntime_input)
134+
onnxruntime_outputs = ort_session.run(None, onnxruntime_input)[0]
268135

269136
torch_outputs = aten_gelu_model(input_gelu_x)
270-
torch_outputs = onnx_program.adapt_torch_outputs_to_onnx(torch_outputs)
271137

272138
assert len(torch_outputs) == len(onnxruntime_outputs)
273139
for torch_output, onnxruntime_output in zip(torch_outputs, onnxruntime_outputs):
@@ -369,25 +235,17 @@ def custom_addandround(input_x):
369235
#
370236

371237
assert onnx_program.model_proto.graph.node[0].domain == "test.customop"
372-
assert onnx_program.model_proto.graph.node[0].op_type == "custom_addandround"
373-
assert onnx_program.model_proto.functions[0].node[0].domain == "test.customop"
374-
assert onnx_program.model_proto.functions[0].node[0].op_type == "CustomOpOne"
375-
assert onnx_program.model_proto.functions[0].node[1].domain == "test.customop"
376-
assert onnx_program.model_proto.functions[0].node[1].op_type == "CustomOpTwo"
238+
assert onnx_program.model_proto.graph.node[0].op_type == "CustomOpOne"
239+
assert onnx_program.model_proto.graph.node[1].domain == "test.customop"
240+
assert onnx_program.model_proto.graph.node[1].op_type == "CustomOpTwo"
377241

378242

379243
######################################################################
380-
# This is how ``custom_addandround_model`` ONNX graph looks using Netron:
381-
#
382-
# .. image:: /_static/img/onnx/custom_addandround_model.png
383-
# :width: 70%
384-
# :align: center
385-
#
386-
# Inside the ``custom_addandround`` function, we can see the two custom operators we
387-
# used in the function (``CustomOpOne``, and ``CustomOpTwo``), and they are from module
388-
# ``test.customop``:
244+
# This is how ``custom_addandround_model`` ONNX graph looks using Netron.
245+
# We can see the two custom operators we used in the function (``CustomOpOne``, and ``CustomOpTwo``),
246+
# and they are from module ``test.customop``:
389247
#
390-
# .. image:: /_static/img/onnx/custom_addandround_function.png
248+
# .. image:: /_static/img/onnx/custom_addandround.png
391249
#
392250
# Custom Ops Registration in ONNX Runtime
393251
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)