openvino/tests/e2e_tests/test_utils/reshape_tests_utils.py

321 lines
13 KiB
Python

# Copyright (C) 2018-2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import copy
import logging as log
import re
from collections import defaultdict
from itertools import zip_longest
from types import SimpleNamespace
import numpy as np
from e2e_tests.common.test_utils import name_aligner
from e2e_tests.pipelines.pipeline_templates.comparators_template import dummy_comparators, eltwise_comparators
from e2e_tests.common.comparator.container import ComparatorsContainer
def should_run_reshape(instance) -> bool:
if not hasattr(instance, 'ie_pipeline'):
# test does not involve IE
return False
if 'infer' not in instance.ie_pipeline:
# test does not involve Infer step
return False
if 'get_ovc_model' not in instance.ie_pipeline['get_ir']:
# can not reshape without `mo`
return False
if hasattr(instance, 'model_info') and instance.model_info.framework != 'dldt':
# downloader models with IRs only are not tested by reshape
return False
if not hasattr(instance, 'input_descriptor'):
# no info for reshape was provided
log.info('Please, specify input_descriptor attribute for {}'.format(instance))
return False
if all([v.get('changeable_dims') is None for v in instance.input_descriptor.values()]):
# model was set as non-reshape-able
return False
return True
def get_reshape_pipeline_pairs(instance) -> list:
supported_pipelines = ['MO', 'IE']
types = getattr(instance, 'requested_reshape_types', supported_pipelines)
if len(types) == 1 or isinstance(types, str):
log.info(f'Only {types} reshape pipeline was set for {instance.__class__.__name__}')
return [types]
else:
pipelines_pairs = []
for pipeline in types[1:]:
pipelines_pairs.append([types[0], pipeline])
return pipelines_pairs
def check_config(default_shapes, layout, changeable_dims):
for input_layer, layer_value in changeable_dims.items():
assert len(layout[input_layer]) == len(default_shapes[input_layer]), \
'Layout {} and default_shapes {} of layer "{}"' \
' must have the same number of values'.format(
layout[input_layer], default_shapes[input_layer], input_layer)
if layer_value is not None:
for dimension in layer_value:
assert dimension in layout[input_layer], \
"Dimension '{}' wasn't found in input '{}'" \
" layout: {}".format(dimension, input_layer, layout[input_layer])
for data in layer_value[dimension]:
assert len(data) == len(dimension), \
'Number of values {} for dimension "{}" in input layer "{}" should be the same' \
" as length of changeable dimension".format(data, dimension, input_layer)
def get_dims_to_change(changeable_dims):
dims_to_change = defaultdict(list)
for layer_name in changeable_dims:
if changeable_dims[layer_name] is None:
dims_to_change[layer_name].append(None)
else:
for dim in changeable_dims[layer_name]:
for _ in range(len(changeable_dims[layer_name][dim])):
dims_to_change[layer_name].append(dim)
dims_to_change = refactor_values(dims_to_change)
return dims_to_change
def get_values_to_change(changeable_dims):
values_to_change = defaultdict(list)
for layer_name in changeable_dims:
if changeable_dims[layer_name] is None:
values_to_change[layer_name].append(None)
else:
for dim in changeable_dims[layer_name]:
for value in changeable_dims[layer_name][dim]:
values_to_change[layer_name].append(value)
return values_to_change
def refactor_values(values):
refactored_values = lambda x: list(zip_longest(*x.values()))
return [tuple(zip(values.keys(), obj)) for obj in refactored_values(values)]
def construct_new_shapes(test_number, default_shapes, layout, dims_to_change, values_to_change, dynamism_type=False):
reshape_config = {k: list(v) for k, v in default_shapes.items()}
for input_layer, input_dimension in dims_to_change[test_number]:
if input_dimension is None:
reshape_config[input_layer] = default_shapes[input_layer]
else:
# list comprehension is necessary in cases when multiply dimensions was set like 'HW'
dim_indexes = [layout[input_layer].index(d) for d in input_dimension]
# we should use default values if None is set as value
for value_index, value in enumerate(values_to_change[input_layer][test_number]):
if value is None:
continue
else:
if not dynamism_type or dynamism_type == 'None':
reshape_config[input_layer][dim_indexes[value_index]] = value
if dynamism_type == 'negative_ones':
reshape_config[input_layer][dim_indexes[value_index]] = -1
if dynamism_type == 'range_values':
reshape_config[input_layer][dim_indexes[value_index]] = sorted([
default_shapes[input_layer][dim_indexes[value_index]], value])
return reshape_config
def get_reshape_configurations(reshape_test_case, dynamism_type) -> list:
"""
This function returns list of reshape configurations.
Reshape configuration here is a list with info for reshape.
It has the following structure:
1. shapes: {input_layer: [input_layer_shapes], next_input_layer: [next_input_layer_shapes]}
2. dimensions are supposed to be changed: {input_layer: dimension, next_input_layer: dimension}
3. layout: layout of each input layer
4. default shapes: dictionary with input layer names and its shapes
"""
input_descriptor = reshape_test_case.input_descriptor
default_shapes = {k: v['default_shape'] for k, v in input_descriptor.items() if not v.get('frozen_input')}
layout = {k: v['layout'] for k, v in input_descriptor.items() if not v.get('frozen_input')}
changeable_dims = {k: v['changeable_dims'] for k, v in input_descriptor.items() if not v.get('frozen_input')}
check_config(default_shapes, layout, changeable_dims)
reshape_configurations = []
# get input layer-changed dimensions pairs for each specified shape value
dims_to_change = get_dims_to_change(changeable_dims)
# construct matrix of shape values for each input layer
values_to_change = get_values_to_change(changeable_dims)
# number of tests is number of input layer-changed dimensions pairs
number_of_tests = len(dims_to_change)
refactored_values = refactor_values(values_to_change)
# construct new shapes from layer-changed dimensions pairs and matrix of values
for test in range(number_of_tests):
reshape_config = construct_new_shapes(test, default_shapes, layout, dims_to_change,
values_to_change, dynamism_type)
reshape_configurations.append(SimpleNamespace(shapes=reshape_config, changed_dims=dict(dims_to_change[test]),
layout=layout, default_shapes=default_shapes,
changed_values=refactored_values[test]))
return reshape_configurations
def get_input_data(shapes):
return {'dynamism_preproc': {'execution_function': lambda data: replicator(data, shapes)}}
def batch_was_changed(shapes, changed_dims, layout, default_shapes):
batch = None
for layer, dimension in changed_dims.items():
if dimension is None:
continue
if len(dimension) > 1:
continue
index = layout[layer].index(dimension)
# we assume that batch index is always == 0
if index != 0:
continue
if shapes[layer][index] != default_shapes[layer][index]:
batch = shapes[layer][index]
return batch
def compare(instance, ref_results, cur_results):
assert len(instance.comparators) == 1 or "dummy" not in instance.comparators, \
"Dummy comparator is not the only one in comparators of instance"
if not ref_results:
ref_results = {}
instance.comparators = dummy_comparators()
else:
instance.comparators = eltwise_comparators(device=getattr(instance, 'device'),
precision=getattr(instance, 'precision'),
a_eps=getattr(instance, 'a_eps', None),
r_eps=getattr(instance, 'r_eps', None))
cur_results = cur_results.fetch_results()
cur_results = cur_results if type(cur_results) is list else [cur_results]
statuses = []
for ref_result, cur_result in zip(ref_results, cur_results):
comparators = ComparatorsContainer(
config=instance.comparators,
infer_result=cur_result,
reference=ref_result.fetch_results(),
result_aligner=name_aligner,
)
log.info('Running comparators:')
comparators.apply_postprocessors()
comparators.apply_all()
statuses.append(comparators.report_statuses())
return all(statuses)
def reorder_shapes_to_old_api(shapes):
reorder_shapes = copy.deepcopy(shapes)
for k, v in shapes.items():
if len(v) in [4, 5]:
reorder_shapes[k] = tuple(np.array(v).take((0, len(v) - 1, *list(range(1, len(v) - 1)))))
return reorder_shapes
def get_mo_input_with_frozen_values(mo_arg_input, shapes):
cmd_mo_input = []
for input in mo_arg_input.split(","):
if "->" in input:
cmd_mo_input.append(input)
else:
input = re.sub(r"[(\[]([0-9 -]*)[)\]]", "", input)
cmd_mo_input.append(input + str(shapes[input]).replace(',', ''))
return cmd_mo_input
def prepare_data_consecutive_inferences(default_shapes, changed_values, layout, dims_to_change):
def construct_input_data(data):
input_data = copy.deepcopy(data)
consecutive_infer_input_data = [data]
changed_data_shapes = get_static_shape(default_shapes, changed_values, layout, dims_to_change)
second_data = replicator(input_data, changed_data_shapes)
consecutive_infer_input_data.append(second_data)
return consecutive_infer_input_data
return {'dynamism_preproc': {'execution_function': lambda data: construct_input_data(data)}}
def get_static_shape(default_shapes, changed_values, layout, dims_to_change):
static_shapes = copy.deepcopy(default_shapes)
static_shapes = {k: list(v) for k, v in static_shapes.items()}
for input_layer, dimension in dims_to_change.items():
if dimension is None:
continue
else:
dim_indexes = [layout[input_layer].index(d) for d in dimension]
for value_index, value in enumerate(dict(changed_values)[input_layer]):
if value is None:
static_shapes[input_layer][dim_indexes[value_index]] = \
default_shapes[input_layer][dim_indexes[value_index]]
else:
static_shapes[input_layer][dim_indexes[value_index]] = value
return static_shapes
def replicator(data, shapes):
for name, shape in shapes.items():
if name not in data:
log.info(f"Input '{name}' from shapes was not found in data")
continue
err_msg = 'Final batch alignment error for layer `{}`: '.format(name)
data[name] = np.array(data[name])
old_shape = np.array(data[name].shape)
new_shape = np.array(shapes[name])
if old_shape.size != new_shape.size:
# Rank resize. We assume that it is Faster-like input with input shape
if np.prod(old_shape) == np.prod(new_shape):
data[name].reshape(new_shape)
old_shape = new_shape
assert old_shape.size == new_shape.size, 'Rank resize detected'
if np.all((new_shape % old_shape) == 0):
assert np.all(old_shape <= new_shape), 'Reshaping to shape that is less than original network shape'
log.info('New shape is evenly divided by original network shape')
multiplier = tuple(np.array(new_shape / old_shape, dtype=np.int_))
data[name] = np.tile(data[name], multiplier)
else:
# TF OD models can not be reshaped in 2x bacause they should keep aspect ratio
log.info('New shape is not evenly divided by original network shape data_shape={}, net_shape={}'
''.format(data[name].shape, new_shape))
assert len(new_shape) == 4, \
"Unsupported by tests reshape: Non 4D input {}, original shape {}".format(new_shape, old_shape)
multiplier = tuple(np.array(new_shape // old_shape + np.ones(new_shape.size), dtype=np.int))
replicated_data = np.tile(data[name], multiplier)
data[name] = replicated_data[0:new_shape[0], 0:new_shape[1], 0:new_shape[2], 0:new_shape[3]]
assert np.array_equal(data[name].shape, new_shape), \
err_msg + 'data_shape={}, net_shape={}'.format(data[name].shape, new_shape)
log.info('Input data was aligned with shapes=`{}`, new_data_shapes=`{}`'
''.format(shapes, {k: v.shape for k, v in data.items()}))
return data