diff --git a/.ci/azure/linux.yml b/.ci/azure/linux.yml index f65ad4b4ca8..47a33eb51e8 100644 --- a/.ci/azure/linux.yml +++ b/.ci/azure/linux.yml @@ -544,6 +544,33 @@ jobs: displayName: 'TensorFlow 2 Layer Tests - Legacy FE' continueOnError: false + - script: | + . $(PY_VENV)/bin/activate + python3 -m pip install -r $(LAYER_TESTS_DIR)/requirements.txt + export PYTHONPATH=$(LAYER_TESTS_DIR):$PYTHONPATH + export TEST_DEVICE=CPU + $(RUN_PREFIX) python3 -m pytest $(LAYER_TESTS_DIR)/mo_python_api_tests/test_mo_convert_complex_params.py --ir_version=11 --junitxml=./TEST-test_mo_convert_complex_params.xmlTEST + displayName: 'MO Python API Tests - Complex Python params' + continueOnError: false + + - script: | + . $(PY_VENV)/bin/activate + python3 -m pip install -r $(LAYER_TESTS_DIR)/requirements.txt + export PYTHONPATH=$(LAYER_TESTS_DIR):$PYTHONPATH + export TEST_DEVICE=CPU + $(RUN_PREFIX) python3 -m pytest $(LAYER_TESTS_DIR)/mo_python_api_tests/test_mo_convert_tf.py --ir_version=11 --junitxml=./TEST-test_mo_convert_tf.xmlTEST + displayName: 'MO Python API Tests - Import TF model from memory' + continueOnError: false + + - script: | + . $(PY_VENV)/bin/activate + python3 -m pip install -r $(LAYER_TESTS_DIR)/requirements.txt + export PYTHONPATH=$(LAYER_TESTS_DIR):$PYTHONPATH + export TEST_DEVICE=CPU + $(RUN_PREFIX) python3 -m pytest $(LAYER_TESTS_DIR)/mo_python_api_tests/test_mo_convert_pytorch.py --ir_version=11 --junitxml=./TEST-test_mo_convert_pytorch.xmlTEST + displayName: 'MO Python API Tests - Import PyTorch model from memory' + continueOnError: false + - task: PublishTestResults@2 condition: always() inputs: diff --git a/src/bindings/python/src/pyopenvino/test_utils/test_utils.cpp b/src/bindings/python/src/pyopenvino/test_utils/test_utils.cpp index 49615f7612d..1bf0c4a2244 100644 --- a/src/bindings/python/src/pyopenvino/test_utils/test_utils.cpp +++ b/src/bindings/python/src/pyopenvino/test_utils/test_utils.cpp @@ -13,16 +13,20 @@ namespace py = pybind11; PYBIND11_MODULE(test_utils_api, m) { m.def( "compare_functions", - [](const ov::Model& lhs, const ov::Model& rhs) { + [](const ov::Model& lhs, const ov::Model& rhs, bool compare_tensor_names) { const auto lhs_ptr = std::const_pointer_cast(lhs.shared_from_this()); const auto rhs_ptr = std::const_pointer_cast(rhs.shared_from_this()); - const auto fc = FunctionsComparator::with_default() - .enable(FunctionsComparator::ATTRIBUTES) - .enable(FunctionsComparator::CONST_VALUES); + auto fc = FunctionsComparator::with_default() + .enable(FunctionsComparator::ATTRIBUTES) + .enable(FunctionsComparator::CONST_VALUES); + + if (!compare_tensor_names) + fc.disable(FunctionsComparator::TENSOR_NAMES); const auto results = fc.compare(lhs_ptr, rhs_ptr); return std::make_pair(results.valid, results.message); }, py::arg("lhs"), - py::arg("rhs")); + py::arg("rhs"), + py::arg("compare_tensor_names") = true); } diff --git a/tests/layer_tests/common/mo_convert_test_class.py b/tests/layer_tests/common/mo_convert_test_class.py index 5b6732ca865..ef53dc35d46 100644 --- a/tests/layer_tests/common/mo_convert_test_class.py +++ b/tests/layer_tests/common/mo_convert_test_class.py @@ -4,8 +4,8 @@ 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 openvino.test_utils import compare_functions +from openvino.tools.mo import convert_model from common.utils.common_utils import generate_ir @@ -16,7 +16,7 @@ class CommonMOConvertTest: output_dir = kwargs['output_dir'] model_name = kwargs['model_name'] del kwargs['output_dir'] - model = convert(**kwargs) + model = convert_model(**kwargs) serialize(model, str(Path(output_dir, model_name + '.xml'))) def _test(self, temp_dir, test_params, ref_params): @@ -24,6 +24,9 @@ class CommonMOConvertTest: Generates two IRs using MO Python API and using cmd tool. Then two IRs are compared. """ + from openvino.runtime import Core + core = Core() + test_params.update({"model_name": 'model_test', "output_dir": temp_dir}) ref_params.update({"model_name": 'model_ref', "output_dir": temp_dir}) @@ -33,19 +36,25 @@ class CommonMOConvertTest: 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) + ir_test = core.read_model(Path(temp_dir, 'model_test.xml')) + ir_ref = core.read_model(Path(temp_dir, 'model_ref.xml')) - def _test_by_ref_graph(self, temp_dir, test_params, ref_graph): + flag, msg = compare_functions(ir_test, ir_ref) + assert flag, '\n'.join(msg) + + def _test_by_ref_graph(self, temp_dir, test_params, ref_graph, compare_tensor_names=True, compare_layout=True): """ Generates IR using MO Python API, reads it and compares with reference graph. """ + from openvino.runtime import Core + core = Core() + test_params.update({"model_name": 'model_test', "output_dir": temp_dir}) - self.generate_ir_python_api(**test_params) + ir_test = core.read_model(Path(temp_dir, 'model_test.xml')) + flag, msg = compare_functions(ir_test, ref_graph, compare_tensor_names=compare_tensor_names) + assert flag, msg - 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) + if compare_layout: + for idx in range(len(ir_test.inputs)): + assert ir_test.inputs[idx].node.layout == ref_graph.inputs[idx].node.layout 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 index f2c67faf71d..f93913d16f9 100644 --- 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 @@ -2,11 +2,15 @@ # SPDX-License-Identifier: Apache-2.0 import pytest +import numpy as np 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 +import openvino.runtime as ov +from openvino.runtime import PartialShape, Model + class TestExtensions(CommonMOConvertTest): def create_onnx_model(self, tmp_dir): @@ -77,44 +81,26 @@ class TestExtensions(CommonMOConvertTest): 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'} - } + shape = PartialShape([2, 3, 4]) + param = ov.opset8.parameter(shape, dtype=np.float32) + param.get_output_tensor(0).set_names({"input"}) + relu = ov.opset8.relu(param) + relu.get_output_tensor(0).set_names({"LeakyRelu_data"}) + elu = ov.opset8.elu(relu, alpha=0.1) + elu.get_output_tensor(0).set_names({"output"}) - return build_graph(nodes_attributes, - [('input', 'input_data'), - ('input_data', 'relu'), - ('relu', 'relu_data'), - ('relu_data', 'elu'), - ('elu', 'elu_data'), - ('elu_data', 'result'), - ]) + return Model([elu], [param], "test") 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'} - } + shape = PartialShape([2, 3, 4]) + param = ov.opset8.parameter(shape, dtype=np.float32) + param.get_output_tensor(0).set_names({"input"}) + relu = ov.opset8.relu(param) + relu.get_output_tensor(0).set_names({"LeakyRelu_data"}) + sigmoid = ov.opset8.sigmoid(relu) + sigmoid.get_output_tensor(0).set_names({"output"}) - return build_graph(nodes_attributes, - [('input', 'input_data'), - ('input_data', 'relu'), - ('relu', 'relu_data'), - ('relu_data', 'sigmoid'), - ('sigmoid', 'sigmoid_data'), - ('sigmoid_data', 'result'), - ]) + return Model([sigmoid], [param], "test") test_data = [ {'params_test': {'extensions': create_custom_extension_leaky_relu_to_relu()}, diff --git a/tests/layer_tests/mo_python_api_tests/test_mo_convert_pytorch.py b/tests/layer_tests/mo_python_api_tests/test_mo_convert_pytorch.py new file mode 100644 index 00000000000..d3826f3d80c --- /dev/null +++ b/tests/layer_tests/mo_python_api_tests/test_mo_convert_pytorch.py @@ -0,0 +1,528 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os + +import numpy +import numpy as np +import openvino.runtime as ov +import pytest +import torch +from openvino.runtime import PartialShape, Dimension, Model + +from common.mo_convert_test_class import CommonMOConvertTest + + +def make_pt_model_one_input(): + from torch import nn + class NeuralNetwork(nn.Module): + def __init__(self): + super(NeuralNetwork, self).__init__() + self.linear_relu_stack = nn.Sequential( + nn.ReLU(), + nn.Sigmoid(), + ) + + def forward(self, x): + logits = self.linear_relu_stack(x) + return logits + + return NeuralNetwork() + + +def make_pt_model_two_inputs(): + from torch import nn + class NeuralNetwork(nn.Module): + def __init__(self): + super(NeuralNetwork, self).__init__() + self.linear_relu_stack = nn.Sequential( + nn.ReLU(), + nn.Sigmoid(), + ) + + def forward(self, x, y): + logits = self.linear_relu_stack(x + y) + return logits + + return NeuralNetwork() + + +def make_ref_pt_model_one_input(shape, dtype=np.float32): + shape = PartialShape(shape) + param1 = ov.opset8.parameter(shape, name="input_0", dtype=dtype) + relu = ov.opset8.relu(param1) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1] + model = Model([sigm], parameter_list, "test") + return model + + +def make_ref_pt_model_two_inputs(shape, dtype=np.float32): + if len(shape) == 2: + param1 = ov.opset8.parameter(PartialShape(shape[0]), name="input_0", dtype=dtype) + param2 = ov.opset8.parameter(PartialShape(shape[1]), name="input_1", dtype=dtype) + else: + shape = PartialShape(shape) + param1 = ov.opset8.parameter(shape, name="input_0", dtype=dtype) + param2 = ov.opset8.parameter(shape, name="input_1", dtype=dtype) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model = Model([sigm], parameter_list, "test") + return model + + +def create_pytorch_nn_module_case1(tmp_dir): + pt_model = make_pt_model_two_inputs() + ref_model = make_ref_pt_model_two_inputs([-1, 3, -1, -1]) + + sample_input1 = torch.zeros(1, 3, 10, 10) + sample_input2 = torch.zeros(1, 3, 10, 10) + sample_input = sample_input1, sample_input2 + + return pt_model, ref_model, {'input_shape': [PartialShape([-1, 3, -1, -1]), PartialShape([-1, 3, -1, -1])], + 'example_input': sample_input} + + +def create_pytorch_nn_module_case2(tmp_dir): + pt_model = make_pt_model_two_inputs() + ref_model = make_ref_pt_model_two_inputs([-1, 3, -1, -1]) + + sample_input1 = torch.zeros(1, 3, 10, 10) + sample_input2 = torch.zeros(1, 3, 10, 10) + sample_input = sample_input1, sample_input2 + + return pt_model, ref_model, {'input_shape': ["[?,3,?,?]", PartialShape([-1, 3, -1, -1])], + 'example_input': sample_input, 'onnx_opset_version': 11} + + +def create_pytorch_nn_module_case3(tmp_dir): + pt_model = make_pt_model_two_inputs() + ref_model = make_ref_pt_model_two_inputs([-1, 3, -1, -1]) + + sample_input1 = torch.zeros(1, 3, 10, 10) + sample_input2 = torch.zeros(1, 3, 10, 10) + sample_input = tuple([sample_input1, sample_input2]) + + return pt_model, ref_model, {'input_shape': "[?,3,?,?],[?,3,?,?]", 'example_input': sample_input} + + +def create_pytorch_nn_module_case4(tmp_dir): + pt_model = make_pt_model_one_input() + + sample_input = torch.zeros(1, 3, 10, 10) + + ref_model = make_ref_pt_model_one_input([1, 3, 10, 10]) + + return pt_model, ref_model, {'example_input': sample_input} + + +def create_pytorch_nn_module_case5(tmp_dir): + pt_model = make_pt_model_one_input() + inp_shape = PartialShape([-1, 3, Dimension(2, -1), Dimension(-1, 10)]) + ref_model = make_ref_pt_model_one_input(inp_shape) + + sample_input = torch.zeros(3, 3, 10, 10) + return pt_model, ref_model, {'example_input': sample_input, + 'input_shape': inp_shape} + + +def create_pytorch_nn_module_case6(tmp_dir): + pt_model = make_pt_model_one_input() + shape = PartialShape([1, 3, Dimension(2, -1), Dimension(-1, 10)]) + ref_model = make_ref_pt_model_one_input(shape) + + return pt_model, ref_model, {'input_shape': shape} + + +def create_pytorch_nn_module_torch_size(tmp_dir): + pt_model = make_pt_model_one_input() + ref_model = make_ref_pt_model_one_input([1, 3, 2, 10]) + + return pt_model, ref_model, {'input_shape': torch.Size([1, 3, 2, 10])} + + +def create_pytorch_nn_module_sample_input_int32(tmp_dir): + pt_model = make_pt_model_one_input() + shape = PartialShape([-1, 3, Dimension(2, -1), Dimension(-1, 10)]) + + sample_input = torch.zeros(1, 3, 10, 10, dtype=torch.int32) + + ref_model = make_ref_pt_model_one_input(shape, dtype=numpy.int32) + + return pt_model, ref_model, {'example_input': sample_input, + 'input_shape': shape} + + +def create_pytorch_nn_module_sample_input_int32_two_inputs(tmp_dir): + pt_model = make_pt_model_two_inputs() + inp_shapes = ["[?,3,?,?]", PartialShape([-1, 3, -1, -1])] + + sample_input1 = torch.zeros(1, 3, 10, 10, dtype=torch.int32) + sample_input2 = torch.zeros(1, 3, 10, 10, dtype=torch.int32) + sample_input = sample_input1, sample_input2 + ref_model = make_ref_pt_model_two_inputs([PartialShape([-1, 3, -1, -1]), inp_shapes[1]], dtype=np.int32) + + return pt_model, ref_model, {'input_shape': inp_shapes, + 'example_input': sample_input, 'onnx_opset_version': 11} + + +def create_pytorch_nn_module_compare_convert_paths_case1(tmp_dir): + from openvino.tools.mo import convert_model + pt_model = make_pt_model_one_input() + + sample_input = torch.zeros(1, 3, 10, 10, dtype=torch.int32) + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, sample_input, onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + return pt_model, ref_model, {'example_input': sample_input, 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_compare_convert_paths_case2(tmp_dir): + from openvino.tools.mo import convert_model + pt_model = make_pt_model_one_input() + + sample_input = torch.zeros(1, 3, 10, 10, dtype=torch.int32) + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, sample_input, onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + return pt_model, ref_model, {'example_input': sample_input, + 'input_shape': [1, 3, 10, 10], + 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_compare_convert_paths_case3(tmp_dir): + from openvino.tools.mo import convert_model + pt_model = make_pt_model_one_input() + + sample_input = torch.zeros(1, 3, 10, 10, dtype=torch.float32) + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, sample_input, onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + return pt_model, ref_model, {'input_shape': [1, 3, 10, 10], + 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_compare_convert_paths_case4(tmp_dir): + from openvino.tools.mo import convert_model + pt_model = make_pt_model_two_inputs() + + sample_input1 = torch.zeros(1, 3, 10, 10, dtype=torch.int32) + sample_input2 = torch.zeros(1, 3, 10, 10, dtype=torch.int32) + sample_input = (sample_input1, sample_input2) + + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, sample_input, onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + + return pt_model, ref_model, {'example_input': sample_input, 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_compare_convert_paths_case5(tmp_dir): + from openvino.tools.mo import convert_model + pt_model = make_pt_model_two_inputs() + + sample_input1 = torch.zeros(1, 3, 10, 10, dtype=torch.int32) + sample_input2 = torch.zeros(1, 3, 10, 10, dtype=torch.int32) + sample_input = tuple([sample_input1, sample_input2]) + + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, sample_input, onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + + return pt_model, ref_model, {'example_input': sample_input, + 'input_shape': [torch.Size([1, 3, 10, 10]), PartialShape([1, 3, 10, 10])], + 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_compare_convert_paths_case6(tmp_dir): + from openvino.tools.mo import convert_model + pt_model = make_pt_model_two_inputs() + + sample_input1 = torch.zeros(1, 3, 10, 10, dtype=torch.float32) + sample_input2 = torch.zeros(1, 3, 10, 10, dtype=torch.float32) + sample_input = tuple([sample_input1, sample_input2]) + + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, sample_input, onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + + return pt_model, ref_model, {'input_shape': [torch.Size([1, 3, 10, 10]), torch.Size([1, 3, 10, 10])], + 'onnx_opset_version': 16} + + +def create_pytorch_jit_script_module(tmp_dir): + import torch + + net = make_pt_model_two_inputs() + scripted_model = torch.jit.script(net) + + model_ref = make_ref_pt_model_two_inputs([1, 3, 5, 5]) + return scripted_model, model_ref, {'input_shape': [PartialShape([1, 3, 5, 5]), PartialShape([1, 3, 5, 5])]} + + +def create_pytorch_jit_script_function(tmp_dir): + import torch + + @torch.jit.script + def scripted_fn(x: torch.Tensor, y: torch.Tensor): + return torch.sigmoid(torch.relu(x + y)) + + inp_shape = PartialShape([Dimension(1, -1), Dimension(-1, 5), 10]) + ref_model = make_ref_pt_model_two_inputs(inp_shape) + return scripted_fn, ref_model, {'input_shape': [inp_shape, inp_shape]} + + +def create_pytorch_nn_module_sample_input_numpy(tmp_dir): + from openvino.tools.mo import convert_model + pt_model = make_pt_model_one_input() + + example_inputs = np.array(torch.zeros(1, 3, 10, 10, dtype=torch.int32)) + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, torch.zeros(1, 3, 10, 10, dtype=torch.int32), onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + return pt_model, ref_model, {'example_input': example_inputs, + 'input_shape': [1, 3, 10, 10], + 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_sample_input_dict(tmp_dir): + from openvino.tools.mo import convert_model + pt_model = make_pt_model_one_input() + + example_inputs = {"x": np.array(torch.zeros(1, 3, 10, 10, dtype=torch.int32))} + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, torch.zeros(1, 3, 10, 10, dtype=torch.int32), onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + return pt_model, ref_model, {'example_input': example_inputs, + 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_sample_input_dict_two_inputs(tmp_dir): + from openvino.tools.mo import convert_model + pt_model = make_pt_model_two_inputs() + + example_inputs = {"y": np.array(torch.zeros(1, 3, 10, 10, dtype=torch.int32)), + "x": np.array(torch.zeros(1, 3, 10, 10, dtype=torch.int32))} + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, {"y": torch.zeros(1, 3, 10, 10, dtype=torch.int32), + "x": torch.zeros(1, 3, 10, 10, dtype=torch.int32)}, onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + return pt_model, ref_model, {'example_input': example_inputs, + 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_sample_list_of_tensors(tmp_dir): + from openvino.tools.mo import convert_model + pt_model = make_pt_model_one_input() + + example_inputs = [torch.zeros(3, 10, 10, dtype=torch.float32)] + + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, torch.unsqueeze(example_inputs[0], 0), onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + return pt_model, ref_model, {'example_input': example_inputs, + 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_sample_input_ov_host_tensor(tmp_dir): + from openvino.tools.mo import convert_model + from openvino.runtime import Tensor + pt_model = make_pt_model_one_input() + + sample_input = Tensor(np.zeros([1, 3, 10, 10], dtype=np.int32)) + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, torch.zeros(1, 3, 10, 10, dtype=torch.int32), onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + return pt_model, ref_model, {'example_input': sample_input, + 'input_shape': [1, 3, 10, 10], + 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_sample_input_ov_host_tensor_two_inputs(tmp_dir): + from openvino.tools.mo import convert_model + from openvino.runtime import Tensor + pt_model = make_pt_model_two_inputs() + + sample_input1 = Tensor(np.zeros([1, 3, 10, 10], dtype=np.int32)) + sample_input2 = Tensor(np.zeros([1, 3, 10, 10], dtype=np.int32)) + sample_input = sample_input1, sample_input2 + + onnx_model_path = os.path.join(tmp_dir, 'export.onnx') + torch.onnx.export(pt_model, tuple([torch.zeros(1, 3, 10, 10, dtype=torch.int32), + torch.zeros(1, 3, 10, 10, dtype=torch.int32)]), + onnx_model_path, opset_version=16) + + ref_model = convert_model(onnx_model_path) + + return pt_model, ref_model, {'example_input': sample_input, + 'onnx_opset_version': 16} + + +def create_pytorch_nn_module_layout_list(tmp_dir): + from openvino.runtime import Layout + pt_model = make_pt_model_two_inputs() + shape = [1, 3, 10, 10] + + shape = PartialShape(shape) + ref_model = make_ref_pt_model_two_inputs(shape) + ref_model.inputs[0].node.layout = Layout('nchw') + ref_model.inputs[1].node.layout = Layout('nhwc') + + return pt_model, ref_model, {'input_shape': [shape, shape], 'layout': ['nchw', Layout('nhwc')], + 'onnx_opset_version': 11} + + +def create_pytorch_nn_module_layout_list_case2(tmp_dir): + from openvino.runtime import Layout + pt_model = make_pt_model_two_inputs() + shape = [1, 3, 10, 10] + + shape = PartialShape(shape) + ref_model = make_ref_pt_model_two_inputs(shape) + ref_model.inputs[0].node.layout = Layout('nchw') + ref_model.inputs[1].node.layout = Layout('nhwc') + + return pt_model, ref_model, {'input_shape': [shape, shape], 'layout': ('nchw', Layout('nhwc')), + 'onnx_opset_version': 11} + + +def create_pytorch_nn_module_mean_list(tmp_dir): + pt_model = make_pt_model_two_inputs() + shape = [1, 10, 10, 3] + + shape = PartialShape(shape) + param1 = ov.opset8.parameter(shape) + param2 = ov.opset8.parameter(shape) + const1 = ov.opset8.constant([[[[0, 0, 0]]]], dtype=np.float32) + const2 = ov.opset8.constant([[[[0, 0, 0]]]], dtype=np.float32) + sub1 = ov.opset8.subtract(param1, const1) + sub2 = ov.opset8.subtract(param2, const2) + add = ov.opset8.add(sub1, sub2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + ref_model = Model([sigm], parameter_list, "test") + + return pt_model, ref_model, {'input_shape': [shape, shape], 'mean_values': [[0, 0, 0], [0, 0, 0]], + 'onnx_opset_version': 11} + + +def create_pytorch_nn_module_scale_list(tmp_dir): + pt_model = make_pt_model_two_inputs() + shape = [1, 10, 10, 3] + + shape = PartialShape(shape) + param1 = ov.opset8.parameter(shape) + param2 = ov.opset8.parameter(shape) + const1 = ov.opset8.constant([[[[1, 1, 1]]]], dtype=np.float32) + const2 = ov.opset8.constant([[[[1, 1, 1]]]], dtype=np.float32) + sub1 = ov.opset8.multiply(param1, const1) + sub2 = ov.opset8.multiply(param2, const2) + add = ov.opset8.add(sub1, sub2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + ref_model = Model([sigm], parameter_list, "test") + + return pt_model, ref_model, {'input_shape': [shape, shape], 'scale_values': [[1, 1, 1], [1, 1, 1]], + 'onnx_opset_version': 11} + + +def create_pytorch_nn_module_shapes_list_static(tmp_dir): + pt_model = make_pt_model_two_inputs() + ref_model = make_ref_pt_model_two_inputs([1, 3, 20, 20]) + + return pt_model, ref_model, {'input_shape': [[1, 3, 20, 20], [1, 3, 20, 20]], 'onnx_opset_version': 11} + + +def create_pytorch_nn_module_shapes_list_dynamic(tmp_dir): + pt_model = make_pt_model_two_inputs() + inp_shapes = [[Dimension(-1), 3, 20, Dimension(20, -1)], [-1, 3, 20, Dimension(-1, 20)]] + + param1 = ov.opset8.parameter(PartialShape(inp_shapes[0]), name="input_0", dtype=np.float32) + param2 = ov.opset8.parameter(PartialShape(inp_shapes[1]), name="input_1", dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + ref_model = Model([sigm], parameter_list, "test") + return pt_model, ref_model, {'input_shape': inp_shapes, 'onnx_opset_version': 11} + + +def create_pytorch_nn_module_shapes_list_dynamic_single_input(tmp_dir): + pt_model = make_pt_model_one_input() + inp_shapes = [[Dimension(-1), 3, 20, Dimension(20, -1)]] + ref_model = make_ref_pt_model_one_input(inp_shapes[0]) + return pt_model, ref_model, {'input_shape': inp_shapes, 'onnx_opset_version': 11} + + +def create_pytorch_nn_module_shapes_list_static_single_input(tmp_dir): + pt_model = make_pt_model_one_input() + inp_shapes = [[1, 3, 20, 20]] + ref_model = make_ref_pt_model_one_input(inp_shapes[0]) + return pt_model, ref_model, {'input_shape': inp_shapes, 'onnx_opset_version': 11} + + +class TestMoConvertPyTorch(CommonMOConvertTest): + test_data = [ + create_pytorch_nn_module_case1, + create_pytorch_nn_module_case2, + create_pytorch_nn_module_case3, + create_pytorch_nn_module_case4, + create_pytorch_nn_module_case5, + create_pytorch_nn_module_case6, + create_pytorch_nn_module_torch_size, + create_pytorch_nn_module_sample_input_int32, + create_pytorch_nn_module_sample_input_int32_two_inputs, + create_pytorch_nn_module_compare_convert_paths_case1, + create_pytorch_nn_module_compare_convert_paths_case2, + create_pytorch_nn_module_compare_convert_paths_case4, + create_pytorch_nn_module_compare_convert_paths_case5, + create_pytorch_nn_module_compare_convert_paths_case6, + create_pytorch_nn_module_sample_input_numpy, + create_pytorch_nn_module_sample_input_ov_host_tensor, + create_pytorch_nn_module_sample_input_ov_host_tensor_two_inputs, + create_pytorch_nn_module_sample_input_dict, + create_pytorch_nn_module_sample_input_dict_two_inputs, + create_pytorch_nn_module_sample_list_of_tensors, + create_pytorch_jit_script_module, + create_pytorch_jit_script_function, + create_pytorch_nn_module_layout_list, + create_pytorch_nn_module_layout_list_case2, + create_pytorch_nn_module_mean_list, + create_pytorch_nn_module_scale_list, + create_pytorch_nn_module_shapes_list_static, + create_pytorch_nn_module_shapes_list_dynamic, + create_pytorch_nn_module_shapes_list_dynamic_single_input, + create_pytorch_nn_module_shapes_list_static_single_input + ] + + @pytest.mark.parametrize("create_model", test_data) + @pytest.mark.nightly + @pytest.mark.precommit + def test_mo_import_from_memory(self, create_model, ie_device, precision, ir_version, + temp_dir, use_new_frontend, use_old_api): + fw_model, graph_ref, mo_params = create_model(temp_dir) + + test_params = {'input_model': fw_model} + if mo_params is not None: + test_params.update(mo_params) + self._test_by_ref_graph(temp_dir, test_params, graph_ref, compare_tensor_names=False) diff --git a/tests/layer_tests/mo_python_api_tests/test_mo_convert_tf.py b/tests/layer_tests/mo_python_api_tests/test_mo_convert_tf.py new file mode 100644 index 00000000000..16dbfc5fac7 --- /dev/null +++ b/tests/layer_tests/mo_python_api_tests/test_mo_convert_tf.py @@ -0,0 +1,356 @@ +# Copyright (C) 2018-2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import numpy as np +import openvino.runtime as ov +import pytest +from openvino.runtime import PartialShape, Model, Dimension + +from common.mo_convert_test_class import CommonMOConvertTest + + +def create_tf_graph_def(tmp_dir): + 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, 2, 3], 'Input') + inp2 = tf.compat.v1.placeholder(tf.float32, [1, 2, 3], 'Input') + relu = tf.nn.relu(inp1 + inp2, name='Relu') + + output = tf.nn.sigmoid(relu, name='Sigmoid') + + tf.compat.v1.global_variables_initializer() + tf_net = sess.graph_def + + shape = PartialShape([1, 2, 3]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + + return tf_net, model_ref, None + + +def create_keras_model(temp_dir): + import tensorflow as tf + + tf.keras.backend.clear_session() + tf.compat.v1.reset_default_graph() + + input_names = ["Input1", "Input2"] + input_shape = [1, 2, 3] + + x1 = tf.keras.Input(shape=input_shape, name=input_names[0]) + x2 = tf.keras.Input(shape=input_shape, name=input_names[1]) + y = tf.nn.sigmoid(tf.nn.relu(x1 + x2)) + keras_net = tf.keras.Model(inputs=[x1, x2], outputs=[y]) + + shape = PartialShape([-1, 1, 2, 3]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + tf.keras.backend.clear_session() + + return keras_net, model_ref, None + + +def create_tf1_wrap_function(tmp_dir): + import tensorflow as tf + + def f(x, y): + return tf.nn.sigmoid(tf.nn.relu(x + y)) + + func = tf.compat.v1.wrap_function(f, [tf.TensorSpec((1, 2, 3), tf.float32), + tf.TensorSpec((1, 2, 3), tf.float32)]) + + shape = PartialShape([1, 2, 3]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + + return func, model_ref, None + + +def create_tf_session(tmp_dir): + import tensorflow as tf + from tensorflow.python.eager.context import graph_mode + + + with graph_mode(): + tf.compat.v1.reset_default_graph() + sess = tf.compat.v1.Session() + inp1 = tf.compat.v1.placeholder(tf.float32, [1, 2, 3], 'Input1') + inp2 = tf.compat.v1.placeholder(tf.float32, [1, 2, 3], 'Input2') + relu = tf.nn.relu(inp1 + inp2, name='Relu') + + output = tf.nn.sigmoid(relu, name='Sigmoid') + + tf.compat.v1.global_variables_initializer() + + shape = PartialShape([1, 2, 3]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + + return sess, model_ref, None + + +def create_tf_module(tmp_dir): + import tensorflow as tf + + class Net(tf.Module): + def __init__(self, name=None): + super(Net, self).__init__(name=name) + + def __call__(self, x, y): + return tf.nn.sigmoid(tf.nn.relu(x + y)) + + shape = PartialShape([1, 2, 3]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + + net = Net() + return net, model_ref, {'input_shape': [PartialShape([1, 2, 3]), PartialShape([1, 2, 3])]} + + +def create_tf_module_layout_list(tmp_dir): + from openvino.runtime import Layout + import tensorflow as tf + + class Net(tf.Module): + def __init__(self, name=None): + super(Net, self).__init__(name=name) + + def __call__(self, x, y): + return tf.nn.sigmoid(tf.nn.relu(x + y)) + + shape = PartialShape([1, 2, 3]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + model_ref.inputs[0].node.layout = Layout('NCH') + model_ref.inputs[1].node.layout = Layout('NHC') + + net = Net() + return net, model_ref, {'input_shape': [PartialShape([1, 2, 3]), PartialShape([1, 2, 3])], 'layout': ["NCH", "NHC"]} + + +def create_tf_module_dynamic(tmp_dir): + import tensorflow as tf + + class Net(tf.Module): + def __init__(self, name=None): + super(Net, self).__init__(name=name) + + def __call__(self, x, y): + return tf.nn.sigmoid(tf.nn.relu(x + y)) + + shape = PartialShape([-1, 3, 4]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + + net = Net() + return net, model_ref, {'input_shape': [PartialShape([-1, Dimension(3, -1), Dimension(4)]), + PartialShape([-1, Dimension(3), Dimension(4, -1)])]} + +def create_keras_layer(tmp_dir): + import tensorflow as tf + class LayerModel(tf.keras.layers.Layer): + + def __init__(self): + super(LayerModel, self).__init__() + + def call(self, x, y): + return tf.sigmoid(tf.nn.relu(x + y)) + + shape = PartialShape([1, 2, 3]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + + net = LayerModel() + return net, model_ref, {'input_shape': [PartialShape([1, 2, 3]), PartialShape([1, 2, 3])]} + +def create_keras_layer_dynamic(tmp_dir): + import tensorflow as tf + class LayerModel(tf.keras.layers.Layer): + + def __init__(self): + super(LayerModel, self).__init__() + + def call(self, x, y): + return tf.sigmoid(tf.nn.relu(x + y)) + + shape = PartialShape([-1, 3, 4]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + + net = LayerModel() + return net, model_ref, {'input_shape': [PartialShape([-1, Dimension(3, -1), Dimension(4)]), + PartialShape([-1, Dimension(3), Dimension(4, -1)])]} + + +def create_tf_checkpoint(tmp_dir): + import tensorflow as tf + + input_names = ["Input1", "Input2"] + input_shape = [1, 2, 3] + + x1 = tf.keras.Input(shape=input_shape, name=input_names[0]) + x2 = tf.keras.Input(shape=input_shape, name=input_names[1]) + y = tf.nn.sigmoid(tf.nn.relu(x1 + x2)) + + model = tf.keras.Model(inputs=[x1, x2], outputs=[y]) + checkpoint = tf.train.Checkpoint(model) + + shape = PartialShape([-1, 1, 2, 3]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + + return checkpoint, model_ref, None + + +def create_tf_function(temp_dir): + import tensorflow as tf + + input_names = ["Input1", "Input2"] + input_shape = [1, 2, 3] + + x1 = tf.keras.Input(shape=input_shape, name=input_names[0]) + x2 = tf.keras.Input(shape=input_shape, name=input_names[1]) + y = tf.nn.sigmoid(tf.nn.relu(x1 + x2)) + keras_net = tf.keras.Model(inputs=[x1, x2], outputs=[y]) + + @tf.function( + input_signature=[tf.TensorSpec(shape=[1, 2, 3], dtype=tf.float32), + tf.TensorSpec(shape=[1, 2, 3], dtype=tf.float32)]) + def f(x): + return keras_net(x) + + shape = PartialShape([-1, 1, 2, 3]) + param1 = ov.opset8.parameter(shape, dtype=np.float32) + param2 = ov.opset8.parameter(shape, dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + + return keras_net, model_ref, None + + +def create_tf_saved_model(temp_dir): + import tensorflow as tf + + input_names = ["Input1", "Input2"] + input_shape = [1, 2, 3] + + x1 = tf.keras.Input(shape=input_shape, name=input_names[0]) + x2 = tf.keras.Input(shape=input_shape, name=input_names[1]) + y = tf.nn.sigmoid(tf.nn.relu(x1 + x2)) + keras_net = tf.keras.Model(inputs=[x1, x2], outputs=[y]) + + shape = PartialShape([-1, 1, 2, 3]) + param1 = ov.opset8.parameter(shape, name="Input1:0", dtype=np.float32) + param2 = ov.opset8.parameter(shape, name="Input2:0", dtype=np.float32) + add = ov.opset8.add(param1, param2) + relu = ov.opset8.relu(add) + sigm = ov.opset8.sigmoid(relu) + + parameter_list = [param1, param2] + model_ref = Model([sigm], parameter_list, "test") + + tf.saved_model.save(keras_net, temp_dir + "/model") + saved_model = tf.saved_model.load(temp_dir + "/model") + + return saved_model, model_ref, None + + +class TestMoConvertTF(CommonMOConvertTest): + test_data = [ + # TF2 + create_keras_model, + create_keras_layer, + create_tf_function, + create_tf_module, + create_tf_checkpoint, + create_tf_saved_model, + create_keras_layer_dynamic, + create_tf_module_dynamic, + create_tf_module_layout_list, + + + # TF1 + create_tf_graph_def, + create_tf1_wrap_function, + create_tf_session, + ] + + @pytest.mark.parametrize("create_model", test_data) + @pytest.mark.nightly + @pytest.mark.precommit_tf_fe + @pytest.mark.precommit + def test_mo_import_from_memory(self, create_model, ie_device, precision, ir_version, + temp_dir, use_new_frontend, use_old_api): + fw_model, graph_ref, mo_params = create_model(temp_dir) + + test_params = {'input_model': fw_model} + if mo_params is not None: + test_params.update(mo_params) + self._test_by_ref_graph(temp_dir, test_params, graph_ref, compare_tensor_names=False) diff --git a/tests/layer_tests/requirements.txt b/tests/layer_tests/requirements.txt index e79a0746982..1e21b2f438a 100644 --- a/tests/layer_tests/requirements.txt +++ b/tests/layer_tests/requirements.txt @@ -1,2 +1,4 @@ requests>=2.25.1 numpy>=1.19.2 +torch +pytest diff --git a/tools/mo/openvino/tools/mo/__init__.py b/tools/mo/openvino/tools/mo/__init__.py index c629cdc740b..7610e68ad73 100644 --- a/tools/mo/openvino/tools/mo/__init__.py +++ b/tools/mo/openvino/tools/mo/__init__.py @@ -1,4 +1,4 @@ # Copyright (C) 2018-2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from .convert import convert, InputCutInfo, LayoutMap +from .convert import convert_model, InputCutInfo, LayoutMap diff --git a/tools/mo/openvino/tools/mo/back/preprocessing.py b/tools/mo/openvino/tools/mo/back/preprocessing.py index ccb44eda2f3..3286bcd6871 100644 --- a/tools/mo/openvino/tools/mo/back/preprocessing.py +++ b/tools/mo/openvino/tools/mo/back/preprocessing.py @@ -59,6 +59,32 @@ def update_mean_scale_to_dict(input_nodes: list, mean_scale_val, scale): return mean_scale_val +def update_layout_to_dict(input_nodes: list, layout: [list, dict]): + """ + Internal function. Updates layout values from array to dictionary + :param: input_nodes Inputs of model + :param: layout Parsed 'layout' object from command line arguments + """ + if isinstance(layout, dict): + return layout + if isinstance(layout, list): + if len(layout) != len(input_nodes): + raise Error('Numbers of inputs and mean/scale values do not match. ' + refer_to_faq_msg(61)) + layout_dict = {} + for idx, node in enumerate(input_nodes): + names_list = list(node.get_tensor().get_names()) + if not names_list: + raise Error("Empty tensor names list for node {}".format(node.name)) + node_name = names_list[0] + layout_dict.update( + { + node_name: layout[idx] + } + ) + return layout_dict + raise Error("Unknown layout type. Expected dict, list. Got {}".format(type(layout))) + + def check_keys_valid(ov_function: Model, dict_to_validate: dict, search_outputs: bool): """ Internal function: checks if keys from cmd line arguments correspond to ov_function's inputs/outputs @@ -360,7 +386,7 @@ def apply_preprocessing(ov_function: Model, argv: argparse.Namespace): layout_values = {} if 'layout_values' in argv and argv.layout_values: - layout_values = argv.layout_values + layout_values = update_layout_to_dict(ov_function.inputs, argv.layout_values) if '' in layout_values: if len(ov_function.inputs) > 1: diff --git a/tools/mo/openvino/tools/mo/convert.py b/tools/mo/openvino/tools/mo/convert.py index 5fe9fc7ea80..ab13934a784 100644 --- a/tools/mo/openvino/tools/mo/convert.py +++ b/tools/mo/openvino/tools/mo/convert.py @@ -8,15 +8,36 @@ InputCutInfo = namedtuple("InputInfo", ["name", "shape", "type", "value"]) LayoutMap = namedtuple("LayoutMap", ["source_layout", "target_layout"]) -def convert(input_model=None, **args): +def convert_model(input_model=None, **args): """ Converts the model from original framework to OpenVino Model. Args: input_model: + Model object in original framework (PyTorch, Tensorflow) or path to model file. Tensorflow*: a file with a pre-trained model (binary or text .pb file after freezing). Caffe*: a model proto file with model weights + Supported formats of input model: + + PyTorch + torch.nn.Module + torch.jit.ScriptModule + torch.jit.ScriptFunction + + TF + tf.compat.v1.GraphDef + tf.compat.v1.wrap_function + tf.compat.v1.session + + TF2 / Keras + tf.keras.Model + tf.keras.layers.Layer + tf.function + tf.Module + tf.train.checkpoint + tf.python.training.tracking.base.Trackable for case when it is output from tf.saved_model.load() + Run convert(help=true) to list all available parameters. Returns: diff --git a/tools/mo/openvino/tools/mo/convert_impl.py b/tools/mo/openvino/tools/mo/convert_impl.py index 925e16d9d43..1f70b5aa75b 100644 --- a/tools/mo/openvino/tools/mo/convert_impl.py +++ b/tools/mo/openvino/tools/mo/convert_impl.py @@ -10,6 +10,8 @@ import sys from collections import OrderedDict from copy import deepcopy +import numpy as np + try: import openvino_telemetry as tm except ImportError: @@ -30,7 +32,7 @@ from openvino.tools.mo.utils.cli_parser import check_available_transforms, \ get_common_cli_options, get_freeze_placeholder_values, get_kaldi_cli_options, get_layout_values, \ get_mean_scale_dictionary, 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, depersonalize + get_all_cli_parser, mo_convert_params, get_model_name_from_args, split_shapes, depersonalize from openvino.tools.mo.utils.error import Error from openvino.tools.mo.utils.find_ie_version import find_ie_version @@ -46,6 +48,7 @@ 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 +from openvino.runtime import PartialShape, Dimension from openvino.runtime import get_version as get_rt_version @@ -158,6 +161,9 @@ def arguments_post_parsing(argv: argparse.Namespace): 'Please use --framework with one from the list: {}.', '--input_model', argv.input_model, frameworks) elif argv.framework not in frameworks: + if argv.framework == 'ir': + raise Error('OpenVINO IR is passed as input_model in convert_model/mo, the IR doesn\'t need ' + 'conversion, please use it in runtime for inference with read_model/compile_model.') raise Error('Framework {} is not a valid target. Please use --framework with one from the list: {}. ' + refer_to_faq_msg(15), argv.framework, frameworks) @@ -486,6 +492,229 @@ def emit_ir(graph: Graph, argv: argparse.Namespace, non_default_params: dict): return func +def get_static_shape(shape: [PartialShape, list, tuple], dynamic_value=None): + # Current function returns list with static dimensions with following logic. + # For dynamic dimensions return lower boundaries if they are set, otherwise + # return upper boundaries if they are set. If dimension is fully dynamic then raise error. + shape_list = [] + for idx, dim in enumerate(shape): + if isinstance(dim, int): + if dim == -1: + shape_list.append(dynamic_value) + continue + shape_list.append(dim) + elif isinstance(dim, np.int64): + if dim == np.int64(-1): + shape_list.append(dynamic_value) + continue + shape_list.append(dim) + elif isinstance(dim, tuple): + # tuple where (min_length, max_length), the format which uses MO cli parser + assert len(dim) == 2, "Unknown dimension type {}".format(dim) + if dim[0] > 0: + shape_list.append(dim[0]) + elif dim[1] < np.iinfo(np.int64).max: + shape_list.append(dim[1]) + else: + shape_list.append(dynamic_value) + continue + elif isinstance(dim, Dimension): + if dim.is_static or dim.get_min_length() > 0: + shape_list.append(dim.get_min_length()) + elif dim.get_max_length() != -1: + shape_list.append(dim.get_max_length()) + else: + shape_list.append(dynamic_value) + continue + else: + raise Error("Unknown dimension type {}".format(dim)) + + return tuple(shape_list) + + +def get_dynamic_dims(shape: [PartialShape, list, tuple]): + dynamic_dims = [] + for idx, dim in enumerate(shape): + if isinstance(dim, int): + if dim == -1: + dynamic_dims.append(idx) + if isinstance(dim, np.int64): + if dim == np.int64(-1): + dynamic_dims.append(idx) + elif isinstance(dim, tuple): + dynamic_dims.append(idx) + elif isinstance(dim, Dimension): + if dim.get_min_length() == 0 and dim.get_max_length() == -1: + dynamic_dims.append(idx) + + return dynamic_dims + + +def check_model_object(argv): + model = argv['input_model'] + if 'tensorflow' in sys.modules: + import tensorflow as tf + from tensorflow.python.training.tracking.base import Trackable + + if isinstance(model, tf.compat.v1.GraphDef): + return "tf" + if isinstance(model, tf.compat.v1.Session): + argv['input_model'] = model.graph_def + return "tf" + if isinstance(model, tf.types.experimental.ConcreteFunction): + argv['input_model'] = model.graph.as_graph_def() + return "tf" + if isinstance(model, tf.keras.Model): + return "tf" + if isinstance(model, tf.train.Checkpoint): + if isinstance(model.root, tf.keras.Model): + argv['input_model'] = model.root + return "tf" + else: + raise Error("Unknown checkpoint format.") + + if isinstance(model, tf.keras.layers.Layer) or isinstance(model, tf.Module): + assert 'input_shape' in argv and argv['input_shape'] is not None, \ + "Converting of {} requires providing of input_shape.".format(type(model)) + assert len(argv['input_shape']) > 0, "Please provide non-empty input shape." + inputs = [] + for shape_idx, shape in enumerate(parse_input_shapes(argv)): + inp_shape = get_static_shape(shape) + batch_size = None + if len(inp_shape) > 1: + batch_size = inp_shape[0] + inp_shape = inp_shape[1:] + inputs.append(tf.keras.Input(shape=inp_shape, batch_size=batch_size)) + outputs = model(*inputs) + argv['input_model'] = tf.keras.Model(inputs, outputs) + argv['input_shape'] = None + return "tf" + if isinstance(model, Trackable): + return "tf" + if 'torch' in sys.modules: + import torch + if isinstance(model, torch.nn.Module) or isinstance(model, torch.jit.ScriptFunction): + return "pytorch" + + import io + if isinstance(model, io.BytesIO): + return 'onnx' + + raise Error('Unknown model type: {}'.format(type(model))) + + +def get_onnx_temp_filename(output_dir): + output_dir = output_dir if output_dir is not None else os.getcwd() + return os.path.normpath(os.path.join(output_dir, "model.onnx")) + + +def to_torch_tensor(tensor): + import torch + from openvino.runtime import Tensor + if isinstance(tensor, torch.Tensor): + return tensor + if isinstance(tensor, np.ndarray): + return torch.tensor(tensor) + if isinstance(tensor, np.ndarray): + return torch.tensor(tensor) + if isinstance(tensor, Tensor): + return torch.tensor(tensor.data) + else: + raise Error("Unexpected type of example_input. Supported types torch.Tensor, np.array or ov.Tensor. " + "Got {}".format(type(tensor))) + + +def convert_pytorch_to_onnx(model, input_shape, opset_version, example_inputs, output_dir): + import io + import torch + + input_names = None + if example_inputs is not None: + inputs = example_inputs + if isinstance(inputs, list): + inputs = [to_torch_tensor(x) for x in inputs] + if len(inputs) == 1: + inputs = torch.unsqueeze(inputs[0], 0) + else: + inputs = inputs + elif isinstance(inputs, tuple): + inputs = [to_torch_tensor(x) for x in inputs] + inputs = tuple(inputs) + elif isinstance(inputs, dict): + for name, tensor in inputs.items(): + assert isinstance(name, str), "Expected dictionary where keys are input names of string type and" \ + " values are tensors. Got key of type {}".format(type(name)) + inputs[name] = to_torch_tensor(tensor) + else: + inputs = to_torch_tensor(inputs) + elif input_shape is not None: + inputs = [] + for shape_idx, shape in enumerate(input_shape): + static_shape = get_static_shape(shape, dynamic_value=1) + inputs.append(torch.zeros(static_shape)) + inputs = tuple(inputs) + else: + raise Error("Please provide input_shape or example_input for converting PyTorch model.") + + dynamic_dims_dict = {} + if input_shape is not None and input_names is None: + input_names = ["input_{}".format(idx) for idx in range(len(input_shape))] + for shape_idx, shape in enumerate(input_shape): + dynamic_dims = get_dynamic_dims(shape) + if len(dynamic_dims) > 0: + dynamic_dims_dict[input_names[shape_idx]] = dynamic_dims + additional_params = {} + if len(dynamic_dims_dict) > 0: + additional_params.update({'dynamic_axes': dynamic_dims_dict}) + if input_names is not None and len(input_names) > 0: + additional_params.update({'input_names': input_names}) + + if os.environ.get('SAVE_TO_BYTES_IO_ONNX_MODEL'): + model_onnx = io.BytesIO() + else: + model_onnx = get_onnx_temp_filename(output_dir) + if opset_version is not None: + additional_params.update({'opset_version': opset_version}) + + torch.onnx.export(model, + inputs, + model_onnx, + **additional_params) + return model_onnx + + +def parse_input_shapes(argv): + input_shapes = None + if 'input_shape' in argv and argv['input_shape'] is not None: + shapes = argv['input_shape'] + if isinstance(shapes, str): + shapes = ["[{}]".format(x) for x in split_shapes(shapes)] + if isinstance(shapes, list) or isinstance(shapes, tuple): + input_shapes = [] + is_single_shape = False + for shape in shapes: + if isinstance(shape, str): + _, shape_tuple, _ = get_placeholder_shapes(argv_input=None, argv_input_shape=shape) + input_shapes.append(shape_tuple) + if is_single_shape: + raise Error("Incorrect format of shape.") + elif isinstance(shape, int) or isinstance(shape, np.int64) or isinstance(shape, Dimension): + is_single_shape = True + input_shapes.append(shape) + else: + input_shapes.append(shape) + if is_single_shape: + return [input_shapes] + else: + return input_shapes + elif isinstance(shapes, PartialShape) or isinstance(shapes, torch.Size): + return [shapes] + else: + raise Error("Unknown type of input shape {}.".format(type(shapes))) + + return input_shapes + + def driver(argv: argparse.Namespace, non_default_params: dict): init_logger(argv.log_level.upper(), argv.silent) @@ -535,8 +764,13 @@ 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)) + + all_params = {} + for key, value in mo_convert_params.items(): + all_params.update(value) + for key, value in kwargs.items(): - if key not in argv and key not in mo_convert_params.keys(): + if key not in argv and key not in all_params.keys(): raise Error("Unrecognized argument: {}".format(key)) if value is not None: setattr(argv, key, value) @@ -551,19 +785,73 @@ def pack_params_to_args_namespace(**kwargs): def params_to_string(**kwargs): + all_params = {} + for key, value in mo_convert_params.items(): + all_params.update(value) + for key, value in kwargs.items(): - if key in mo_convert_params.keys(): - param_data = mo_convert_params[key] + if key in all_params: + param_data = all_params[key] if param_data.to_string is not None: kwargs[key] = param_data.to_string(value) return kwargs +def add_line_breaks(text: str, char_num: int, line_break: str): + words = text.split(" ") + cnt = 0 + for i, w in enumerate(words): + cnt += len(w) + if '\n' in w: + cnt = len(w) - w.find('\n') - 1 + if cnt > char_num: + if words[i][-1] not in ['\n', '\t']: + words[i] = w + '\n' + cnt = 0 + text = ' '.join(words).replace("\n ", "\n") + return line_break + text.replace("\n", line_break) + + 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))) + for group_name, group in mo_convert_params.items(): + if group_name == "optional": + print("optional arguments:") + elif group_name == "fw_agnostic": + print("Framework-agnostic parameters:") + elif group_name == "tf": + print("TensorFlow*-specific parameters:") + elif group_name == "caffe": + print("Caffe*-specific parameters:") + elif group_name == "mxnet": + print("Mxnet-specific parameters:") + elif group_name == "kaldi": + print("Kaldi-specific parameters:") + elif group_name == "pytorch": + print("Pytorch-specific parameters:") + else: + raise Error("Unknown parameters group {}.".format(group_name)) + for param_name in group: + param_data = group[param_name] + text = param_data.description.format(param_data.possible_types_python_api) + text = add_line_breaks(text, 56, "\n\t\t\t") + print(" --{} {}".format(param_name, text)) + print() + + +def input_model_is_object(argv): + if isinstance(argv['input_model'], str): + return False + if argv['input_model'] is None: + return False + return True + + +def remove_tmp_onnx_model(out_dir): + if not os.environ.get('SAVE_TO_BYTES_IO_ONNX_MODEL'): + tmp_onnx_model = get_onnx_temp_filename(out_dir) + + if os.path.exists(tmp_onnx_model): + os.remove(tmp_onnx_model) def _convert(**args): @@ -574,13 +862,63 @@ def _convert(**args): 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, non_default_params = pack_params_to_args_namespace(**args) - - if argv.model_name is None: - argv.model_name = get_model_name_from_args(argv) - try: + model_framework = None + inp_model_is_object = input_model_is_object(args) + if inp_model_is_object: + model_framework = check_model_object(args) + if model_framework == "pytorch" and not os.environ.get('USE_PYTORCH_FRONTEND'): + + opset_version = None + if 'onnx_opset_version' in args and args['onnx_opset_version'] is not None: + opset_version = args['onnx_opset_version'] + + example_inputs = None + if 'example_input' in args and args['example_input'] is not None: + example_inputs = args['example_input'] + + out_dir = args['output_dir'] if 'output_dir' in args else None + + model_onnx = convert_pytorch_to_onnx(args['input_model'], + parse_input_shapes(args), + opset_version, + example_inputs, + out_dir) + + + args['input_model'] = model_onnx + if os.environ.get('SAVE_TO_BYTES_IO_ONNX_MODEL'): + args['use_legacy_frontend'] = True + args['example_input'] = None + args['onnx_opset_version'] = None + + try: + ov_model = _convert(**args) + except Exception as e: + remove_tmp_onnx_model(out_dir) + raise e + + remove_tmp_onnx_model(out_dir) + return ov_model + args = params_to_string(**args) + argv, non_default_params = pack_params_to_args_namespace(**args) + + if inp_model_is_object: + argv.model_name = "model" + if argv.model_name is None: + argv.model_name = get_model_name_from_args(argv) + + if model_framework is not None: + if argv.framework is not None: + if argv.framework != model_framework: + raise Error("Provided model does not correspond to provided framework. The provided " + "framework is {}, the model type is {} which is expected to be {} framework.".format( + argv.framework, + type(argv.input_model), + model_framework)) + else: + argv.framework = model_framework + # 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) @@ -603,4 +941,4 @@ def _convert(**args): telemetry.send_event('mo', 'conversion_result', 'fail') telemetry.end_session('mo') telemetry.force_shutdown(1.0) - raise e + raise e.with_traceback(None) diff --git a/tools/mo/openvino/tools/mo/front/extractor.py b/tools/mo/openvino/tools/mo/front/extractor.py index 82772ace962..7082ebb13a0 100644 --- a/tools/mo/openvino/tools/mo/front/extractor.py +++ b/tools/mo/openvino/tools/mo/front/extractor.py @@ -633,6 +633,8 @@ def input_user_data_repack(graph: Graph, input_user_shapes: [None, list, dict, n if input_user_shapes is None: # None User did not provide neither --input nor --input_shape keys _input_shapes = None + elif isinstance(input_user_shapes, list) and len(input_user_shapes) > 1 and isinstance(input_user_shapes[0], PartialShape): + raise Error('Please provide input layer names for input layer shapes. ' + refer_to_faq_msg(58)) elif isinstance(input_user_shapes, list) or isinstance(input_user_shapes, dict): # list [layer names w or w/o ports]. User provided only --input key # dict {layer names w or w/o ports as keys: shapes as values}. User provided both --input and --input_shape diff --git a/tools/mo/openvino/tools/mo/front/tf/loader.py b/tools/mo/openvino/tools/mo/front/tf/loader.py index b3d6e66180e..ae74b6f7ef2 100644 --- a/tools/mo/openvino/tools/mo/front/tf/loader.py +++ b/tools/mo/openvino/tools/mo/front/tf/loader.py @@ -16,9 +16,6 @@ from openvino.tools.mo.utils.versions_checker import get_environment_setup os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' try: import tensorflow.compat.v1 as tf_v1 - - # disable eager execution of TensorFlow 2 environment immediately - tf_v1.disable_eager_execution() import tensorflow as tf from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2 except ImportError: @@ -175,9 +172,81 @@ def deducing_metagraph_path(meta_graph_file: str): return meta_graph_file +def freeze_tf2_concrete_function(model, concrete_func, env_setup): + + if "tensorflow" in env_setup and env_setup["tensorflow"] >= LooseVersion("2.2.0"): + frozen_func = convert_variables_to_constants_v2(concrete_func, + lower_control_flow=False, + aggressive_inlining=True) # pylint: disable=E1123 + else: + frozen_func = convert_variables_to_constants_v2(concrete_func, + lower_control_flow=False) # pylint: disable=E1123 + graph_def = frozen_func.graph.as_graph_def(add_shapes=True) + + input_names = [] + if hasattr(model, 'inputs') and model.inputs is not None: + # Extract tensor names order from Keras model + input_names = [tensor.name for tensor in model.inputs] + + # After model freezing output tensor names are changing and recieve "Func/PartitionedCall" prefix, + # so output_names from saved_model cannot be used. Here tensor names from frozen graph are used, + # as TF adds indexed Identity nodes during freezing to each output, so this indexing is used for + # order alignment. + output_names = [tensor.name for tensor in frozen_func.outputs] + + inputs_outputs_order = (input_names, output_names) + + return graph_def, {}, 'tf2', inputs_outputs_order + + +def prepare_graph_def(model): + from tensorflow.python.training.tracking.base import Trackable + if isinstance(model, tf_v1.GraphDef): + nodes_to_clear_device = model.node + for node in nodes_to_clear_device: + node.device = "" + return model, {}, "tf", None + if isinstance(model, tf.keras.Model): + env_setup = get_environment_setup("tf") + + assert hasattr(model, "inputs") and model.inputs is not None, "Model inputs specification is required." + + model_inputs = [] + for inp in model.inputs: + if isinstance(inp, tf.Tensor): + model_inputs.append(inp) + elif tf.keras.backend.is_keras_tensor(inp): + model_inputs.append(inp.type_spec) + else: + raise Error("Unknown input tensor type {}".format(type(input))) + + @tf.function + def tf_function(x): + return model(x) + + conc_func = tf_function.get_concrete_function(model_inputs) + return freeze_tf2_concrete_function(model, conc_func, env_setup) + if isinstance(model, Trackable): + env_setup = get_environment_setup("tf") + return saved_model_load(model, env_setup) + raise Exception("Unknown model type {}.".format(type(model))) + + +def saved_model_load(imported, env_setup): + # to get a signature by key throws KeyError for TF 1.x SavedModel format in case TF 2.x installed + concrete_func = imported.signatures[tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY] + # the aggressive inlining parameter needs to freeze a table of embeddings for Keras Embedding operation + # and a model with Embedding operation cannot properly converted to IR without this function parameter + + return freeze_tf2_concrete_function(imported, concrete_func, env_setup) + + def load_tf_graph_def(graph_file_name: str = "", is_binary: bool = True, checkpoint: str = "", model_dir: str = "", saved_model_tags: list = [], meta_graph_file: str = "", user_output_node_names_list: list = []): + + if not isinstance(graph_file_name, str) and graph_file_name is not None: + return prepare_graph_def(graph_file_name) # As a provisional solution, use a native TF methods to load a model protobuf graph_def = tf_v1.GraphDef() if isinstance(graph_file_name, str) and (re.match(r'.*\.(ckpt|meta)$', graph_file_name)): @@ -230,8 +299,6 @@ def load_tf_graph_def(graph_file_name: str = "", is_binary: bool = True, checkpo # saved model directory try: env_setup = get_environment_setup("tf") - # enable eager execution temporarily while TensorFlow 2 model is being loaded - tf_v1.enable_eager_execution() try: # Code to extract Keras model. @@ -241,38 +308,8 @@ def load_tf_graph_def(graph_file_name: str = "", is_binary: bool = True, checkpo except: imported = tf.saved_model.load(model_dir, saved_model_tags) # pylint: disable=E1120 - # to get a signature by key throws KeyError for TF 1.x SavedModel format in case TF 2.x installed - concrete_func = imported.signatures[tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY] - # the aggressive inlining parameter needs to freeze a table of embeddings for Keras Embedding operation - # and a model with Embedding operation cannot properly converted to IR without this function parameter - if "tensorflow" in env_setup and env_setup["tensorflow"] >= LooseVersion("2.2.0"): - frozen_func = convert_variables_to_constants_v2(concrete_func, - lower_control_flow=False, - aggressive_inlining=True) # pylint: disable=E1123 - else: - frozen_func = convert_variables_to_constants_v2(concrete_func, - lower_control_flow=False) # pylint: disable=E1123 - graph_def = frozen_func.graph.as_graph_def(add_shapes=True) - # disable eager execution since next steps are executed with a graph in non-eager mode - tf_v1.disable_eager_execution() - - input_names = [] - if hasattr(imported, 'inputs') and imported.inputs is not None: - # Extract tensor names order from Keras model - input_names = [tensor.name for tensor in imported.inputs] - - # After model freezing output tensor names are changing and recieve "Func/PartitionedCall" prefix, - # so output_names from saved_model cannot be used. Here tensor names from frozen graph are used, - # as TF adds indexed Identity nodes during freezing to each output, so this indexing is used for - # order alignment. - output_names = [tensor.name for tensor in frozen_func.outputs] - - inputs_outputs_order = (input_names, output_names) - - return graph_def, variables_values, 'tf2', inputs_outputs_order + return saved_model_load(imported, env_setup) except: - # disable eager execution since TensorFlow 1 model is handled - tf_v1.disable_eager_execution() # code to extract GraphDef for TF 1.0 SavedModel format tags = saved_model_tags if saved_model_tags is not None else [tf_v1.saved_model.tag_constants.SERVING] with tf_v1.Session() as sess: diff --git a/tools/mo/openvino/tools/mo/front/tf/partial_infer/tf.py b/tools/mo/openvino/tools/mo/front/tf/partial_infer/tf.py index 6e43c4890fb..6ac4d096e86 100644 --- a/tools/mo/openvino/tools/mo/front/tf/partial_infer/tf.py +++ b/tools/mo/openvino/tools/mo/front/tf/partial_infer/tf.py @@ -11,8 +11,6 @@ import numpy as np os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' try: import tensorflow.compat.v1 as tf_v1 - # disable eager execution of TensorFlow 2 environment immediately - tf_v1.disable_eager_execution() except ImportError: import tensorflow as tf_v1 @@ -20,6 +18,7 @@ except ImportError: tf_v1.get_logger().setLevel("ERROR") from google.protobuf import text_format +from tensorflow.python.eager.context import graph_mode from openvino.tools.mo.front.extractor import node_defs_to_str from openvino.tools.mo.front.tf.extractors.utils import tf_dtype_extractor, tf_tensor_shape, get_tf_node_port @@ -27,7 +26,6 @@ from openvino.tools.mo.graph.graph import Node from openvino.tools.mo.utils.graph import node_incoming_neighbourhood, node_outcoming_neighbourhood from openvino.tools.mo.front.common.partial_infer.utils import mo_array - def tf_native_tf_node_infer(node: Node): """ The infer function should be used to infer shape and data type of the TF operation not supported by IE. @@ -58,7 +56,9 @@ def tf_native_tf_node_infer(node: Node): for ind in range(len(tmp_node.out_edges())): tmp_node_attrs['output_tensors_names'].append(tmp_node.id + ":" + str(ind)) - tf_subgraph_infer(tmp_node) + with graph_mode(): + tf_subgraph_infer(tmp_node) + # the shape and value has been inferred and saved to the tmp_node's out nodes attribute. Let's copy it back! for tmp_out_port, tmp_out_node in tmp_node.out_nodes().items(): if tmp_out_node.value is not None: diff --git a/tools/mo/openvino/tools/mo/load/onnx/loader.py b/tools/mo/openvino/tools/mo/load/onnx/loader.py index ef86e35a6d2..58ae6e729d7 100644 --- a/tools/mo/openvino/tools/mo/load/onnx/loader.py +++ b/tools/mo/openvino/tools/mo/load/onnx/loader.py @@ -24,8 +24,16 @@ class ONNXLoader(Loader): run_not_recursively = True def load(self, graph: Graph): + import onnx + import io argv = graph.graph['cmd_params'] - model_proto = load_onnx_model(argv.input_model) + if isinstance(argv.input_model, str): + model_proto = load_onnx_model(argv.input_model) + elif isinstance(argv.input_model, io.BytesIO): + model_proto = onnx.load_model_from_string(argv.input_model.getvalue()) + else: + raise Error('Unknown ONNX model type: {}'.format(type(argv.input_model))) + model_graph = model_proto.graph # pylint: disable=no-member # print(model_graph) # assert len(model_graph) == 1, "An ONNX model contains more than 1 graph: unsupported" diff --git a/tools/mo/openvino/tools/mo/load/tf/loader.py b/tools/mo/openvino/tools/mo/load/tf/loader.py index cff517fb7b7..2c86f7aa58d 100644 --- a/tools/mo/openvino/tools/mo/load/tf/loader.py +++ b/tools/mo/openvino/tools/mo/load/tf/loader.py @@ -7,8 +7,6 @@ import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' try: import tensorflow.compat.v1 as tf_v1 - # disable eager execution of TensorFlow 2 environment immediately - tf_v1.disable_eager_execution() except ImportError: import tensorflow as tf_v1 diff --git a/tools/mo/openvino/tools/mo/main.py b/tools/mo/openvino/tools/mo/main.py index 55c363b0534..a78ce44b31f 100644 --- a/tools/mo/openvino/tools/mo/main.py +++ b/tools/mo/openvino/tools/mo/main.py @@ -11,7 +11,7 @@ try: except ImportError: import openvino.tools.mo.utils.telemetry_stub as tm -from openvino.tools.mo.convert import convert +from openvino.tools.mo.convert import convert_model from openvino.tools.mo.pipeline.common import get_ir_version from openvino.tools.mo.utils.cli_parser import get_model_name_from_args from openvino.tools.mo.utils.logger import init_logger @@ -40,7 +40,7 @@ def main(cli_parser: argparse.ArgumentParser, framework=None): ngraph_function = None try: - ngraph_function = convert(**argv) + ngraph_function = convert_model(**argv) ov_update_message = get_ov_update_message() ov_api20_message = get_ov_api20_message() if ov_update_message is not None: diff --git a/tools/mo/openvino/tools/mo/middle/CustomSubgraphCall.py b/tools/mo/openvino/tools/mo/middle/CustomSubgraphCall.py index b0719e8d8d8..12b46b8e2ca 100644 --- a/tools/mo/openvino/tools/mo/middle/CustomSubgraphCall.py +++ b/tools/mo/openvino/tools/mo/middle/CustomSubgraphCall.py @@ -55,8 +55,6 @@ class CustomSubgraphCall(MiddleReplacementPattern): """ try: import tensorflow.compat.v1 as tf_v1 - # disable eager execution of TensorFlow 2 environment immediately - tf_v1.disable_eager_execution() except ImportError: import tensorflow as tf_v1 # in some environment suppressing through TF_CPP_MIN_LOG_LEVEL does not work @@ -278,8 +276,6 @@ class CustomSubgraphCall(MiddleReplacementPattern): """ try: import tensorflow.compat.v1 as tf_v1 - # disable eager execution of TensorFlow 2 environment immediately - tf_v1.disable_eager_execution() except ImportError: import tensorflow as tf_v1 # in some environment suppressing through TF_CPP_MIN_LOG_LEVEL does not work diff --git a/tools/mo/openvino/tools/mo/moc_frontend/extractor.py b/tools/mo/openvino/tools/mo/moc_frontend/extractor.py index b8621de402b..641bbe530e9 100644 --- a/tools/mo/openvino/tools/mo/moc_frontend/extractor.py +++ b/tools/mo/openvino/tools/mo/moc_frontend/extractor.py @@ -188,7 +188,14 @@ def fe_input_user_data_repack( } """ _input_shapes = [] - if isinstance(input_user_shapes, list) or isinstance(input_user_shapes, dict): + if isinstance(input_user_shapes, list) and len(input_user_shapes) > 1 and isinstance(input_user_shapes[0], PartialShape): + for shape in input_user_shapes: + assert isinstance(shape, PartialShape), "Got incorrect format of input shapes." + model_inputs = input_model.get_inputs() + assert len(model_inputs) == len(input_user_shapes) + for idx, model_input in enumerate(model_inputs): + _input_shapes.append({"node": model_input, "shape": input_user_shapes[idx]}) + elif isinstance(input_user_shapes, list) or isinstance(input_user_shapes, dict): for input_name in input_user_shapes: node = decode_name_with_port( input_model, input_name, framework, IOType.Input diff --git a/tools/mo/openvino/tools/mo/moc_frontend/pipeline.py b/tools/mo/openvino/tools/mo/moc_frontend/pipeline.py index 4f4f5017770..796f48d3700 100644 --- a/tools/mo/openvino/tools/mo/moc_frontend/pipeline.py +++ b/tools/mo/openvino/tools/mo/moc_frontend/pipeline.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import argparse +import io import logging as log from typing import List import sys @@ -27,7 +28,11 @@ def moc_pipeline(argv: argparse.Namespace, moc_front_end: FrontEnd): :param: moc_front_end: Loaded Frontend for converting input model :return: converted nGraph function ready for serialization """ - input_model = moc_front_end.load(argv.input_model) + if isinstance(argv.input_model, io.BytesIO): + raise Exception("ONNX frontend does not support input model as BytesIO object. " + "Please use use_legacy_frontend=True to convert the model.") + else: + input_model = moc_front_end.load(argv.input_model) user_shapes, outputs, freeze_placeholder = fe_user_data_repack( input_model, argv.placeholder_shapes, argv.placeholder_data_types, @@ -80,6 +85,9 @@ def moc_pipeline(argv: argparse.Namespace, moc_front_end: FrontEnd): inputs_equal, outputs_equal)) def create_target_input_shapes(new_input_places): + if isinstance(new_input_places, list) and len(new_input_places) > 1 \ + and isinstance(new_input_places[0], tuple): + return new_input_places new_input_place_names = [x.get_names()[0] for x in new_input_places] shapes = [shape for shape in argv.placeholder_shapes.values()] return dict(zip(new_input_place_names, shapes)) diff --git a/tools/mo/openvino/tools/mo/utils/class_registration.py b/tools/mo/openvino/tools/mo/utils/class_registration.py index 2dd89f5beaf..e178af25fd6 100644 --- a/tools/mo/openvino/tools/mo/utils/class_registration.py +++ b/tools/mo/openvino/tools/mo/utils/class_registration.py @@ -63,12 +63,21 @@ class ClassType(Enum): def _update(cls, registered_list: list, registered_dict: dict, key: str, enabled_transforms: list, - disabled_transforms: list): + disabled_transforms: list, exclude_modules: set): new_keys = {} # maps a custom name to class new_keys_lower = {} # translates lowered custom name to its original form # print('Registering new subclasses for', cls) for c in cls.__subclasses__(): + # skip importing loaders of other frameworks + if cls.__name__ == 'Loader': + need_exclude = False + for framework in exclude_modules: + if framework in c.__module__: + need_exclude = True + break + if need_exclude: + continue # Force enabling operations if hasattr(c, 'id') and c.id in enabled_transforms or \ ".".join([c.__module__, c.__name__]) in enabled_transforms: @@ -87,10 +96,9 @@ def _update(cls, registered_list: list, registered_dict: dict, key: str, enabled if hasattr(c, key) and getattr(c, key) is not None: k = getattr(c, key) if k.lower() in new_keys_lower: - raise Error( - 'Attempt to register of custom name {} for the second time as class {}. ' \ - 'Note that custom names are case-insensitive. ' + - refer_to_faq_msg(55), k, c) + log.warning('Attempt to register of custom name {} for the second time as class {}. ' + 'Note that custom names are case-insensitive. ' + refer_to_faq_msg(55), k, c) + continue else: new_keys_lower[k.lower()] = k new_keys[k] = c @@ -100,9 +108,9 @@ def _update(cls, registered_list: list, registered_dict: dict, key: str, enabled registered_dict.update(new_keys) -def update_registration(classes: list, enabled_transforms: list, disabled_transforms: list): +def update_registration(classes: list, enabled_transforms: list, disabled_transforms: list, exclude_modules: set): for cls in classes: - _update(cls, cls.registered_cls, cls.registered_ops, 'op', enabled_transforms, disabled_transforms) + _update(cls, cls.registered_cls, cls.registered_ops, 'op', enabled_transforms, disabled_transforms, exclude_modules) _registered_classes_dict.setdefault(cls.class_type(), set()).add(cls) @@ -326,3 +334,7 @@ def apply_replacements(graph: Graph, replacements_type: list): """ replacers_order = get_replacers_order(replacements_type) apply_replacements_list(graph, replacers_order) + + +def clear_registered_classes_dict(): + _registered_classes_dict.clear() diff --git a/tools/mo/openvino/tools/mo/utils/cli_parser.py b/tools/mo/openvino/tools/mo/utils/cli_parser.py index 22cb9c95d3d..4c917c6bed9 100644 --- a/tools/mo/openvino/tools/mo/utils/cli_parser.py +++ b/tools/mo/openvino/tools/mo/utils/cli_parser.py @@ -74,6 +74,15 @@ def path_to_str(path): raise Exception("Incorrect type of {} expected str or Path, got {}".format(path, type(path))) +def path_to_str_or_object(value): + if value is None or isinstance(value, str): + return value + elif isinstance(value, Path): + return str(value) + else: + return value + + def paths_to_str(paths): if paths is None: return None @@ -271,7 +280,7 @@ def layout_to_str(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))) + "where key is operation name and value is layout or list of layouts, got {}".format(type(layout))) def source_target_layout_to_str(value): @@ -320,6 +329,13 @@ def layout_param_to_str(value): 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) + if isinstance(value, openvino.tools.mo.LayoutMap): + return layoutmap_to_str(value) + if isinstance(value, list) or isinstance(value, tuple): + values_str = [] + for layout in value: + values_str.append(layoutmap_to_str(layout)) + return ",".join(values_str) return layoutmap_to_str(value) @@ -401,13 +417,26 @@ def transform_param_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), + 'optional': + { + 'help': ParamDescription( + 'Print available parameters.', '', '', None), 'framework': ParamDescription( 'Name of the framework used to train the input model.', '', '', None), + }, + 'fw_agnostic': + { + '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', '', + 'Model object in original framework (PyTorch, Tensorflow) or path to model file. \n' + + 'Supported object formats of input model:\n PyTorch - torch.nn.Module, torch.jit.ScriptModule, torch.jit.ScriptFunction' + + 'TF - tf.compat.v1.GraphDef, tf.compat.v1.wrap_function, tf.compat.v1.session\n ' + + 'TF2 / Keras - tf.keras.Model, tf.keras.layers.Layer, tf.function, tf.Module, tf.train.checkpoint, ' + + 'tf.python.training.tracking.base.Trackable for case when it is output from tf.saved_model.load().\n' + + 'File formats examples:\n', + path_to_str_or_object), 'model_name': ParamDescription( 'Model_name parameter passed to the final create_ir transform. ' + 'This parameter is used to name ' + @@ -536,10 +565,11 @@ mo_convert_params = { '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=' + 'Examples:' + + ' "--transform LowLatency2" or \n' + + ' "--transform Pruning" or \n' + + ' "--transform LowLatency2[use_const_initializer=False]" or \n' + + ' "--transform \"MakeStateful[param_res_names=\n' '{{\'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 ' @@ -588,6 +618,17 @@ mo_convert_params = { '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), + }, + "caffe": + { + '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), '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. ' + @@ -598,6 +639,9 @@ mo_convert_params = { '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), + }, + "tf": + { '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), @@ -624,14 +668,9 @@ mo_convert_params = { '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), + }, + "mxnet": + { 'input_symbol': ParamDescription( 'Symbol file (for example, model-symbol.json) that contains a topology structure ' + 'and layer attributes', '', '', path_to_str), @@ -650,6 +689,9 @@ mo_convert_params = { 'enable_ssd_gluoncv': ParamDescription( "Enable pattern matchers replacers for converting gluoncv ssd topologies.", '', '', None), + }, + "kaldi": + { 'counts': ParamDescription( "Path to the counts file", '', '', path_to_str), 'remove_output_softmax': ParamDescription( @@ -657,8 +699,14 @@ mo_convert_params = { 'remove_memory': ParamDescription( "Removes the Memory layer and use additional inputs outputs instead", '', '', None), - 'help': ParamDescription( - 'Print available parameters.', '', '', None), + }, + "pytorch": + { + 'example_input': ParamDescription('Sample of model input in original framework. ' + 'For PyTorch it can be torch.Tensor.', '', '', None), + 'onnx_opset_version': ParamDescription('Version of ONNX opset that is used for converting from PyTorch to ONNX.', + '', '', None) + } } @@ -891,9 +939,10 @@ def get_common_cli_parser(parser: argparse.ArgumentParser = None): if not parser: parser = argparse.ArgumentParser() common_group = parser.add_argument_group('Framework-agnostic parameters') + mo_convert_params_common = mo_convert_params['fw_agnostic'] # Common parameters common_group.add_argument('--input_model', '-w', '-m', - help=mo_convert_params['input_model'].description, + help=mo_convert_params_common['input_model'].description, action=CanonicalizePathCheckExistenceAction, type=readable_file_or_dir) common_group.add_argument('--model_name', '-n', @@ -907,8 +956,8 @@ def get_common_cli_parser(parser: argparse.ArgumentParser = None): action=CanonicalizePathAction, type=writable_dir) common_group.add_argument('--input_shape', - help=mo_convert_params['input_shape'].description.format( - mo_convert_params['input_shape'].possible_types_command_line)) + help=mo_convert_params_common['input_shape'].description.format( + mo_convert_params_common['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 ' + @@ -935,39 +984,39 @@ def get_common_cli_parser(parser: argparse.ArgumentParser = None): 'DEBUG', 'NOTSET'], default='ERROR') common_group.add_argument('--input', - help=mo_convert_params['input'].description.format( - mo_convert_params['input'].possible_types_command_line)) + help=mo_convert_params_common['input'].description.format( + mo_convert_params_common['input'].possible_types_command_line)) common_group.add_argument('--output', - help=mo_convert_params['output'].description.format( - mo_convert_params['output'].possible_types_command_line)) + help=mo_convert_params_common['output'].description.format( + mo_convert_params_common['output'].possible_types_command_line)) common_group.add_argument('--mean_values', '-ms', - help=mo_convert_params['mean_values'].description.format( - mo_convert_params['mean_values'].possible_types_command_line), + help=mo_convert_params_common['mean_values'].description.format( + mo_convert_params_common['mean_values'].possible_types_command_line), default=()) common_group.add_argument('--scale_values', - help=mo_convert_params['scale_values'].description.format( - mo_convert_params['scale_values'].possible_types_command_line), + help=mo_convert_params_common['scale_values'].description.format( + mo_convert_params_common['scale_values'].possible_types_command_line), default=()) common_group.add_argument('--source_layout', - help=mo_convert_params['source_layout'].description.format( - mo_convert_params['source_layout'].possible_types_command_line), + help=mo_convert_params_common['source_layout'].description.format( + mo_convert_params_common['source_layout'].possible_types_command_line), default=()) common_group.add_argument('--target_layout', - help=mo_convert_params['target_layout'].description.format( - mo_convert_params['target_layout'].possible_types_command_line), + help=mo_convert_params_common['target_layout'].description.format( + mo_convert_params_common['target_layout'].possible_types_command_line), default=()) common_group.add_argument('--layout', - help=mo_convert_params['layout'].description.format( - mo_convert_params['layout'].possible_types_command_line), + help=mo_convert_params_common['layout'].description.format( + mo_convert_params_common['layout'].possible_types_command_line), default=()) # TODO: isn't it a weights precision type common_group.add_argument('--data_type', - help=mo_convert_params['data_type'].description, + help=mo_convert_params_common['data_type'].description, choices=["FP16", "FP32", "half", "float"], default='float') common_group.add_argument('--transform', - help=mo_convert_params['transform'].description.format( - mo_convert_params['transform'].possible_types_command_line), + help=mo_convert_params_common['transform'].description.format( + mo_convert_params_common['transform'].possible_types_command_line), default="") common_group.add_argument('--disable_fusing', help='[DEPRECATED] Turn off fusing of linear operations to Convolution.', @@ -984,22 +1033,22 @@ 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=mo_convert_params['extensions'].description.format( - mo_convert_params['extensions'].possible_types_command_line), + help=mo_convert_params_common['extensions'].description.format( + mo_convert_params_common['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=mo_convert_params['batch'].description) + help=mo_convert_params_common['batch'].description) common_group.add_argument("--version", action='version', version='Version of Model Optimizer is: {}'.format(get_version()), - help=mo_convert_params['version'].description) + help=mo_convert_params_common['version'].description) common_group.add_argument('--silent', - help=mo_convert_params['silent'].description, + help=mo_convert_params_common['silent'].description, type=check_bool, default=True) common_group.add_argument('--freeze_placeholder_with_value', @@ -1009,26 +1058,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=mo_convert_params['static_shape'].description, + help=mo_convert_params_common['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=mo_convert_params['progress'].description, + help=mo_convert_params_common['progress'].description, action='store_true', default=False) common_group.add_argument('--stream_output', - help=mo_convert_params['stream_output'].description, + help=mo_convert_params_common['stream_output'].description, action='store_true', default=False) common_group.add_argument('--transformations_config', - help=mo_convert_params['transformations_config'].description.format( - mo_convert_params['transformations_config'].possible_types_command_line), + help=mo_convert_params_common['transformations_config'].description.format( + mo_convert_params_common['transformations_config'].possible_types_command_line), action=CanonicalizeTransformationPathCheckExistenceAction) common_group.add_argument("--use_new_frontend", - help=mo_convert_params['use_new_frontend'].description, + help=mo_convert_params_common['use_new_frontend'].description, action='store_true', default=False) common_group.add_argument("--use_legacy_frontend", - help=mo_convert_params['use_legacy_frontend'].description, + help=mo_convert_params_common['use_legacy_frontend'].description, action='store_true', default=False) return parser @@ -1143,18 +1192,19 @@ def get_caffe_cli_parser(parser: argparse.ArgumentParser = None): get_common_cli_parser(parser=parser) caffe_group = parser.add_argument_group('Caffe*-specific parameters') + mo_convert_params_caffe = mo_convert_params['caffe'] caffe_group.add_argument('--input_proto', '-d', - help=mo_convert_params['input_proto'].description, + help=mo_convert_params_caffe['input_proto'].description, type=str, action=CanonicalizePathCheckExistenceAction) caffe_group.add_argument('--caffe_parser_path', - help=mo_convert_params['caffe_parser_path'].description, + help=mo_convert_params_caffe['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=mo_convert_params['k'].description, + help=mo_convert_params_caffe['k'].description, type=str, default=os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'extensions', 'front', 'caffe', @@ -1175,11 +1225,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=mo_convert_params['disable_omitting_optional'].description, + help=mo_convert_params_caffe['disable_omitting_optional'].description, action='store_true', default=False) caffe_group.add_argument('--enable_flattening_nested_params', - help=mo_convert_params['enable_flattening_nested_params'].description, + help=mo_convert_params_caffe['enable_flattening_nested_params'].description, action='store_true', default=False) return parser @@ -1196,39 +1246,40 @@ def get_tf_cli_parser(parser: argparse.ArgumentParser = None): if not parser: parser = argparse.ArgumentParser(usage='%(prog)s [options]') get_common_cli_parser(parser=parser) + mo_convert_params_tf = mo_convert_params['tf'] tf_group = parser.add_argument_group('TensorFlow*-specific parameters') tf_group.add_argument('--input_model_is_text', - help=mo_convert_params['input_model_is_text'].description, + help=mo_convert_params_tf['input_model_is_text'].description, action='store_true') tf_group.add_argument('--input_checkpoint', type=str, default=None, - help=mo_convert_params['input_checkpoint'].description, + help=mo_convert_params_tf['input_checkpoint'].description, action=CanonicalizePathCheckExistenceAction) tf_group.add_argument('--input_meta_graph', - help=mo_convert_params['input_meta_graph'].description, + help=mo_convert_params_tf['input_meta_graph'].description, action=CanonicalizePathCheckExistenceAction, type=readable_file) tf_group.add_argument('--saved_model_dir', default=None, - help=mo_convert_params['saved_model_dir'].description, + help=mo_convert_params_tf['saved_model_dir'].description, action=CanonicalizePathCheckExistenceAction, type=readable_dirs) tf_group.add_argument('--saved_model_tags', type=str, default=None, - help=mo_convert_params['saved_model_tags'].description) + help=mo_convert_params_tf['saved_model_tags'].description) tf_group.add_argument('--tensorflow_custom_operations_config_update', - help=mo_convert_params['tensorflow_custom_operations_config_update'].description, + help=mo_convert_params_tf['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) tf_group.add_argument('--tensorflow_object_detection_api_pipeline_config', - help=mo_convert_params['tensorflow_object_detection_api_pipeline_config'].description, + help=mo_convert_params_tf['tensorflow_object_detection_api_pipeline_config'].description, action=CanonicalizePathCheckExistenceAction) tf_group.add_argument('--tensorboard_logdir', - help=mo_convert_params['tensorboard_logdir'].description, + help=mo_convert_params_tf['tensorboard_logdir'].description, default=None, action=CanonicalizePathCheckExistenceAction) tf_group.add_argument('--tensorflow_custom_layer_libraries', - help=mo_convert_params['tensorflow_custom_layer_libraries'].description, + help=mo_convert_params_tf['tensorflow_custom_layer_libraries'].description, default=None, action=CanonicalizePathCheckExistenceAction) tf_group.add_argument('--disable_nhwc_to_nchw', @@ -1251,26 +1302,27 @@ def get_mxnet_cli_parser(parser: argparse.ArgumentParser = None): get_common_cli_parser(parser=parser) mx_group = parser.add_argument_group('Mxnet-specific parameters') + mo_convert_params_mxnet = mo_convert_params['mxnet'] mx_group.add_argument('--input_symbol', - help=mo_convert_params['input_symbol'].description, + help=mo_convert_params_mxnet['input_symbol'].description, type=str, action=CanonicalizePathCheckExistenceAction) mx_group.add_argument("--nd_prefix_name", - help=mo_convert_params['nd_prefix_name'].description, + help=mo_convert_params_mxnet['nd_prefix_name'].description, default=None) mx_group.add_argument("--pretrained_model_name", - help=mo_convert_params['pretrained_model_name'].description, + help=mo_convert_params_mxnet['pretrained_model_name'].description, default=None) mx_group.add_argument("--save_params_from_nd", action='store_true', - help=mo_convert_params['save_params_from_nd'].description) + help=mo_convert_params_mxnet['save_params_from_nd'].description) mx_group.add_argument("--legacy_mxnet_model", action='store_true', - help=mo_convert_params['legacy_mxnet_model'].description) + help=mo_convert_params_mxnet['legacy_mxnet_model'].description) mx_group.add_argument("--enable_ssd_gluoncv", action='store_true', - help=mo_convert_params['enable_ssd_gluoncv'].description, + help=mo_convert_params_mxnet['enable_ssd_gluoncv'].description, default=False) return parser @@ -1289,19 +1341,20 @@ def get_kaldi_cli_parser(parser: argparse.ArgumentParser = None): get_common_cli_parser(parser=parser) kaldi_group = parser.add_argument_group('Kaldi-specific parameters') + mo_convert_params_kaldi = mo_convert_params['kaldi'] kaldi_group.add_argument("--counts", - help=mo_convert_params['counts'].description, + help=mo_convert_params_kaldi['counts'].description, default=None, action=CanonicalizePathCheckExistenceIfNeededAction) kaldi_group.add_argument("--remove_output_softmax", - help=mo_convert_params['remove_output_softmax'].description, + help=mo_convert_params_kaldi['remove_output_softmax'].description, action='store_true', default=False) kaldi_group.add_argument("--remove_memory", - help=mo_convert_params['remove_memory'].description, + help=mo_convert_params_kaldi['remove_memory'].description, action='store_true', default=False) return parser @@ -1569,7 +1622,38 @@ def write_found_layout(name: str, found_layout: str, parsed: dict, dest: str = N parsed[name] = {'source_layout': s_layout, 'target_layout': t_layout} -def parse_layouts_by_destination(s: str, parsed: dict, dest: str = None) -> None: +def write_found_layout_list(idx: int, found_layout: str, parsed: list, dest: str = None): + """ + Writes found layout data to the 'parsed' dict. + :param idx: idx of of the node to add layout + :param found_layout: string containing layout for the node + :param parsed: list where result will be stored + :param dest: type of the command line: + * 'source' is --source_layout + * 'target' is --target_layout + * None is --layout + """ + s_layout = None + t_layout = None + if idx < len(parsed): + s_layout = parsed[idx]['source_layout'] + t_layout = parsed[idx]['target_layout'] + if dest == 'source': + s_layout = found_layout + elif dest == 'target': + t_layout = found_layout + else: + s_layout, t_layout = split_layouts_by_arrow(found_layout) + validate_layout(s_layout) + validate_layout(t_layout) + + if idx < len(parsed): + parsed[idx] = {'source_layout': s_layout, 'target_layout': t_layout} + else: + parsed.append({'source_layout': s_layout, 'target_layout': t_layout}) + + +def parse_layouts_by_destination(s: str, parsed: dict, parsed_list: list, dest: str = None) -> None: """ Parses layout command line to get all names and layouts from it. Adds all found data in the 'parsed' dict. :param s: string to parse @@ -1584,29 +1668,25 @@ def parse_layouts_by_destination(s: str, parsed: dict, dest: str = None) -> None # single layout case write_found_layout('', list_s[0], parsed, dest) else: - for layout_str in list_s: + for idx, layout_str in enumerate(list_s): # case for: "name1(nhwc->[n,c,h,w])" - p1 = re.compile(r'(\S+)\((\S+)\)') + p1 = re.compile(r'(\w*)\((\S+)\)') m1 = p1.match(layout_str) # case for: "name1[n,h,w,c]->[n,c,h,w]" - p2 = re.compile(r'(\S+)(\[\S*\])') + p2 = re.compile(r'(\w*)(\[\S*\])') m2 = p2.match(layout_str) if m1: found_g = m1.groups() elif m2: found_g = m2.groups() else: - error_msg = "Invalid usage of --{}layout parameter. Please use following syntax for each tensor " \ - "or operation name:" \ - "\n name(nchw)" \ - "\n name[n,c,h,w]".format(dest + '_' if dest else '') - if dest is None: - error_msg += "\n name(nhwc->[n,h,w,c])" \ - "\n name[n,h,w,c]->[n,c,h,w]" - error_msg += '\n Please do not forget to surround whole expression with quotes, otherwise' \ - ' symbols >[]() would be treated as special characters.' - raise Error(error_msg) - write_found_layout(found_g[0], found_g[1], parsed, dest) + # case for layout without name + write_found_layout_list(idx, layout_str, parsed_list, dest) + continue + if len(found_g[0]) > 0: + write_found_layout(found_g[0], found_g[1], parsed, dest) + else: + write_found_layout_list(idx, found_g[1], parsed_list, dest) def get_layout_values(argv_layout: str = '', argv_source_layout: str = '', argv_target_layout: str = ''): @@ -1621,13 +1701,20 @@ def get_layout_values(argv_layout: str = '', argv_source_layout: str = '', argv_ raise Error("--layout is used as well as --source_layout and/or --target_layout which is not allowed, please " "use one of them.") res = {} + res_list = [] if argv_layout: - parse_layouts_by_destination(argv_layout, res) + parse_layouts_by_destination(argv_layout, res, res_list) if argv_source_layout: - parse_layouts_by_destination(argv_source_layout, res, 'source') + parse_layouts_by_destination(argv_source_layout, res, res_list, 'source') if argv_target_layout: - parse_layouts_by_destination(argv_target_layout, res, 'target') - return res + parse_layouts_by_destination(argv_target_layout, res, res_list, 'target') + if len(res) > 0 and len(res_list) > 0: + raise Error("Some layout values are provided with names, and some without names. " + "Please provide ether all layouts with names or all layouts without names.") + if len(res) > 0: + return res + else: + return res_list def get_freeze_placeholder_values(argv_input: str, argv_freeze_placeholder_with_value: str): @@ -1708,6 +1795,18 @@ def split_inputs(input_str): +def split_shapes(argv_input_shape: str): + 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) + tuple_reg = r'((\({}{}\))|(\[{}{}\]))'.format(first_digit_reg, next_digits_reg, + first_digit_reg, next_digits_reg) + + full_reg = r'^{}(\s*,\s*{})*$|^$'.format(tuple_reg, tuple_reg) + if not re.match(full_reg, argv_input_shape): + raise Error('Input shape "{}" cannot be parsed. ' + refer_to_faq_msg(57), argv_input_shape) + return re.findall(r'[(\[]([0-9,\.\? -]+)[)\]]', argv_input_shape) + def get_placeholder_shapes(argv_input: str, argv_input_shape: str, argv_batch=None): """ Parses input layers names and input shapes from the cli and returns the parsed object. @@ -1769,16 +1868,9 @@ def get_placeholder_shapes(argv_input: str, argv_input_shape: str, argv_batch=No inputs_list = list() placeholder_shapes = None - 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) - tuple_reg = r'((\({}{}\))|(\[{}{}\]))'.format(first_digit_reg, next_digits_reg, - first_digit_reg, next_digits_reg) + if argv_input_shape: - full_reg = r'^{}(\s*,\s*{})*$|^$'.format(tuple_reg, tuple_reg) - if not re.match(full_reg, argv_input_shape): - raise Error('Input shape "{}" cannot be parsed. ' + refer_to_faq_msg(57), argv_input_shape) - shapes = re.findall(r'[(\[]([0-9,\.\? -]+)[)\]]', argv_input_shape) + shapes = split_shapes(argv_input_shape) if argv_input: inputs = split_inputs(argv_input) @@ -1786,10 +1878,9 @@ def get_placeholder_shapes(argv_input: str, argv_input_shape: str, argv_batch=No # check number of shapes with no input provided if argv_input_shape and not argv_input: - if len(shapes) > 1: - raise Error('Please provide input layer names for input layer shapes. ' + refer_to_faq_msg(58)) - else: - placeholder_shapes = PartialShape(shapes[0]) + placeholder_shapes = [PartialShape(shape) for shape in shapes] + if len(placeholder_shapes) == 1: + placeholder_shapes = PartialShape(placeholder_shapes[0]) # check if number of shapes does not match number of passed inputs elif argv_input and (len(shapes) == len(inputs) or len(shapes) == 0): # clean inputs from values for freezing diff --git a/tools/mo/openvino/tools/mo/utils/convert.py b/tools/mo/openvino/tools/mo/utils/convert.py index 34b94847f40..e37711cc718 100755 --- a/tools/mo/openvino/tools/mo/utils/convert.py +++ b/tools/mo/openvino/tools/mo/utils/convert.py @@ -9,8 +9,6 @@ import sys os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' try: import tensorflow.compat.v1 as tf_v1 - # disable eager execution of TensorFlow 2 environment immediately - tf_v1.disable_eager_execution() except ImportError: import tensorflow as tf_v1 diff --git a/tools/mo/openvino/tools/mo/utils/import_extensions.py b/tools/mo/openvino/tools/mo/utils/import_extensions.py index 8131273fbd8..18b135e6213 100644 --- a/tools/mo/openvino/tools/mo/utils/import_extensions.py +++ b/tools/mo/openvino/tools/mo/utils/import_extensions.py @@ -12,10 +12,22 @@ from openvino.tools.mo.load.loader import Loader from openvino.tools.mo.middle.replacement import MiddleReplacementPattern from openvino.tools.mo.ops.op import Op from openvino.tools.mo.utils.class_registration import _check_unique_ids, update_registration, \ - get_enabled_and_disabled_transforms + get_enabled_and_disabled_transforms, clear_registered_classes_dict from openvino.tools.mo.utils.model_analysis import AnalyzeAction +def get_internal_dirs(framework: str, get_front_classes: callable): + front_classes = get_front_classes() + return { + ('ops', ): [Op], + ('analysis',): [AnalyzeAction], + ('load', framework): [Loader], + ('front', ): front_classes, + ('front', framework): front_classes, + ('front', framework, 'extractors'): front_classes, + ('middle', ): [MiddleReplacementPattern], + ('back', ): [BackReplacementPattern]} + def import_by_path(path: str, middle_names: list = (), prefix: str = ''): for module_loader, name, ispkg in pkgutil.iter_modules([path]): importlib.import_module('{}{}.{}'.format(prefix, '.'.join(middle_names), name)) @@ -60,27 +72,28 @@ def load_dir(framework: str, path: str, get_front_classes: callable): enabled_transforms, disabled_transforms = get_enabled_and_disabled_transforms() - front_classes = get_front_classes() - internal_dirs = { - ('ops', ): [Op], - ('analysis',): [AnalyzeAction], - ('load', framework): [Loader], - ('front', ): front_classes, - ('front', framework): front_classes, - ('front', framework, 'extractors'): front_classes, - ('middle', ): [MiddleReplacementPattern], - ('back', ): [BackReplacementPattern]} + internal_dirs = get_internal_dirs(framework, get_front_classes) prefix = 'openvino.tools.' if ext == 'mo' else '' + exclude_modules = {'tf', 'onnx', 'kaldi', 'mxnet', 'caffe'} + exclude_modules.remove(framework) + for p in internal_dirs.keys(): import_by_path(os.path.join(path, *p), [ext, *p], prefix) - update_registration(internal_dirs[p], enabled_transforms, disabled_transforms) + update_registration(internal_dirs[p], enabled_transforms, disabled_transforms, exclude_modules) sys.path.remove(root_dir) def load_dirs(framework: str, dirs: list, get_front_classes: callable): if dirs is None: return + internal_dirs = get_internal_dirs(framework, get_front_classes) + + for p, dir_names in internal_dirs.items(): + for d in dir_names: + d.registered_cls = [] + d.registered_ops = {} + clear_registered_classes_dict() mo_inner_extensions = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'mo')) dirs.insert(0, mo_inner_extensions) diff --git a/tools/mo/openvino/tools/mo/utils/ir_reader/layer_to_class.py b/tools/mo/openvino/tools/mo/utils/ir_reader/layer_to_class.py index 635023266ed..0a5f393f9e3 100644 --- a/tools/mo/openvino/tools/mo/utils/ir_reader/layer_to_class.py +++ b/tools/mo/openvino/tools/mo/utils/ir_reader/layer_to_class.py @@ -65,7 +65,7 @@ def collect_ops(path: str): import_by_path(os.path.join(path, 'mo', 'ops'), ['mo', 'ops'], 'openvino.tools.') update_registration(classes=[Op, Activation, Elementwise, UnaryElementwise, LogicalElementwise, EmbeddingBagBase, ReduceOp, Scatter, ScatterNDBase, FFTBase], - enabled_transforms=[], disabled_transforms=[]) + enabled_transforms=[], disabled_transforms=[], exclude_modules=set()) def collect_extenders(path: str): @@ -76,7 +76,7 @@ def collect_extenders(path: str): """ import_by_path(os.path.join(path, 'mo', 'utils', 'ir_reader', 'extenders'), ['mo', 'utils', 'ir_reader', 'extenders'], 'openvino.tools.') - update_registration(classes=[Extender], enabled_transforms=[], disabled_transforms=[]) + update_registration(classes=[Extender], enabled_transforms=[], disabled_transforms=[], exclude_modules=set()) def collect_node_outputs(node: Node) -> dict: diff --git a/tools/mo/openvino/tools/mo/utils/summarize_graph.py b/tools/mo/openvino/tools/mo/utils/summarize_graph.py index edabc1b5a3a..f1a0395980f 100644 --- a/tools/mo/openvino/tools/mo/utils/summarize_graph.py +++ b/tools/mo/openvino/tools/mo/utils/summarize_graph.py @@ -11,8 +11,6 @@ import sys os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' try: import tensorflow.compat.v1 as tf_v1 - # disable eager execution of TensorFlow 2 environment immediately - tf_v1.disable_eager_execution() except ImportError: import tensorflow as tf_v1 diff --git a/tools/mo/openvino/tools/mo/utils/tensorboard_util.py b/tools/mo/openvino/tools/mo/utils/tensorboard_util.py index 9a25fa166e6..5c52ace9955 100644 --- a/tools/mo/openvino/tools/mo/utils/tensorboard_util.py +++ b/tools/mo/openvino/tools/mo/utils/tensorboard_util.py @@ -7,13 +7,12 @@ import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' try: import tensorflow.compat.v1 as tf_v1 - # disable eager execution of TensorFlow 2 environment immediately - tf_v1.disable_eager_execution() except ImportError: import tensorflow as tf_v1 #in some environment suppressing through TF_CPP_MIN_LOG_LEVEL does not work tf_v1.get_logger().setLevel("ERROR") +from tensorflow.python.eager.context import graph_mode try: import tensorflow.contrib # pylint: disable=no-name-in-module,import-error @@ -27,8 +26,9 @@ def dump_for_tensorboard(graph_def: tf_v1.GraphDef, logdir: str): try: # TODO: graph_def is a deprecated argument, use graph instead print('Writing an event file for the tensorboard...') - with tf_v1.summary.FileWriter(logdir=logdir, graph_def=graph_def) as writer: - writer.flush() + with graph_mode(): + with tf_v1.summary.FileWriter(logdir=logdir, graph_def=graph_def) as writer: + writer.flush() print('Done writing an event file.') except Exception as err: raise Error('Cannot write an event file for the tensorboard to directory "{}". ' + 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 index b4a94365aee..ca9ca6cde46 100644 --- a/tools/mo/unit_tests/mo/convert/import_from_mo_test.py +++ b/tools/mo/unit_tests/mo/convert/import_from_mo_test.py @@ -4,6 +4,7 @@ import os import tempfile +import numpy as np from generator import generator, generate from openvino.runtime import serialize @@ -16,7 +17,6 @@ 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(*[ @@ -24,15 +24,16 @@ class ConvertImportMOTest(UnitTestWithMockedTelemetry): ({'input': InputCutInfo(name='LeakyRelu_out', shape=None, type=None, value=None)}), ({'layout': {'input': LayoutMap(source_layout='NCHW', target_layout='NHWC')}}), ]) + # Checks convert import from openvino.tools.mo def test_import(self, params): - from openvino.tools.mo import convert + from openvino.tools.mo import convert_model 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) + ov_model = convert_model(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) @@ -93,14 +94,13 @@ class ConvertImportMOTest(UnitTestWithMockedTelemetry): ('sigmoid_data', 'result'), ]) - from openvino.tools.mo import convert + from openvino.tools.mo import convert_model 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) + ov_model = convert_model(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')) diff --git a/tools/mo/unit_tests/mo/convert/meta_data_test.py b/tools/mo/unit_tests/mo/convert/meta_data_test.py index a1f1961cabb..2b3f371e9f6 100644 --- a/tools/mo/unit_tests/mo/convert/meta_data_test.py +++ b/tools/mo/unit_tests/mo/convert/meta_data_test.py @@ -9,7 +9,7 @@ from generator import generator from openvino.runtime import get_version as get_rt_version from openvino.runtime import serialize -from openvino.tools.mo import convert +from openvino.tools.mo import convert_model from openvino.tools.mo.utils import import_extensions from openvino.tools.mo.utils.version import get_version from unit_tests.mo.unit_test_with_mocked_telemetry import UnitTestWithMockedTelemetry @@ -93,7 +93,7 @@ class MetaDataTest(UnitTestWithMockedTelemetry): model_path = save_to_onnx(model, tmpdir) out_xml = os.path.join(tmpdir, "model.xml") - ov_model = convert(model_path) + ov_model = convert_model(model_path) check_meta_data(ov_model) serialize(ov_model, out_xml.encode('utf-8'), out_xml.replace('.xml', '.bin').encode('utf-8')) diff --git a/tools/mo/unit_tests/mo/front/tf/convert_to_pb_test.py b/tools/mo/unit_tests/mo/front/tf/convert_to_pb_test.py index c43125f33d7..77f975abf16 100644 --- a/tools/mo/unit_tests/mo/front/tf/convert_to_pb_test.py +++ b/tools/mo/unit_tests/mo/front/tf/convert_to_pb_test.py @@ -34,19 +34,21 @@ class ConvertToPBTests(unittest.TestCase): def test_meta_format(self): try: import tensorflow.compat.v1 as tf_v1 - tf_v1.disable_eager_execution() except ImportError: import tensorflow as tf_v1 + from tensorflow.python.eager.context import graph_mode with tempfile.TemporaryDirectory(dir=self.test_directory) as tmp_dir: - a = tf_v1.get_variable("A", initializer=tf_v1.constant(3, shape=[2])) - b = tf_v1.get_variable("B", initializer=tf_v1.constant(5, shape=[2])) - tf_v1.add(a, b, name='Add') - init_op = tf_v1.global_variables_initializer() - saver = tf_v1.train.Saver() - with tf_v1.Session() as sess: - sess.run(init_op) - saver.save(sess, os.path.join(tmp_dir, 'model')) + with graph_mode(): + a = tf_v1.get_variable("A", initializer=tf_v1.constant(3, shape=[2])) + b = tf_v1.get_variable("B", initializer=tf_v1.constant(5, shape=[2])) + tf_v1.add(a, b, name='Add') + init_op = tf_v1.global_variables_initializer() + saver = tf_v1.train.Saver() + with tf_v1.Session() as sess: + sess.run(init_op) + saver.save(sess, os.path.join(tmp_dir, 'model')) + self.argv.input_meta_graph = os.path.join(tmp_dir, 'model.meta') self.argv.output_dir = tmp_dir path_to_pb = convert_to_pb(self.argv) 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 index 277505f1026..8c6db084091 100644 --- a/tools/mo/unit_tests/mo/utils/args_to_string_test.py +++ b/tools/mo/unit_tests/mo/utils/args_to_string_test.py @@ -225,3 +225,18 @@ class TestConvertingConvertArgumentsToString(UnitTestWithMockedTelemetry): 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)}) + + layout = ["nhwc", "[n,c]"] + self.assertTrue(layout_param_to_str(layout) == "nhwc,[n,c]") + + layout = ["abc->cab", "..nc"] + self.assertTrue(layout_param_to_str(layout) == "abc->cab,..nc") + + layout_map1 = LayoutMap(source_layout=Layout("n??"), target_layout=None) + layout = [layout_map1, "..nc"] + self.assertTrue(layout_param_to_str(layout) == "[N,?,?],..nc") + + layout_map2 = LayoutMap(source_layout=Layout("nhwc"), target_layout=("nchw")) + layout_map3 = LayoutMap(source_layout="abc", target_layout="cab") + layout = [layout_map2, layout_map3] + self.assertTrue(layout_param_to_str(layout) == "[N,H,W,C]->nchw,abc->cab") diff --git a/tools/mo/unit_tests/mo/utils/cli_parser_test.py b/tools/mo/unit_tests/mo/utils/cli_parser_test.py index 093c1d534c2..bbe96562409 100644 --- a/tools/mo/unit_tests/mo/utils/cli_parser_test.py +++ b/tools/mo/unit_tests/mo/utils/cli_parser_test.py @@ -738,11 +738,6 @@ class TestShapesParsing(UnitTestWithMockedTelemetry): input_shapes = "(1,22,333,123), (-1,45,7,1), (-1,456,7,1)" self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) - def test_get_shapes_several_shapes_no_input(self): - argv_input = "" - input_shapes = "(1,22,333,123), (-1,45,7,1), (-1,456,7,1)" - self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) - def test_get_shapes_one_input_one_shape(self): argv_input = "inp1" input_shapes = "(1,22,333,123)" @@ -774,10 +769,6 @@ class TestShapesParsing(UnitTestWithMockedTelemetry): exp_res = np.array([12, 4, 1]) assert np.array_equal(result, exp_res) - def test_get_shapes_no_input_two_shapes(self): - argv_input = "" - input_shapes = "(12,4,1),(5,4,3)" - self.assertRaises(Error, get_placeholder_shapes, argv_input, input_shapes) def test_get_shapes_one_input_no_shape(self): argv_input = "inp1" @@ -1606,20 +1597,351 @@ class TestLayoutParsing(unittest.TestCase): res = get_layout_values(argv_layout=argv_layout) print(res) - def test_get_layout_raises_multiple_layouts_without_names(self): - argv_layout = "nhwc->nchw,nhwc->nchw" - with self.assertRaises(Error): - res = get_layout_values(argv_layout=argv_layout) - print(res) - def test_get_layout_raises_multiple_layouts_without_names_source_layout(self): +class TestLayoutParsingEmptyNames(unittest.TestCase): + def test_get_layout_1(self): + argv_layout = "([n,h,w,c]),([n,h,w,c]->[n,c,h,w])" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': None}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_2(self): + argv_layout = "(nhwc),(nhwc->nchw)" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': 'nhwc', 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_3(self): + argv_layout = "(n...c),(n...c->nc...)" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'n...c', 'target_layout': None}, + {'source_layout': 'n...c', 'target_layout': 'nc...'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_scalar(self): + argv_layout = "(nhwc),([])" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_3(self): + argv_source_layout = "(nhwc),(nchw)" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': 'nchw', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_4(self): + argv_source_layout = "([n,h,w,c]),([n,c,h,w])" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_5(self): + argv_source_layout = "(nhwc),([n,c,h,w])" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_6(self): + argv_source_layout = "(nhwc),[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_scalar(self): + argv_source_layout = "(nhwc),([])" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_3(self): + argv_target_layout = "(nhwc),(nchw)" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_4(self): + argv_target_layout = "([n,h,w,c]),([n,c,h,w])" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': '[n,h,w,c]'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_5(self): + argv_target_layout = "(nhwc),([n,c,h,w])" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_6(self): + argv_target_layout = "(nhwc),[n,c,h,w]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_scalar(self): + argv_target_layout = "(nhwc),[]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_3(self): + argv_source_layout = "(nhwc),(nhwc)" + argv_target_layout = "(nchw),(nchw)" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': 'nhwc', 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_4(self): + argv_source_layout = "([n,h,w,c]),([n,h,w,c])" + argv_target_layout = "([n,c,h,w]),([n,c,h,w])" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_5(self): + argv_source_layout = "(nhwc),[n,h,w,c]" + argv_target_layout = "(nchw),[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_scalar(self): + argv_source_layout = "(nhwc),[]" + argv_target_layout = "(nchw),[]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': '[]', 'target_layout': '[]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + +class TestLayoutParsingEmptyNamesNoBrackets(unittest.TestCase): + def test_get_layout_1(self): + argv_layout = "[n,h,w,c],[n,h,w,c]->[n,c,h,w]" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': None}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_2(self): + argv_layout = "nhwc,nhwc->nchw" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': 'nhwc', 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_3(self): + argv_layout = "n...c,n...c->nc..." + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'n...c', 'target_layout': None}, + {'source_layout': 'n...c', 'target_layout': 'nc...'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_scalar(self): + argv_layout = "nhwc,[]" + result = get_layout_values(argv_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_3(self): + argv_source_layout = "nhwc,nchw" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': 'nchw', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_4(self): + argv_source_layout = "[n,h,w,c],[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_5(self): + argv_source_layout = "nhwc,[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_6(self): + argv_source_layout = "nhwc,[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[n,c,h,w]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_layout_scalar(self): + argv_source_layout = "nhwc,[]" + result = get_layout_values(argv_source_layout=argv_source_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': None}, + {'source_layout': '[]', 'target_layout': None}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_3(self): + argv_target_layout = "nhwc,nchw" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_4(self): + argv_target_layout = "[n,h,w,c],[n,c,h,w]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': '[n,h,w,c]'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_5(self): + argv_target_layout = "nhwc,[n,c,h,w]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_6(self): + argv_target_layout = "nhwc,[n,c,h,w]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_target_layout_scalar(self): + argv_target_layout = "nhwc,[]" + result = get_layout_values(argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': None, 'target_layout': 'nhwc'}, + {'source_layout': None, 'target_layout': '[]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_3(self): argv_source_layout = "nhwc,nhwc" - with self.assertRaises(Error): - res = get_layout_values(argv_source_layout=argv_source_layout) - print(res) - - def test_get_layout_raises_multiple_layouts_without_names_target_layout(self): argv_target_layout = "nchw,nchw" - with self.assertRaises(Error): - res = get_layout_values(argv_target_layout=argv_target_layout) - print(res) + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': 'nhwc', 'target_layout': 'nchw'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_4(self): + argv_source_layout = "[n,h,w,c],[n,h,w,c]" + argv_target_layout = "[n,c,h,w],[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_5(self): + argv_source_layout = "nhwc,[n,h,w,c]" + argv_target_layout = "nchw,[n,c,h,w]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': '[n,h,w,c]', 'target_layout': '[n,c,h,w]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def test_get_layout_source_target_layout_scalar(self): + argv_source_layout = "nhwc,[]" + argv_target_layout = "nchw,[]" + result = get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout) + exp_res = [{'source_layout': 'nhwc', 'target_layout': 'nchw'}, + {'source_layout': '[]', 'target_layout': '[]'}] + self.assertEqual(exp_res, result) + for i in range(len(exp_res)): + assert np.array_equal(result[i], exp_res[i]) + + def wrong_case_1(self): + argv_source_layout = "[n,h,w,c]),[n,h,w,c]" + argv_target_layout = "[n,c,h,w],[n,c,h,w]" + self.assertRaises(get_layout_values(argv_source_layout=argv_source_layout, argv_target_layout=argv_target_layout)) + + def wrong_case_2(self): + argv_source_layout = "[nchv" + self.assertRaises(get_layout_values(argv_source_layout=argv_source_layout)) + + def wrong_case_3(self): + argv_source_layout = "nchv->" + self.assertRaises(get_layout_values(argv_source_layout=argv_source_layout)) \ No newline at end of file