[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 <katarzyna.mitrus@intel.com>
This commit is contained in:
Jan Iwaszkiewicz 2024-04-15 13:21:31 +02:00 committed by GitHub
parent 2700cef43f
commit ea413a654e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 50 additions and 88 deletions

View File

@ -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()},
)

View File

@ -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

View File

@ -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]

View File

@ -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