From 940844e71f6fa5603576b2d0d51da043a5634b0a Mon Sep 17 00:00:00 2001 From: Anastasia Popova Date: Fri, 23 Sep 2022 15:29:00 +0200 Subject: [PATCH] mo.convert() method. (#11347) * convert() method added. * Moved conversion to convert() method. * Fixed commits. * Output dir fix. * Added objects support for extesions param. * Added support for transformations_config extension objects. * Input to str unit tests. * Added tests, added comments. * Updated BOM. * Removed commented code. * Fixed extension passing. * Small corrections. * Fixed for python 3.6. * Small fix. * Moved dir creating to ov.serialize(), removed mo.serialize(), small fixes. * Small fix. * Small correction. * Removed coping of params, moved convert implemetation to separate module. * Import fixes. * Moved hiding of exceptions to main(). * Updated comment. * Fixed unit tests. * Comment changed. * Fixed dir creating. * Tests fixed. * Small fixes. * Test fix. * Added meta data generation, removed printing of execution time for silent mode. * Import fix. * Conflict fix. * Fixed error. * Fix for custom config. * Added version, data_type params to help. * Added mo.convert() full-functional tests. * Small corrections. * Comment correction. * Moved convert to openvino package, moved LayotMap and InputCutInfo to openvino.convert. * Added help param. * Wrong change removed. * Small fix. * Removed unnecessary comments. * Removed .xml extension check from append_ir_info. * Added missed file. * Fixed error. * Fix for bool value in InputCutInfo. * Moved InputCutInfo, LayoutMap to openvino.tools.mo. * Moved InputCutInfo, LayoutMap to openvino.tools.mo. * Moved check and read_model to emit_ir. * Small correction. * Added comment. * Added unit_tests with convert(). * Small corrections. * Removed convert alias from openvino. * Fixed conflicting unit tests. * Removed unnecessary warnings. * Params check fix. * Small correction. * Added paths checks. * Added negative tests for to_str methods, fixed errors. * Added tuples support in input parameter. * Moved reminders to update OV and use API 2.0 to main(). * Returned .mapping file generating. * Added positional input_model param. * Added test for unnamed input_model. * Optimize imports. * Added more informative error for brackets syntax in --input. * Conflict fix. * Conflict fix. --- src/core/src/pass/serialize.cpp | 5 + .../common/mo_convert_test_class.py | 51 + .../mo_python_api_tests/conftest.py | 13 + .../test_mo_convert_complex_params.py | 206 ++++ .../test_mo_convert_extensions.py | 136 +++ tools/mo/automation/package_BOM.txt | 1 + tools/mo/openvino/tools/mo/__init__.py | 1 + .../tools/mo/back/offline_transformations.py | 27 +- tools/mo/openvino/tools/mo/convert.py | 26 + tools/mo/openvino/tools/mo/convert_impl.py | 573 +++++++++++ tools/mo/openvino/tools/mo/main.py | 571 +---------- tools/mo/openvino/tools/mo/main_caffe.py | 2 +- tools/mo/openvino/tools/mo/main_kaldi.py | 2 +- tools/mo/openvino/tools/mo/main_mxnet.py | 2 +- tools/mo/openvino/tools/mo/main_onnx.py | 3 +- tools/mo/openvino/tools/mo/main_paddle.py | 3 +- tools/mo/openvino/tools/mo/main_tf.py | 2 +- .../openvino/tools/mo/middle/PartialInfer.py | 11 - .../tools/mo/moc_frontend/check_config.py | 56 +- .../tools/mo/moc_frontend/serialize.py | 23 +- tools/mo/openvino/tools/mo/pipeline/common.py | 3 - .../mo/openvino/tools/mo/utils/cli_parser.py | 927 +++++++++++++++--- .../tools/mo/utils/versions_checker.py | 23 +- tools/mo/setup.py | 2 + .../mo/convert/import_from_mo_test.py | 108 ++ tools/mo/unit_tests/mo/convert/utils.py | 52 + .../unit_tests/mo/extensions_test_actual.py | 4 +- tools/mo/unit_tests/mo/main_test_actual.py | 4 +- .../mo/unit_test_with_mocked_telemetry.py | 3 + .../mo/utils/args_to_string_test.py | 227 +++++ .../mo/utils/freeze_placholder_test.py | 6 +- .../mo/utils/mo_fallback_test_actual.py | 42 +- .../mo/utils/test_mo_model_analysis_actual.py | 2 +- 33 files changed, 2326 insertions(+), 791 deletions(-) create mode 100644 tests/layer_tests/common/mo_convert_test_class.py create mode 100644 tests/layer_tests/mo_python_api_tests/conftest.py create mode 100644 tests/layer_tests/mo_python_api_tests/test_mo_convert_complex_params.py create mode 100644 tests/layer_tests/mo_python_api_tests/test_mo_convert_extensions.py create mode 100644 tools/mo/openvino/tools/mo/convert.py create mode 100644 tools/mo/openvino/tools/mo/convert_impl.py create mode 100644 tools/mo/unit_tests/mo/convert/import_from_mo_test.py create mode 100644 tools/mo/unit_tests/mo/convert/utils.py create mode 100644 tools/mo/unit_tests/mo/utils/args_to_string_test.py diff --git a/src/core/src/pass/serialize.cpp b/src/core/src/pass/serialize.cpp index 1b7eb69ed54..9131e1f6a4e 100644 --- a/src/core/src/pass/serialize.cpp +++ b/src/core/src/pass/serialize.cpp @@ -18,6 +18,7 @@ #include "ngraph/opsets/opset1.hpp" #include "openvino/op/util/framework_node.hpp" #include "openvino/pass/constant_folding.hpp" +#include "openvino/util/file_util.hpp" #include "pugixml.hpp" #include "transformations/hash.hpp" #include "transformations/rt_info/primitives_priority_attribute.hpp" @@ -1022,6 +1023,10 @@ bool pass::Serialize::run_on_model(const std::shared_ptr& f_or if (m_xmlFile && m_binFile) { serializeFunc(*m_xmlFile, *m_binFile, f, m_version, m_custom_opsets); } else { + auto xmlDir = ov::util::get_directory(m_xmlPath); + if (xmlDir != m_xmlPath) + ov::util::create_directory_recursive(xmlDir); + std::ofstream bin_file(m_binPath, std::ios::out | std::ios::binary); NGRAPH_CHECK(bin_file, "Can't open bin file: \"" + m_binPath + "\""); diff --git a/tests/layer_tests/common/mo_convert_test_class.py b/tests/layer_tests/common/mo_convert_test_class.py new file mode 100644 index 00000000000..5b6732ca865 --- /dev/null +++ b/tests/layer_tests/common/mo_convert_test_class.py @@ -0,0 +1,51 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from pathlib import Path + +from openvino.runtime import serialize +from openvino.tools.mo import convert +from openvino.tools.mo.utils.ir_engine.ir_engine import IREngine + +from common.utils.common_utils import generate_ir + + +class CommonMOConvertTest: + @staticmethod + def generate_ir_python_api(**kwargs): + output_dir = kwargs['output_dir'] + model_name = kwargs['model_name'] + del kwargs['output_dir'] + model = convert(**kwargs) + serialize(model, str(Path(output_dir, model_name + '.xml'))) + + def _test(self, temp_dir, test_params, ref_params): + """ + Generates two IRs using MO Python API and using cmd tool. + Then two IRs are compared. + """ + test_params.update({"model_name": 'model_test', "output_dir": temp_dir}) + ref_params.update({"model_name": 'model_ref', "output_dir": temp_dir}) + + self.generate_ir_python_api(**test_params) + + exit_code, stderr = generate_ir(**ref_params) + assert not exit_code, ( + "Reference IR generation failed with {} exit code: {}".format(exit_code, stderr)) + + ir_test = IREngine(Path(temp_dir, 'model_test.xml'), Path(temp_dir, 'model_test.bin')) + ir_ref = IREngine(Path(temp_dir, 'model_ref.xml'), Path(temp_dir, 'model_ref.bin')) + flag, resp = ir_test.compare(ir_ref) + assert flag, '\n'.join(resp) + + def _test_by_ref_graph(self, temp_dir, test_params, ref_graph): + """ + Generates IR using MO Python API, reads it and compares with reference graph. + """ + test_params.update({"model_name": 'model_test', "output_dir": temp_dir}) + + self.generate_ir_python_api(**test_params) + + ir_test = IREngine(Path(temp_dir, 'model_test.xml'), Path(temp_dir, 'model_test.bin')) + flag, resp = ir_test.compare(ref_graph) + assert flag, '\n'.join(resp) diff --git a/tests/layer_tests/mo_python_api_tests/conftest.py b/tests/layer_tests/mo_python_api_tests/conftest.py new file mode 100644 index 00000000000..15f2606de30 --- /dev/null +++ b/tests/layer_tests/mo_python_api_tests/conftest.py @@ -0,0 +1,13 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import inspect + +from common.layer_test_class import get_params + + +def pytest_generate_tests(metafunc): + test_gen_attrs_names = list(inspect.signature(get_params).parameters) + params = get_params() + + metafunc.parametrize(test_gen_attrs_names, params, scope="function") diff --git a/tests/layer_tests/mo_python_api_tests/test_mo_convert_complex_params.py b/tests/layer_tests/mo_python_api_tests/test_mo_convert_complex_params.py new file mode 100644 index 00000000000..3d218d5de08 --- /dev/null +++ b/tests/layer_tests/mo_python_api_tests/test_mo_convert_complex_params.py @@ -0,0 +1,206 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import numpy as np +import pytest +from openvino.tools.mo.convert import InputCutInfo, LayoutMap + +from common.mo_convert_test_class import CommonMOConvertTest +from common.tf_layer_test_class import save_to_pb +from openvino.runtime import Model, Layout, PartialShape, Shape, layout_helpers, Type, Dimension + +class TestComplexParams(CommonMOConvertTest): + def create_tf_model(self, tmp_dir): + # + # Create Tensorflow model with multiple inputs/outputs + # + + import tensorflow as tf + + tf.compat.v1.reset_default_graph() + + with tf.compat.v1.Session() as sess: + inp1 = tf.compat.v1.placeholder(tf.float32, [1, 3, 2, 2], 'Input1') + inp2 = tf.compat.v1.placeholder(tf.float32, [1, 3, 2, 2], 'Input2') + inp3 = tf.compat.v1.placeholder(tf.float32, [1, 3, 2, 2], 'Input3') + + relu1 = tf.nn.relu(inp1, name='Relu1') + relu2 = tf.nn.relu(inp2, name='Relu2') + relu3 = tf.nn.relu(inp3, name='Relu3') + + concat = tf.concat([relu1, relu2, relu3], axis=0, name='Concat') + + outputs = tf.split(concat, 3) + outputs_list = [] + for i, output in enumerate(outputs): + outputs_list.append(tf.nn.sigmoid(output, name='Sigmoid_{}'.format(i))) + + tf.compat.v1.global_variables_initializer() + tf_net = sess.graph_def + + # save model to .pb and return path to the model + return save_to_pb(tf_net, tmp_dir) + + def create_tf_model_single_input_output(self, tmp_dir): + # + # Create Tensorflow model with single input/output + # + + import tensorflow as tf + + tf.compat.v1.reset_default_graph() + + with tf.compat.v1.Session() as sess: + inp = tf.compat.v1.placeholder(tf.float32, [1, 3, 2, 2], 'Input') + + relu = tf.nn.relu(inp, name='Relu') + + output = tf.nn.sigmoid(relu, name='Sigmoid') + + tf.compat.v1.global_variables_initializer() + tf_net = sess.graph_def + + # save model to .pb and return path to the model + return save_to_pb(tf_net, tmp_dir) + + def create_tf_param_res_model(self, tmp_dir): + # + # Create Tensorflow model with following pattern: + # Input ---\ + # Add --> Identity + # Input1 ---/ + # + # This graph is needed for transform test. Input and Identity are replaced with ReadValue and Assign ops. + + import tensorflow as tf + + tf.compat.v1.reset_default_graph() + + with tf.compat.v1.Session() as sess: + inp = tf.compat.v1.placeholder(tf.float32, [1, 3, 2, 2], 'Input') + inp1 = tf.compat.v1.placeholder(tf.float32, [1, 3, 2, 2], 'Input1') + sum1 = tf.add(inp, inp1, "Add1") + result = tf.identity(sum1, name='Identity') + + tf.compat.v1.global_variables_initializer() + tf_net = sess.graph_def + + # save model to .pb and return path to the model + return save_to_pb(tf_net, tmp_dir) + + test_data = [ + {'params_test': {'input_shape': [PartialShape([2, 3, 4]), + [2, 3, 4], + [Dimension(2), Dimension(3), Dimension(4)]], + 'input':['Input1', 'Input2', 'Relu3']}, + 'params_ref': {'input_shape': "[2,3,4],[2,3,4],[2,3,4]", 'input': 'Input1,Input2,Relu3'}}, + {'params_test': {'input_shape': [PartialShape([Dimension(), Dimension(1, 3), Dimension(4, -1), Dimension(-1, 5)]), + [Dimension(), Dimension(1, 3), 4, Dimension(-1, 5)], + [Dimension(), 3, Dimension(4, -1), Dimension(-1, 5)]], + 'input':['Input1', 'Input2', 'Relu3']}, + 'params_ref': {'input_shape': "[?,1..3,4..,..5],[?,1..3,4,..5],[?,3,4..,..5]", 'input': 'Input1,Input2,Relu3'}}, + {'params_test': {'input': [InputCutInfo("Relu1", Shape([3, 2]), Type(np.int32), None), + InputCutInfo("Relu2", PartialShape([Dimension(3, 10), Dimension(2, -1)]), np.int32, None), + InputCutInfo("Relu3", [3, 2], Type(np.int32), [1, 2, 3, 4, 5, 6])]}, + 'params_ref': {'input': "Relu1[3 2]{i32},Relu2[3..10 2..]{i32},Relu3[3 2]{i32}->[1 2 3 4 5 6]"}}, + {'params_test': {'input': [("Relu1", Shape([3, 2]), Type(np.int32)), + (np.int32, "Relu2", PartialShape([Dimension(3, 10), Dimension(2, -1)])), + ([3, 2],"Relu3", Type(np.int32))]}, + 'params_ref': {'input': "Relu1[3 2]{i32},Relu2[3..10 2..]{i32},Relu3[3 2]{i32}"}}, + {'params_test': {'output': ["Sigmoid_0", "Sigmoid_2"]}, + 'params_ref': {'output': "Sigmoid_0,Sigmoid_2"}}, + {'params_test': {'mean_values': {'Input1': [0.5,1.3,0.67], 'Input2':[4.2, 6.7, 3.15], 'Input3':[0.757, 4.6, 7.3]}}, + 'params_ref': {'mean_values': "Input1[0.5,1.3,0.67],Input2[4.2,6.7,3.15],Input3[0.757,4.6,7.3]"}}, + {'params_test': { + 'mean_values': [[0.5, 1.3, 0.67], [4.2, 6.7, 3.15], [0.757, 4.6, 7.3]]}, + 'params_ref': {'mean_values': "[0.5,1.3,0.67],[4.2,6.7,3.15],[0.757,4.6,7.3]"}}, + {'params_test': {'scale_values': {'Input1': [0.5,1.3,0.67], 'Input2':[4.2, 6.7, 3.15], 'Input3':[0.757, 4.6, 7.3]}}, + 'params_ref': {'scale_values': "Input1[0.5,1.3,0.67],Input2[4.2,6.7,3.15],Input3[0.757,4.6,7.3]"}}, + {'params_test': { + 'scale_values': [[0.5, 1.3, 0.67], [4.2, 6.7, 3.15], [0.757, 4.6, 7.3]]}, + 'params_ref': {'scale_values': "[0.5,1.3,0.67],[4.2,6.7,3.15],[0.757,4.6,7.3]"}}, + {'params_test': { + 'source_layout': {'Input1': Layout("nchw"), 'Input2': "nchw", 'Input3': "nc??"}}, + 'params_ref': {'source_layout': "Input1(nchw),Input2(nchw),Input3(nc??)"}}, + {'params_test': { + 'target_layout': {'Input1': Layout("nhwc"), 'Input2': "nhwc", 'Input3': "n??c"}}, + 'params_ref': {'target_layout': "Input1(nhwc),Input2(nhwc),Input3(n??c)"}}, + {'params_test': { + 'layout': {'Input1': LayoutMap(source_layout=Layout("nchw"), target_layout="nhwc"), + 'Input2': LayoutMap(source_layout="nc??", target_layout=Layout("n??c")), + 'Input3': LayoutMap(source_layout="abcd", target_layout="acdb")}}, + 'params_ref': {'layout': "Input1(nchw->nhwc),Input2(nc??->n??c),Input3(abcd->acdb)"}}, + + ] + + @pytest.mark.parametrize("params", test_data) + @pytest.mark.nightly + def test_mo_convert_tf_model(self, params, ie_device, precision, ir_version, + temp_dir, use_new_frontend, use_old_api): + tf_net_path = self.create_tf_model(temp_dir) + + test_params = params['params_test'] + ref_params = params['params_ref'] + test_params.update({'input_model': tf_net_path}) + ref_params.update({'input_model': tf_net_path}) + self._test(temp_dir, test_params, ref_params) + + test_data = [ + {'params_test': {'input_shape': PartialShape([2, 3, 4])}, + 'params_ref': {'input_shape': "[2,3,4]"}}, + {'params_test': {'input_shape': [Dimension(), Dimension(1, 3), 4, Dimension(-1, 5)]}, + 'params_ref': {'input_shape': "[?,1..3,4,..5]"}}, + {'params_test': {'input': InputCutInfo("Relu", [3, 2], Type(np.int32), [1, 2, 3, 4, 5, 6])}, + 'params_ref': {'input': "Relu[3 2]{i32}->[1 2 3 4 5 6]"}}, + {'params_test': {'input': ("Relu", [3, 2], Type(np.int32))}, + 'params_ref': {'input': "Relu[3 2]{i32}"}}, + {'params_test': {'input': ("Relu", Type(np.int32))}, + 'params_ref': {'input': "Relu{i32}"}}, + {'params_test': {'input': ("Relu", [3, 2])}, + 'params_ref': {'input': "Relu[3 2]"}}, + {'params_test': {'input': ("Relu")}, + 'params_ref': {'input': "Relu"}}, + {'params_test': {'mean_values': [0.5, 1.3, 0.67]}, + 'params_ref': {'mean_values': "[0.5,1.3,0.67]"}}, + {'params_test': {'scale_values': [0.5, 1.3, 0.67]}, + 'params_ref': {'scale_values': "[0.5,1.3,0.67]"}}, + {'params_test': {'source_layout': Layout("nchw")}, + 'params_ref': {'source_layout': "nchw"}}, + {'params_test': {'target_layout': Layout("nchw")}, + 'params_ref': {'target_layout': "nchw"}}, + {'params_test': {'layout': LayoutMap(source_layout=Layout("nchw"), target_layout="nhwc")}, + 'params_ref': {'layout': "nchw->nhwc"}}, + {'params_test': {'layout': Layout("nchw")}, + 'params_ref': {'layout': "nchw"}} + ] + + @pytest.mark.parametrize("params", test_data) + @pytest.mark.nightly + @pytest.mark.precommit + def test_mo_convert_tf_model_single_input_output(self, params, ie_device, precision, ir_version, + temp_dir, use_new_frontend, use_old_api): + tf_net_path = self.create_tf_model_single_input_output(temp_dir) + + test_params = params['params_test'] + ref_params = params['params_ref'] + test_params.update({'input_model': tf_net_path}) + ref_params.update({'input_model': tf_net_path}) + self._test(temp_dir, test_params, ref_params) + + test_data = [ + { + 'params_test': {'transform': ('MakeStateful', {'param_res_names': {'Input:0': 'Identity:0'}})}, + 'params_ref': {'transform': "MakeStateful[param_res_names={\'Input:0\':\'Identity:0\'}]"}} + ] + + @pytest.mark.parametrize("params", test_data) + @pytest.mark.nightly + def test_mo_convert_transform(self, params, ie_device, precision, ir_version, + temp_dir, use_new_frontend, use_old_api): + tf_net_path = self.create_tf_param_res_model(temp_dir) + + test_params = params['params_test'] + ref_params = params['params_ref'] + test_params.update({'input_model': tf_net_path}) + ref_params.update({'input_model': tf_net_path}) + self._test(temp_dir, test_params, ref_params) diff --git a/tests/layer_tests/mo_python_api_tests/test_mo_convert_extensions.py b/tests/layer_tests/mo_python_api_tests/test_mo_convert_extensions.py new file mode 100644 index 00000000000..f2c67faf71d --- /dev/null +++ b/tests/layer_tests/mo_python_api_tests/test_mo_convert_extensions.py @@ -0,0 +1,136 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import pytest + +from common.mo_convert_test_class import CommonMOConvertTest +from common.onnx_layer_test_class import save_to_onnx +from unit_tests.utils.graph import build_graph + + +class TestExtensions(CommonMOConvertTest): + def create_onnx_model(self, tmp_dir): + # + # Create ONNX model + # + + import onnx + from onnx import helper + from onnx import TensorProto + + shape = [2, 3, 4] + + input = helper.make_tensor_value_info('input', TensorProto.FLOAT, shape) + output = helper.make_tensor_value_info('output', TensorProto.FLOAT, shape) + + node_def = onnx.helper.make_node( + 'LeakyRelu', + inputs=['input'], + outputs=['LeakyRelu_data'], + alpha=0.1 + ) + node_def2 = onnx.helper.make_node( + 'Elu', + inputs=['LeakyRelu_data'], + outputs=['output'], + alpha=0.1 + ) + + # Create the graph (GraphProto) + graph_def = helper.make_graph( + [node_def, node_def2], + 'test_model', + [input], + [output], + ) + + # Create the model (ModelProto) + onnx_net = helper.make_model(graph_def, producer_name='test_model') + + # save model to .onnx and return path to the model + return save_to_onnx(onnx_net, tmp_dir) + + def create_custom_extension_leaky_relu_to_relu(): + # replaces LeakyRelu with Relu + from openvino.frontend import ConversionExtension + from openvino.frontend import NodeContext + import openvino.runtime.opset8 as ops + + def custom_converter(node: NodeContext): + input = node.get_input(0) + relu = ops.relu(input) + return [relu.output(0)] + + return ConversionExtension("LeakyRelu", custom_converter) + + def create_custom_extension_elu_to_sigmoid(): + # replaces Elu with Sigmoid + from openvino.frontend import ConversionExtension + from openvino.frontend import NodeContext + import openvino.runtime.opset8 as ops + + def custom_converter(node: NodeContext): + input = node.get_input(0) + sigm = ops.sigmoid(input) + return [sigm.output(0)] + + return ConversionExtension("Elu", custom_converter) + + def create_ref_graph1(): + nodes_attributes = { + 'input': {'kind': 'op', 'type': 'Parameter'}, + 'input_data': {'shape': [2, 3, 4], 'kind': 'data'}, + 'relu': {'kind': 'op', 'type': 'ReLU'}, + 'relu_data': {'shape': [2, 3, 4], 'kind': 'data'}, + 'elu': {'kind': 'op', 'type': 'Elu'}, + 'elu_data': {'shape': [2, 3, 4], 'kind': 'data'}, + 'result': {'kind': 'op', 'type': 'Result'} + } + + return build_graph(nodes_attributes, + [('input', 'input_data'), + ('input_data', 'relu'), + ('relu', 'relu_data'), + ('relu_data', 'elu'), + ('elu', 'elu_data'), + ('elu_data', 'result'), + ]) + + def create_ref_graph2(): + nodes_attributes = { + 'input': {'kind': 'op', 'type': 'Parameter'}, + 'input_data': {'shape': [2, 3, 4], 'kind': 'data'}, + 'relu': {'kind': 'op', 'type': 'ReLU'}, + 'relu_data': {'shape': [2, 3, 4], 'kind': 'data'}, + 'sigmoid': {'kind': 'op', 'type': 'Sigmoid'}, + 'sigmoid_data': {'shape': [2, 3, 4], 'kind': 'data'}, + 'result': {'kind': 'op', 'type': 'Result'} + } + + return build_graph(nodes_attributes, + [('input', 'input_data'), + ('input_data', 'relu'), + ('relu', 'relu_data'), + ('relu_data', 'sigmoid'), + ('sigmoid', 'sigmoid_data'), + ('sigmoid_data', 'result'), + ]) + + test_data = [ + {'params_test': {'extensions': create_custom_extension_leaky_relu_to_relu()}, + 'ref_graph': create_ref_graph1()}, + {'params_test': {'extensions': [create_custom_extension_leaky_relu_to_relu(), + create_custom_extension_elu_to_sigmoid()]}, + 'ref_graph': create_ref_graph2()} + ] + + @pytest.mark.parametrize("params", test_data) + @pytest.mark.nightly + @pytest.mark.precommit + def test_mo_convert_extensions(self, params, ie_device, precision, ir_version, + temp_dir, use_new_frontend, use_old_api): + onnx_net_path = self.create_onnx_model(temp_dir) + + test_params = params['params_test'] + test_params.update({'input_model': onnx_net_path}) + self._test_by_ref_graph(temp_dir, test_params, params['ref_graph']) diff --git a/tools/mo/automation/package_BOM.txt b/tools/mo/automation/package_BOM.txt index ebe4694de46..5fbb907e65c 100644 --- a/tools/mo/automation/package_BOM.txt +++ b/tools/mo/automation/package_BOM.txt @@ -81,6 +81,7 @@ openvino/tools/mo/back/TopKNormalizer.py openvino/tools/mo/back/TransposeDFT.py openvino/tools/mo/back/TransposeReduceFusing.py openvino/tools/mo/back/UselessConcatRemoval.py +openvino/tools/mo/convert.py openvino/tools/mo/front/__init__.py openvino/tools/mo/front/ArgOpsSqueeze.py openvino/tools/mo/front/ATenToEmbeddingBag.py diff --git a/tools/mo/openvino/tools/mo/__init__.py b/tools/mo/openvino/tools/mo/__init__.py index 9a8c239afe7..c629cdc740b 100644 --- a/tools/mo/openvino/tools/mo/__init__.py +++ b/tools/mo/openvino/tools/mo/__init__.py @@ -1,3 +1,4 @@ # Copyright (C) 2018-2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +from .convert import convert, InputCutInfo, LayoutMap diff --git a/tools/mo/openvino/tools/mo/back/offline_transformations.py b/tools/mo/openvino/tools/mo/back/offline_transformations.py index 932d0cb05f3..ac0364badbf 100644 --- a/tools/mo/openvino/tools/mo/back/offline_transformations.py +++ b/tools/mo/openvino/tools/mo/back/offline_transformations.py @@ -7,7 +7,7 @@ from typing import List from openvino.tools.mo.front.extractor import create_params_with_custom_types from openvino.tools.mo.utils.cli_parser import parse_transform from openvino.tools.mo.utils.error import Error - +from openvino.runtime import Model def get_available_transformations(): try: @@ -49,28 +49,9 @@ def compress_model(func: object): compress_model_transformation(func) -def apply_offline_transformations(input_model: str, argv: argparse.Namespace): - # This variable is only needed by GenerateMappingFile transformation - # to produce correct mapping - extract_names = argv.framework in ['tf', 'mxnet', 'kaldi'] - - from openvino.runtime import serialize # pylint: disable=import-error,no-name-in-module - from openvino.offline_transformations import generate_mapping_file # pylint: disable=import-error,no-name-in-module - from openvino.frontend import FrontEndManager # pylint: disable=no-name-in-module,import-error +def apply_offline_transformations(func: Model, argv: argparse.Namespace): from openvino.tools.mo.back.preprocessing import apply_preprocessing # pylint: disable=no-name-in-module,import-error - fem = FrontEndManager() - - # We have to separate fe object lifetime from fem to - # avoid segfault during object destruction. So fe must - # be destructed before fem object explicitly. - def read_model(path_to_xml): - fe = fem.load_by_framework(framework="ir") - function = fe.convert(fe.load(path_to_xml)) - return function - - func = read_model(input_model + "_tmp.xml") - # Apply preprocessing (mean/scale/reverse_channels/convert_layout/etc) apply_preprocessing(ov_function=func, argv=argv) @@ -83,6 +64,4 @@ def apply_offline_transformations(input_model: str, argv: argparse.Namespace): if "compress_fp16" in argv and argv.compress_fp16: compress_model(func) - serialize(func, str(input_model + ".xml").encode('utf-8'), (input_model + ".bin").encode('utf-8')) - path_to_mapping = input_model + ".mapping" - generate_mapping_file(func, path_to_mapping.encode('utf-8'), extract_names) + return func diff --git a/tools/mo/openvino/tools/mo/convert.py b/tools/mo/openvino/tools/mo/convert.py new file mode 100644 index 00000000000..5fe9fc7ea80 --- /dev/null +++ b/tools/mo/openvino/tools/mo/convert.py @@ -0,0 +1,26 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from collections import namedtuple +from openvino.tools.mo.convert_impl import _convert + +InputCutInfo = namedtuple("InputInfo", ["name", "shape", "type", "value"]) +LayoutMap = namedtuple("LayoutMap", ["source_layout", "target_layout"]) + + +def convert(input_model=None, **args): + """ + Converts the model from original framework to OpenVino Model. + + Args: + input_model: + Tensorflow*: a file with a pre-trained model (binary or text .pb file after freezing). + Caffe*: a model proto file with model weights + + Run convert(help=true) to list all available parameters. + + Returns: + openvino.runtime.Model + """ + args.update({'input_model': input_model}) + return _convert(**args) diff --git a/tools/mo/openvino/tools/mo/convert_impl.py b/tools/mo/openvino/tools/mo/convert_impl.py new file mode 100644 index 00000000000..49e76eb1a5b --- /dev/null +++ b/tools/mo/openvino/tools/mo/convert_impl.py @@ -0,0 +1,573 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import datetime +import logging as log +import os +import platform +import sys +from collections import OrderedDict +from copy import deepcopy + +try: + import openvino_telemetry as tm +except ImportError: + import openvino.tools.mo.utils.telemetry_stub as tm + +from openvino.tools.mo.back.SpecialNodesFinalization import RemoveConstOps, CreateConstNodesReplacement, NormalizeTI +from openvino.tools.mo.moc_frontend.check_config import legacy_transformations_config_used, \ + new_extensions_used, new_transformations_config_used, input_freezig_used +from openvino.tools.mo.moc_frontend.pipeline import moc_pipeline +from openvino.tools.mo.moc_frontend.serialize import moc_emit_ir +from openvino.tools.mo.graph.graph import Graph +from openvino.tools.mo.middle.pattern_match import for_graph_and_each_sub_graph_recursively +from openvino.tools.mo.pipeline.common import prepare_emit_ir +from openvino.tools.mo.pipeline.unified import unified_pipeline +from openvino.tools.mo.utils import import_extensions +from openvino.tools.mo.utils.cli_parser import check_available_transforms, \ + get_advanced_cli_options, get_available_front_ends, get_caffe_cli_options, \ + get_common_cli_options, get_freeze_placeholder_values, get_kaldi_cli_options, get_layout_values, \ + get_mean_scale_dictionary, get_meta_info, get_mxnet_cli_options, get_onnx_cli_options, \ + get_placeholder_shapes, get_tf_cli_options, get_tuple_values, parse_transform, parse_tuple_pairs, \ + get_all_cli_parser, mo_convert_params, get_model_name_from_args + +from openvino.tools.mo.utils.error import Error +from openvino.tools.mo.utils.find_ie_version import find_ie_version +from openvino.tools.mo.utils.guess_framework import deduce_legacy_frontend_by_namespace +from openvino.tools.mo.utils.logger import init_logger, progress_printer +from openvino.tools.mo.utils.utils import refer_to_faq_msg +from openvino.tools.mo.utils.telemetry_utils import send_params_info, send_framework_info +from openvino.tools.mo.utils.version import get_simplified_mo_version, get_simplified_ie_version +from openvino.tools.mo.utils.versions_checker import check_requirements # pylint: disable=no-name-in-module +from openvino.tools.mo.utils.telemetry_utils import get_tid +from openvino.tools.mo.front.common.partial_infer.utils import mo_array +from openvino.tools.mo.moc_frontend.check_config import legacy_extensions_used + +# pylint: disable=no-name-in-module,import-error +from openvino.frontend import FrontEndManager, ProgressReporterExtension, TelemetryExtension, JsonConfigExtension + + +def load_extensions(argv: argparse.Namespace, is_tf: bool, is_caffe: bool, is_mxnet: bool, is_kaldi: bool, + is_onnx: bool): + extensions = None + if hasattr(argv, 'extensions') and argv.extensions and argv.extensions != '': + extensions = argv.extensions + if is_tf: + from openvino.tools.mo.front.tf.register_custom_ops import get_front_classes + import_extensions.load_dirs(argv.framework, extensions, get_front_classes) + elif is_caffe: + send_framework_info('caffe') + from openvino.tools.mo.front.caffe.register_custom_ops import get_front_classes + import_extensions.load_dirs(argv.framework, extensions, get_front_classes) + elif is_mxnet: + send_framework_info('mxnet') + from openvino.tools.mo.front.mxnet.register_custom_ops import get_front_classes + import_extensions.load_dirs(argv.framework, extensions, get_front_classes) + elif is_kaldi: + send_framework_info('kaldi') + from openvino.tools.mo.front.kaldi.register_custom_ops import get_front_classes + import_extensions.load_dirs(argv.framework, extensions, get_front_classes) + elif is_onnx: + send_framework_info('onnx') + from openvino.tools.mo.front.onnx.register_custom_ops import get_front_classes + import_extensions.load_dirs(argv.framework, extensions, get_front_classes) + + +def replace_ext(name: str, old: str, new: str): + base, ext = os.path.splitext(name) + log.debug("base: {}, ext: {}".format(base, ext)) + if ext == old: + return base + new + + +def print_argv(argv: argparse.Namespace, is_caffe: bool, is_tf: bool, is_mxnet: bool, is_kaldi: bool, is_onnx: bool, + model_name: str): + print('Model Optimizer arguments:') + props = OrderedDict() + props['common_args'] = get_common_cli_options(model_name) + props['advanced_args'] = get_advanced_cli_options() + if is_caffe: + props['caffe_args'] = get_caffe_cli_options() + if is_tf: + props['tf_args'] = get_tf_cli_options() + if is_mxnet: + props['mxnet_args'] = get_mxnet_cli_options() + if is_kaldi: + props['kaldi_args'] = get_kaldi_cli_options() + if is_onnx: + props['onnx_args'] = get_onnx_cli_options() + + framework_specifics_map = { + 'common_args': 'Common parameters:', + 'advanced_args': 'Advanced parameters:', + 'caffe_args': 'Caffe specific parameters:', + 'tf_args': 'TensorFlow specific parameters:', + 'mxnet_args': 'MXNet specific parameters:', + 'kaldi_args': 'Kaldi specific parameters:', + 'onnx_args': 'ONNX specific parameters:', + } + + lines = [] + for key in props: + lines.append(framework_specifics_map[key]) + for (op, desc) in props[key].items(): + if isinstance(desc, list): + lines.append('\t{}: \t{}'.format(desc[0], desc[1](getattr(argv, op, 'NONE')))) + else: + if op == 'k': + default_path = os.path.join(os.path.dirname(sys.argv[0]), + 'openvino/tools/mo/front/caffe/CustomLayersMapping.xml') + if getattr(argv, op, 'NONE') == default_path: + lines.append('\t{}: \t{}'.format(desc, 'Default')) + continue + lines.append('\t{}: \t{}'.format(desc, getattr(argv, op, 'NONE'))) + print('\n'.join(lines), flush=True) + + +def arguments_post_parsing(argv: argparse.Namespace): + use_legacy_frontend = argv.use_legacy_frontend + use_new_frontend = argv.use_new_frontend + + if use_new_frontend and use_legacy_frontend: + raise Error('Options --use_new_frontend and --use_legacy_frontend must not be used simultaneously ' + 'in the Model Optimizer command-line') + + moc_front_end, available_moc_front_ends = get_moc_frontends(argv) + + if not moc_front_end and use_new_frontend: + raise Error('Option --use_new_frontend is specified but the Model Optimizer is unable to find new frontend. ' + 'Please ensure that your environment contains new frontend for the input model format or ' + 'try to convert the model without specifying --use_new_frontend option.') + + is_tf, is_caffe, is_mxnet, is_kaldi, is_onnx = \ + deduce_legacy_frontend_by_namespace(argv) if not moc_front_end else [False, False, False, False, False] + + is_legacy_frontend = any([is_tf, is_caffe, is_mxnet, is_kaldi, is_onnx]) + if not is_legacy_frontend and use_legacy_frontend: + raise Error('Option --use_legacy_frontend is specified but Model Optimizer does not have legacy frontend ' + 'for the input model format. Please try to convert the model without specifying --use_legacy_frontend option.') + + # handle a default case, i.e. use_new_frontend and use_legacy_frontend are not specified, when no frontend is found + if not is_legacy_frontend and not moc_front_end: + legacy_frameworks = ['tf', 'caffe', 'mxnet', 'kaldi', 'onnx'] + frameworks = list(set(legacy_frameworks + available_moc_front_ends)) + if not argv.framework: + raise Error('Framework name can not be deduced from the given options: {}={}. ' + 'Please use --framework with one from the list: {}.', + '--input_model', argv.input_model, frameworks) + elif argv.framework not in frameworks: + raise Error('Framework {} is not a valid target. Please use --framework with one from the list: {}. ' + + refer_to_faq_msg(15), argv.framework, frameworks) + + if is_legacy_frontend: + if new_extensions_used(argv): + raise Error('New kind of extensions used on legacy path') + if new_transformations_config_used(argv): + raise Error('New kind of transformations configuration used on legacy path') + + if is_tf and not argv.input_model and not argv.saved_model_dir and not argv.input_meta_graph: + raise Error('Path to input model or saved model dir is required: use --input_model, --saved_model_dir or ' + '--input_meta_graph') + elif is_mxnet and not argv.input_model and not argv.input_symbol and not argv.pretrained_model_name: + raise Error('Path to input model or input symbol or pretrained_model_name is required: use --input_model or ' + '--input_symbol or --pretrained_model_name') + elif is_caffe and not argv.input_model and not argv.input_proto: + raise Error('Path to input model or input proto is required: use --input_model or --input_proto') + elif (is_kaldi or is_onnx) and not argv.input_model: + raise Error('Path to input model is required: use --input_model.') + + log.debug(str(argv)) + log.debug("Model Optimizer started") + + log.debug('Output model name would be {}{{.xml, .bin}}'.format(argv.model_name)) + + # if --input_proto is not provided, try to retrieve another one + # by suffix substitution from model file name + if is_caffe and not argv.input_proto: + argv.input_proto = replace_ext(argv.input_model, '.caffemodel', '.prototxt') + + if not argv.input_proto: + raise Error("Cannot find prototxt file: for Caffe please specify --input_proto - a " + + "protobuf file that stores topology and --input_model that stores " + + "pretrained weights. " + + refer_to_faq_msg(20)) + log.info('Deduced name for prototxt: {}'.format(argv.input_proto)) + + if not argv.silent: + print_argv(argv, is_caffe, is_tf, is_mxnet, is_kaldi, is_onnx, argv.model_name) + + # This try-except is additional reinsurance that the IE + # dependency search does not break the MO pipeline + def raise_ie_not_found(): + raise Error("Could not find the Inference Engine or nGraph Python API.\n" + "Consider building the Inference Engine and nGraph Python APIs from sources or " + "try to install OpenVINO (TM) Toolkit using \"install_prerequisites.{}\"".format( + "bat" if sys.platform == "windows" else "sh")) + + try: + if not find_ie_version(silent=argv.silent): + raise_ie_not_found() + except Exception as e: + log.error(e) + raise_ie_not_found() + + if 'data_type' in argv and argv.data_type in ['FP16', 'half']: + argv.data_type = 'FP32' + argv.compress_fp16 = True + else: + argv.compress_fp16 = False + + # This is just to check that transform key is valid and transformations are available + check_available_transforms(parse_transform(argv.transform)) + + # For C++ frontends there are no specific Python installation requirements, check only generic ones + if moc_front_end: + ret_code = check_requirements(silent=argv.silent) + else: + ret_code = check_requirements(framework=argv.framework, silent=argv.silent) + if ret_code: + raise Error('check_requirements exited with return code {}'.format(ret_code)) + + if is_tf and argv.tensorflow_use_custom_operations_config is not None: + argv.transformations_config = argv.tensorflow_use_custom_operations_config + + if is_caffe and argv.mean_file and argv.mean_values: + raise Error('Both --mean_file and mean_values are specified. Specify either mean file or mean values. ' + + refer_to_faq_msg(17)) + elif is_caffe and argv.mean_file and argv.mean_file_offsets: + values = get_tuple_values(argv.mean_file_offsets, t=int, num_exp_values=2) + mean_file_offsets = mo_array([int(x) for x in values[0].split(',')]) + if not all([offset >= 0 for offset in mean_file_offsets]): + raise Error("Negative value specified for --mean_file_offsets option. " + "Please specify positive integer values in format '(x,y)'. " + + refer_to_faq_msg(18)) + argv.mean_file_offsets = mean_file_offsets + + if argv.scale and argv.scale_values: + raise Error( + 'Both --scale and --scale_values are defined. Specify either scale factor or scale values per input ' + + 'channels. ' + refer_to_faq_msg(19)) + + if argv.scale and argv.scale < 1.0: + log.error("The scale value is less than 1.0. This is most probably an issue because the scale value specifies " + "floating point value which all input values will be *divided*.", extra={'is_warning': True}) + + if argv.input_model and (is_tf and argv.saved_model_dir): + raise Error('Both --input_model and --saved_model_dir are defined. ' + 'Specify either input model or saved model directory.') + if is_tf: + if argv.saved_model_tags is not None: + if ' ' in argv.saved_model_tags: + raise Error('Incorrect saved model tag was provided. Specify --saved_model_tags with no spaces in it') + argv.saved_model_tags = argv.saved_model_tags.split(',') + + argv.output = argv.output.split(',') if argv.output else None + + inputs_list, argv.placeholder_shapes, argv.placeholder_data_types = get_placeholder_shapes( + argv.input, argv.input_shape, argv.batch) + argv.inputs_list = inputs_list + + mean_values = parse_tuple_pairs(argv.mean_values) + scale_values = parse_tuple_pairs(argv.scale_values) + mean_scale = get_mean_scale_dictionary(mean_values, scale_values, argv.input) + argv.mean_scale_values = mean_scale + argv.layout_values = get_layout_values(argv.layout, argv.source_layout, argv.target_layout) + + if not os.path.exists(argv.output_dir): + try: + os.makedirs(argv.output_dir) + except PermissionError as e: + raise Error("Failed to create directory {}. Permission denied! " + + refer_to_faq_msg(22), + argv.output_dir) from e + else: + if not os.access(argv.output_dir, os.W_OK): + raise Error("Output directory {} is not writable for current user. " + + refer_to_faq_msg(22), argv.output_dir) + + log.debug("Placeholder shapes : {}".format(argv.placeholder_shapes)) + + argv.freeze_placeholder_with_value, argv.input = get_freeze_placeholder_values(argv.input, + argv.freeze_placeholder_with_value) + + load_extensions(argv, is_tf, is_caffe, is_mxnet, is_kaldi, is_onnx) + + return argv + + +def check_fallback(argv: argparse.Namespace): + fallback_reasons = {} + + # Some frontend such as PDPD does not have legacy path so it has no reasons to fallback + if not any(deduce_legacy_frontend_by_namespace(argv)): + return fallback_reasons + + # There is no possibility for fallback if a user strictly wants to use new frontend + if argv.use_new_frontend: + return fallback_reasons + + fallback_reasons['extensions'] = legacy_extensions_used + fallback_reasons['transformations_config'] = legacy_transformations_config_used + + reasons = [reason for reason, is_applicable in fallback_reasons.items() if is_applicable(argv)] + return reasons + + +def get_default_frontends(): + # Set which frontend to use by default, values should be 'new' or 'legacy' + default_frontends = { + 'onnx': 'new', + 'tf': 'legacy' + } + return default_frontends + + +def get_moc_frontends(argv: argparse.Namespace): + fem = argv.feManager + + # Read user flags: + use_legacy_frontend = argv.use_legacy_frontend + use_new_frontend = argv.use_new_frontend + + if not fem or use_legacy_frontend: + return None, [] + + available_moc_front_ends = get_available_front_ends(fem) + + if not argv.framework and argv.input_model: + moc_front_end = fem.load_by_model(argv.input_model) + if not moc_front_end: + return None, available_moc_front_ends + argv.framework = moc_front_end.get_name() + elif argv.framework in available_moc_front_ends: + moc_front_end = fem.load_by_framework(argv.framework) + else: + return None, [] + + default_frontends = get_default_frontends() + # Disable MOC frontend if default is set to legacy and no user override + if default_frontends.get(moc_front_end.get_name()) == 'legacy' and not use_new_frontend: + return None, available_moc_front_ends + + # This check as a workaround to skip IR frontend + if not moc_front_end.get_name() in available_moc_front_ends: + return None, available_moc_front_ends + + return moc_front_end, available_moc_front_ends + + +def prepare_ir(argv: argparse.Namespace): + argv = arguments_post_parsing(argv) + t = tm.Telemetry() + graph = None + ngraph_function = None + moc_front_end, available_moc_front_ends = get_moc_frontends(argv) + if moc_front_end: + fallback_reasons = check_fallback(argv) + if len(fallback_reasons) == 0: + t.send_event("mo", "conversion_method", moc_front_end.get_name() + "_frontend") + moc_front_end.add_extension(TelemetryExtension("mo", t.send_event, t.send_error, t.send_stack_trace)) + moc_front_end.add_extension(ProgressReporterExtension(progress_printer(argv))) + if legacy_transformations_config_used(argv): + raise Error('Legacy extensions are not supported for the new frontend') + if legacy_extensions_used(argv): + raise Error('Legacy transformations configuration is not supported for the new frontend') + if new_transformations_config_used(argv): + moc_front_end.add_extension(JsonConfigExtension(argv.transformations_config)) + if new_extensions_used(argv): + for extension in argv.extensions: + moc_front_end.add_extension(extension) + ngraph_function = moc_pipeline(argv, moc_front_end) + + return graph, ngraph_function + else: # apply fallback + reasons_message = ", ".join(fallback_reasons) + load_extensions(argv, *list(deduce_legacy_frontend_by_namespace(argv))) + t.send_event("mo", "fallback_reason", reasons_message) + log.warning("The IR preparation was executed by the legacy MO path. " + "This is a fallback scenario applicable only for some specific cases. " + f"The detailed reason why fallback was executed: not supported {reasons_message} were used. " + "You can specify --use_new_frontend flag to force using the Frontend MO path to avoid additional checks. " + + refer_to_faq_msg(105)) + + t.send_event("mo", "conversion_method", "mo_legacy") + graph = unified_pipeline(argv) + + return graph, ngraph_function + + +def emit_ir(graph: Graph, argv: argparse.Namespace): + # We have to separate fe object lifetime from fem to + # avoid segfault during object destruction. So fe must + # be destructed before fem object explicitly. + def read_model(path_to_xml): + fe = fem.load_by_framework(framework="ir") + function = fe.convert(fe.load(path_to_xml)) + return function + + NormalizeTI().find_and_replace_pattern(graph) + for_graph_and_each_sub_graph_recursively(graph, RemoveConstOps().find_and_replace_pattern) + for_graph_and_each_sub_graph_recursively(graph, CreateConstNodesReplacement().find_and_replace_pattern) + + if 'feManager' in argv: + del argv.feManager + + mean_data = deepcopy(graph.graph['mf']) if 'mf' in graph.graph else None + input_names = deepcopy(graph.graph['input_names']) if 'input_names' in graph.graph else [] + + prepare_emit_ir(graph=graph, + data_type=graph.graph['cmd_params'].data_type, + output_dir=argv.output_dir, + output_model_name=argv.model_name, + mean_data=mean_data, + input_names=input_names, + meta_info=get_meta_info(argv), + use_temporary_path=True) + + # This graph cleanup is required to avoid double memory consumption + graph.clear() + + output_dir = argv.output_dir if argv.output_dir != '.' else os.getcwd() + orig_model_name = os.path.normpath(os.path.join(output_dir, argv.model_name)) + + fem = FrontEndManager() + func = read_model(orig_model_name + "_tmp.xml") + + return_code = "not executed" + if not(argv.framework == 'tf' and argv.tensorflow_custom_operations_config_update): + try: + from openvino.tools.mo.back.offline_transformations import apply_offline_transformations + func = apply_offline_transformations(func, argv) + if "compress_fp16" in argv and argv.compress_fp16: + # restore data_type cmd parameter + argv.data_type = 'FP16' + return_code = 0 + except Exception as e: + return_code = "failed" + log.error(e) + + message = str(dict({ + "platform": platform.system(), + "mo_version": get_simplified_mo_version(), + "ie_version": get_simplified_ie_version(env=os.environ), + "python_version": sys.version, + "return_code": return_code + })) + t = tm.Telemetry() + t.send_event('mo', 'offline_transformations_status', message) + + if return_code != 0: + raise Error("offline transformations step has failed.") + + for suf in [".xml", ".bin", ".mapping"]: + # remove existing files + path_to_file = orig_model_name + "_tmp" + suf + if os.path.exists(path_to_file): + os.remove(path_to_file) + return func + + +def driver(argv: argparse.Namespace): + init_logger(argv.log_level.upper(), argv.silent) + + start_time = datetime.datetime.now() + + graph, ngraph_function = prepare_ir(argv) + if graph is not None: + res_ngraph_function = emit_ir(graph, argv) + else: + res_ngraph_function = moc_emit_ir(ngraph_function, argv) + + if res_ngraph_function is None: + return res_ngraph_function + + if not argv.silent: + elapsed_time = datetime.datetime.now() - start_time + print('[ SUCCESS ] Total execution time: {:.2f} seconds. '.format(elapsed_time.total_seconds())) + try: + import resource + mem_usage = round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024) + if sys.platform == 'darwin': + mem_usage = round(mem_usage / 1024) + print('[ SUCCESS ] Memory consumed: {} MB. '.format(mem_usage)) + except ImportError: + pass + + return res_ngraph_function + + +def args_dict_to_list(cli_parser, **kwargs): + result = [] + for key, value in kwargs.items(): + if value is not None and cli_parser.get_default(key) != value: + # skip parser checking for non str objects + if not isinstance(value, str): + continue + result.append('--{}'.format(key)) + if not isinstance(value, bool): + result.append(value) + + return result + + +def pack_params_to_args_namespace(**kwargs): + fe_manager = FrontEndManager() + cli_parser = get_all_cli_parser(fe_manager) + argv = cli_parser.parse_args(args_dict_to_list(cli_parser, **kwargs)) + for key, value in kwargs.items(): + if key not in argv and key not in mo_convert_params.keys(): + raise Error("Unrecognized argument: {}".format(key)) + if value is not None: + setattr(argv, key, value) + send_params_info(argv, cli_parser) + return argv + + +def params_to_string(**kwargs): + for key, value in kwargs.items(): + if key in mo_convert_params.keys(): + param_data = mo_convert_params[key] + if param_data.to_string is not None: + kwargs[key] = param_data.to_string(value) + return kwargs + + +def show_mo_convert_help(): + print('MO convert parameters:') + for param_name in mo_convert_params.keys(): + param_data = mo_convert_params[param_name] + print("{}: {}".format(param_name, param_data.description.format(param_data.possible_types_python_api))) + + +def _convert(**args): + if 'help' in args and args['help']: + show_mo_convert_help() + return None + + telemetry = tm.Telemetry(tid=get_tid(), app_name='Model Optimizer', app_version=get_simplified_mo_version()) + telemetry.start_session('mo') + telemetry.send_event('mo', 'version', get_simplified_mo_version()) + args = params_to_string(**args) + argv = pack_params_to_args_namespace(**args) + + if argv.model_name is None: + argv.model_name = get_model_name_from_args(argv) + + try: + # Initialize logger with 'ERROR' as default level to be able to form nice messages + # before arg parser deliver log_level requested by user + init_logger('ERROR', False) + + argv.feManager = FrontEndManager() + ngraph_function = driver(argv) + + telemetry.send_event('mo', 'conversion_result', 'success') + telemetry.end_session('mo') + telemetry.force_shutdown(1.0) + return ngraph_function + except Exception as e: + telemetry.send_event('mo', 'conversion_result', 'fail') + telemetry.end_session('mo') + telemetry.force_shutdown(1.0) + raise e diff --git a/tools/mo/openvino/tools/mo/main.py b/tools/mo/openvino/tools/mo/main.py index b57296a08bb..280a08bb15c 100644 --- a/tools/mo/openvino/tools/mo/main.py +++ b/tools/mo/openvino/tools/mo/main.py @@ -2,545 +2,53 @@ # SPDX-License-Identifier: Apache-2.0 import argparse -import datetime -import logging as log import os -import platform import sys -import traceback -from collections import OrderedDict -from copy import deepcopy +import logging as log try: import openvino_telemetry as tm except ImportError: import openvino.tools.mo.utils.telemetry_stub as tm -from openvino.tools.mo.back.SpecialNodesFinalization import RemoveConstOps, CreateConstNodesReplacement, NormalizeTI -from openvino.tools.mo.back.ie_ir_ver_2.emitter import append_ir_info -from openvino.tools.mo.moc_frontend.check_config import legacy_extensions_used, legacy_transformations_config_used, \ - new_extensions_used, new_transformations_config_used, input_freezig_used -from openvino.tools.mo.moc_frontend.pipeline import moc_pipeline -from openvino.tools.mo.moc_frontend.serialize import moc_emit_ir -from openvino.tools.mo.graph.graph import Graph -from openvino.tools.mo.middle.pattern_match import for_graph_and_each_sub_graph_recursively -from openvino.tools.mo.pipeline.common import prepare_emit_ir, get_ir_version -from openvino.tools.mo.pipeline.unified import unified_pipeline -from openvino.tools.mo.utils import import_extensions -from openvino.tools.mo.utils.cli_parser import check_available_transforms, \ - get_advanced_cli_options, get_available_front_ends, get_caffe_cli_options, \ - get_common_cli_options, get_freeze_placeholder_values, get_kaldi_cli_options, get_layout_values, \ - get_mean_scale_dictionary, get_meta_info, get_model_name, get_mxnet_cli_options, get_onnx_cli_options, \ - get_placeholder_shapes, get_tf_cli_options, get_tuple_values, parse_transform, parse_tuple_pairs +from openvino.tools.mo.convert import convert +from openvino.tools.mo.pipeline.common import get_ir_version +from openvino.tools.mo.utils.cli_parser import get_model_name_from_args, get_meta_info +from openvino.tools.mo.utils.logger import init_logger from openvino.tools.mo.utils.error import Error, FrameworkError -from openvino.tools.mo.utils.find_ie_version import find_ie_version +import traceback from openvino.tools.mo.utils.get_ov_update_message import get_ov_update_message, get_ov_api20_message -from openvino.tools.mo.utils.guess_framework import deduce_legacy_frontend_by_namespace -from openvino.tools.mo.utils.logger import init_logger, progress_printer from openvino.tools.mo.utils.model_analysis import AnalysisResults -from openvino.tools.mo.utils.utils import refer_to_faq_msg -from openvino.tools.mo.utils.telemetry_utils import send_params_info, send_framework_info -from openvino.tools.mo.utils.version import get_simplified_mo_version, get_simplified_ie_version -from openvino.tools.mo.utils.versions_checker import check_requirements # pylint: disable=no-name-in-module -from openvino.tools.mo.utils.telemetry_utils import get_tid -from openvino.tools.mo.front.common.partial_infer.utils import mo_array +from openvino.tools.mo.back.ie_ir_ver_2.emitter import append_ir_info # pylint: disable=no-name-in-module,import-error -from openvino.frontend import FrontEndManager, ProgressReporterExtension, TelemetryExtension, JsonConfigExtension +from openvino.frontend import FrontEndManager +from openvino.offline_transformations import generate_mapping_file +from openvino.runtime import serialize -def replace_ext(name: str, old: str, new: str): - base, ext = os.path.splitext(name) - log.debug("base: {}, ext: {}".format(base, ext)) - if ext == old: - return base + new +def main(cli_parser: argparse.ArgumentParser, framework=None): + argv = cli_parser.parse_args() + argv.model_name = get_model_name_from_args(argv) + argv = vars(argv) + # Initialize logger with 'ERROR' as default level to be able to form nice messages + # before arg parser deliver log_level requested by user + init_logger('ERROR', False) -def print_argv(argv: argparse.Namespace, is_caffe: bool, is_tf: bool, is_mxnet: bool, is_kaldi: bool, is_onnx: bool, - model_name: str): - print('Model Optimizer arguments:') - props = OrderedDict() - props['common_args'] = get_common_cli_options(model_name) - props['advanced_args'] = get_advanced_cli_options() - if is_caffe: - props['caffe_args'] = get_caffe_cli_options() - if is_tf: - props['tf_args'] = get_tf_cli_options() - if is_mxnet: - props['mxnet_args'] = get_mxnet_cli_options() - if is_kaldi: - props['kaldi_args'] = get_kaldi_cli_options() - if is_onnx: - props['onnx_args'] = get_onnx_cli_options() + if framework is not None: + argv['framework'] = framework - framework_specifics_map = { - 'common_args': 'Common parameters:', - 'advanced_args': 'Advanced parameters:', - 'caffe_args': 'Caffe specific parameters:', - 'tf_args': 'TensorFlow specific parameters:', - 'mxnet_args': 'MXNet specific parameters:', - 'kaldi_args': 'Kaldi specific parameters:', - 'onnx_args': 'ONNX specific parameters:', - } - - lines = [] - for key in props: - lines.append(framework_specifics_map[key]) - for (op, desc) in props[key].items(): - if isinstance(desc, list): - lines.append('\t{}: \t{}'.format(desc[0], desc[1](getattr(argv, op, 'NONE')))) - else: - if op == 'k': - default_path = os.path.join(os.path.dirname(sys.argv[0]), - 'openvino/tools/mo/front/caffe/CustomLayersMapping.xml') - if getattr(argv, op, 'NONE') == default_path: - lines.append('\t{}: \t{}'.format(desc, 'Default')) - continue - lines.append('\t{}: \t{}'.format(desc, getattr(argv, op, 'NONE'))) - print('\n'.join(lines), flush=True) - - -def get_default_frontends(): - # Set which frontend to use by default, values should be 'new' or 'legacy' - default_frontends = { - 'onnx': 'new', - 'tf': 'legacy' - } - return default_frontends - - -def get_moc_frontends(argv: argparse.Namespace): - fem = argv.feManager - - # Read user flags: - use_legacy_frontend = argv.use_legacy_frontend - use_new_frontend = argv.use_new_frontend - - if not fem or use_legacy_frontend: - return None, [] - - available_moc_front_ends = get_available_front_ends(fem) - - if not argv.framework and argv.input_model: - moc_front_end = fem.load_by_model(argv.input_model) - if not moc_front_end: - return None, available_moc_front_ends - argv.framework = moc_front_end.get_name() - elif argv.framework in available_moc_front_ends: - moc_front_end = fem.load_by_framework(argv.framework) - else: - return None, [] - - default_frontends = get_default_frontends() - # Disable MOC frontend if default is set to legacy and no user override - if default_frontends.get(moc_front_end.get_name()) == 'legacy' and not use_new_frontend: - return None, available_moc_front_ends - - # This check as a workaround to skip IR frontend - if not moc_front_end.get_name() in available_moc_front_ends: - return None, available_moc_front_ends - - return moc_front_end, available_moc_front_ends - - -def arguments_post_parsing(argv: argparse.Namespace): - use_legacy_frontend = argv.use_legacy_frontend - use_new_frontend = argv.use_new_frontend - - if use_new_frontend and use_legacy_frontend: - raise Error('Options --use_new_frontend and --use_legacy_frontend must not be used simultaneously ' - 'in the Model Optimizer command-line') - - moc_front_end, available_moc_front_ends = get_moc_frontends(argv) - - if not moc_front_end and use_new_frontend: - raise Error('Option --use_new_frontend is specified but the Model Optimizer is unable to find new frontend. ' - 'Please ensure that your environment contains new frontend for the input model format or ' - 'try to convert the model without specifying --use_new_frontend option.') - - is_tf, is_caffe, is_mxnet, is_kaldi, is_onnx = \ - deduce_legacy_frontend_by_namespace(argv) if not moc_front_end else [False, False, False, False, False] - - is_legacy_frontend = any([is_tf, is_caffe, is_mxnet, is_kaldi, is_onnx]) - if not is_legacy_frontend and use_legacy_frontend: - raise Error('Option --use_legacy_frontend is specified but Model Optimizer does not have legacy frontend ' - 'for the input model format. Please try to convert the model without specifying --use_legacy_frontend option.') - - # handle a default case, i.e. use_new_frontend and use_legacy_frontend are not specified, when no frontend is found - if not is_legacy_frontend and not moc_front_end: - legacy_frameworks = ['tf', 'caffe', 'mxnet', 'kaldi', 'onnx'] - frameworks = list(set(legacy_frameworks + available_moc_front_ends)) - if not argv.framework: - raise Error('Framework name can not be deduced from the given options: {}={}. ' - 'Please use --framework with one from the list: {}.', - '--input_model', argv.input_model, frameworks) - elif argv.framework not in frameworks: - raise Error('Framework {} is not a valid target. Please use --framework with one from the list: {}. ' + - refer_to_faq_msg(15), argv.framework, frameworks) - - if is_legacy_frontend: - if new_extensions_used(argv): - raise Error('New kind of extensions used on legacy path') - if new_transformations_config_used(argv): - raise Error('New kind of transformations configuration used on legacy path') - - if is_tf and not argv.input_model and not argv.saved_model_dir and not argv.input_meta_graph: - raise Error('Path to input model or saved model dir is required: use --input_model, --saved_model_dir or ' - '--input_meta_graph') - elif is_mxnet and not argv.input_model and not argv.input_symbol and not argv.pretrained_model_name: - raise Error('Path to input model or input symbol or pretrained_model_name is required: use --input_model or ' - '--input_symbol or --pretrained_model_name') - elif is_caffe and not argv.input_model and not argv.input_proto: - raise Error('Path to input model or input proto is required: use --input_model or --input_proto') - elif (is_kaldi or is_onnx) and not argv.input_model: - raise Error('Path to input model is required: use --input_model.') - - log.debug(str(argv)) - log.debug("Model Optimizer started") - - model_name = "" - if argv.model_name: - model_name = argv.model_name - elif argv.input_model: - model_name = get_model_name(argv.input_model) - elif is_tf and argv.saved_model_dir: - model_name = "saved_model" - elif is_tf and argv.input_meta_graph: - model_name = get_model_name(argv.input_meta_graph) - elif is_mxnet and argv.input_symbol: - model_name = get_model_name(argv.input_symbol) - argv.model_name = model_name - - log.debug('Output model name would be {}{{.xml, .bin}}'.format(argv.model_name)) - - # if --input_proto is not provided, try to retrieve another one - # by suffix substitution from model file name - if is_caffe and not argv.input_proto: - argv.input_proto = replace_ext(argv.input_model, '.caffemodel', '.prototxt') - - if not argv.input_proto: - raise Error("Cannot find prototxt file: for Caffe please specify --input_proto - a " + - "protobuf file that stores topology and --input_model that stores " + - "pretrained weights. " + - refer_to_faq_msg(20)) - log.info('Deduced name for prototxt: {}'.format(argv.input_proto)) - - if not argv.silent: - print_argv(argv, is_caffe, is_tf, is_mxnet, is_kaldi, is_onnx, argv.model_name) - - # This try-except is additional reinsurance that the IE - # dependency search does not break the MO pipeline - def raise_ie_not_found(): - raise Error("Could not find the Inference Engine or nGraph Python API.\n" - "Consider building the Inference Engine and nGraph Python APIs from sources or " - "try to install OpenVINO (TM) Toolkit using \"install_prerequisites.{}\"".format( - "bat" if sys.platform == "windows" else "sh")) - - try: - if not find_ie_version(silent=argv.silent): - raise_ie_not_found() - except Exception as e: - log.error(e) - raise_ie_not_found() - - if 'data_type' in argv and argv.data_type in ['FP16', 'half']: - argv.data_type = 'FP32' - argv.compress_fp16 = True - else: - argv.compress_fp16 = False - - # This is just to check that transform key is valid and transformations are available - check_available_transforms(parse_transform(argv.transform)) - - # For C++ frontends there are no specific Python installation requirements, check only generic ones - if moc_front_end: - ret_code = check_requirements() - else: - ret_code = check_requirements(framework=argv.framework) - if ret_code: - raise Error('check_requirements exited with return code {}'.format(ret_code)) - - if is_tf and argv.tensorflow_use_custom_operations_config is not None: - argv.transformations_config = argv.tensorflow_use_custom_operations_config - - if is_caffe and argv.mean_file and argv.mean_values: - raise Error('Both --mean_file and mean_values are specified. Specify either mean file or mean values. ' + - refer_to_faq_msg(17)) - elif is_caffe and argv.mean_file and argv.mean_file_offsets: - values = get_tuple_values(argv.mean_file_offsets, t=int, num_exp_values=2) - mean_file_offsets = mo_array([int(x) for x in values[0].split(',')]) - if not all([offset >= 0 for offset in mean_file_offsets]): - raise Error("Negative value specified for --mean_file_offsets option. " - "Please specify positive integer values in format '(x,y)'. " + - refer_to_faq_msg(18)) - argv.mean_file_offsets = mean_file_offsets - - if argv.scale and argv.scale_values: - raise Error( - 'Both --scale and --scale_values are defined. Specify either scale factor or scale values per input ' + - 'channels. ' + refer_to_faq_msg(19)) - - if argv.scale and argv.scale < 1.0: - log.error("The scale value is less than 1.0. This is most probably an issue because the scale value specifies " - "floating point value which all input values will be *divided*.", extra={'is_warning': True}) - - if argv.input_model and (is_tf and argv.saved_model_dir): - raise Error('Both --input_model and --saved_model_dir are defined. ' - 'Specify either input model or saved model directory.') - if is_tf: - if argv.saved_model_tags is not None: - if ' ' in argv.saved_model_tags: - raise Error('Incorrect saved model tag was provided. Specify --saved_model_tags with no spaces in it') - argv.saved_model_tags = argv.saved_model_tags.split(',') - - argv.output = argv.output.split(',') if argv.output else None - - inputs_list, argv.placeholder_shapes, argv.placeholder_data_types = get_placeholder_shapes( - argv.input, argv.input_shape, argv.batch) - argv.inputs_list = inputs_list - - mean_values = parse_tuple_pairs(argv.mean_values) - scale_values = parse_tuple_pairs(argv.scale_values) - mean_scale = get_mean_scale_dictionary(mean_values, scale_values, argv.input) - argv.mean_scale_values = mean_scale - argv.layout_values = get_layout_values(argv.layout, argv.source_layout, argv.target_layout) - - if not os.path.exists(argv.output_dir): - try: - os.makedirs(argv.output_dir) - except PermissionError as e: - raise Error("Failed to create directory {}. Permission denied! " + - refer_to_faq_msg(22), - argv.output_dir) from e - else: - if not os.access(argv.output_dir, os.W_OK): - raise Error("Output directory {} is not writable for current user. " + - refer_to_faq_msg(22), argv.output_dir) - - log.debug("Placeholder shapes : {}".format(argv.placeholder_shapes)) - - argv.freeze_placeholder_with_value, argv.input = get_freeze_placeholder_values(argv.input, - argv.freeze_placeholder_with_value) - - load_extensions(argv, is_tf, is_caffe, is_mxnet, is_kaldi, is_onnx) - - return argv - - -def load_extensions(argv: argparse.Namespace, is_tf: bool, is_caffe: bool, is_mxnet: bool, is_kaldi: bool, - is_onnx: bool): - extensions = None - if hasattr(argv, 'extensions') and argv.extensions and argv.extensions != '': - extensions = argv.extensions.split(',') - if is_tf: - from openvino.tools.mo.front.tf.register_custom_ops import get_front_classes - import_extensions.load_dirs(argv.framework, extensions, get_front_classes) - elif is_caffe: - send_framework_info('caffe') - from openvino.tools.mo.front.caffe.register_custom_ops import get_front_classes - import_extensions.load_dirs(argv.framework, extensions, get_front_classes) - elif is_mxnet: - send_framework_info('mxnet') - from openvino.tools.mo.front.mxnet.register_custom_ops import get_front_classes - import_extensions.load_dirs(argv.framework, extensions, get_front_classes) - elif is_kaldi: - send_framework_info('kaldi') - from openvino.tools.mo.front.kaldi.register_custom_ops import get_front_classes - import_extensions.load_dirs(argv.framework, extensions, get_front_classes) - elif is_onnx: - send_framework_info('onnx') - from openvino.tools.mo.front.onnx.register_custom_ops import get_front_classes - import_extensions.load_dirs(argv.framework, extensions, get_front_classes) - - -def check_fallback(argv: argparse.Namespace): - fallback_reasons = {} - - # Some frontend such as PDPD does not have legacy path so it has no reasons to fallback - if not any(deduce_legacy_frontend_by_namespace(argv)): - return fallback_reasons - - # There is no possibility for fallback if a user strictly wants to use new frontend - if argv.use_new_frontend: - return fallback_reasons - - fallback_reasons['extensions'] = legacy_extensions_used - fallback_reasons['transformations_config'] = legacy_transformations_config_used - - reasons = [reason for reason, is_applicable in fallback_reasons.items() if is_applicable(argv)] - return reasons - - -def prepare_ir(argv: argparse.Namespace): - argv = arguments_post_parsing(argv) - t = tm.Telemetry() - graph = None ngraph_function = None - moc_front_end, available_moc_front_ends = get_moc_frontends(argv) - if moc_front_end: - fallback_reasons = check_fallback(argv) - if len(fallback_reasons) == 0: - t.send_event("mo", "conversion_method", moc_front_end.get_name() + "_frontend") - moc_front_end.add_extension(TelemetryExtension("mo", t.send_event, t.send_error, t.send_stack_trace)) - moc_front_end.add_extension(ProgressReporterExtension(progress_printer(argv))) - if legacy_transformations_config_used(argv): - raise Error('Legacy extensions are not supported for the new frontend') - if legacy_extensions_used(argv): - raise Error('Legacy transformations configuration is not supported for the new frontend') - if new_transformations_config_used(argv): - moc_front_end.add_extension(JsonConfigExtension(argv.transformations_config)) - if new_extensions_used(argv): - for extension in argv.extensions.split(','): - moc_front_end.add_extension(extension) - ngraph_function = moc_pipeline(argv, moc_front_end) - - return graph, ngraph_function - else: # apply fallback - reasons_message = ", ".join(fallback_reasons) - load_extensions(argv, *list(deduce_legacy_frontend_by_namespace(argv))) - t.send_event("mo", "fallback_reason", reasons_message) - log.warning("The IR preparation was executed by the legacy MO path. " - "This is a fallback scenario applicable only for some specific cases. " - f"The detailed reason why fallback was executed: not supported {reasons_message} were used. " - "You can specify --use_new_frontend flag to force using the Frontend MO path to avoid additional checks. " + - refer_to_faq_msg(105)) - - t.send_event("mo", "conversion_method", "mo_legacy") - graph = unified_pipeline(argv) - - return graph, ngraph_function - - -def emit_ir(graph: Graph, argv: argparse.Namespace): - NormalizeTI().find_and_replace_pattern(graph) - for_graph_and_each_sub_graph_recursively(graph, RemoveConstOps().find_and_replace_pattern) - for_graph_and_each_sub_graph_recursively(graph, CreateConstNodesReplacement().find_and_replace_pattern) - - if 'feManager' in argv: - del argv.feManager - - mean_data = deepcopy(graph.graph['mf']) if 'mf' in graph.graph else None - input_names = deepcopy(graph.graph['input_names']) if 'input_names' in graph.graph else [] - - prepare_emit_ir(graph=graph, - data_type=graph.graph['cmd_params'].data_type, - output_dir=argv.output_dir, - output_model_name=argv.model_name, - mean_data=mean_data, - input_names=input_names, - meta_info=get_meta_info(argv), - use_temporary_path=True) - - # This graph cleanup is required to avoid double memory consumption - graph.clear() - - if not (argv.framework == 'tf' and argv.tensorflow_custom_operations_config_update): - output_dir = argv.output_dir if argv.output_dir != '.' else os.getcwd() - orig_model_name = os.path.normpath(os.path.join(output_dir, argv.model_name)) - - return_code = "not executed" - try: - from openvino.tools.mo.back.offline_transformations import apply_offline_transformations - apply_offline_transformations(orig_model_name, argv) - if "compress_fp16" in argv and argv.compress_fp16: - # restore data_type cmd parameter - argv.data_type = 'FP16' - return_code = 0 - except Exception as e: - return_code = "failed" - log.error(e) - - message = str(dict({ - "platform": platform.system(), - "mo_version": get_simplified_mo_version(), - "ie_version": get_simplified_ie_version(env=os.environ), - "python_version": sys.version, - "return_code": return_code - })) - t = tm.Telemetry() - t.send_event('mo', 'offline_transformations_status', message) - - if return_code != 0: - raise Error("offline transformations step has failed.") - - for suf in [".xml", ".bin", ".mapping"]: - # remove existing files - path_to_file = orig_model_name + "_tmp" + suf - if os.path.exists(path_to_file): - os.remove(path_to_file) - - # add meta information to IR - append_ir_info(file=orig_model_name, - meta_info=get_meta_info(argv), - mean_data=mean_data, - input_names=input_names, - legacy_path=True) - - print('[ SUCCESS ] Generated IR version {} model.'.format(get_ir_version(argv))) - print('[ SUCCESS ] XML file: {}.xml'.format(orig_model_name)) - print('[ SUCCESS ] BIN file: {}.bin'.format(orig_model_name)) - - return 0 - - -def driver(argv: argparse.Namespace): - init_logger(argv.log_level.upper(), argv.silent) - - start_time = datetime.datetime.now() - - graph, ngraph_function = prepare_ir(argv) - if graph is not None: - ret_res = emit_ir(graph, argv) - else: - ret_res = moc_emit_ir(ngraph_function, argv) - - if ret_res != 0: - return ret_res - - elapsed_time = datetime.datetime.now() - start_time - print('[ SUCCESS ] Total execution time: {:.2f} seconds. '.format(elapsed_time.total_seconds())) - try: - import resource - mem_usage = round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024) - if sys.platform == 'darwin': - mem_usage = round(mem_usage / 1024) - print('[ SUCCESS ] Memory consumed: {} MB. '.format(mem_usage)) - except ImportError: - pass - - return ret_res - - -def main(cli_parser: argparse.ArgumentParser, fem: FrontEndManager, framework: str): - telemetry = tm.Telemetry(tid=get_tid(), app_name='Model Optimizer', app_version=get_simplified_mo_version()) - telemetry.start_session('mo') - telemetry.send_event('mo', 'version', get_simplified_mo_version()) - try: - # Initialize logger with 'ERROR' as default level to be able to form nice messages - # before arg parser deliver log_level requested by user - init_logger('ERROR', False) - - argv = cli_parser.parse_args() - send_params_info(argv, cli_parser) - if framework: - argv.framework = framework - argv.feManager = fem - - ov_update_message = None - ov_api20_message = None - if not hasattr(argv, 'silent') or not argv.silent: - ov_update_message = get_ov_update_message() - ov_api20_message = get_ov_api20_message() - ret_code = driver(argv) - if ov_update_message: + ngraph_function = convert(**argv) + ov_update_message = get_ov_update_message() + ov_api20_message = get_ov_api20_message() + if ov_update_message is not None: print(ov_update_message) - if ov_api20_message and ret_code == 0: + if ov_api20_message is not None and ngraph_function is not None: print(ov_api20_message) - telemetry.send_event('mo', 'conversion_result', 'success') - telemetry.end_session('mo') - telemetry.force_shutdown(1.0) - return ret_code + except (FileNotFoundError, NotADirectoryError) as e: log.error('File {} was not found'.format(str(e).split('No such file or directory:')[1])) log.debug(traceback.format_exc()) @@ -564,14 +72,29 @@ def main(cli_parser: argparse.ArgumentParser, fem: FrontEndManager, framework: s log.error("---------------- END OF BUG REPORT --------------") log.error("-------------------------------------------------") - telemetry.send_event('mo', 'conversion_result', 'fail') - telemetry.end_session('mo') - telemetry.force_shutdown(1.0) - return 1 + if ngraph_function is None: + return 1 + + output_dir = argv['output_dir'] if argv['output_dir'] != '.' else os.getcwd() + model_path_no_ext = os.path.normpath(os.path.join(output_dir, argv['model_name'])) + model_path = model_path_no_ext + '.xml' + + serialize(ngraph_function, model_path.encode('utf-8'), model_path.replace('.xml', '.bin').encode('utf-8')) + + # add meta information to IR + append_ir_info(file=model_path_no_ext, meta_info=get_meta_info(argv)) + + # generate .mapping file + path_to_mapping = model_path_no_ext + ".mapping" + extract_names = argv['framework'] in ['tf', 'mxnet', 'kaldi'] + generate_mapping_file(ngraph_function, path_to_mapping, extract_names) + + print('[ SUCCESS ] Generated IR version {} model.'.format(get_ir_version(argv))) + print('[ SUCCESS ] XML file: {}'.format(model_path)) + print('[ SUCCESS ] BIN file: {}'.format(model_path.replace('.xml', '.bin'))) + return 0 if __name__ == "__main__": from openvino.tools.mo.utils.cli_parser import get_all_cli_parser - - fe_manager = FrontEndManager() - sys.exit(main(get_all_cli_parser(fe_manager), fe_manager, None)) + sys.exit(main(get_all_cli_parser(FrontEndManager()), None)) diff --git a/tools/mo/openvino/tools/mo/main_caffe.py b/tools/mo/openvino/tools/mo/main_caffe.py index 622087f686c..95921fd30c7 100644 --- a/tools/mo/openvino/tools/mo/main_caffe.py +++ b/tools/mo/openvino/tools/mo/main_caffe.py @@ -7,4 +7,4 @@ from openvino.tools.mo.utils.cli_parser import get_caffe_cli_parser if __name__ == "__main__": from openvino.tools.mo.main import main - sys.exit(main(get_caffe_cli_parser(), None, 'caffe')) + sys.exit(main(get_caffe_cli_parser(), 'caffe')) diff --git a/tools/mo/openvino/tools/mo/main_kaldi.py b/tools/mo/openvino/tools/mo/main_kaldi.py index 550e99d819b..b86409e2238 100644 --- a/tools/mo/openvino/tools/mo/main_kaldi.py +++ b/tools/mo/openvino/tools/mo/main_kaldi.py @@ -7,4 +7,4 @@ from openvino.tools.mo.utils.cli_parser import get_kaldi_cli_parser if __name__ == "__main__": from openvino.tools.mo.main import main - sys.exit(main(get_kaldi_cli_parser(), None, 'kaldi')) + sys.exit(main(get_kaldi_cli_parser(), 'kaldi')) diff --git a/tools/mo/openvino/tools/mo/main_mxnet.py b/tools/mo/openvino/tools/mo/main_mxnet.py index 08bb514a3aa..09a665ada56 100644 --- a/tools/mo/openvino/tools/mo/main_mxnet.py +++ b/tools/mo/openvino/tools/mo/main_mxnet.py @@ -7,4 +7,4 @@ from openvino.tools.mo.utils.cli_parser import get_mxnet_cli_parser if __name__ == "__main__": from openvino.tools.mo.main import main - sys.exit(main(get_mxnet_cli_parser(), None, 'mxnet')) + sys.exit(main(get_mxnet_cli_parser(), 'mxnet')) diff --git a/tools/mo/openvino/tools/mo/main_onnx.py b/tools/mo/openvino/tools/mo/main_onnx.py index b661b6d7bdf..7cbf426165f 100644 --- a/tools/mo/openvino/tools/mo/main_onnx.py +++ b/tools/mo/openvino/tools/mo/main_onnx.py @@ -7,6 +7,5 @@ from openvino.tools.mo.utils.cli_parser import get_onnx_cli_parser if __name__ == "__main__": from openvino.tools.mo.main import main - from openvino.frontend import FrontEndManager # pylint: disable=no-name-in-module,import-error - sys.exit(main(get_onnx_cli_parser(), FrontEndManager(), 'onnx')) + sys.exit(main(get_onnx_cli_parser(), 'onnx')) diff --git a/tools/mo/openvino/tools/mo/main_paddle.py b/tools/mo/openvino/tools/mo/main_paddle.py index 09c667488d9..40160917655 100644 --- a/tools/mo/openvino/tools/mo/main_paddle.py +++ b/tools/mo/openvino/tools/mo/main_paddle.py @@ -10,5 +10,4 @@ from openvino.frontend import FrontEndManager # pylint: disable=no-name-in-modu if __name__ == "__main__": from openvino.tools.mo.main import main - fem = FrontEndManager() - sys.exit(main(get_all_cli_parser(fem), fem, 'paddle')) + sys.exit(main(get_all_cli_parser(FrontEndManager()), 'paddle')) diff --git a/tools/mo/openvino/tools/mo/main_tf.py b/tools/mo/openvino/tools/mo/main_tf.py index 5f7e4692cab..22b879849ff 100644 --- a/tools/mo/openvino/tools/mo/main_tf.py +++ b/tools/mo/openvino/tools/mo/main_tf.py @@ -7,4 +7,4 @@ from openvino.tools.mo.utils.cli_parser import get_tf_cli_parser if __name__ == "__main__": from openvino.tools.mo.main import main - sys.exit(main(get_tf_cli_parser(), None, 'tf')) + sys.exit(main(get_tf_cli_parser(), 'tf')) diff --git a/tools/mo/openvino/tools/mo/middle/PartialInfer.py b/tools/mo/openvino/tools/mo/middle/PartialInfer.py index 30fac68e988..b47b73878b0 100644 --- a/tools/mo/openvino/tools/mo/middle/PartialInfer.py +++ b/tools/mo/openvino/tools/mo/middle/PartialInfer.py @@ -27,15 +27,4 @@ class PartialInfer(MiddleReplacementPattern): if not is_fully_defined(param_shape): parameter_name = parameter.soft_get('name', parameter.id) dynamic_inputs[parameter_name] = param_shape - if dynamic_inputs: - log.error('The model contains input(s) with partially defined shapes: {}. ' - 'Starting from the 2022.1 release the Model Optimizer can generate an IR with partially defined ' - 'input shapes ("-1" dimension in the TensorFlow model or dimension with string value in the ONNX ' - 'model). Some of the OpenVINO plugins require model input shapes to be static, so you should ' - 'call "reshape" method in the Inference Engine and specify static input shapes. For optimal ' - 'performance, it is still recommended to update input shapes with fixed ones using "--input" or ' - '"--input_shape" command-line parameters.' - .format(','.join(f'name="{name}" shape="{unmask_shape(param_shape)}"' - for name, param_shape in dynamic_inputs.items())), - extra={'is_warning': True}) partial_infer(graph) diff --git a/tools/mo/openvino/tools/mo/moc_frontend/check_config.py b/tools/mo/openvino/tools/mo/moc_frontend/check_config.py index 56fe627c84f..2045a4133d2 100644 --- a/tools/mo/openvino/tools/mo/moc_frontend/check_config.py +++ b/tools/mo/openvino/tools/mo/moc_frontend/check_config.py @@ -10,17 +10,38 @@ from openvino.tools.mo.utils.error import Error def any_extensions_used(argv: argparse.Namespace): - return hasattr(argv, 'extensions') and argv.extensions is not None and len(argv.extensions) > 0 \ - and argv.extensions != import_extensions.default_path() # extensions arg has default value + # Checks that extensions are provided. + # Allowed types are string containing path to legacy extension directory + # or path to new extension .so file, or classes inherited from BaseExtension. + if not hasattr(argv, 'extensions') or argv.extensions is None: + return False + + if isinstance(argv.extensions, list) and len(argv.extensions) > 0: + has_non_default_path = False + has_non_str_objects = False + for ext in argv.extensions: + if not isinstance(ext, str): + has_non_str_objects = True + continue + if len(ext) == 0 or ext == import_extensions.default_path(): + continue + has_non_default_path = True + + return has_non_default_path or has_non_str_objects + + raise Exception("Expected list of extensions, got {}.".format(type(argv.extensions))) def legacy_extensions_used(argv: argparse.Namespace): if any_extensions_used(argv): - extensions = argv.extensions.split(',') + extensions = argv.extensions legacy_ext_counter = 0 for extension in extensions: - path = Path(extension) - if not path.is_file(): + if not isinstance(extension, str): + continue + if extension == import_extensions.default_path(): + continue + if not Path(extension).is_file(): legacy_ext_counter += 1 if legacy_ext_counter == len(extensions): return True # provided only legacy extensions @@ -33,11 +54,16 @@ def legacy_extensions_used(argv: argparse.Namespace): def new_extensions_used(argv: argparse.Namespace): if any_extensions_used(argv): - extensions = argv.extensions.split(',') + extensions = argv.extensions + if not isinstance(extensions, list): + extensions = [extensions] new_ext_counter = 0 - for extension in argv.extensions.split(','): - path = Path(extension) - if path.is_file() and (path.suffix == '.so' or path.suffix == '.dll'): + for extension in extensions: + if isinstance(extension, str): + path = Path(extension) + if path.is_file() and (path.suffix == '.so' or path.suffix == '.dll'): + new_ext_counter += 1 + else: new_ext_counter += 1 if new_ext_counter == len(extensions): return True # provided only new extensions @@ -71,9 +97,10 @@ def is_new_json_config(json_file_path: str): def get_transformations_config_path(argv: argparse.Namespace) -> Path: if hasattr(argv, 'transformations_config') \ and argv.transformations_config is not None and len(argv.transformations_config): - path = Path(argv.transformations_config) - if path.is_file(): - return path + if isinstance(argv.transformations_config, str): + path = Path(argv.transformations_config) + if path.is_file(): + return path return None @@ -81,6 +108,11 @@ def new_transformations_config_used(argv: argparse.Namespace): path = get_transformations_config_path(argv) if path != None: return is_new_json_config(path) + + if hasattr(argv, 'transformations_config') \ + and argv.transformations_config is not None and not isinstance(argv.transformations_config, str): + return True + return False diff --git a/tools/mo/openvino/tools/mo/moc_frontend/serialize.py b/tools/mo/openvino/tools/mo/moc_frontend/serialize.py index 40b3fba0176..2ef016129ee 100644 --- a/tools/mo/openvino/tools/mo/moc_frontend/serialize.py +++ b/tools/mo/openvino/tools/mo/moc_frontend/serialize.py @@ -38,26 +38,5 @@ def moc_emit_ir(ngraph_function: Model, argv: argparse.Namespace): from openvino.tools.mo.back.offline_transformations import compress_model compress_model(ngraph_function) - orig_model_name = os.path.normpath(os.path.join(output_dir, argv.model_name)) - - from openvino.runtime import serialize # pylint: disable=import-error,no-name-in-module - from openvino.offline_transformations import generate_mapping_file # pylint: disable=import-error,no-name-in-module - serialize(ngraph_function, (orig_model_name + ".xml").encode('utf-8'), (orig_model_name + ".bin").encode('utf-8')) - del argv.feManager - - path_to_mapping = orig_model_name + ".mapping" - extract_names = argv.framework in ['tf', 'mxnet', 'kaldi'] - generate_mapping_file(ngraph_function, path_to_mapping.encode('utf-8'), extract_names) - - # add meta information to IR - append_ir_info(file=orig_model_name, - meta_info=get_meta_info(argv), - mean_data=None, - input_names=None, - legacy_path=False) - - print('[ SUCCESS ] Generated IR version {} model.'.format(get_ir_version(argv))) - print('[ SUCCESS ] XML file: {}.xml'.format(orig_model_name)) - print('[ SUCCESS ] BIN file: {}.bin'.format(orig_model_name)) - return 0 + return ngraph_function diff --git a/tools/mo/openvino/tools/mo/pipeline/common.py b/tools/mo/openvino/tools/mo/pipeline/common.py index aa2f70717ae..5276a7440c9 100644 --- a/tools/mo/openvino/tools/mo/pipeline/common.py +++ b/tools/mo/openvino/tools/mo/pipeline/common.py @@ -7,7 +7,6 @@ import os from operator import itemgetter import networkx as nx - from openvino.tools.mo.back.RemoveUselessConvert import RemoveUselessConvert from openvino.tools.mo.back.ResultRename import ResultRename from openvino.tools.mo.back.ie_ir_ver_2.emitter import port_renumber, serialize_constants, generate_ie_ir, \ @@ -16,11 +15,9 @@ from openvino.tools.mo.back.op_versioning import OpVersioning from openvino.tools.mo.graph.graph import Node, Graph from openvino.tools.mo.middle.passes import tensor_names, convert_data_type from openvino.tools.mo.middle.passes.convert_data_type import data_type_str_to_np -from openvino.tools.mo.middle.passes.eliminate import shape_inference from openvino.tools.mo.middle.passes.infer import type_infer from openvino.tools.mo.middle.pattern_match import for_graph_and_each_sub_graph_recursively from openvino.tools.mo.ops.Cast import Cast -from openvino.tools.mo.ops.op import Op from openvino.tools.mo.utils.error import Error diff --git a/tools/mo/openvino/tools/mo/utils/cli_parser.py b/tools/mo/openvino/tools/mo/utils/cli_parser.py index a438034b1f1..dabadeb276d 100644 --- a/tools/mo/openvino/tools/mo/utils/cli_parser.py +++ b/tools/mo/openvino/tools/mo/utils/cli_parser.py @@ -6,22 +6,688 @@ import ast import logging as log import os import re -from collections import OrderedDict +from collections import OrderedDict, namedtuple from distutils.util import strtobool from itertools import zip_longest +from pathlib import Path from operator import xor from typing import List, Union +import numbers import numpy as np +from openvino.runtime import Layout, PartialShape, Dimension, Shape, Type +import openvino from openvino.tools.mo.front.extractor import split_node_in_port from openvino.tools.mo.middle.passes.convert_data_type import destination_type_to_np_data_type +from openvino.tools.mo.middle.passes.convert_data_type import np_data_type_to_destination_type from openvino.tools.mo.utils import import_extensions from openvino.tools.mo.utils.error import Error from openvino.tools.mo.utils.utils import refer_to_faq_msg, get_mo_root_dir from openvino.tools.mo.utils.version import get_version +def extension_path_to_str_or_extensions_class(extension): + if isinstance(extension, str): + return extension + elif isinstance(extension, Path): + return str(extension) + else: + # Return unknown object as is. + # The type of the object will be checked by frontend.add_extension() method + return extension + + +def transformations_config_to_str(value): + if value is None: + return value + return extension_path_to_str_or_extensions_class(value) + + +def extensions_to_str_or_extensions_class(extensions): + if extensions is None: + return [import_extensions.default_path()] + extensions_list = [] + if isinstance(extensions, str): + extensions_list = extensions.split(',') + elif isinstance(extensions, list): + for ext in extensions: + ext = extension_path_to_str_or_extensions_class(ext) + extensions_list.append(ext) + else: + extensions_list = [extension_path_to_str_or_extensions_class(extensions)] + + for ext in extensions_list: + if isinstance(ext, str): + readable_file_or_dir(ext) + return extensions_list + + +def path_to_str(path): + if path is None: + return None + if isinstance(path, str): + return path + elif isinstance(path, Path): + return str(path) + else: + raise Exception("Incorrect type of {} expected str or Path, got {}".format(path, type(path))) + + +def paths_to_str(paths): + if paths is None: + return None + if isinstance(paths, list): + paths_str = [] + for path in paths: + paths_str.append(path_to_str(path)) + return ','.join(paths_str) + else: + path_to_str(paths) + + +def str_list_to_str(values): + if values is None: + return None + if isinstance(values, str): + return values + elif isinstance(values, list): + for value in values: + if not isinstance(value, str): + raise Error("Incorrect argument. {} expected to string, got type {}.".format(value, type(value))) + return ','.join(values) + else: + raise Error("Incorrect argument. {} expected to string or list of strings, got type {}.".format(values, type(values))) + + +def dimension_to_str(dim: Dimension): + # TODO: replace this code with Dimension to string conversion method from openvino.runtime when 69092 is done + if dim.is_static: + return str(dim.get_length()) + if dim.get_min_length() > 0: + dim_str = str(dim.get_min_length()) + ".." + if dim.get_max_length() != -1: + dim_str += str(dim.get_max_length()) + return dim_str + elif dim.get_max_length() != -1: + return ".." + str(dim.get_max_length()) + else: + return "?" + + +def partial_shape_to_str(shape: PartialShape, separator: str): + # TODO: replace this code with PartialShape to string conversion method from openvino.runtime when 69092 is done + if shape.rank.is_dynamic: + return "[...]" + dims = [] + for i in range(shape.rank.get_length()): + dims.append(dimension_to_str(shape.get_dimension(i))) + + return "[" + separator.join(dims) + "]" + + +def is_shape_type(value): + if isinstance(value, PartialShape): + return True + if isinstance(value, Shape): + return True + if isinstance(value, list) or isinstance(value, tuple): + for dim in value: + if not (isinstance(dim, Dimension) or isinstance(dim, int)): + return False + return True + return False + + +def shape_to_str(shape, separator): + if isinstance(shape, str): + return shape + if isinstance(shape, PartialShape): + return partial_shape_to_str(shape, separator) + if isinstance(shape, Shape): + return partial_shape_to_str(PartialShape(shape), separator) + if isinstance(shape, list) or isinstance(shape, tuple): + dims = [] + for dim in shape: + if isinstance(dim, Dimension): + dims.append(dimension_to_str(dim)) + elif isinstance(dim, int): + dims.append(str(dim)) + else: + raise Exception("Incorrect type of dimension. Expected Dimension or int, got {}".format(type(dim))) + return "[" + separator.join(dims) + "]" + raise Exception("Incorrect shape type. Expected PartialShape, Shape, [Dimension, ...] or [int, ...], " + "got {}".format(type(shape))) + + +def input_shape_to_str(input_shape): + if input_shape is None or isinstance(input_shape, str): + return input_shape + if isinstance(input_shape, list): + if len(input_shape) > 0 and isinstance(input_shape[0], int) or isinstance(input_shape[0], Dimension): + # The case when shape is specified as list of int or Dimension + return shape_to_str(input_shape, ',') + # The case when list of shapes is specified + shapes = [] + for shape in input_shape: + shapes.append(shape_to_str(shape, ',')) + return ','.join(shapes) + return shape_to_str(input_shape, ',') + + +def type_to_str(type_obj): + if isinstance(type_obj, str): + return type_obj + if isinstance(type_obj, type): + return np_data_type_to_destination_type(type_obj) + if isinstance(type_obj, Type): + return type_obj.get_type_name() + raise Exception("Incorrect type. Expected Type or numpy type, got {}".format(type(type_obj))) + + +def value_to_str(value, separator): + if isinstance(value, np.ndarray): + values = [] + for x in np.nditer(value): + values.append(str(x)) + return "[" + separator.join(values) + "]" + if isinstance(value, list): + values = [] + for x in value: + if not isinstance(x, numbers.Number): + raise Exception("Incorrect value type. Expected numeric value, got {}".format(type(x))) + values.append(str(x)) + return "[" + separator.join(values) + "]" + if isinstance(value, bool): + return "True" if value else "False" + raise Exception("Incorrect value type. Expected np.ndarray or list, got {}".format(type(value))) + + +def single_input_to_str(input): + if isinstance(input, str): + return input + if isinstance(input, openvino.tools.mo.InputCutInfo): + if not isinstance(input.name, str): + raise Exception("Input name should be string, got {}".format(input.name)) + input_str = input.name + assert input_str is not None, "Incorrect InputCutInfo. 'name' should be set." + if input.shape is not None: + input_str += shape_to_str(input.shape, " ") + if input.type is not None: + input_str += "{" + type_to_str(input.type) + "}" + if input.value is not None: + input_str += "->" + value_to_str(input.value, " ") + return input_str + if isinstance(input, tuple): + name = None + inp_type = None + shape = None + for val in input: + if isinstance(val, str): + if name is not None: + raise Exception("More than one input name provided: {}".format(input)) + name = val + elif isinstance(val, type) or isinstance(val, Type): + if inp_type is not None: + raise Exception("More than one input type provided: {}".format(input)) + inp_type = type_to_str(val) + elif is_shape_type(val): + if shape is not None: + raise Exception("More than one input shape provided: {}".format(input)) + shape = shape_to_str(val, " ") + else: + raise Exception("Incorrect input parameters provided. Expected input name and " + "optionally input type or input shape. Got unknown object: {}".format(val)) + if name is None: + raise Exception("Input name was not provided for following input {}.".format(input)) + if shape is not None: + name += shape + if inp_type is not None: + name += "{" + inp_type + "}" + return name + + raise Exception("Unexpected object provided for input. Expected openvino.tools.mo.InputCutInfo " + "or tuple or str. Got {}".format(type(input))) + + +def input_to_str(input): + if input is None or isinstance(input, str): + return input + if isinstance(input, list): + inputs_str = [] + for inp in input: + inputs_str.append(single_input_to_str(inp)) + return ','.join(inputs_str) + return single_input_to_str(input) + + +def mean_scale_value_to_str(value): + # default empty value + if isinstance(value, tuple) and len(value) == 0: + return value + + if isinstance(value, str): + return value + if isinstance(value, dict): + values_str = [] + for op_name, val in value.items(): + if not isinstance(op_name, str): + raise Exception("Incorrect operation name type. Expected string, got {}".format(type(op_name))) + values_str.append(op_name + value_to_str(val, ",")) + return ",".join(values_str) + if isinstance(value, list) or isinstance(value, tuple): + list_of_lists = False + for val in value: + if isinstance(val, list) or isinstance(val, tuple): + list_of_lists = True + break + if list_of_lists: + values_str = [] + for val in value: + values_str.append(value_to_str(val, ",")) + return ",".join(values_str) + else: + return value_to_str(value, ",") + return value_to_str(value, ",") + + +def layout_to_str(layout): + if isinstance(layout, str): + return layout + if isinstance(layout, Layout): + return layout.to_string() + raise Exception("Incorrect layout type. Expected Layout or string or dictionary, " + "where key is operation name and value is Layout, got {}".format(type(layout))) + + +def source_target_layout_to_str(value): + # default empty value + if isinstance(value, tuple) and len(value) == 0: + return value + + if isinstance(value, str): + return value + if isinstance(value, dict): + values_str = [] + for op_name, layout in value.items(): + if not isinstance(op_name, str): + raise Exception("Incorrect operation name type. Expected string, got {}".format(type(op_name))) + values_str.append(op_name + "(" + layout_to_str(layout) + ")") + return ",".join(values_str) + + return layout_to_str(value) + + +def layoutmap_to_str(value): + if isinstance(value, str): + return value + if isinstance(value, openvino.tools.mo.LayoutMap): + assert value.source_layout is not None, "Incorrect layout map. 'source_layout' should be set." + source_layout = layout_to_str(value.source_layout) + if value.target_layout is not None: + target_layout = layout_to_str(value.target_layout) + source_layout += "->" + target_layout + return source_layout + return layout_to_str(value) + + +def layout_param_to_str(value): + # default empty value + if isinstance(value, tuple) and len(value) == 0: + return value + + if isinstance(value, str): + return value + + if isinstance(value, dict): + values_str = [] + for op_name, layout in value.items(): + if not isinstance(op_name, str): + raise Exception("Incorrect operation name type. Expected string, got {}".format(type(op_name))) + values_str.append(op_name + "(" + layoutmap_to_str(layout) + ")") + return ",".join(values_str) + + return layoutmap_to_str(value) + + +def batch_to_int(value): + if value is None or isinstance(value, int): + return value + if isinstance(value, Dimension): + if not value.is_static: + # TODO: Ticket 88676 + raise Exception("Dynamic batch for --batch parameter is not supported.") + else: + return value.get_length() + raise Exception("Incorrect batch value. Expected int, got {}.".format(type(value))) + + +def transform_param_value_to_str(value): + # This function supports parsing of parameters of MakeStateful, LowLatency2, Pruning. + # If available transforms list is extended this method should be extended for new transforms. + if isinstance(value, str): + return value + if isinstance(value, bool): + return str(value) + if isinstance(value, dict): + # param_res_names dictionary for MakeStateful transform + values_str = [] + for input_name, output_name in value.items(): + assert isinstance(input_name, str), "Incorrect input name. " \ + "Expected string, got {}".format(type(input_name)) + assert isinstance(output_name, str), "Incorrect output name. " \ + "Expected string, got {}".format(type(output_name)) + values_str.append("\'{}\':\'{}\'".format(input_name, output_name)) + return "{" + ','.join(values_str) + "}" + raise Exception("Unknown parameter type.") + + +def transform_to_str(value): + from openvino.tools.mo.back.offline_transformations import get_available_transformations + + if isinstance(value, str): + return value + + if isinstance(value, tuple): + assert 1 <= len(value) <= 2, "Incorrect definition of transformation in transform argument: " \ + "expected two elements in tuple, provided {}. " \ + "Supported transforms are: {}".format( + len(value), + list(get_available_transformations().keys())) + transform_name = value[0] + assert isinstance(transform_name, str), "Incorrect transform name type. " \ + "Expected string, got {}".format(type(transform_name)) + if len(value) == 2: + params = value[1] + assert isinstance(params, dict), "Incorrect transform params type. " \ + "Expected dictionary, got {}".format(type(params)) + params_str_list = [] + for param_name, val in params.items(): + assert isinstance(param_name, str), "Incorrect transform parameter name type. " \ + "Expected string, got {}".format(type(param_name)) + val_str = transform_param_value_to_str(val) + params_str_list.append(param_name + "=" + val_str) + transform_name += '[' + ','.join(params_str_list) + ']' + return transform_name + raise Exception("Incorrect transform type. Expected tuple with transform name and " + "dictionary with transform parameters. Got object of type {}".format(type(value))) + + +def transform_param_to_str(value): + if value is None or isinstance(value, str): + return value + if isinstance(value, list): + transforms_str = [] + for transform in value: + transforms_str.append(transform_to_str(transform)) + return ','.join(transforms_str) + return transform_to_str(value) + + +ParamDescription = namedtuple("ParamData", + ["description", "possible_types_command_line", "possible_types_python_api", "to_string"]) +mo_convert_params = { + 'input_model': ParamDescription( + 'Tensorflow*: a file with a pre-trained model ' + + ' (binary or text .pb file after freezing).\n' + + ' Caffe*: a model proto file with model weights', '', '', + path_to_str), + 'framework': ParamDescription( + 'Name of the framework used to train the input model.', '', '', None), + 'model_name': ParamDescription( + 'Model_name parameter passed to the final create_ir transform. ' + + 'This parameter is used to name ' + + 'a network in a generated IR and output .xml/.bin files.', '', '', None), + 'input_shape': ParamDescription( + 'Input shape(s) that should be fed to an input node(s) of the model. {}' + 'Shape is defined as a comma-separated list of integer numbers enclosed in ' + 'parentheses or square brackets, for example [1,3,227,227] or (1,227,227,3), where ' + 'the order of dimensions depends on the framework input layout of the model. ' + 'For example, [N,C,H,W] is used for ONNX* models and [N,H,W,C] for TensorFlow* ' + 'models. The shape can contain undefined dimensions (? or -1) and ' + 'should fit the dimensions defined in the input ' + 'operation of the graph. Boundaries of undefined dimension can be specified with ' + 'ellipsis, for example [1,1..10,128,128]. One boundary can be undefined, for ' + 'example [1,..100] or [1,3,1..,1..]. If there are multiple inputs in the model, ' + '--input_shape should contain definition of shape for each input separated by a ' + 'comma, for example: [1,3,227,227],[2,4] for a model with two inputs with 4D and 2D ' + 'shapes. Alternatively, specify shapes with the --input option.', '', + 'Input shapes can be defined by passing a list of objects of type ' + 'PartialShape, Shape, [Dimension, ...] or [int, ...] or by a string ' + 'of the following format. ', input_shape_to_str), + 'scale': ParamDescription( + 'All input values coming from original network inputs will be ' + + 'divided by this ' + + 'value. When a list of inputs is overridden by the --input ' + + 'parameter, this scale ' + + 'is not applied for any input that does not match with ' + + 'the original input of the model.' + + 'If both --mean_values and --scale are specified, ' + + 'the mean is subtracted first and then scale is applied ' + + 'regardless of the order of options in command line.', '', '', None), + 'reverse_input_channels': ParamDescription( + 'Switch the input channels order from RGB to BGR (or vice versa). Applied to ' + 'original inputs of the model if and only if a number of channels equals 3. ' + 'When --mean_values/--scale_values are also specified, reversing of channels will ' + 'be applied to user\'s input data first, so that numbers in --mean_values ' + 'and --scale_values go in the order of channels used in the original model. ' + 'In other words, if both options are specified, then the data flow in the model ' + 'looks as following: ' + 'Parameter -> ReverseInputChannels -> Mean apply-> Scale apply -> the original body of the model.', + '', '', None), + 'log_level': ParamDescription( + 'Logger level', '', '', None), + 'input': ParamDescription( + '{}Quoted list of comma-separated input nodes names with shapes, data types, ' + 'and values for freezing. The order of inputs in converted model is the same as ' + 'order of specified operation names. The shape and value are specified as space-separated ' + 'lists. The data type of input node is specified in braces and ' + 'can have one of the values: f64 (float64), f32 (float32), f16 (float16), ' + 'i64 (int64), i32 (int32), u8 (uint8), boolean (bool). Data type is optional. ' + 'If it\'s not specified explicitly then there are two options: ' + 'if input node is a parameter, data type is taken from the original node dtype, ' + 'if input node is not a parameter, data type is set to f32. ' + 'Example, to set `input_1` with shape [1 100], and Parameter node `sequence_len` ' + 'with scalar input with value `150`, and boolean input `is_training` with ' + '`False` value use the following format: ' + '"input_1[1 10],sequence_len->150,is_training->False". ' + 'Another example, use the following format to set input port 0 of the node ' + '`node_name1` with the shape [3 4] as an input node and freeze output port 1 ' + 'of the node `node_name2` with the value [20 15] of the int32 type and shape [2]: ' + '"0:node_name1[3 4],node_name2:1[2]{{i32}}->[20 15]".', '', + 'Input can be set by passing a list of InputCutInfo objects or by a list of tuples. ' + 'Each tuple should contain input name and optionally input type or input shape. ' + 'Example: input=("op_name", PartialShape([-1, 3, 100, 100]), Type(np.float32)). ' + 'Alternatively input can be set by a string or list of strings of the following format. ', + input_to_str), + 'output': ParamDescription( + 'The name of the output operation of the model or list of names. ' + + 'For TensorFlow*, do not add :0 to this name.' + 'The order of outputs in converted model is the same as order of ' + 'specified operation names.', '', '', str_list_to_str), + 'mean_values': ParamDescription( + 'Mean values to be used for the input image per channel. {}' + + 'Values to be provided in the (R,G,B) or [R,G,B] format. ' + + 'Can be defined for desired input of the model, for example: ' + + '"--mean_values data[255,255,255],info[255,255,255]". ' + + 'The exact meaning and order ' + + 'of channels depend on how the original model was trained.', '', + 'Mean values can be set by passing a dictionary, ' + 'where key is input name and value is mean value. ' + 'For example mean_values={\'data\':[255,255,255],\'info\':[255,255,255]}. ' + 'Or mean values can be set by a string of the following format. ', + mean_scale_value_to_str), + 'scale_values': ParamDescription( + 'Scale values to be used for the input image per channel. {}' + + 'Values are provided in the (R,G,B) or [R,G,B] format. ' + + 'Can be defined for desired input of the model, for example: ' + + '"--scale_values data[255,255,255],info[255,255,255]". ' + + 'The exact meaning and order ' + + 'of channels depend on how the original model was trained.' + + 'If both --mean_values and --scale_values are specified, ' + + 'the mean is subtracted first and then scale is applied ' + + 'regardless of the order of options in command line.', '', + 'Scale values can be set by passing a dictionary, ' + 'where key is input name and value is scale value. ' + 'For example scale_values={\'data\':[255,255,255],\'info\':[255,255,255]}. ' + 'Or scale values can be set by a string of the following format. ', + mean_scale_value_to_str), + 'source_layout': ParamDescription( + 'Layout of the input or output of the model in the framework. {}Layout can' + ' be specified in the short form, e.g. nhwc, or in complex form, e.g. "[n,h,w,c]".' + ' Example for many names: ' + '"in_name1([n,h,w,c]),in_name2(nc),out_name1(n),out_name2(nc)". Layout can be ' + 'partially defined, "?" can be used to specify undefined layout for one dimension, ' + '"..." can be used to specify undefined layout for multiple dimensions, for example ' + '"?c??", "nc...", "n...c", etc.', '', + 'Layout can be set by passing a dictionary, where key is input name and value is ' + 'LayoutMap object. Or layout can be set by string of the following format. ', + source_target_layout_to_str), + 'target_layout': ParamDescription( + 'Same as --source_layout, but specifies target layout that will be in the model ' + 'after processing by ModelOptimizer.', '', '', source_target_layout_to_str), + 'layout': ParamDescription( + 'Combination of --source_layout and --target_layout. Can\'t be used with either of ' + 'them. If model has one input it is sufficient to specify layout of this input, for' + ' example --layout nhwc. To specify layouts of many tensors, names must be provided,' + ' for example: --layout "name1(nchw),name2(nc)". It is possible to instruct ' + 'ModelOptimizer to change layout, for example: ' + '--layout "name1(nhwc->nchw),name2(cn->nc)". Also "*" in long layout form can be' + ' used to fuse dimensions, for example "[n,c,...]->[n*c,...]".', '', '', layout_param_to_str), + 'data_type': ParamDescription( + 'Data type for all intermediate tensors and weights. ' + + 'If original model is in FP32 and --data_type=FP16 is specified, all model weights ' + + 'and biases are compressed to FP16.', '', '', None), + 'transform': ParamDescription( + 'Apply additional transformations. {}' + + '"--transform transformation_name1[args],transformation_name2..." ' + + 'where [args] is key=value pairs separated by semicolon. ' + + 'Examples: "--transform LowLatency2" or ' + + ' "--transform Pruning" or ' + + ' "--transform LowLatency2[use_const_initializer=False]" or ' + + ' "--transform \"MakeStateful[param_res_names=' + '{{\'input_name_1\':\'output_name_1\',\'input_name_2\':\'output_name_2\'}}]\"" ' + + 'Available transformations: "LowLatency2", "MakeStateful", "Pruning"', 'Usage: ', + '\'transform\' can be set by a list of tuples, where the first element is ' + 'transform name and the second element is transform parameters. ' + 'For example: [(\'LowLatency2\', {{\'use_const_initializer\': False}}), ...]', + transform_param_to_str), + 'extensions': ParamDescription( + "{} For the legacy MO path (if `--use_legacy_frontend` is used), " + "a directory or a comma-separated list of directories with extensions are supported. " + "To disable all extensions including those that are placed at the default location, " + "pass an empty string.", + "Paths or a comma-separated list of paths to libraries (.so or .dll) with extensions.", + "Paths to libraries (.so or .dll) with extensions, comma-separated list of paths, " + "objects derived from BaseExtension class or lists of objects.", + extensions_to_str_or_extensions_class), + 'batch': ParamDescription( + 'Input batch size', '', '', batch_to_int), + 'silent': ParamDescription( + 'Prevent any output messages except those that correspond to log level equals ' + 'ERROR, that can be set with the following option: --log_level. ' + 'By default, log level is already ERROR. ', '', '', None), + 'version': ParamDescription( + "Version of Model Optimizer", '', '', None + ), + 'static_shape': ParamDescription( + 'Enables IR generation for fixed input shape (folding `ShapeOf` operations and ' + 'shape-calculating sub-graphs to `Constant`). Changing model input shape using ' + 'the OpenVINO Runtime API in runtime may fail for such an IR.', '', '', None), + 'progress': ParamDescription( + 'Enable model conversion progress display.', '', '', None), + 'stream_output': ParamDescription( + 'Switch model conversion progress display to a multiline mode.', '', '', None), + 'transformations_config': ParamDescription( + 'Use the configuration file with transformations ' + 'description{}. Transformations file can be specified as relative path ' + 'from the current directory, as absolute path or as a' + 'relative path from the mo root directory.', '', + ' or pass object derived from BaseExtension class.', + transformations_config_to_str), + 'use_new_frontend': ParamDescription( + 'Force the usage of new Frontend of Model Optimizer for model conversion into IR. ' + 'The new Frontend is C++ based and is available for ONNX* and PaddlePaddle* models. ' + 'Model optimizer uses new Frontend for ONNX* and PaddlePaddle* by default that means ' + '`--use_new_frontend` and `--use_legacy_frontend` options are not specified.', '', '', None), + 'use_legacy_frontend': ParamDescription( + 'Force the usage of legacy Frontend of Model Optimizer for model conversion into IR. ' + 'The legacy Frontend is Python based and is available for TensorFlow*, ONNX*, MXNet*, ' + 'Caffe*, and Kaldi* models.', '', '', None), + 'disable_omitting_optional': ParamDescription( + 'Disable omitting optional attributes to be used for custom layers. ' + + 'Use this option if you want to transfer all attributes of a custom layer to IR. ' + + 'Default behavior is to transfer the attributes with default values ' + 'and the attributes defined by the user to IR.', + '', '', None), + 'enable_flattening_nested_params': ParamDescription( + 'Enable flattening optional params to be used for custom layers. ' + + 'Use this option if you want to transfer attributes of a custom layer to IR with flattened nested parameters. ' + + 'Default behavior is to transfer the attributes without flattening nested parameters.', '', '', None), + 'input_model_is_text': ParamDescription( + 'TensorFlow*: treat the input model file as a text protobuf format. If not specified, ' + + 'the Model Optimizer treats it as a binary file by default.', '', '', None), + 'input_checkpoint': ParamDescription( + 'TensorFlow*: variables file to load.', '', '', path_to_str), + 'input_meta_graph': ParamDescription( + 'Tensorflow*: a file with a meta-graph of the model before freezing', '', '', + path_to_str), + 'saved_model_dir': ParamDescription( + 'TensorFlow*: directory with a model in SavedModel format ' + 'of TensorFlow 1.x or 2.x version.', '', '', path_to_str), + 'saved_model_tags': ParamDescription( + "Group of tag(s) of the MetaGraphDef to load, in string format, separated by ','. " + "For tag-set contains multiple tags, all tags must be passed in.", '', '', str_list_to_str), + 'tensorflow_custom_operations_config_update': ParamDescription( + 'TensorFlow*: update the configuration file with node name patterns with input/output ' + 'nodes information.', '', '', path_to_str), + 'tensorflow_object_detection_api_pipeline_config': ParamDescription( + 'TensorFlow*: path to the pipeline configuration file used to generate model created ' + 'with help of Object Detection API.', '', '', path_to_str), + 'tensorboard_logdir': ParamDescription( + 'TensorFlow*: dump the input graph to a given directory that should be used with TensorBoard.', '', '', + path_to_str), + 'tensorflow_custom_layer_libraries': ParamDescription( + 'TensorFlow*: comma separated list of shared libraries with TensorFlow* custom ' + 'operations implementation.', '', '', path_to_str), + 'input_proto': ParamDescription( + 'Deploy-ready prototxt file that contains a topology structure ' + + 'and layer attributes', '', '', path_to_str), + 'caffe_parser_path': ParamDescription( + 'Path to Python Caffe* parser generated from caffe.proto', '', '', + path_to_str), + 'k': ParamDescription( + 'Path to CustomLayersMapping.xml to register custom layers', '', '', path_to_str), + 'input_symbol': ParamDescription( + 'Symbol file (for example, model-symbol.json) that contains a topology structure ' + + 'and layer attributes', '', '', path_to_str), + 'nd_prefix_name': ParamDescription( + "Prefix name for args.nd and argx.nd files.", '', '', None), + 'pretrained_model_name': ParamDescription( + "Name of a pretrained MXNet model without extension and epoch number. " + "This model will be merged with args.nd and argx.nd files", + '', '', None), + 'save_params_from_nd': ParamDescription( + "Enable saving built parameters file from .nd files", '', '', None), + 'legacy_mxnet_model': ParamDescription( + "Enable MXNet loader to make a model compatible with the latest MXNet version. " + "Use only if your model was trained with MXNet version lower than 1.0.0", + '', '', None), + 'enable_ssd_gluoncv': ParamDescription( + "Enable pattern matchers replacers for converting gluoncv ssd topologies.", + '', '', None), + 'counts': ParamDescription( + "Path to the counts file", '', '', path_to_str), + 'remove_output_softmax': ParamDescription( + "Removes the SoftMax layer that is the output layer", '', '', None), + 'remove_memory': ParamDescription( + "Removes the Memory layer and use additional inputs outputs instead", '', '', + None), + 'help': ParamDescription( + 'Print available parameters.', '', '', None), +} + + class DeprecatedStoreTrue(argparse.Action): def __init__(self, nargs=0, **kw): super().__init__(nargs=nargs, **kw) @@ -253,9 +919,7 @@ def get_common_cli_parser(parser: argparse.ArgumentParser = None): common_group = parser.add_argument_group('Framework-agnostic parameters') # Common parameters common_group.add_argument('--input_model', '-w', '-m', - help='Tensorflow*: a file with a pre-trained model ' + - ' (binary or text .pb file after freezing).\n' + - ' Caffe*: a model proto file with model weights', + help=mo_convert_params['input_model'].description, action=CanonicalizePathCheckExistenceAction, type=readable_file_or_dir) common_group.add_argument('--model_name', '-n', @@ -269,18 +933,8 @@ def get_common_cli_parser(parser: argparse.ArgumentParser = None): action=CanonicalizePathAction, type=writable_dir) common_group.add_argument('--input_shape', - help='Input shape(s) that should be fed to an input node(s) of the model. ' - 'Shape is defined as a comma-separated list of integer numbers enclosed in ' - 'parentheses or square brackets, for example [1,3,227,227] or (1,227,227,3), where ' - 'the order of dimensions depends on the framework input layout of the model. ' - 'For example, [N,C,H,W] is used for ONNX* models and [N,H,W,C] for TensorFlow* ' - 'models. The shape can contain undefined dimensions (? or -1) and should fit the dimensions defined in the input ' - 'operation of the graph. Boundaries of undefined dimension can be specified with ' - 'ellipsis, for example [1,1..10,128,128]. One boundary can be undefined, for ' - 'example [1,..100] or [1,3,1..,1..]. If there are multiple inputs in the model, ' - '--input_shape should contain definition of shape for each input separated by a ' - 'comma, for example: [1,3,227,227],[2,4] for a model with two inputs with 4D and 2D ' - 'shapes. Alternatively, specify shapes with the --input option.') + help=mo_convert_params['input_shape'].description.format( + mo_convert_params['input_shape'].possible_types_command_line)) common_group.add_argument('--scale', '-s', type=float, help='All input values coming from original network inputs will be ' + @@ -307,86 +961,39 @@ def get_common_cli_parser(parser: argparse.ArgumentParser = None): 'DEBUG', 'NOTSET'], default='ERROR') common_group.add_argument('--input', - help='Quoted list of comma-separated input nodes names with shapes, data types, ' - 'and values for freezing. The order of inputs in converted model is the same as ' - 'order of specified operation names. The shape and value are specified as space-separated ' - 'lists. The data type of input node is specified in braces and ' - 'can have one of the values: f64 (float64), f32 (float32), f16 (float16), ' - 'i64 (int64), i32 (int32), u8 (uint8), boolean (bool). Data type is optional. ' - 'If it\'s not specified explicitly then there are two options: ' - 'if input node is a parameter, data type is taken from the original node dtype, ' - 'if input node is not a parameter, data type is set to f32. ' - 'Example, to set `input_1` with shape [1 100], and Parameter node `sequence_len` ' - 'with scalar input with value `150`, and boolean input `is_training` with ' - '`False` value use the following format: ' - '"input_1[1 10],sequence_len->150,is_training->False". ' - 'Another example, use the following format to set input port 0 of the node ' - '`node_name1` with the shape [3 4] as an input node and freeze output port 1 ' - 'of the node `node_name2` with the value [20 15] of the int32 type and shape [2]: ' - '"0:node_name1[3 4],node_name2:1[2]{i32}->[20 15]".') + help=mo_convert_params['input'].description.format( + mo_convert_params['input'].possible_types_command_line)) common_group.add_argument('--output', - help='The name of the output operation of the model. ' + - 'For TensorFlow*, do not add :0 to this name.' - 'The order of outputs in converted model is the same as order of ' - 'specified operation names.') + help=mo_convert_params['output'].description.format( + mo_convert_params['output'].possible_types_command_line)) common_group.add_argument('--mean_values', '-ms', - help='Mean values to be used for the input image per channel. ' + - 'Values to be provided in the (R,G,B) or [R,G,B] format. ' + - 'Can be defined for desired input of the model, for example: ' + - '"--mean_values data[255,255,255],info[255,255,255]". ' + - 'The exact meaning and order ' + - 'of channels depend on how the original model was trained.', + help=mo_convert_params['mean_values'].description.format( + mo_convert_params['mean_values'].possible_types_command_line), default=()) common_group.add_argument('--scale_values', - help='Scale values to be used for the input image per channel. ' + - 'Values are provided in the (R,G,B) or [R,G,B] format. ' + - 'Can be defined for desired input of the model, for example: ' + - '"--scale_values data[255,255,255],info[255,255,255]". ' + - 'The exact meaning and order ' + - 'of channels depend on how the original model was trained.' + - 'If both --mean_values and --scale_values are specified, ' + - 'the mean is subtracted first and then scale is applied ' + - 'regardless of the order of options in command line.', + help=mo_convert_params['scale_values'].description.format( + mo_convert_params['scale_values'].possible_types_command_line), default=()) common_group.add_argument('--source_layout', - help='Layout of the input or output of the model in the framework. Layout can' - ' be specified in the short form, e.g. nhwc, or in complex form, e.g. "[n,h,w,c]".' - ' Example for many names: ' - '"in_name1([n,h,w,c]),in_name2(nc),out_name1(n),out_name2(nc)". Layout can be ' - 'partially defined, "?" can be used to specify undefined layout for one dimension, ' - '"..." can be used to specify undefined layout for multiple dimensions, for example ' - '"?c??", "nc...", "n...c", etc.', + help=mo_convert_params['source_layout'].description.format( + mo_convert_params['source_layout'].possible_types_command_line), default=()) common_group.add_argument('--target_layout', - help='Same as --source_layout, but specifies target layout that will be in the model ' - 'after processing by ModelOptimizer.', + help=mo_convert_params['target_layout'].description.format( + mo_convert_params['target_layout'].possible_types_command_line), default=()) common_group.add_argument('--layout', - help='Combination of --source_layout and --target_layout. Can\'t be used with either of ' - 'them. If model has one input it is sufficient to specify layout of this input, for' - ' example --layout nhwc. To specify layouts of many tensors, names must be provided,' - ' for example: --layout "name1(nchw),name2(nc)". It is possible to instruct ' - 'ModelOptimizer to change layout, for example: ' - '--layout "name1(nhwc->nchw),name2(cn->nc)". Also "*" in long layout form can be' - ' used to fuse dimensions, for example "[n,c,...]->[n*c,...]".', + help=mo_convert_params['layout'].description.format( + mo_convert_params['layout'].possible_types_command_line), default=()) # TODO: isn't it a weights precision type common_group.add_argument('--data_type', - help='Data type for all intermediate tensors and weights. ' + - 'If original model is in FP32 and --data_type=FP16 is specified, all model weights ' + - 'and biases are compressed to FP16.', + help=mo_convert_params['data_type'].description, choices=["FP16", "FP32", "half", "float"], default='float') common_group.add_argument('--transform', - help='Apply additional transformations. ' + - 'Usage: "--transform transformation_name1[args],transformation_name2..." ' + - 'where [args] is key=value pairs separated by semicolon. ' + - 'Examples: "--transform LowLatency2" or ' + - ' "--transform Pruning" or ' + - ' "--transform LowLatency2[use_const_initializer=False]" or ' + - ' "--transform \"MakeStateful[param_res_names=' - '{\'input_name_1\':\'output_name_1\',\'input_name_2\':\'output_name_2\'}]\"" ' + - 'Available transformations: "LowLatency2", "MakeStateful", "Pruning"', + help=mo_convert_params['transform'].description.format( + mo_convert_params['transform'].possible_types_command_line), default="") common_group.add_argument('--disable_fusing', help='[DEPRECATED] Turn off fusing of linear operations to Convolution.', @@ -403,29 +1010,24 @@ def get_common_cli_parser(parser: argparse.ArgumentParser = None): action=DeprecatedStoreTrue, default=False) # we use CanonicalizeDirCheckExistenceAction instead of readable_dirs to handle empty strings common_group.add_argument("--extensions", - help="Paths or a comma-separated list of paths to libraries (.so or .dll) " - "with extensions. For the legacy MO path (if `--use_legacy_frontend` is used), " - "a directory or a comma-separated list of directories with extensions are supported. " - "To disable all extensions including those that are placed at the default location, " - "pass an empty string.", - default=import_extensions.default_path(), + help=mo_convert_params['extensions'].description.format( + mo_convert_params['extensions'].possible_types_command_line), + default=[import_extensions.default_path()], action=CanonicalizePathCheckExistenceAction, type=readable_dirs_or_files_or_empty) common_group.add_argument("--batch", "-b", type=check_positive, default=None, - help="Input batch size") + help=mo_convert_params['batch'].description) common_group.add_argument("--version", action='version', version='Version of Model Optimizer is: {}'.format(get_version()), - help="Version of Model Optimizer") + help=mo_convert_params['version'].description) common_group.add_argument('--silent', - help='Prevent any output messages except those that correspond to log level equals ' - 'ERROR, that can be set with the following option: --log_level. ' - 'By default, log level is already ERROR. ', - action='store_true', - default=False) + help=mo_convert_params['silent'].description, + type=check_bool, + default=True) common_group.add_argument('--freeze_placeholder_with_value', help='Replaces input layer with constant node with ' 'provided value, for example: "node_name->True". ' @@ -433,35 +1035,26 @@ def get_common_cli_parser(parser: argparse.ArgumentParser = None): 'Use --input option to specify a value for freezing.', default=None) common_group.add_argument('--static_shape', - help='Enables IR generation for fixed input shape (folding `ShapeOf` operations and ' - 'shape-calculating sub-graphs to `Constant`). Changing model input shape using ' - 'the OpenVINO Runtime API in runtime may fail for such an IR.', + help=mo_convert_params['static_shape'].description, action='store_true', default=False) common_group.add_argument('--disable_weights_compression', help='[DEPRECATED] Disable compression and store weights with original precision.', action=DeprecatedStoreTrue, default=False) common_group.add_argument('--progress', - help='Enable model conversion progress display.', + help=mo_convert_params['progress'].description, action='store_true', default=False) common_group.add_argument('--stream_output', - help='Switch model conversion progress display to a multiline mode.', + help=mo_convert_params['stream_output'].description, action='store_true', default=False) common_group.add_argument('--transformations_config', - help='Use the configuration file with transformations ' - 'description. File can be specified as relative path ' - 'from the current directory, as absolute path or as a' - 'relative path from the mo root directory', + help=mo_convert_params['transformations_config'].description.format( + mo_convert_params['transformations_config'].possible_types_command_line), action=CanonicalizeTransformationPathCheckExistenceAction) common_group.add_argument("--use_new_frontend", - help='Force the usage of new Frontend of Model Optimizer for model conversion into IR. ' - 'The new Frontend is C++ based and is available for ONNX* and PaddlePaddle* models. ' - 'Model optimizer uses new Frontend for ONNX* and PaddlePaddle* by default that means ' - '`--use_new_frontend` and `--use_legacy_frontend` options are not specified.', + help=mo_convert_params['use_new_frontend'].description, action='store_true', default=False) common_group.add_argument("--use_legacy_frontend", - help='Force the usage of legacy Frontend of Model Optimizer for model conversion into IR. ' - 'The legacy Frontend is Python based and is available for TensorFlow*, ONNX*, MXNet*, ' - 'Caffe*, and Kaldi* models.', + help=mo_convert_params['use_legacy_frontend'].description, action='store_true', default=False) return parser @@ -578,19 +1171,19 @@ def get_caffe_cli_parser(parser: argparse.ArgumentParser = None): caffe_group = parser.add_argument_group('Caffe*-specific parameters') caffe_group.add_argument('--input_proto', '-d', - help='Deploy-ready prototxt file that contains a topology structure ' + - 'and layer attributes', + help=mo_convert_params['input_proto'].description, type=str, action=CanonicalizePathCheckExistenceAction) caffe_group.add_argument('--caffe_parser_path', - help='Path to Python Caffe* parser generated from caffe.proto', + help=mo_convert_params['caffe_parser_path'].description, type=str, default=os.path.join(os.path.dirname(__file__), os.pardir, 'front', 'caffe', 'proto'), action=CanonicalizePathCheckExistenceAction) caffe_group.add_argument('-k', - help='Path to CustomLayersMapping.xml to register custom layers', + help=mo_convert_params['k'].description, type=str, - default=os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'extensions', 'front', 'caffe', + default=os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'extensions', + 'front', 'caffe', 'CustomLayersMapping.xml'), action=CanonicalizePathCheckExistenceAction) caffe_group.add_argument('--mean_file', '-mf', @@ -608,15 +1201,11 @@ def get_caffe_cli_parser(parser: argparse.ArgumentParser = None): 'from the upper left corner of the mean image', default=None) caffe_group.add_argument('--disable_omitting_optional', - help='Disable omitting optional attributes to be used for custom layers. ' + - 'Use this option if you want to transfer all attributes of a custom layer to IR. ' + - 'Default behavior is to transfer the attributes with default values and the attributes defined by the user to IR.', + help=mo_convert_params['disable_omitting_optional'].description, action='store_true', default=False) caffe_group.add_argument('--enable_flattening_nested_params', - help='Enable flattening optional params to be used for custom layers. ' + - 'Use this option if you want to transfer attributes of a custom layer to IR with flattened nested parameters. ' + - 'Default behavior is to transfer the attributes without flattening nested parameters.', + help=mo_convert_params['enable_flattening_nested_params'].description, action='store_true', default=False) return parser @@ -636,41 +1225,36 @@ def get_tf_cli_parser(parser: argparse.ArgumentParser = None): tf_group = parser.add_argument_group('TensorFlow*-specific parameters') tf_group.add_argument('--input_model_is_text', - help='TensorFlow*: treat the input model file as a text protobuf format. If not specified, ' + - 'the Model Optimizer treats it as a binary file by default.', + help=mo_convert_params['input_model_is_text'].description, action='store_true') - tf_group.add_argument('--input_checkpoint', type=str, default=None, help="TensorFlow*: variables file to load.", + tf_group.add_argument('--input_checkpoint', type=str, default=None, + help=mo_convert_params['input_checkpoint'].description, action=CanonicalizePathCheckExistenceAction) tf_group.add_argument('--input_meta_graph', - help='Tensorflow*: a file with a meta-graph of the model before freezing', + help=mo_convert_params['input_meta_graph'].description, action=CanonicalizePathCheckExistenceAction, type=readable_file) tf_group.add_argument('--saved_model_dir', default=None, - help='TensorFlow*: directory with a model in SavedModel format ' - 'of TensorFlow 1.x or 2.x version.', + help=mo_convert_params['saved_model_dir'].description, action=CanonicalizePathCheckExistenceAction, type=readable_dirs) tf_group.add_argument('--saved_model_tags', type=str, default=None, - help="Group of tag(s) of the MetaGraphDef to load, in string format, separated by ','. " - "For tag-set contains multiple tags, all tags must be passed in.") + help=mo_convert_params['saved_model_tags'].description) tf_group.add_argument('--tensorflow_custom_operations_config_update', - help='TensorFlow*: update the configuration file with node name patterns with input/output ' - 'nodes information.', + help=mo_convert_params['tensorflow_custom_operations_config_update'].description, action=CanonicalizePathCheckExistenceAction) tf_group.add_argument('--tensorflow_use_custom_operations_config', - help='Use the configuration file with custom operation description.', - action=DeprecatedCanonicalizePathCheckExistenceAction) + help='Use the configuration file with custom operation description.', + action=DeprecatedCanonicalizePathCheckExistenceAction) tf_group.add_argument('--tensorflow_object_detection_api_pipeline_config', - help='TensorFlow*: path to the pipeline configuration file used to generate model created ' - 'with help of Object Detection API.', + help=mo_convert_params['tensorflow_object_detection_api_pipeline_config'].description, action=CanonicalizePathCheckExistenceAction) tf_group.add_argument('--tensorboard_logdir', - help='TensorFlow*: dump the input graph to a given directory that should be used with TensorBoard.', + help=mo_convert_params['tensorboard_logdir'].description, default=None, action=CanonicalizePathCheckExistenceAction) tf_group.add_argument('--tensorflow_custom_layer_libraries', - help='TensorFlow*: comma separated list of shared libraries with TensorFlow* custom ' - 'operations implementation.', + help=mo_convert_params['tensorflow_custom_layer_libraries'].description, default=None, action=CanonicalizePathCheckExistenceAction) tf_group.add_argument('--disable_nhwc_to_nchw', @@ -695,25 +1279,24 @@ def get_mxnet_cli_parser(parser: argparse.ArgumentParser = None): mx_group = parser.add_argument_group('Mxnet-specific parameters') mx_group.add_argument('--input_symbol', - help='Symbol file (for example, model-symbol.json) that contains a topology structure ' + - 'and layer attributes', + help=mo_convert_params['input_symbol'].description, type=str, action=CanonicalizePathCheckExistenceAction) mx_group.add_argument("--nd_prefix_name", - help="Prefix name for args.nd and argx.nd files.", + help=mo_convert_params['nd_prefix_name'].description, default=None) mx_group.add_argument("--pretrained_model_name", - help="Name of a pretrained MXNet model without extension and epoch number. This model will be merged with args.nd and argx.nd files", + help=mo_convert_params['pretrained_model_name'].description, default=None) mx_group.add_argument("--save_params_from_nd", action='store_true', - help="Enable saving built parameters file from .nd files") + help=mo_convert_params['save_params_from_nd'].description) mx_group.add_argument("--legacy_mxnet_model", action='store_true', - help="Enable MXNet loader to make a model compatible with the latest MXNet version. Use only if your model was trained with MXNet version lower than 1.0.0") + help=mo_convert_params['legacy_mxnet_model'].description) mx_group.add_argument("--enable_ssd_gluoncv", action='store_true', - help="Enable pattern matchers replacers for converting gluoncv ssd topologies.", + help=mo_convert_params['enable_ssd_gluoncv'].description, default=False) return parser @@ -734,17 +1317,17 @@ def get_kaldi_cli_parser(parser: argparse.ArgumentParser = None): kaldi_group = parser.add_argument_group('Kaldi-specific parameters') kaldi_group.add_argument("--counts", - help="Path to the counts file", + help=mo_convert_params['counts'].description, default=None, action=CanonicalizePathCheckExistenceIfNeededAction) kaldi_group.add_argument("--remove_output_softmax", - help="Removes the SoftMax layer that is the output layer", + help=mo_convert_params['remove_output_softmax'].description, action='store_true', default=False) kaldi_group.add_argument("--remove_memory", - help="Removes the Memory layer and use additional inputs outputs instead", + help=mo_convert_params['remove_memory'].description, action='store_true', default=False) return parser @@ -1171,6 +1754,16 @@ def get_placeholder_shapes(argv_input: str, argv_input_shape: str, argv_batch=No are_shapes_specified_through_input = False inputs_list = list() if argv_input: + range_reg = r'([0-9]*\.\.[0-9]*)' + first_digit_reg = r'([0-9]+|-1|\?|{})'.format(range_reg) + next_digits_reg = r'(,{})+'.format(first_digit_reg) + brackets_reg = r'(.*\[{}{}\].*)'.format(first_digit_reg, next_digits_reg, + first_digit_reg, next_digits_reg) + if re.match(brackets_reg, argv_input): + raise Error('Error in input {}. Shape with comma separator is not supported in --input param. ' + 'Please use shape syntax with whitespace separator. Example --input="data[1 3 100 100]".'.format( + argv_input)) + for input_value in argv_input.split(','): node_name, shape, _, data_type = parse_input_value(input_value) placeholder_shapes[node_name] = shape @@ -1469,6 +2062,23 @@ def get_model_name(path_input_model: str) -> str: return 'model' if parsed_name.startswith('.') or len(parsed_name) == 0 else parsed_name +def get_model_name_from_args(argv: argparse.Namespace): + model_name = "" + if hasattr(argv, 'model_name'): + if argv.model_name: + model_name = argv.model_name + elif argv.input_model: + model_name = get_model_name(argv.input_model) + elif argv.saved_model_dir: + model_name = "saved_model" + elif argv.input_meta_graph: + model_name = get_model_name(argv.input_meta_graph) + elif argv.input_symbol: + model_name = get_model_name(argv.input_symbol) + argv.model_name = model_name + return model_name + + def get_absolute_path(path_to_file: str) -> str: """ Deduces absolute path of the file by a given path to the file @@ -1600,6 +2210,17 @@ def check_positive(value): return int_value +def check_bool(value): + if isinstance(value, bool): + return value + elif isinstance(value, str): + if value.lower() not in ['true', 'false']: + raise argparse.ArgumentTypeError("expected a True/False value") + return value.lower() == 'true' + else: + raise argparse.ArgumentTypeError("expected a bool or str type") + + def depersonalize(value: str, key: str): dir_keys = [ 'output_dir', 'extensions', 'saved_model_dir', 'tensorboard_logdir', 'caffe_parser_path' @@ -1617,9 +2238,17 @@ def depersonalize(value: str, key: str): return ','.join(res) -def get_meta_info(argv: argparse.Namespace): +def get_meta_info(argv: [argparse.Namespace, dict]): meta_data = {'unset': []} - for key, value in argv.__dict__.items(): + dict_items = None + if isinstance(argv, argparse.Namespace): + dict_items = argv.__dict__.items() + elif isinstance(argv, dict): + dict_items = argv.items() + else: + raise Error('Incorrect type of argv. Expected dict or argparse.Namespace, got {}'.format(type(dict_items))) + + for key, value in dict_items: if value is not None: value = depersonalize(value, key) meta_data[key] = value diff --git a/tools/mo/openvino/tools/mo/utils/versions_checker.py b/tools/mo/openvino/tools/mo/utils/versions_checker.py index 3b3e32208d8..9a53573387c 100644 --- a/tools/mo/openvino/tools/mo/utils/versions_checker.py +++ b/tools/mo/openvino/tools/mo/utils/versions_checker.py @@ -231,7 +231,7 @@ def get_environment_setup(framework): return env_setup -def check_requirements(framework=None): +def check_requirements(framework=None, silent=True): """ Please do not add parameter type annotations (param:type). Because we import this file while checking Python version. @@ -241,6 +241,7 @@ def check_requirements(framework=None): Logs a warning in case of permissible dissatisfaction Logs an error in cases of critical dissatisfaction :param framework: framework name + :param silent: determines if it is required to print warning messages :return: exit code (0 - execution successful, 1 - error) """ framework_suffix = "_{}".format(framework) @@ -249,9 +250,10 @@ def check_requirements(framework=None): framework_suffix = "" elif framework == "tf": if "tensorflow" in env_setup and env_setup["tensorflow"] < LooseVersion("2.0.0"): - log.error('\t\nSupport of the Model Optimizer tool in TensorFlow 1.x environment is deprecated.' - 'It is highly recommended to use TensorFlow 2.x.\n', - extra={'is_warning': True}) + if not silent: + log.error('\t\nSupport of the Model Optimizer tool in TensorFlow 1.x environment is deprecated.' + 'It is highly recommended to use TensorFlow 2.x.\n', + extra={'is_warning': True}) file_name = "requirements{}.txt".format(framework_suffix) @@ -282,9 +284,10 @@ def check_requirements(framework=None): not_satisfied_versions.append((name, 'not installed', '')) continue except Exception as e: - log.error('Error happened while importing {} module. It may happen due to unsatisfied requirements of ' - 'that module. Please run requirements installation script once more.\n' - 'Details on module importing failure: {}'.format(name, e)) + if not silent: + log.error('Error happened while importing {} module. It may happen due to unsatisfied requirements of ' + 'that module. Please run requirements installation script once more.\n' + 'Details on module importing failure: {}'.format(name, e)) not_satisfied_versions.append((name, 'package error', 'required: {} {}'.format(key, required_version))) continue @@ -304,7 +307,9 @@ def check_requirements(framework=None): for module in not_satisfied_versions: missed_modules_message += "\t{}: {}, {}\n".format(module[0], module[1], module[2]) if exit_code: - log.error(message.format(missed_modules_message, helper_command)) + if not silent: + log.error(message.format(missed_modules_message, helper_command)) else: - log.error(message.format(missed_modules_message, helper_command), extra={'is_warning': True}) + if not silent: + log.error(message.format(missed_modules_message, helper_command), extra={'is_warning': True}) return exit_code diff --git a/tools/mo/setup.py b/tools/mo/setup.py index 8b949b658af..add53137eea 100644 --- a/tools/mo/setup.py +++ b/tools/mo/setup.py @@ -37,6 +37,8 @@ for item in os.listdir(prefix): if re.match(r'mo(.*)\.py|main(.*)\.py', item): py_modules.append(prefix.replace('/', '.') + item.split('.')[0]) py_modules.append(prefix.replace('/', '.') + 'subprocess_main') +py_modules.append(prefix.replace('/', '.') + 'convert') +py_modules.append(prefix.replace('/', '.') + 'convert_impl') py_modules.append(prefix.replace('/', '.') + '__main__') # Minimal set of dependencies diff --git a/tools/mo/unit_tests/mo/convert/import_from_mo_test.py b/tools/mo/unit_tests/mo/convert/import_from_mo_test.py new file mode 100644 index 00000000000..b4a94365aee --- /dev/null +++ b/tools/mo/unit_tests/mo/convert/import_from_mo_test.py @@ -0,0 +1,108 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os +import tempfile + +from generator import generator, generate +from openvino.runtime import serialize + +from openvino.tools.mo import InputCutInfo, LayoutMap +from openvino.tools.mo.utils.ir_engine.ir_engine import IREngine +from unit_tests.mo.unit_test_with_mocked_telemetry import UnitTestWithMockedTelemetry +from unit_tests.utils.graph import build_graph +from utils import create_onnx_model, save_to_onnx + + +@generator +class ConvertImportMOTest(UnitTestWithMockedTelemetry): + # Checks convert import from openvino.tools.mo + test_directory = os.path.dirname(os.path.realpath(__file__)) + + @generate(*[ + ({}), + ({'input': InputCutInfo(name='LeakyRelu_out', shape=None, type=None, value=None)}), + ({'layout': {'input': LayoutMap(source_layout='NCHW', target_layout='NHWC')}}), + ]) + def test_import(self, params): + from openvino.tools.mo import convert + + with tempfile.TemporaryDirectory(dir=self.test_directory) as tmpdir: + model = create_onnx_model() + model_path = save_to_onnx(model, tmpdir) + out_xml = os.path.join(tmpdir, "model.xml") + + ov_model = convert(input_model=model_path, **params) + serialize(ov_model, out_xml.encode('utf-8'), out_xml.replace('.xml', '.bin').encode('utf-8')) + assert os.path.exists(out_xml) + + def test_unnamed_input_model(self): + def create_onnx_model(): + # + # Create ONNX model + # + + import onnx + from onnx import helper + from onnx import TensorProto + + shape = [1, 2, 3] + + input = helper.make_tensor_value_info('input', TensorProto.FLOAT, shape) + output = helper.make_tensor_value_info('output', TensorProto.FLOAT, shape) + + node_def = onnx.helper.make_node( + 'Relu', + inputs=['input'], + outputs=['Relu_out'], + ) + node_def2 = onnx.helper.make_node( + 'Sigmoid', + inputs=['Relu_out'], + outputs=['output'], + ) + + # Create the graph (GraphProto) + graph_def = helper.make_graph( + [node_def, node_def2], + 'test_model', + [input], + [output], + ) + + # Create the model (ModelProto) + onnx_net = helper.make_model(graph_def, producer_name='test_model') + return onnx_net + + nodes_attributes = { + 'input': {'kind': 'op', 'type': 'Parameter'}, + 'input_data': {'shape': [1, 2, 3], 'kind': 'data'}, + 'relu': {'kind': 'op', 'type': 'ReLU'}, + 'relu_data': {'shape': [1, 2, 3], 'kind': 'data'}, + 'sigmoid': {'kind': 'op', 'type': 'Sigmoid'}, + 'sigmoid_data': {'shape': [1, 2, 3], 'kind': 'data'}, + 'result': {'kind': 'op', 'type': 'Result'} + } + + ref_graph = build_graph(nodes_attributes, + [('input', 'input_data'), + ('input_data', 'relu'), + ('relu', 'relu_data'), + ('relu_data', 'sigmoid'), + ('sigmoid', 'sigmoid_data'), + ('sigmoid_data', 'result'), + ]) + + from openvino.tools.mo import convert + with tempfile.TemporaryDirectory(dir=self.test_directory) as tmpdir: + + model = create_onnx_model() + model_path = save_to_onnx(model, tmpdir) + out_xml = os.path.join(tmpdir, "model.xml") + + ov_model = convert(model_path) + serialize(ov_model, out_xml.encode('utf-8'), out_xml.replace('.xml', '.bin').encode('utf-8')) + + ir = IREngine(out_xml, out_xml.replace('.xml', '.bin')) + flag, resp = ir.compare(ref_graph) + assert flag, '\n'.join(resp) diff --git a/tools/mo/unit_tests/mo/convert/utils.py b/tools/mo/unit_tests/mo/convert/utils.py new file mode 100644 index 00000000000..86ca0700297 --- /dev/null +++ b/tools/mo/unit_tests/mo/convert/utils.py @@ -0,0 +1,52 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os + + +def create_onnx_model(): + # + # Create ONNX model + # + + import onnx + from onnx import helper + from onnx import TensorProto + + shape = [1, 3, 2, 2] + + input = helper.make_tensor_value_info('input', TensorProto.FLOAT, shape) + output = helper.make_tensor_value_info('output', TensorProto.FLOAT, shape) + + node_def = onnx.helper.make_node( + 'LeakyRelu', + inputs=['input'], + outputs=['LeakyRelu_out'], + alpha=0.1 + ) + node_def2 = onnx.helper.make_node( + 'Elu', + inputs=['LeakyRelu_out'], + outputs=['output'], + alpha=0.1 + ) + + # Create the graph (GraphProto) + graph_def = helper.make_graph( + [node_def, node_def2], + 'test_model', + [input], + [output], + ) + + # Create the model (ModelProto) + onnx_net = helper.make_model(graph_def, producer_name='test_model') + return onnx_net + + +def save_to_onnx(onnx_model, path_to_saved_onnx_model): + import onnx + path = os.path.join(path_to_saved_onnx_model, 'model.onnx') + onnx.save(onnx_model, path) + assert os.path.isfile(path), "model.onnx haven't been saved here: {}".format(path_to_saved_onnx_model) + return path diff --git a/tools/mo/unit_tests/mo/extensions_test_actual.py b/tools/mo/unit_tests/mo/extensions_test_actual.py index c8f6bf12367..215a36ff9e5 100644 --- a/tools/mo/unit_tests/mo/extensions_test_actual.py +++ b/tools/mo/unit_tests/mo/extensions_test_actual.py @@ -12,7 +12,7 @@ import json import argparse from pathlib import Path from itertools import chain -from openvino.tools.mo.main import prepare_ir +from openvino.tools.mo.convert_impl import prepare_ir from openvino.frontend import ( FrontEndManager, ) # pylint: disable=no-name-in-module,import-error @@ -127,7 +127,7 @@ class TestMoFallback(unittest.TestCase): def test_conersion_if_extensions_is_used(self): args = base_args_config() args.input_model = "test_model.onnx" - args.extensions = get_builtin_extensions_path() + args.extensions = [get_builtin_extensions_path()] graph, model = prepare_ir(args) diff --git a/tools/mo/unit_tests/mo/main_test_actual.py b/tools/mo/unit_tests/mo/main_test_actual.py index f5f26f5daa2..2907c14d2b0 100644 --- a/tools/mo/unit_tests/mo/main_test_actual.py +++ b/tools/mo/unit_tests/mo/main_test_actual.py @@ -22,9 +22,9 @@ ngraph_needed = pytest.mark.skipif(not ngraph_available, class TestMainErrors(unittest.TestCase): @patch('argparse.ArgumentParser.parse_args', return_value=argparse.Namespace()) - @patch('openvino.tools.mo.main.driver', side_effect=FrameworkError('FW ERROR MESSAGE')) + @patch('openvino.tools.mo.convert_impl.driver', side_effect=FrameworkError('FW ERROR MESSAGE')) @ngraph_needed def test_FrameworkError(self, mock_argparse, mock_driver): with self.assertLogs() as logger: - main(argparse.ArgumentParser(), None, 'framework_string') + main(argparse.ArgumentParser()) self.assertEqual(logger.output, ['ERROR:root:FW ERROR MESSAGE']) diff --git a/tools/mo/unit_tests/mo/unit_test_with_mocked_telemetry.py b/tools/mo/unit_tests/mo/unit_test_with_mocked_telemetry.py index dbb6f51002b..e0fbe6342b7 100644 --- a/tools/mo/unit_tests/mo/unit_test_with_mocked_telemetry.py +++ b/tools/mo/unit_tests/mo/unit_test_with_mocked_telemetry.py @@ -14,3 +14,6 @@ class UnitTestWithMockedTelemetry(unittest.TestCase): def setUp(self): tm.Telemetry.__init__ = Mock(return_value=None) tm.Telemetry.send_event = Mock() + tm.Telemetry.start_session = Mock() + tm.Telemetry.end_session = Mock() + tm.Telemetry.force_shutdown = Mock() diff --git a/tools/mo/unit_tests/mo/utils/args_to_string_test.py b/tools/mo/unit_tests/mo/utils/args_to_string_test.py new file mode 100644 index 00000000000..4d8114e27a8 --- /dev/null +++ b/tools/mo/unit_tests/mo/utils/args_to_string_test.py @@ -0,0 +1,227 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import numpy as np +from openvino.runtime import Layout, PartialShape, Dimension, Shape, Type + +from openvino.tools.mo import InputCutInfo, LayoutMap +from openvino.tools.mo.utils.cli_parser import input_to_str, mean_scale_value_to_str, \ + transform_param_to_str, input_shape_to_str, str_list_to_str, source_target_layout_to_str, layout_param_to_str +from unit_tests.mo.unit_test_with_mocked_telemetry import UnitTestWithMockedTelemetry + + +class TestConvertingConvertArgumentsToString(UnitTestWithMockedTelemetry): + def test_input_to_str(self): + inp1 = InputCutInfo(name="data:0", shape=None, type=None, value=None) + self.assertTrue(input_to_str(inp1) == "data:0") + + inp2 = InputCutInfo("data:0", [1, 3, 100, 100], type=None, value=None) + self.assertTrue(input_to_str(inp2) == "data:0[1 3 100 100]") + + inp3 = InputCutInfo("data:0", type=np.int32, value=None, shape=None) + self.assertTrue(input_to_str(inp3) == "data:0{i32}") + + inp4 = InputCutInfo("data:0", value=[2, 4, 5], type=None, shape=None) + self.assertTrue(input_to_str(inp4) == "data:0->[2 4 5]") + + inp5 = InputCutInfo("data:0", [1, 3, 100, 100], np.uint8, value=None) + self.assertTrue(input_to_str(inp5) == "data:0[1 3 100 100]{u8}") + + inp6 = InputCutInfo("data:0", [2, 5, 7], value=[1, 2, 3, 4, 5], type=None) + self.assertTrue(input_to_str(inp6) == "data:0[2 5 7]->[1 2 3 4 5]") + + inp7 = InputCutInfo("0:data1", type=np.float64, value=[1.6, 7.2, 5.66], shape=None) + self.assertTrue(input_to_str(inp7) == "0:data1{f64}->[1.6 7.2 5.66]") + + inp8 = InputCutInfo("data2", [4, 5, 6], np.int64, [5, 4, 3, 2, 1]) + self.assertTrue(input_to_str(inp8) == "data2[4 5 6]{i64}->[5 4 3 2 1]") + + inp9 = InputCutInfo("data", [1], np.bool, True) + self.assertTrue(input_to_str(inp9) == "data[1]{boolean}->True") + + inp = [inp6, inp7, inp8] + self.assertTrue(input_to_str(inp) == "data:0[2 5 7]->[1 2 3 4 5]," + "0:data1{f64}->[1.6 7.2 5.66]," + "data2[4 5 6]{i64}->[5 4 3 2 1]") + + inp = ["data:0[2 5 7]->[1 2 3 4 5]", "0:data1{f64}->[1.6 7.2 5.66]", "data2[4 5 6]{i64}->[5 4 3 2 1]"] + self.assertTrue(input_to_str(inp) == "data:0[2 5 7]->[1 2 3 4 5]," + "0:data1{f64}->[1.6 7.2 5.66]," + "data2[4 5 6]{i64}->[5 4 3 2 1]") + + inp9 = InputCutInfo("data1", PartialShape([Dimension(-1), Dimension(2, -1), + Dimension(-1, 10), 100, Dimension(2, 12)]), type=None, value=None) + self.assertTrue(input_to_str(inp9) == "data1[? 2.. ..10 100 2..12]") + + inp10 = InputCutInfo("data2", [Dimension(-1), Dimension(2, -1), + Dimension(-1, 10), 100, Dimension(2, 12)], np.uint8, value=None) + self.assertTrue(input_to_str(inp10) == "data2[? 2.. ..10 100 2..12]{u8}") + + inp11 = InputCutInfo("data3", Shape([4, 5, 6]), np.int64, [5, 4, 3, 2, 1]) + self.assertTrue(input_to_str(inp11) == "data3[4 5 6]{i64}->[5 4 3 2 1]") + + inp12 = InputCutInfo("data4", PartialShape.dynamic(), type=None, value=None) + self.assertTrue(input_to_str(inp12) == "data4[...]") + + inp = [inp9, inp10, inp11, inp12] + self.assertTrue(input_to_str(inp) == "data1[? 2.. ..10 100 2..12]," + "data2[? 2.. ..10 100 2..12]{u8}," + "data3[4 5 6]{i64}->[5 4 3 2 1]," + "data4[...]") + + inp1 = ("data:0") + self.assertTrue(input_to_str(inp1) == "data:0") + + inp2 = ([1, 3, 100, 100], "data:0") + self.assertTrue(input_to_str(inp2) == "data:0[1 3 100 100]") + + inp3 = ("data:0", np.int32) + self.assertTrue(input_to_str(inp3) == "data:0{i32}") + + inp4 = (np.uint8, [1, 3, 100, 100], "data:0") + self.assertTrue(input_to_str(inp4) == "data:0[1 3 100 100]{u8}") + + inp = [inp1, inp2, inp3, inp4] + self.assertTrue(input_to_str(inp) == "data:0," + "data:0[1 3 100 100]," + "data:0{i32}," + "data:0[1 3 100 100]{u8}") + + inp5 = ("data1", PartialShape([Dimension(-1), Dimension(2, -1), Dimension(-1, 10), 100, Dimension(2, 12)])) + self.assertTrue(input_to_str(inp5) == "data1[? 2.. ..10 100 2..12]") + + inp6 = ("data2", [Dimension(-1), Dimension(2, -1), Dimension(-1, 10), 100, Dimension(2, 12)], np.uint8) + self.assertTrue(input_to_str(inp6) == "data2[? 2.. ..10 100 2..12]{u8}") + + inp7 = ("data3", Shape([4, 5, 6]), np.int64) + self.assertTrue(input_to_str(inp7) == "data3[4 5 6]{i64}") + + inp8 = ("data4", PartialShape.dynamic()) + self.assertTrue(input_to_str(inp8) == "data4[...]") + + inp = [inp5, inp6, inp7, inp8] + self.assertTrue(input_to_str(inp) == "data1[? 2.. ..10 100 2..12]," + "data2[? 2.. ..10 100 2..12]{u8}," + "data3[4 5 6]{i64}," + "data4[...]") + + self.assertRaises(Exception, input_to_str, **{"input": InputCutInfo(0.5, [1, 2, 3], None, None)}) + self.assertRaises(Exception, input_to_str, **{"input": InputCutInfo("name", 0.5, None, None)}) + self.assertRaises(Exception, input_to_str, **{"input": InputCutInfo("name", [1, 2, 3], 0.5, None)}) + self.assertRaises(Exception, input_to_str, **{"input": InputCutInfo("name", [1, 2, 3], None, np.int)}) + self.assertRaises(Exception, input_to_str, **{"input": InputCutInfo("name", [1, 2, 3], None, np.int)}) + self.assertRaises(Exception, input_to_str, **{"input": ([2, 3], Shape([1, 2]))}) + self.assertRaises(Exception, input_to_str, **{"input": ("name", [np.int, 2, 3])}) + self.assertRaises(Exception, input_to_str, **{"input": ("name", "name1", [2, 3])}) + self.assertRaises(Exception, input_to_str, **{"input": ("name", [2, 3], Shape([1, 2]))}) + self.assertRaises(Exception, input_to_str, **{"input": ("name", np.int, Type(np.float))}) + self.assertRaises(Exception, input_to_str, **{"input": Exception}) + self.assertRaises(Exception, input_to_str, **{"input": ("name", Exception)}) + self.assertRaises(Exception, input_to_str, **{"input": ("name", Dimension(1))}) + + def test_mean_scale_value_to_str(self): + values = [0.5, 1.3, 0.67] + self.assertTrue(mean_scale_value_to_str(values) == "[0.5,1.3,0.67]") + + values = {"input": [0.5, 1.3, 0.67]} + self.assertTrue(mean_scale_value_to_str(values) == "input[0.5,1.3,0.67]") + + values = {"input1": [0.5, 1.3, 0.67], "input2": [4.2, 6.7, 3.15], "input3": [0.757, 4.6, 7.3]} + self.assertTrue(mean_scale_value_to_str(values) == + "input1[0.5,1.3,0.67],input2[4.2,6.7,3.15],input3[0.757,4.6,7.3]") + + self.assertRaises(Exception, mean_scale_value_to_str, **{"value": {("a", "b"): [0.5, 1.3, 0.67]}}) + self.assertRaises(Exception, mean_scale_value_to_str, **{"value": {"name": Dimension(1)}}) + self.assertRaises(Exception, mean_scale_value_to_str, **{"value": Dimension(1)}) + + def test_transform_param_to_str(self): + transform = 'MakeStateful' + self.assertTrue(transform_param_to_str(transform) == "MakeStateful") + + transform1 = ('LowLatency2', {'use_const_initializer': False}) + self.assertTrue(transform_param_to_str(transform1) == + "LowLatency2[use_const_initializer=False]") + + transform2 = ('MakeStateful', {'param_res_names': { + 'input_name_1': 'output_name_1', 'input_name_2': 'output_name_2'}}) + self.assertTrue(transform_param_to_str(transform2) == + "MakeStateful[param_res_names={\'input_name_1\':\'output_name_1\'," + "\'input_name_2\':\'output_name_2\'}]") + + transform = [transform1, transform2] + + self.assertTrue(transform_param_to_str(transform) == "LowLatency2[use_const_initializer=False]," + "MakeStateful[param_res_names={" + "\'input_name_1\':\'output_name_1\'," + "\'input_name_2\':\'output_name_2\'}]") + + self.assertRaises(Exception, transform_param_to_str, **{"value": ('LowLatency2', + {'use_const_initializer': False}, + "param")}) + self.assertRaises(Exception, transform_param_to_str, **{"value": (("a", "b"), {})}) + self.assertRaises(Exception, transform_param_to_str, **{"value": ('LowLatency2', Dimension(1))}) + self.assertRaises(Exception, transform_param_to_str, **{"value": ('LowLatency2', + {('a', 'b'): False})}) + self.assertRaises(Exception, transform_param_to_str, **{"value": Dimension(1)}) + + def test_input_shape_to_str(self): + input_shape1 = [1, 3, 100, 100] + self.assertTrue(input_shape_to_str(input_shape1) == "[1,3,100,100]") + + input_shape2 = PartialShape([1, 3, 100, 100]) + self.assertTrue(input_shape_to_str(input_shape2) == "[1,3,100,100]") + + input_shape3 = PartialShape([Dimension(-1), Dimension(2, -1), Dimension(-1, 10), 100, Dimension(2, 12)]) + self.assertTrue(input_shape_to_str(input_shape3) == "[?,2..,..10,100,2..12]") + + input_shape4 = PartialShape.dynamic() + self.assertTrue(input_shape_to_str(input_shape4) == "[...]") + + input_shape5 = Shape([1, 2, 3, 4]) + self.assertTrue(input_shape_to_str(input_shape5) == "[1,2,3,4]") + + input_shape6 = [Dimension(-1), Dimension(2, -1), Dimension(-1, 10), 100, Dimension(2, 12)] + self.assertTrue(input_shape_to_str(input_shape6) == "[?,2..,..10,100,2..12]") + + input_shape = [input_shape1, input_shape2, input_shape3, input_shape4, input_shape5, input_shape6] + self.assertTrue(input_shape_to_str(input_shape) == "[1,3,100,100],[1,3,100,100],[?,2..,..10,100,2..12]," + "[...],[1,2,3,4],[?,2..,..10,100,2..12]") + + self.assertRaises(Exception, input_shape_to_str, **{"input_shape": [np.int, 1]}) + self.assertRaises(Exception, input_shape_to_str, **{"input_shape": Dimension(1)}) + + def test_str_list_to_str(self): + list_str = ["data1", "data2", "data3"] + self.assertTrue(str_list_to_str(list_str) == "data1,data2,data3") + + list_str = "data1" + self.assertTrue(str_list_to_str(list_str) == "data1") + + self.assertRaises(Exception, str_list_to_str, **{"values": [np.int, 1]}) + self.assertRaises(Exception, str_list_to_str, **{"values": Dimension(1)}) + + def test_source_target_layout_to_str(self): + layout = {"input1": Layout("nhwc"), "input2": Layout("n??"), "input3": "nchw"} + self.assertTrue(source_target_layout_to_str(layout) == "input1([N,H,W,C]),input2([N,?,?]),input3(nchw)") + + self.assertRaises(Exception, source_target_layout_to_str, **{"value": {"op": Dimension(1)}}) + self.assertRaises(Exception, source_target_layout_to_str, **{"value": {("a", "b"): Layout("nhwc")}}) + self.assertRaises(Exception, source_target_layout_to_str, **{"value": Dimension(1)}) + + def test_layout_param_to_str_to_str(self): + layout = {"input1": Layout("nhwc"), "input2": Layout("n??"), "input3": "nchw"} + self.assertTrue(layout_param_to_str(layout) == "input1([N,H,W,C]),input2([N,?,?]),input3(nchw)") + + layout_map1 = LayoutMap(source_layout=Layout("n??"), target_layout=None) + layout_map2 = LayoutMap(source_layout=Layout("nhwc"), target_layout=("nchw")) + layout_map3 = LayoutMap(source_layout="abc", target_layout="cab") + + layout = {"input1": layout_map1, "input2": layout_map2, "input3": layout_map3, "input4": Layout("nhwc"), + "input5": "n?"} + + self.assertTrue(layout_param_to_str(layout) == "input1([N,?,?]),input2([N,H,W,C]->nchw)," + "input3(abc->cab),input4([N,H,W,C]),input5(n?)") + + self.assertRaises(Exception, layout_param_to_str, **{"value": {"op": Dimension(1)}}) + self.assertRaises(Exception, layout_param_to_str, **{"value": {("a", "b"): Layout("nhwc")}}) + self.assertRaises(Exception, layout_param_to_str, **{"value": Dimension(1)}) diff --git a/tools/mo/unit_tests/mo/utils/freeze_placholder_test.py b/tools/mo/unit_tests/mo/utils/freeze_placholder_test.py index 1a02fcb3134..726777f1eda 100644 --- a/tools/mo/unit_tests/mo/utils/freeze_placholder_test.py +++ b/tools/mo/unit_tests/mo/utils/freeze_placholder_test.py @@ -6,7 +6,7 @@ from unittest.mock import patch, Mock import pytest from openvino.runtime import Core -from openvino.tools.mo.main import prepare_ir +from openvino.tools.mo.convert_impl import prepare_ir from openvino.frontend import ( FrontEndManager, FrontEnd, @@ -146,7 +146,7 @@ class TestMoFreezePlaceholder(unittest.TestCase): ], ) def test_freeze_placeholder_with_value_onnx_fe(self, input_freezing_value, use_new_fe, inputs, expected, dtype=None): - with patch("openvino.tools.mo.main.get_default_frontends") as default_fe: + with patch("openvino.tools.mo.convert_impl.get_default_frontends") as default_fe: default_fe.return_value = get_test_default_frontends() args = base_args_config(use_new_fe=use_new_fe) args.input_model = "test_model.onnx" @@ -213,7 +213,7 @@ class TestMoFreezePlaceholder(unittest.TestCase): ], ) def test_freeze_placeholder_with_value_mul(self, input_freezing_value, use_new_fe, inputs, expected, dtype=None): - with patch("openvino.tools.mo.main.get_default_frontends") as default_fe: + with patch("openvino.tools.mo.convert_impl.get_default_frontends") as default_fe: default_fe.return_value = get_test_default_frontends() args = base_args_config(use_new_fe=use_new_fe) args.input_model = "test_model_2.onnx" diff --git a/tools/mo/unit_tests/mo/utils/mo_fallback_test_actual.py b/tools/mo/unit_tests/mo/utils/mo_fallback_test_actual.py index ca7e09136d0..0ecf8e27c57 100644 --- a/tools/mo/unit_tests/mo/utils/mo_fallback_test_actual.py +++ b/tools/mo/unit_tests/mo/utils/mo_fallback_test_actual.py @@ -5,7 +5,7 @@ import unittest from unittest.mock import patch, Mock import openvino -from openvino.tools.mo.main import prepare_ir +from openvino.tools.mo.convert_impl import prepare_ir from openvino.tools.mo.utils.error import Error from openvino.frontend import FrontEndManager, FrontEnd # pylint: disable=no-name-in-module,import-error from onnx.helper import make_graph, make_model, make_tensor_value_info @@ -199,15 +199,15 @@ class TestMoFallback(unittest.TestCase): shutil.rmtree(self.paddle_dir) - @generate(*[('dir_to_extension', None, None, 'mo_legacy', 'extensions'), # fallback - ('dir_to_extension', None, True, None, None), # exception - ('dir_to_extension', True, None, 'mo_legacy', None), - ('', True, None, 'mo_legacy', None), - ('', None, True, 'onnx_frontend', None), + @generate(*[(['dir_to_extension'], None, None, 'mo_legacy', 'extensions'), # fallback + (['dir_to_extension'], None, True, None, None), # exception + (['dir_to_extension'], True, None, 'mo_legacy', None), + ([''], True, None, 'mo_legacy', None), + ([''], None, True, 'onnx_frontend', None), (None, None, None, 'onnx_frontend', None), ]) def test_fallback_if_extension_specified(self, extension, use_legacy, use_new_fe, conversion_method, fallback_reason): - with patch('openvino.tools.mo.main.get_default_frontends') as default_fe: + with patch('openvino.tools.mo.convert_impl.get_default_frontends') as default_fe: default_fe.return_value = get_test_default_frontends() args = base_args_config(use_legacy, use_new_fe) args.extensions = extension @@ -231,10 +231,10 @@ class TestMoFallback(unittest.TestCase): (None, True, 'onnx_frontend'), ]) def test_fallback_if_new_extension_specified(self, use_legacy, use_new_fe, conversion_method): - with patch('openvino.tools.mo.main.get_default_frontends') as default_fe: + with patch('openvino.tools.mo.convert_impl.get_default_frontends') as default_fe: default_fe.return_value = get_test_default_frontends() args = base_args_config(use_legacy, use_new_fe) - args.extensions = 'onnx_fe_ext.so' + args.extensions = ['onnx_fe_ext.so'] args.input_model = "test_model.onnx" if conversion_method: @@ -250,10 +250,10 @@ class TestMoFallback(unittest.TestCase): (None, True, 'onnx_frontend'), ]) def test_fallback_if_two_new_extension_specified(self, use_legacy, use_new_fe, conversion_method): - with patch('openvino.tools.mo.main.get_default_frontends') as default_fe: + with patch('openvino.tools.mo.convert_impl.get_default_frontends') as default_fe: default_fe.return_value = get_test_default_frontends() args = base_args_config(use_legacy, use_new_fe) - args.extensions = 'onnx_fe_ext.so,onnx_fe_ext_2.so' + args.extensions = ['onnx_fe_ext.so', 'onnx_fe_ext_2.so'] args.input_model = "test_model.onnx" if conversion_method: @@ -270,7 +270,7 @@ class TestMoFallback(unittest.TestCase): (None, None, None, 'onnx_frontend', None), ]) def test_fallback_if_tranformations_config_specified(self, trans_config, use_legacy, use_new_fe, expected_path, fallback_reason): - with patch('openvino.tools.mo.main.get_default_frontends') as default_fe: + with patch('openvino.tools.mo.convert_impl.get_default_frontends') as default_fe: default_fe.return_value = get_test_default_frontends() args = base_args_config(use_legacy, use_new_fe) args.input_model = "test_model.onnx" @@ -291,7 +291,7 @@ class TestMoFallback(unittest.TestCase): ('test_config_3.json', None, None, 'mo_legacy', 'transformations_config'), # 'library' attribute in no transformations ]) def test_fallback_if_new_tranformations_config_specified(self, trans_config, use_legacy, use_new_fe, conversion_method, fallback_reason): - with patch('openvino.tools.mo.main.get_default_frontends') as default_fe: + with patch('openvino.tools.mo.convert_impl.get_default_frontends') as default_fe: default_fe.return_value = get_test_default_frontends() args = base_args_config(use_legacy, use_new_fe) args.input_model = "test_model.onnx" @@ -313,7 +313,7 @@ class TestMoFallback(unittest.TestCase): def test_exception_if_new_trans_config_on_legacy_path(self): - with patch('openvino.tools.mo.main.get_default_frontends') as default_fe: + with patch('openvino.tools.mo.convert_impl.get_default_frontends') as default_fe: default_fe.return_value = get_test_default_frontends() args = base_args_config(use_legacy_fe=True) args.input_model = "test_model.onnx" @@ -325,7 +325,7 @@ class TestMoFallback(unittest.TestCase): def test_exeption_if_mixed_types_of_trans_configs(self): - with patch('openvino.tools.mo.main.get_default_frontends') as default_fe: + with patch('openvino.tools.mo.convert_impl.get_default_frontends') as default_fe: default_fe.return_value = get_test_default_frontends() args = base_args_config() args.input_model = "test_model.onnx" @@ -335,13 +335,13 @@ class TestMoFallback(unittest.TestCase): prepare_ir(args) - @generate(*[('dir_to_extension', 'fake_config.json', None, 'mo_legacy', 'extensions, transformations_config'), # fallback + @generate(*[(['dir_to_extension'], 'fake_config.json', None, 'mo_legacy', 'extensions, transformations_config'), # fallback (None, 'fake_config.json', None, 'mo_legacy', 'transformations_config'), # fallback - ('dir_to_extension', None, None, 'mo_legacy', 'extensions'), # fallback + (['dir_to_extension'], None, None, 'mo_legacy', 'extensions'), # fallback (None, None, True, 'onnx_frontend', None), ]) def test_fallback_if_both_extension_and_trans_config_specified(self, extension, trans_config, use_new_fe, expected_path, fallback_reason): - with patch('openvino.tools.mo.main.get_default_frontends') as default_fe: + with patch('openvino.tools.mo.convert_impl.get_default_frontends') as default_fe: default_fe.return_value = get_test_default_frontends() args = base_args_config(use_new_fe=use_new_fe) args.extensions = extension @@ -363,7 +363,7 @@ class TestMoFallback(unittest.TestCase): (None, None, True, 'onnx_frontend'), ]) def test_fallback_if_legacy_set_as_default(self, trans_config, use_legacy, use_new_fe, expected_path): - with patch('openvino.tools.mo.main.get_default_frontends') as default_fe: + with patch('openvino.tools.mo.convert_impl.get_default_frontends') as default_fe: default_fe.return_value = {'onnx': 'legacy', 'tf': 'legacy'} args = base_args_config(use_legacy, use_new_fe) args.input_model = "test_model.onnx" @@ -376,7 +376,7 @@ class TestMoFallback(unittest.TestCase): tm.Telemetry.send_event.assert_any_call('mo', 'fallback_reason') - @generate(*[(None, None, 'test_config_1.json', 'paddle_frontend'), + @generate(*[(None, None, ['test_config_1.json'], 'paddle_frontend'), (True, None, None, 'paddle_frontend'), (None, None, None, 'paddle_frontend'), ]) @@ -396,7 +396,7 @@ class TestMoFallback(unittest.TestCase): def test_exception_if_old_extensions_used_for_pdpd(self): args = base_args_config() args.framework = 'paddle' - args.extensions = 'dir_to_extension' + args.extensions = ['dir_to_extension'] args.input_model = 'paddle_dir/relu/relu.pdmodel' with pytest.raises(Error) as ex: # not called diff --git a/tools/mo/unit_tests/mo/utils/test_mo_model_analysis_actual.py b/tools/mo/unit_tests/mo/utils/test_mo_model_analysis_actual.py index 19d7f782b84..3c4bbb3824b 100644 --- a/tools/mo/unit_tests/mo/utils/test_mo_model_analysis_actual.py +++ b/tools/mo/unit_tests/mo/utils/test_mo_model_analysis_actual.py @@ -9,7 +9,7 @@ import os from os import environ import json import argparse -from openvino.tools.mo.main import prepare_ir +from openvino.tools.mo.convert_impl import prepare_ir from openvino.frontend import FrontEndManager # pylint: disable=no-name-in-module,import-error from openvino.tools.mo.moc_frontend.analysis import json_model_analysis_dump