From ea413a654e4b166df9c31628a309e9c6682f83e8 Mon Sep 17 00:00:00 2001 From: Jan Iwaszkiewicz Date: Mon, 15 Apr 2024 13:21:31 +0200 Subject: [PATCH] [PyOV] Add node name to autogenerated nodes by default (#23712) ### Details: - Automatically adding the node name to all autogenerated constants. Example: `NodeName/Constant_X` - Removed `apply_affix_on` approach. - Adjusted testcases to check compatibility. - The change is backward incompatible -- breaking cases unknown. - To be added: extend other operations `as_node/as_nodes` to follow this example, as this approach is not automatically applicable. ### Tickets: - CVS-133139 --------- Co-authored-by: Katarzyna Mitrus --- .../src/openvino/runtime/opset13/ops.py | 12 +--- .../src/openvino/runtime/utils/decorators.py | 49 +++++----------- .../src/openvino/runtime/utils/types.py | 19 +++--- .../python/tests/test_graph/test_affix_ops.py | 58 +++++++------------ 4 files changed, 50 insertions(+), 88 deletions(-) diff --git a/src/bindings/python/src/openvino/runtime/opset13/ops.py b/src/bindings/python/src/openvino/runtime/opset13/ops.py index 0c7ce39c5f3..be5391d0d8a 100644 --- a/src/bindings/python/src/openvino/runtime/opset13/ops.py +++ b/src/bindings/python/src/openvino/runtime/opset13/ops.py @@ -15,7 +15,7 @@ from openvino.runtime import Node, Shape, Type, Output from openvino.runtime.op import Constant, Result from openvino.runtime.opset1 import convert_like from openvino.runtime.opset_utils import _get_node_factory -from openvino.runtime.utils.decorators import apply_affix_on, binary_op, nameable_op, unary_op +from openvino.runtime.utils.decorators import binary_op, nameable_op, unary_op from openvino.runtime.utils.types import ( NumericData, NodeInput, @@ -350,7 +350,6 @@ def result(data: Union[Node, Output, NumericData], name: Optional[str] = None) - @nameable_op -@apply_affix_on("data", "input_low", "input_high", "output_low", "output_high") def fake_quantize( data: NodeInput, input_low: NodeInput, @@ -360,9 +359,6 @@ def fake_quantize( levels: int, auto_broadcast: str = "NUMPY", name: Optional[str] = None, - *, - prefix: Optional[str] = None, - suffix: Optional[str] = None, ) -> Node: r"""Perform an element-wise linear quantization on input data. @@ -375,10 +371,6 @@ def fake_quantize( :param auto_broadcast: The type of broadcasting specifies rules used for auto-broadcasting of input tensors. :param name: Optional name of the new node. - :param prefix: Optional keyword-only string to apply before original names of - all generated input nodes (for example: passed as numpy arrays). - :param suffix: Optional keyword-only string to apply after original names of - all generated input nodes (for example: passed as numpy arrays). :return: New node with quantized value. Input floating point values are quantized into a discrete set of floating point values. @@ -400,6 +392,6 @@ def fake_quantize( """ return _get_node_factory_opset13().create( "FakeQuantize", - as_nodes(data, input_low, input_high, output_low, output_high), + as_nodes(data, input_low, input_high, output_low, output_high, name=name), {"levels": levels, "auto_broadcast": auto_broadcast.upper()}, ) diff --git a/src/bindings/python/src/openvino/runtime/utils/decorators.py b/src/bindings/python/src/openvino/runtime/utils/decorators.py index 991edc519db..5e446943ffa 100644 --- a/src/bindings/python/src/openvino/runtime/utils/decorators.py +++ b/src/bindings/python/src/openvino/runtime/utils/decorators.py @@ -4,15 +4,21 @@ from functools import wraps from inspect import getfullargspec -from typing import Any, Callable, List +from typing import Any, Callable, List, Optional from openvino.runtime import Node, Output from openvino.runtime.utils.types import NodeInput, as_node, as_nodes -def _set_node_friendly_name(node: Node, /, **kwargs: Any) -> Node: +def _get_name(**kwargs: Any) -> Node: if "name" in kwargs: - node.friendly_name = kwargs["name"] + return kwargs["name"] + return None + + +def _set_node_friendly_name(node: Node, *, name: Optional[str] = None) -> Node: + if name is not None: + node.friendly_name = name return node @@ -22,47 +28,20 @@ def nameable_op(node_factory_function: Callable) -> Callable: @wraps(node_factory_function) def wrapper(*args: Any, **kwargs: Any) -> Node: node = node_factory_function(*args, **kwargs) - node = _set_node_friendly_name(node, **kwargs) + node = _set_node_friendly_name(node, name=_get_name(**kwargs)) return node return wrapper -def _apply_affix(node: Node, prefix: str = "", suffix: str = "") -> Node: - node.friendly_name = prefix + node.friendly_name + suffix - return node - - -def apply_affix_on(*node_names: Any) -> Callable: - """Add prefix and/or suffix to all openvino names of operators defined as arguments.""" - - def decorator(func: Callable) -> Callable: - @wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> Node: - arg_names = getfullargspec(func).args - arg_mapping = dict(zip(arg_names, args)) - for node_name in node_names: - # Apply only on auto-generated nodes. Create such node and apply affixes. - # Any Node instance supplied by the user is keeping the name as-is. - if node_name in arg_mapping and not isinstance(arg_mapping[node_name], (Node, Output)): - arg_mapping[node_name] = _apply_affix(as_node(arg_mapping[node_name]), - prefix=kwargs.get("prefix", ""), - suffix=kwargs.get("suffix", ""), - ) - results = func(**arg_mapping, **kwargs) - return results - return wrapper - return decorator - - def unary_op(node_factory_function: Callable) -> Callable: """Convert the first input value to a Constant Node if a numeric value is detected.""" @wraps(node_factory_function) def wrapper(input_value: NodeInput, *args: Any, **kwargs: Any) -> Node: - input_node = as_node(input_value) + input_node = as_node(input_value, name=_get_name(**kwargs)) node = node_factory_function(input_node, *args, **kwargs) - node = _set_node_friendly_name(node, **kwargs) + node = _set_node_friendly_name(node, name=_get_name(**kwargs)) return node return wrapper @@ -73,9 +52,9 @@ def binary_op(node_factory_function: Callable) -> Callable: @wraps(node_factory_function) def wrapper(left: NodeInput, right: NodeInput, *args: Any, **kwargs: Any) -> Node: - left, right = as_nodes(left, right) + left, right = as_nodes(left, right, name=_get_name(**kwargs)) node = node_factory_function(left, right, *args, **kwargs) - node = _set_node_friendly_name(node, **kwargs) + node = _set_node_friendly_name(node, name=_get_name(**kwargs)) return node return wrapper diff --git a/src/bindings/python/src/openvino/runtime/utils/types.py b/src/bindings/python/src/openvino/runtime/utils/types.py index 61214a386d3..52f1faf8e1e 100644 --- a/src/bindings/python/src/openvino/runtime/utils/types.py +++ b/src/bindings/python/src/openvino/runtime/utils/types.py @@ -5,7 +5,7 @@ """Functions related to converting between Python and numpy types and openvino types.""" import logging -from typing import List, Union +from typing import List, Union, Optional import numpy as np @@ -145,7 +145,7 @@ def get_shape(data: NumericData) -> TensorShape: return [] -def make_constant_node(value: NumericData, dtype: Union[NumericType, Type] = None) -> Constant: +def make_constant_node(value: NumericData, dtype: Union[NumericType, Type] = None, *, name: Optional[str] = None) -> Constant: """Return an openvino Constant node with the specified value.""" ndarray = get_ndarray(value) if dtype is not None: @@ -153,18 +153,23 @@ def make_constant_node(value: NumericData, dtype: Union[NumericType, Type] = Non else: element_type = get_element_type(ndarray.dtype) - return Constant(element_type, Shape(ndarray.shape), ndarray.flatten().tolist()) + const = Constant(element_type, Shape(ndarray.shape), ndarray.flatten().tolist()) + + if name: + const.friendly_name = name + "/" + const.friendly_name + + return const -def as_node(input_value: NodeInput) -> Node: +def as_node(input_value: NodeInput, name: Optional[str] = None) -> Node: """Return input values as nodes. Scalars will be converted to Constant nodes.""" if issubclass(type(input_value), Node): return input_value if issubclass(type(input_value), Output): return input_value - return make_constant_node(input_value) + return make_constant_node(input_value, name=name) -def as_nodes(*input_values: NodeInput) -> List[Node]: +def as_nodes(*input_values: NodeInput, name: Optional[str] = None) -> List[Node]: """Return input values as nodes. Scalars will be converted to Constant nodes.""" - return [as_node(input_value) for input_value in input_values] + return [as_node(input_value, name=name) for input_value in input_values] diff --git a/src/bindings/python/tests/test_graph/test_affix_ops.py b/src/bindings/python/tests/test_graph/test_affix_ops.py index 7e42b38172c..f9e196348a9 100644 --- a/src/bindings/python/tests/test_graph/test_affix_ops.py +++ b/src/bindings/python/tests/test_graph/test_affix_ops.py @@ -10,15 +10,13 @@ import openvino.runtime.opset13 as ov from openvino import Type -@pytest.mark.parametrize("prefix_string", [ +@pytest.mark.parametrize("op_name", [ "ABC", - "custom_prefix_", + "Fakeee", + "123456", + "FakeQuantize", ]) -@pytest.mark.parametrize("suffix_string", [ - "XYZ", - "_custom_suffix", -]) -def test_affix_not_applied_on_nodes(prefix_string, suffix_string): +def test_affix_not_applied_on_nodes(op_name): levels = np.int32(4) data_shape = [1, 2, 3, 4] bound_shape = [] @@ -45,12 +43,12 @@ def test_affix_not_applied_on_nodes(prefix_string, suffix_string): parameter_output_low, parameter_output_high, levels, - prefix=prefix_string, - suffix=suffix_string, + name=op_name, ) # Check if node was created correctly assert model.get_type_name() == "FakeQuantize" + assert model.get_friendly_name() == op_name assert model.get_output_size() == 1 assert list(model.get_output_shape(0)) == [1, 2, 3, 4] @@ -61,15 +59,13 @@ def test_affix_not_applied_on_nodes(prefix_string, suffix_string): assert output_high_name == parameter_output_high.friendly_name -@pytest.mark.parametrize("prefix_string", [ +@pytest.mark.parametrize("op_name", [ "ABC", - "custom_prefix_", + "Fakeee", + "123456", + "FakeQuantize", ]) -@pytest.mark.parametrize("suffix_string", [ - "XYZ", - "_custom_suffix", -]) -def test_affix_not_applied_on_output(prefix_string, suffix_string): +def test_affix_not_applied_on_output(op_name): levels = np.int32(4) data_shape = [1, 2, 3, 4] bound_shape = [] @@ -100,12 +96,12 @@ def test_affix_not_applied_on_output(prefix_string, suffix_string): parameter_output_low, parameter_output_high, levels, - prefix=prefix_string, - suffix=suffix_string, + name=op_name, ) # Check if node was created correctly assert model.get_type_name() == "FakeQuantize" + assert model.get_friendly_name() == op_name assert model.get_output_size() == 1 assert list(model.get_output_shape(0)) == [1, 2, 3, 4] @@ -116,17 +112,13 @@ def test_affix_not_applied_on_output(prefix_string, suffix_string): assert output_high_name == parameter_output_high.friendly_name -@pytest.mark.parametrize("prefix_string", [ - "", +@pytest.mark.parametrize("op_name", [ "ABC", - "custom_prefix_", + "Fakeee", + "123456", + "FakeQuantize", ]) -@pytest.mark.parametrize("suffix_string", [ - "", - "XYZ", - "_custom_suffix", -]) -def test_fake_quantize_affix(prefix_string, suffix_string): +def test_fake_quantize_prefix(op_name): levels = np.int32(4) data_shape = [1, 2, 3, 4] bound_shape = [1] @@ -144,21 +136,15 @@ def test_fake_quantize_affix(prefix_string, suffix_string): d_arr, e_arr, levels, - prefix=prefix_string, - suffix=suffix_string, + name=op_name, ) # Check if node was created correctly assert model.get_type_name() == "FakeQuantize" + assert model.get_friendly_name() == op_name assert model.get_output_size() == 1 assert list(model.get_output_shape(0)) == [1, 2, 3, 4] - # Check that data parameter and node itself do not change: - if prefix_string != "": - assert prefix_string not in model.friendly_name - if suffix_string != "": - assert suffix_string not in model.friendly_name # Check that other parameters change: for node_input in model.inputs(): generated_node = node_input.get_source_output().get_node() - assert prefix_string in generated_node.friendly_name - assert suffix_string in generated_node.friendly_name + assert op_name + "/" in generated_node.friendly_name