[OVC] Fix output parsing (#19425)

* fix parsing output

* use always coma backspace to separate outputs ', '

* update docstring

* call parser only for ovc cli tool

* docstring correction

* separate docs for cli tool and convert_model; other minor changes

* drop redundant arg from cli_parser_test.py

* more solid test cases added

* remove redundant argv.framework from cli_parser_test.py

* shape correction in concat

* Apply suggestions from code review

fix: coma -> comma

Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com>

* apply review suggestions

* remove extra ')'

---------

Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com>
This commit is contained in:
Pavel Esir 2023-09-01 11:20:22 +02:00 committed by GitHub
parent 2cf8f2bc1f
commit b8226db465
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 277 additions and 17 deletions

View File

@ -1,13 +1,14 @@
# Copyright (C) 2018-2023 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import numpy as np
import openvino.runtime as ov
import os
import pytest
import tempfile
import unittest
from openvino.runtime import Model, Layout, PartialShape, Shape, layout_helpers, Type, Dimension
import numpy as np
import openvino.runtime as ov
import pytest
from openvino.runtime import PartialShape, Type, Dimension
from common.mo_convert_test_class import CommonMOConvertTest
from common.tf_layer_test_class import save_to_pb
@ -158,6 +159,124 @@ class TestComplexParams(CommonMOConvertTest):
ref_params.update({'input_model': tf_net_path})
self._test(temp_dir, test_params, ref_params)
@staticmethod
def create_onnx_model_with_comma_in_names(temp_dir):
import onnx
from onnx import helper
from onnx import TensorProto
input_1 = helper.make_tensor_value_info('input_1', TensorProto.FLOAT, [1, 3, 2, 2])
input_2 = helper.make_tensor_value_info('input_2', TensorProto.FLOAT, [1, 3, 2, 2])
output = helper.make_tensor_value_info('relu_1,relu_2', TensorProto.FLOAT, [1, 3, 4, 2])
node_def_1 = onnx.helper.make_node(
'Relu',
inputs=['input_1'],
outputs=['Relu_1_data'],
name='relu_1'
)
node_def_2 = onnx.helper.make_node(
'Relu',
inputs=['input_2'],
outputs=['Relu_2_data'],
name='relu_2'
)
node_def_3 = onnx.helper.make_node(
'Concat',
inputs=['Relu_1_data', 'Relu_2_data'],
outputs=['relu_1,relu_2'],
axis=3,
)
graph_def = helper.make_graph(
[node_def_1, node_def_2, node_def_3],
'test_model',
[input_1, input_2],
[output],
)
onnx_net = helper.make_model(graph_def, producer_name='test_model')
model_path = temp_dir + '/test_model.onnx'
onnx.save(onnx_net, model_path)
return model_path
@staticmethod
def create_ref_graph_with_comma_in_names():
from openvino.runtime.opset12 import relu, concat
from openvino.runtime.op import Parameter
import openvino as ov
parameter1 = Parameter(ov.Type.f32, ov.Shape([1, 3, 2, 2]))
parameter2 = Parameter(ov.Type.f32, ov.Shape([1, 3, 2, 2]))
relu_1 = relu(parameter1)
relu_2 = relu(parameter2)
output = concat([relu_1, relu_2], 3)
return ov.Model([output], [parameter1, parameter2])
@staticmethod
def create_onnx_model_with_several_outputs(temp_dir):
import onnx
from onnx import helper
from onnx import TensorProto
shape = [1, 3, 2, 2]
input_1 = helper.make_tensor_value_info('input_1', TensorProto.FLOAT, shape)
input_2 = helper.make_tensor_value_info('input_2', TensorProto.FLOAT, shape)
concat_output = helper.make_tensor_value_info('concat', TensorProto.FLOAT, shape)
relu_output = helper.make_tensor_value_info('Relu_1_data', TensorProto.FLOAT, shape)
node_def_1 = onnx.helper.make_node(
'Relu',
inputs=['input_1'],
outputs=['Relu_1_data'],
name='relu_1'
)
node_def_2 = onnx.helper.make_node(
'Relu',
inputs=['input_2'],
outputs=['Relu_2_data'],
name='relu_2'
)
node_def_3 = onnx.helper.make_node(
'Concat',
inputs=['Relu_1_data', 'Relu_2_data'],
outputs=['concat'],
axis=3,
)
graph_def = helper.make_graph(
[node_def_1, node_def_2, node_def_3],
'test_model',
[input_1, input_2],
[relu_output, concat_output],
)
onnx_net = helper.make_model(graph_def, producer_name='test_model')
model_path = temp_dir + '/test_model.onnx'
onnx.save(onnx_net, model_path)
return model_path
@pytest.mark.nightly
@pytest.mark.precommit
def test_ovc_convert_model_with_comma_in_names(self, ie_device, precision, ir_version,
temp_dir, use_new_frontend, use_old_api):
onnx_net_path = self.create_onnx_model_with_comma_in_names(temp_dir)
ref_model = self.create_ref_graph_with_comma_in_names()
test_params = {'input_model': onnx_net_path, 'output': 'relu_1,relu_2'}
self._test_by_ref_graph(temp_dir, test_params, ref_model, compare_tensor_names=False)
@pytest.mark.nightly
@pytest.mark.precommit
def test_ovc_convert_model_with_several_output(self, ie_device, precision, ir_version,
temp_dir, use_new_frontend, use_old_api):
onnx_net_path = self.create_onnx_model_with_several_outputs(temp_dir)
convert_model_params = {'input_model': onnx_net_path, 'output': ['Relu_1_data', 'concat']}
cli_tool_params = {'input_model': onnx_net_path, 'output': 'Relu_1_data,concat'}
self._test(temp_dir, convert_model_params, cli_tool_params)
class NegativeCases(unittest.TestCase):
test_directory = os.path.dirname(os.path.realpath(__file__))

View File

@ -68,11 +68,12 @@ def convert_model(
If data type is not specified explicitly data type is taken from the original node data type.
:param output:
The name of the output operation of the model or list of names. For TensorFlow*,
do not add :0 to this name. The order of outputs in converted model is the
same as order of specified operation names. For PaddlePaddle model represented
as a Python object, you can specify outputs as a PaddlePaddle Python objects or
a list of such objects.
One or more comma-separated model outputs to be preserved in the converted model. Other outputs are removed.
If `output` parameter is not specified then all outputs from the original model are preserved.
Do not add :0 to the names for TensorFlow. The order of outputs in the converted model is the same as
the order of specified names. Example: output="out_1", or output=["out_1", "out_2"].
For PaddlePaddle model represented as a Python object, you can specify outputs as a PaddlePaddle Python
objects or a list of such objects.
:param example_input:
Sample of model input in original framework.
For PyTorch it can be torch.Tensor.

View File

@ -5,11 +5,11 @@ import argparse
import datetime
import logging as log
import os
import pathlib
import sys
import traceback
from collections import OrderedDict
from pathlib import Path
from typing import Iterable, Callable
try:
import openvino_telemetry as tm
@ -39,6 +39,7 @@ from openvino.tools.ovc.moc_frontend.paddle_frontend_utils import paddle_fronten
from openvino.frontend import FrontEndManager, OpConversionFailure, TelemetryExtension
from openvino.runtime import get_version as get_rt_version
from openvino.runtime import Type, PartialShape
import re
try:
from openvino.frontend.tensorflow.utils import create_tf_graph_iterator, type_supported_by_tf_fe, \
@ -76,20 +77,43 @@ def print_argv(argv: argparse.Namespace):
print('\n'.join(lines), flush=True)
def check_iterable(iterable: Iterable, func: Callable):
for element in iterable:
if not func(element):
return False
return True
def arguments_post_parsing(argv: argparse.Namespace):
# TODO: This function looks similar to another one. Check for code duplicates.
log.debug("Model Conversion API started")
if not argv.is_python_api_used:
log.debug('Output model name would be {}{{.xml, .bin}}'.format(argv.output_model))
if argv.verbose:
if is_verbose(argv):
print_argv(argv)
params_parsing(argv)
argv.output = argv.output.split(',') if isinstance(argv.output, (str, pathlib.Path)) else argv.output
log.debug("Placeholder shapes : {}".format(argv.placeholder_shapes))
if not hasattr(argv, 'output') or argv.output is None:
return argv
if argv.is_python_api_used:
error_msg = f"output '{argv.output}' is incorrect, it should be string or a list/tuple of strings"
assert isinstance(argv.output, (str, list, tuple)), error_msg
if isinstance(argv.output, list):
assert check_iterable(argv.output, lambda x: isinstance(x, str)), error_msg
else:
argv.output = [argv.output]
else:
assert isinstance(argv.output, str)
error_msg = f"output '{argv.output}' is incorrect, output names should not be empty or contain spaces"
processed_output = re.split(r'\s*,\s*', argv.output.strip())
assert check_iterable(processed_output, lambda x: x.find(' ') == -1), error_msg
assert check_iterable(processed_output, lambda x: len(x) > 0), error_msg
argv.output = processed_output
return argv
@ -342,7 +366,7 @@ def params_parsing(argv: argparse.Namespace):
data_type_list.append(inp.type)
argv.placeholder_shapes = shape_list if shape_list else None
argv.placeholder_data_types = data_type_list if data_type_list else {}
if argv.framework == "pytorch" and getattr(argv, "example_input", None) is not None:
if hasattr(argv, "framework") and argv.framework == "pytorch" and getattr(argv, "example_input", None) is not None:
extract_input_info_from_example(argv, inputs)

View File

@ -26,6 +26,14 @@ def get_convert_model_help_specifics():
'If the dimension is set as an integer (like 100 in [1,100]), such a dimension is not supposed '
'to be changed later, during a model conversion it is treated as a static value. '
'Example with unnamed inputs: \"[1,100],[1,?]\".'},
'output':
{'description':
'One or more comma-separated model outputs to be preserved in the converted model. '
'Other outputs are removed. If `output` parameter is not specified then all outputs from '
'the original model are preserved. '
'Do not add :0 to the names for TensorFlow. The order of outputs in the converted model is the '
'same as the order of specified names. '
'Example: ovc model.onnx output=out_1,out_2'},
'extension':
{'description':
'Paths or a comma-separated list of paths to libraries '

View File

@ -14,7 +14,7 @@ import numpy as np
from openvino.tools.ovc.cli_parser import input_to_input_cut_info, check_positive, writable_dir, \
readable_file_or_object, get_all_cli_parser, get_mo_convert_params
from openvino.tools.ovc.convert_impl import pack_params_to_args_namespace
from openvino.tools.ovc.convert_impl import pack_params_to_args_namespace, arguments_post_parsing, args_to_argv
from openvino.tools.ovc.error import Error
from unit_tests.ovc.unit_test_with_mocked_telemetry import UnitTestWithMockedTelemetry
from openvino.runtime import PartialShape, Dimension, Layout
@ -366,7 +366,7 @@ class TestPackParamsToArgsNamespace(unittest.TestCase):
from openvino.frontend import ConversionExtension
args = {'input_model': os.path.dirname(__file__),
'extension': ConversionExtension("Ext", lambda x: x),
'input': ['name', ("a", [1,2,3], ov.Type.f32)],
'input': ['name', ("a", [1, 2, 3], ov.Type.f32)],
'output': ["a", "b", "c"]}
cli_parser = get_all_cli_parser()
@ -375,12 +375,120 @@ class TestPackParamsToArgsNamespace(unittest.TestCase):
assert argv.input_model == args['input_model']
assert argv.extension == args['extension']
assert argv.input == ['name', ("a", [1,2,3], ov.Type.f32)]
assert argv.output == ["a","b","c"]
assert argv.output == ["a", "b", "c"]
for arg, value in vars(argv).items():
if arg not in args and arg != 'is_python_api_used':
assert value == cli_parser.get_default(arg)
def test_output_post_parsing_1(self):
args = {'input_model': os.path.dirname(__file__),
'input': "input_1[1,2,3]",
'output_model': os.getcwd() + "model.xml",
'output': "a,b,c"}
argv = args_to_argv(**args)
argv.is_python_api_used = False
argv = arguments_post_parsing(argv)
assert argv.output == ["a", "b", "c"]
def test_output_post_parsing_2(self):
args = {'input_model': os.path.dirname(__file__),
'input': "input_1[1,2,3]",
'output_model': os.getcwd() + "model.xml",
'output': "a, b, c"}
argv = args_to_argv(**args)
argv.is_python_api_used = False
argv = arguments_post_parsing(argv)
assert argv.output == ["a", "b", "c"]
def test_output_post_parsing_3(self):
args = {'input_model': os.path.dirname(__file__),
'input': "input_1[1,2,3]",
'output_model': os.getcwd() + "model.xml",
'output': "a,b, c"}
argv = args_to_argv(**args)
argv.is_python_api_used = False
argv = arguments_post_parsing(argv)
assert argv.output == ["a", "b", "c"]
def test_output_post_parsing_4(self):
args = {'input_model': os.path.dirname(__file__),
'input': "input_1[1,2,3]",
'output_model': os.getcwd() + "model.xml",
'output': "a , b , c"}
argv = args_to_argv(**args)
argv.is_python_api_used = False
argv = arguments_post_parsing(argv)
assert argv.output == ["a", "b", "c"]
def test_output_post_parsing_5(self):
args = {'input_model': os.path.dirname(__file__),
'input': "input_1[1,2,3]",
'output_model': os.getcwd() + "model.xml",
'output': "a,b"}
argv = args_to_argv(**args)
argv.is_python_api_used = True
argv = arguments_post_parsing(argv)
assert argv.output == ["a,b"] # post parsing should decorate single string into a list
def test_output_post_parsing_6(self):
args = {'input_model': os.path.dirname(__file__),
'input': "input_1[1,2,3]",
'output_model': os.getcwd() + "model.xml",
'output': ["first na me", "second name"]}
argv = args_to_argv(**args)
argv.is_python_api_used = True
argv = arguments_post_parsing(argv)
# when used in python api should be able to handle names with spaces
assert argv.output == ["first na me", "second name"]
def test_raises_output_post_parsing_1(self):
args = {'input_model': os.path.dirname(__file__),
'input': "input_1[1,2,3]",
'output_model': os.getcwd() + "model.xml",
'output': ["a,b, c", 23]}
argv = args_to_argv(**args)
argv.is_python_api_used = True
self.assertRaises(AssertionError, arguments_post_parsing, argv)
def test_raises_output_post_parsing_2(self):
args = {'input_model': os.path.dirname(__file__),
'input': "input_1[1,2,3]",
'output_model': os.getcwd() + "model.xml",
'output': "na me, full_name"}
argv = args_to_argv(**args)
argv.is_python_api_used = False
with self.assertRaisesRegex(AssertionError, ".*output names should not be empty or contain spaces"):
arguments_post_parsing(argv)
def test_raises_output_post_parsing_3(self):
args = {'input_model': os.path.dirname(__file__),
'input': "input_1[1,2,3]",
'output_model': os.getcwd() + "model.xml",
'output': "a,,b,c"}
argv = args_to_argv(**args)
argv.is_python_api_used = False
with self.assertRaisesRegex(AssertionError, ".*output names should not be empty or contain spaces"):
arguments_post_parsing(argv)
def test_not_existing_dir(self):
args = {"input_model": "abc"}
cli_parser = get_all_cli_parser()