From c545b9b6731c8fc25f4283017bdb433a780907d3 Mon Sep 17 00:00:00 2001 From: channings Date: Fri, 13 Nov 2020 13:20:30 +0800 Subject: [PATCH] Add ONNX Exporter (#27831) * add onnx export module, test=develop * add unit test for paddle.onnx.export * adjust api & doc * fix some typo --- python/paddle/__init__.py | 1 + .../fluid/tests/unittests/test_onnx_export.py | 78 +++++++++++++ python/paddle/onnx/__init__.py | 18 +++ python/paddle/onnx/export.py | 105 ++++++++++++++++++ python/setup.py.in | 1 + python/unittest_py/requirements.txt | 1 + 6 files changed, 204 insertions(+) create mode 100644 python/paddle/fluid/tests/unittests/test_onnx_export.py create mode 100644 python/paddle/onnx/__init__.py create mode 100644 python/paddle/onnx/export.py diff --git a/python/paddle/__init__.py b/python/paddle/__init__.py index f66f52000b2..a650ec4faa1 100755 --- a/python/paddle/__init__.py +++ b/python/paddle/__init__.py @@ -268,6 +268,7 @@ from .fluid.layers import crop_tensor as crop #DEFINE_ALIAS from . import jit from . import static from . import amp +from . import onnx # high-level api from .hapi import Model diff --git a/python/paddle/fluid/tests/unittests/test_onnx_export.py b/python/paddle/fluid/tests/unittests/test_onnx_export.py new file mode 100644 index 00000000000..79d36063d77 --- /dev/null +++ b/python/paddle/fluid/tests/unittests/test_onnx_export.py @@ -0,0 +1,78 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import os +import pickle +import unittest +import numpy as np +import paddle +from paddle.static import InputSpec + + +class LinearNet(paddle.nn.Layer): + def __init__(self): + super(LinearNet, self).__init__() + self._linear = paddle.nn.Linear(128, 10) + + def forward(self, x): + return self._linear(x) + + +class Logic(paddle.nn.Layer): + def __init__(self): + super(Logic, self).__init__() + + def forward(self, x, y, z): + if z: + return x + else: + return y + + +class TestExportWithTensor(unittest.TestCase): + def setUp(self): + self.x_spec = paddle.static.InputSpec( + shape=[None, 128], dtype='float32') + + def test_with_tensor(): + model = LinearNet() + paddle.onnx.export(model, 'linear_net', input_spec=[self.x_spec]) + + +class TestExportWithTensor(unittest.TestCase): + def setUp(self): + self.x = paddle.to_tensor(np.random.random((1, 128))) + + def test_with_tensor(self): + model = LinearNet() + paddle.onnx.export(model, 'linear_net', input_spec=[self.x]) + + +class TestExportPrunedGraph(unittest.TestCase): + def setUp(self): + self.x = paddle.to_tensor(np.array([1])) + self.y = paddle.to_tensor(np.array([-1])) + + def test_prune_graph(self): + model = Logic() + paddle.jit.to_static(model) + out = model(self.x, self.y, z=True) + paddle.onnx.export( + model, 'pruned', input_spec=[self.x], output_spec=[out]) + + +if __name__ == '__main__': + unittest.main() diff --git a/python/paddle/onnx/__init__.py b/python/paddle/onnx/__init__.py new file mode 100644 index 00000000000..885d1968ce1 --- /dev/null +++ b/python/paddle/onnx/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function +from .export import export + +__all__ = ['export'] diff --git a/python/paddle/onnx/export.py b/python/paddle/onnx/export.py new file mode 100644 index 00000000000..4b99b42bb04 --- /dev/null +++ b/python/paddle/onnx/export.py @@ -0,0 +1,105 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from paddle.utils import try_import + +__all__ = ['export'] + + +def export(layer, path, input_spec=None, opset_version=9, **configs): + """ + Export Layer to ONNX format, which can use for inference via onnxruntime or other backends. + For more details, Please refer to `paddle2onnx `_ . + + Args: + layer (Layer): The Layer to be exported. + path (str): The path prefix to export model. The format is ``dirname/file_prefix`` or ``file_prefix`` , + and the exported ONNX file suffix is ``.onnx`` . + input_spec (list[InputSpec|Tensor], optional): Describes the input of the exported model's forward + method, which can be described by InputSpec or example Tensor. If None, all input variables of + the original Layer's forward method would be the inputs of the exported ``ONNX`` model. Default: None. + opset_version(int, optional): Opset version of exported ONNX model. + Now, stable supported opset version include 9, 10, 11. Default: 9. + **configs (dict, optional): Other export configuration options for compatibility. We do not + recommend using these configurations, they may be removed in the future. If not necessary, + DO NOT use them. Default None. + The following options are currently supported: + (1) output_spec (list[Tensor]): Selects the output targets of the exported model. + By default, all return variables of original Layer's forward method are kept as the + output of the exported model. If the provided ``output_spec`` list is not all output variables, + the exported model will be pruned according to the given ``output_spec`` list. + Returns: + None + Examples: + .. code-block:: python + + import paddle + import numpy as np + + class LinearNet(paddle.nn.Layer): + def __init__(self): + super(LinearNet, self).__init__() + self._linear = paddle.nn.Linear(128, 10) + + def forward(self, x): + return self._linear(x) + + # Export model with 'InputSpec' to support dynamic input shape. + def export_linear_net(): + model = LinearNet() + x_spec = paddle.static.InputSpec(shape=[None, 128], dtype='float32') + paddle.onnx.export(model, 'linear_net', input_spec=[x_spec]) + + export_linear_net() + + class Logic(paddle.nn.Layer): + def __init__(self): + super(Logic, self).__init__() + + def forward(self, x, y, z): + if z: + return x + else: + return y + + # Export model with 'Tensor' to support pruned model by set 'output_spec'. + def export_logic(): + model = Logic() + x = paddle.to_tensor(np.array([1])) + y = paddle.to_tensor(np.array([2])) + # Static and run model. + paddle.jit.to_static(model) + out = model(x, y, z=True) + paddle.onnx.export(model, 'pruned', input_spec=[x], output_spec=[out]) + + export_logic() + """ + + p2o = try_import('paddle2onnx') + + file_prefix = os.path.basename(path) + if file_prefix == "": + raise ValueError("The input path MUST be format of dirname/file_prefix " + "[dirname\\file_prefix in Windows system], but " + "the file_prefix is empty in received path: {}".format( + path)) + save_file = path + '.onnx' + + p2o.dygraph2onnx( + layer, + save_file, + input_spec=input_spec, + opset_version=opset_version, + **configs) diff --git a/python/setup.py.in b/python/setup.py.in index a4570c9d195..df43e4a3171 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -213,6 +213,7 @@ packages=['paddle', 'paddle.static', 'paddle.static.nn', 'paddle.tensor', + 'paddle.onnx', ] with open('@PADDLE_SOURCE_DIR@/python/requirements.txt') as f: diff --git a/python/unittest_py/requirements.txt b/python/unittest_py/requirements.txt index b61ba138441..19748f6f8f7 100644 --- a/python/unittest_py/requirements.txt +++ b/python/unittest_py/requirements.txt @@ -4,3 +4,4 @@ pycrypto ; platform_system != "Windows" mock opencv-python<=4.2.0.32 visualdl ; python_version>="3.5" +paddle2onnx>=0.4 -- GitLab