Fix preprocessing in MO to use friendly names as well as tensor names (#9206)

* Fix preprocessing in MO to use friendly names as well as tensor names

* Apply review comments

* Revert not necessary changes

* Apply review feedback
This commit is contained in:
Maxim Vafin 2021-12-17 19:05:43 +03:00 committed by GitHub
parent ad10edbd2e
commit 36a6774067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 52 deletions

View File

@ -38,52 +38,6 @@ def compress_model(func: object):
compress_model_transformation(func)
def add_layouts(ov_function, argv: argparse.Namespace):
from openvino.preprocess import PrePostProcessor # pylint: disable=no-name-in-module,import-error
from openvino.runtime import Layout # pylint: disable=import-error,no-name-in-module
prep = PrePostProcessor(ov_function)
layout_values = argv.layout_values
if '' in layout_values:
if len(ov_function.inputs) == 1:
layout_values = {
list(ov_function.input().get_tensor().get_names())[0]: {
'source_layout': layout_values[''].get('source_layout'),
'target_layout': layout_values[''].get('target_layout')
}
}
else:
input_names = [list(ov_input.get_tensor().get_names())[0] for ov_input in ov_function.inputs]
raise Error('Layout without name can be specified for models with only one input, '
'but provided model has {} inputs: \'{}\'. '
'Please specify explicitly input/output name for --layout option'
.format(len(input_names), input_names))
set_layout_names = set(layout_values.keys())
for idx, ov_input in enumerate(ov_function.inputs):
found = set.intersection(set(ov_input.get_tensor().get_names()), set_layout_names)
assert len(found) <= 1, 'More then one name point to the same node'
if len(found) == 1:
node_name = list(found)[0]
found_layout = layout_values[node_name]
if found_layout['source_layout']:
prep.input(node_name).model().set_layout(Layout(found_layout['source_layout']))
if found_layout['target_layout']:
prep.input(node_name).tensor().set_layout(Layout(found_layout['target_layout']))
for idx, ov_output in enumerate(ov_function.outputs):
found = set.intersection(set(ov_output.get_tensor().get_names()), set_layout_names)
assert len(found) <= 1, 'More then one name point to the same node'
if len(found) == 1:
node_name = list(found)[0]
found_layout = layout_values[node_name]
if found_layout['source_layout']:
prep.output(node_name).model().set_layout(Layout(found_layout['source_layout']))
if found_layout['target_layout']:
prep.output(node_name).tensor().set_layout(Layout(found_layout['target_layout']))
prep.build()
def apply_offline_transformations(input_model: str, argv: argparse.Namespace):
# This variable is only needed by GenerateMappingFile transformation
# to produce correct mapping
@ -91,6 +45,7 @@ def apply_offline_transformations(input_model: str, argv: argparse.Namespace):
from openvino.offline_transformations_pybind import generate_mapping_file, serialize # pylint: disable=import-error,no-name-in-module
from openvino.frontend import FrontEndManager, FrontEnd # pylint: disable=no-name-in-module,import-error
from openvino.tools.mo.back.preprocessing import apply_preprocessing # pylint: disable=no-name-in-module,import-error
fem = FrontEndManager()
@ -104,7 +59,29 @@ def apply_offline_transformations(input_model: str, argv: argparse.Namespace):
func = read_model(input_model + "_tmp.xml")
add_layouts(func, argv) # TODO: replace with preprocessing
# TODO: use ngraph preprocessing (Mean/Scale/ReverseInputChannels) for legacy frontends
reverse_input_channels = False
if 'reverse_input_channels' in argv:
reverse_input_channels = argv.reverse_input_channels
argv.reverse_input_channels = False
mean_scale_values = {}
if 'mean_scale_values' in argv:
mean_scale_values = argv.mean_scale_values
argv.mean_scale_values = {}
scale = None
if 'scale' in argv:
scale = argv.scale
argv.scale = None
# Apply preprocessing for layouts only
apply_preprocessing(ov_function=func, argv=argv)
if 'reverse_input_channels' in argv:
argv.reverse_input_channels = reverse_input_channels
if 'mean_scale_values' in argv:
argv.mean_scale_values = mean_scale_values
if 'scale' in argv:
argv.scale = scale
apply_user_transformations(func, parse_transform(argv.transform))
apply_moc_transformations(func)

View File

@ -59,7 +59,7 @@ def update_mean_scale_to_dict(input_nodes: list, mean_scale_val, scale):
return mean_scale_val
def check_keys_valid(ov_function: Model, keys: list, search_outputs: bool):
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
Throws if some key is not found
@ -70,7 +70,28 @@ def check_keys_valid(ov_function: Model, keys: list, search_outputs: bool):
if search_outputs:
nodes += ov_function.outputs
for name in keys:
# We need to replace all node names from dict to tensor names
rename_dict = {}
# Find names for replacing
for name in dict_to_validate.keys():
for ov_node in nodes:
if name in ov_node.get_tensor().get_names():
break
elif name == ov_node.get_node().get_friendly_name():
assert len(ov_node.get_tensor().get_names()) > 0, 'Node must have at least one tensor name'
new_name = list(ov_node.get_tensor().get_names())[0]
rename_dict[name] = new_name
break
# Replace found node names with tensor names
for name, new_name in rename_dict.items():
assert name in dict_to_validate, 'Key {} is not in initial dict'.format(name)
assert new_name not in dict_to_validate, 'Key {} is already in initial dict'.format(new_name)
dict_to_validate[new_name] = dict_to_validate[name]
del dict_to_validate[name]
# validate the dict
for name in dict_to_validate.keys():
node_found = False
for ov_node in nodes:
if name in ov_node.get_tensor().get_names():
@ -349,8 +370,8 @@ def apply_preprocessing(ov_function: Model, argv: argparse.Namespace):
'target_layout': layout_values[''].get('target_layout')
}
}
check_keys_valid(ov_function=ov_function, keys=mean_scale_values.keys(), search_outputs=False)
check_keys_valid(ov_function=ov_function, keys=layout_values.keys(), search_outputs=True)
check_keys_valid(ov_function=ov_function, dict_to_validate=mean_scale_values, search_outputs=False)
check_keys_valid(ov_function=ov_function, dict_to_validate=layout_values, search_outputs=True)
layout_values = update_layout_is_input_flag(ov_function, layout_values)
layout_values = guess_source_layouts_by_mean_scale(ov_function, layout_values, mean_scale_values)

View File

@ -40,7 +40,7 @@ def create_function2(shape1=[2, 2], shape2=[2, 2], dtype1=np.float32, dtype2=np.
def create_function1(shape1=[2, 2]):
input1 = ops.parameter(shape1, dtype=np.float32, name="input1")
input1.get_output_tensor(0).set_names({'input1', 'input1a'})
input1.get_output_tensor(0).set_names({'input1a', 'input1b'})
relu1 = ops.relu(input1)
res1 = ops.result(relu1, "res1")
res1.get_output_tensor(0).set_names({'res1', 'res1a'})
@ -651,3 +651,17 @@ class TestPreprocessingMOC(unittest.TestCase):
# Verify that guessed layout (?C??) is not appeared in input2
self.assertEqual(function.get_parameters()[1].layout, Layout())
def test_friendly_name(self):
argv = Namespace(mean_scale_values={'input1': {'mean': np.array([2., 4., 8.]), 'scale': None}},
layout_values={'input1': {'source_layout': 'nchw'}},
scale=None)
function = create_function1(shape1=[1, 3, 224, 224])
process_function(ov_function=function, argv=argv)
op_node = list(function.get_parameters()[0].output(0).get_target_inputs())[0].get_node()
self.assertTrue(op_node.get_type_name() == 'Subtract' or op_node.get_type_name() == 'Add')
self.check_mean_constant(op_node, expected=[2., 4., 8.], shape=[1, 3, 1, 1])
# Verify that layout (nchw) is appeared in input1
self.assertEqual(function.get_parameters()[0].layout, Layout('nchw'))