From 17e91cabfd04d037f3af3a264fcafb17c6d8b9c0 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Sat, 5 Aug 2023 18:12:49 +0300 Subject: [PATCH 01/45] Add amax, amin, unstack functions --- nncf/experimental/tensor/functions.py | 88 +++++++++++++++---- nncf/experimental/tensor/numpy_functions.py | 20 ++++- nncf/experimental/tensor/torch_functions.py | 21 +++++ .../template_test_nncf_tensor.py | 64 ++++++++++++++ 4 files changed, 174 insertions(+), 19 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 30f27a65cce..620c3e70209 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -48,7 +48,7 @@ def device(a: TTensor) -> TensorDeviceType: @functools.singledispatch @_tensor_guard -def squeeze(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: +def squeeze(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: """ Remove axes of length one from a. @@ -63,7 +63,7 @@ def squeeze(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTenso @functools.singledispatch @_tensor_guard -def flatten(a: TTensor) -> TTensor: +def flatten(a: TTensor) -> Tensor: """ Return a copy of the tensor collapsed into one dimension. @@ -75,7 +75,7 @@ def flatten(a: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def max(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin +def max(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Return the maximum of an array or maximum along an axis. @@ -88,7 +88,20 @@ def max(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: @functools.singledispatch @_tensor_guard -def min(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin +def amax(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin + """ + Return the maximum of an array or maximum along an axis. + + :param a: The input tensor. + :param axis: Axis or axes along which to operate. By default, flattened input is used. + :return: Maximum of a. + """ + return Tensor(amax(a.data, axis)) + + +@functools.singledispatch +@_tensor_guard +def min(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Return the minimum of an array or minimum along an axis. @@ -101,7 +114,20 @@ def min(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: @functools.singledispatch @_tensor_guard -def abs(a: TTensor) -> TTensor: # pylint: disable=redefined-builtin +def amin(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin + """ + Return the minimum of an array or minimum along an axis. + + :param a: The input tensor. + :param axis: Axis or axes along which to operate. By default, flattened input is used. + :return: Minimum of a. + """ + return Tensor(amin(a.data, axis)) + + +@functools.singledispatch +@_tensor_guard +def abs(a: TTensor) -> Tensor: # pylint: disable=redefined-builtin """ Calculate the absolute value element-wise. @@ -113,7 +139,7 @@ def abs(a: TTensor) -> TTensor: # pylint: disable=redefined-builtin @functools.singledispatch @_tensor_guard -def astype(a: TTensor, data_type: TensorDataType) -> TTensor: +def astype(a: TTensor, data_type: TensorDataType) -> Tensor: """ Copy of the tensor, cast to a specified type. @@ -139,7 +165,7 @@ def dtype(a: TTensor) -> TensorDataType: @functools.singledispatch @_tensor_guard -def reshape(a: TTensor, shape: List[int]) -> TTensor: +def reshape(a: TTensor, shape: List[int]) -> Tensor: """ Gives a new shape to a tensor without changing its data. @@ -152,7 +178,7 @@ def reshape(a: TTensor, shape: List[int]) -> TTensor: @functools.singledispatch @_tensor_guard -def all(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin +def all(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Test whether all tensor elements along a given axis evaluate to True. @@ -165,7 +191,7 @@ def all(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: @functools.singledispatch @_tensor_guard -def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> TTensor: +def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> Tensor: """ Returns True if two arrays are element-wise equal within a tolerance. @@ -191,7 +217,7 @@ def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, e @functools.singledispatch @_tensor_guard -def any(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin +def any(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Test whether any tensor elements along a given axis evaluate to True. @@ -204,7 +230,7 @@ def any(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: @functools.singledispatch @_tensor_guard -def count_nonzero(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: +def count_nonzero(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: """ Counts the number of non-zero values in the tensor input. @@ -218,7 +244,7 @@ def count_nonzero(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> @functools.singledispatch @_tensor_guard -def isempty(a: TTensor) -> TTensor: +def isempty(a: TTensor) -> Tensor: """ Return True if input tensor is empty. @@ -230,7 +256,7 @@ def isempty(a: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def isclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> TTensor: +def isclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> Tensor: """ Returns a boolean array where two arrays are element-wise equal within a tolerance. @@ -256,7 +282,7 @@ def isclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, eq @functools.singledispatch @_tensor_guard -def maximum(x1: TTensor, x2: TTensor) -> TTensor: +def maximum(x1: TTensor, x2: TTensor) -> Tensor: """ Element-wise maximum of tensor elements. @@ -269,7 +295,7 @@ def maximum(x1: TTensor, x2: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def minimum(x1: TTensor, x2: TTensor) -> TTensor: +def minimum(x1: TTensor, x2: TTensor) -> Tensor: """ Element-wise minimum of tensor elements. @@ -282,7 +308,7 @@ def minimum(x1: TTensor, x2: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def ones_like(a: TTensor) -> TTensor: +def ones_like(a: TTensor) -> Tensor: """ Return a tensor of ones with the same shape and type as a given tensor. @@ -294,7 +320,7 @@ def ones_like(a: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def where(condition: TTensor, x: TTensor, y: TTensor) -> TTensor: +def where(condition: TTensor, x: TTensor, y: TTensor) -> Tensor: """ Return elements chosen from x or y depending on condition. @@ -314,7 +340,7 @@ def where(condition: TTensor, x: TTensor, y: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def zeros_like(a: TTensor) -> TTensor: +def zeros_like(a: TTensor) -> Tensor: """ Return an tensor of zeros with the same shape and type as a given tensor. @@ -324,11 +350,37 @@ def zeros_like(a: TTensor) -> TTensor: return Tensor(zeros_like(a.data)) +def stack(x: List[TTensor], axis: int = 0) -> TTensor: + """ + Stacks a list or deque of NNCFTensors rank-R tensors into one NNCFTensor rank-(R+1) tensor. + + :param x: List or deque of NNCFTensors. + :param axis: The axis to stack along. + :return: Stacked NNCFTensor. + """ + + +@functools.singledispatch +@_tensor_guard +def unstack(a: Tensor, axis: int = 0) -> List[Tensor]: + """ + Unstack a NNCFTensor into list. + + :param a: NNCFTensor to unstack. + :param axis: The axis to unstack along. + :return: List of NNCFTensor. + """ + res = unstack(a.data, axis=axis) + return [Tensor(i) for i in res] + + __all__ = [ "device", "squeeze", "flatten", + "amax", "max", + "amin", "min", "abs", "astype", diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index be070db4bdb..a8ca92bf6db 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -9,7 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Tuple, Union +from typing import List, Optional, Tuple, Union import numpy as np @@ -52,12 +52,24 @@ def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = return np.max(a, axis=axis) +@functions.amax.register(np.ndarray) +@functions.amax.register(np.number) +def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: + return np.amax(a, axis=axis) + + @functions.min.register(np.ndarray) @functions.min.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.min(a, axis=axis) +@functions.amin.register(np.ndarray) +@functions.amin.register(np.number) +def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: + return np.amin(a, axis=axis) + + @functions.abs.register(np.ndarray) @functions.abs.register(np.number) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: @@ -162,3 +174,9 @@ def _( @functions.zeros_like.register(np.number) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.zeros_like(a) + + +@functions.unstack.register(np.ndarray) +@functions.unstack.register(np.number) +def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: + return [np.squeeze(e, axis) for e in np.split(x, x.shape[axis], axis=axis)] diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 09ef0f1b886..67ba3d60ff1 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -56,6 +56,13 @@ def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.T return torch.max(a, dim=axis).values +@functions.amax.register(torch.Tensor) +def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: + if axis is None: + return torch.amax(a) + return torch.amax(a, dim=axis) + + @functions.min.register(torch.Tensor) def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: if axis is None: @@ -63,6 +70,13 @@ def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.T return torch.min(a, dim=axis).values +@functions.amin.register(torch.Tensor) +def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: + if axis is None: + return torch.amin(a) + return torch.amin(a, dim=axis) + + @functions.abs.register(torch.Tensor) def _(a: torch.Tensor) -> torch.Tensor: return torch.absolute(a) @@ -146,3 +160,10 @@ def _( @functions.zeros_like.register(torch.Tensor) def _(a: torch.Tensor) -> torch.Tensor: return torch.zeros_like(a) + + +@functions.unstack.register(torch.Tensor) +def unstack(x: torch.Tensor, axis: int = 0) -> List[torch.Tensor]: + if list(x.shape) == []: + x = x.unsqueeze(0) + return [i for i in torch.unbind(x, dim=axis)] diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index 9fff5e9de1c..c08d0c1874d 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -243,6 +243,26 @@ def test_fn_max(self, val, axis, ref): assert res.data == ref_tensor assert isinstance(res, Tensor) + @pytest.mark.parametrize( + "val, axis, ref", + ( + (1, None, 1), + ([1], None, 1), + ([[[[1], [2]], [[3], [4]]]], None, 4), + ([[1, 2], [3, 4]], 1, [2, 4]), + ), + ) + def test_fn_amax(self, val, axis, ref): + tensor = self.to_tensor(val) + nncf_tensor = Tensor(tensor) + ref_tensor = self.to_tensor(ref) + res = functions.amax(nncf_tensor, axis=axis) + if isinstance(ref, list): + assert all(res.data == ref_tensor) + else: + assert res.data == ref_tensor + assert isinstance(res, Tensor) + @pytest.mark.parametrize( "val, axis, ref", ( @@ -281,6 +301,25 @@ def test_fn_min(self, val, axis, ref): assert res.data == ref_tensor assert isinstance(res, Tensor) + @pytest.mark.parametrize( + "val, axis, ref", + ( + (1, None, 1), + ([1], None, 1), + ([[[[1], [2]], [[3], [4]]]], None, 1), + ([[1, 2], [3, 4]], 1, [1, 3]), + ), + ) + def test_fn_amin(self, val, axis, ref): + nncf_tensor = Tensor(self.to_tensor(val)) + ref_tensor = self.to_tensor(ref) + res = functions.amin(nncf_tensor, axis=axis) + if isinstance(ref, list): + assert all(res.data == ref_tensor) + else: + assert res.data == ref_tensor + assert isinstance(res, Tensor) + @pytest.mark.parametrize( "val, ref", ( @@ -546,3 +585,28 @@ def test_fn_reshape(self): def test_not_implemented(self): with pytest.raises(NotImplementedError, match="is not implemented for"): functions.device({}, [1, 2]) + + @pytest.mark.parametrize( + "x, axis, ref", + ( + ( + [[0.8, 0.2, 0.2], [0.1, 0.7, 0.1]], + 0, + [[0.8, 0.2, 0.2], [0.1, 0.7, 0.1]], + ), + ( + [[0.8, 0.2, 0.2], [0.1, 0.7, 0.1]], + 1, + [[0.8, 0.1], [0.2, 0.7], [0.2, 0.1]], + ), + ), + ) + def test_fn_unstack(self, x, axis, ref): + tensor = Tensor(self.to_tensor(x)) + ref = [self.to_tensor(r) for r in ref] + + res = functions.unstack(tensor, axis=axis) + + assert isinstance(res, list) + for i in range(len(ref)): + assert all(res[i] == ref[i]) From d87a634bd1a34740b3557ded6840531aa73518d7 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Sat, 5 Aug 2023 23:44:51 +0300 Subject: [PATCH 02/45] unstack --- nncf/experimental/tensor/functions.py | 9 ++++++- nncf/experimental/tensor/numpy_functions.py | 5 ++++ nncf/experimental/tensor/torch_functions.py | 9 +++++-- .../template_test_nncf_tensor.py | 24 +++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 620c3e70209..08afa3956c6 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -350,7 +350,8 @@ def zeros_like(a: TTensor) -> Tensor: return Tensor(zeros_like(a.data)) -def stack(x: List[TTensor], axis: int = 0) -> TTensor: +@functools.singledispatch +def stack(x: List[TTensor], axis: int = 0) -> Tensor: """ Stacks a list or deque of NNCFTensors rank-R tensors into one NNCFTensor rank-(R+1) tensor. @@ -358,6 +359,12 @@ def stack(x: List[TTensor], axis: int = 0) -> TTensor: :param axis: The axis to stack along. :return: Stacked NNCFTensor. """ + if isinstance(x, List): + unwrapped_x = [i.data for i in x] + # singledispatch cannot dispatch function by element in a list + res = stack.registry[type(unwrapped_x[0])](unwrapped_x, axis=axis) + return Tensor(res) + raise NotImplementedError(f"Function `stack` is not implemented for {type(x)}") @functools.singledispatch diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index a8ca92bf6db..196ea9b680e 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -175,6 +175,11 @@ def _( def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.zeros_like(a) +@functions.stack.register(np.ndarray) +@functions.stack.register(np.number) +def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: + return np.stack(x, axis=axis) + @functions.unstack.register(np.ndarray) @functions.unstack.register(np.number) diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 67ba3d60ff1..7186734d6ec 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -162,8 +162,13 @@ def _(a: torch.Tensor) -> torch.Tensor: return torch.zeros_like(a) +@functions.stack.register(torch.Tensor) +def _(x: List[torch.Tensor], axis: int = 0) -> List[torch.Tensor]: + return torch.stack(x, dim=axis) + + @functions.unstack.register(torch.Tensor) -def unstack(x: torch.Tensor, axis: int = 0) -> List[torch.Tensor]: - if list(x.shape) == []: +def _(x: torch.Tensor, axis: int = 0) -> List[torch.Tensor]: + if not list(x.shape): x = x.unsqueeze(0) return [i for i in torch.unbind(x, dim=axis)] diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index c08d0c1874d..07d9aa5956f 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -610,3 +610,27 @@ def test_fn_unstack(self, x, axis, ref): assert isinstance(res, list) for i in range(len(ref)): assert all(res[i] == ref[i]) + + @pytest.mark.parametrize( + "x, axis, ref", + ( + ( + [[0.8, 0.2, 0.2], [0.1, 0.7, 0.1]], + 0, + [[0.8, 0.2, 0.2], [0.1, 0.7, 0.1]], + ), + ( + [[0.8, 0.2, 0.2], [0.1, 0.7, 0.1]], + 1, + [[0.8, 0.1], [0.2, 0.7], [0.2, 0.1]], + ), + ), + ) + def test_fn_stack(self, x, axis, ref): + tensor = [Tensor(self.to_tensor(i)) for i in x] + ref = self.to_tensor(ref) + + res = functions.stack(tensor, axis=axis) + + assert isinstance(res, Tensor) + assert functions.all(res.data == ref) From 20b75f603e387faa99863b874c974de9dc9847f0 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Sat, 5 Aug 2023 23:46:16 +0300 Subject: [PATCH 03/45] unused func --- nncf/quantization/algorithms/min_max/torch_backend.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nncf/quantization/algorithms/min_max/torch_backend.py b/nncf/quantization/algorithms/min_max/torch_backend.py index 82c858b2f28..e4e34f4ee3c 100644 --- a/nncf/quantization/algorithms/min_max/torch_backend.py +++ b/nncf/quantization/algorithms/min_max/torch_backend.py @@ -114,10 +114,6 @@ def hw_config(self) -> HWConfig: def quant_trait_op_dict(self) -> Dict[int, OperatorMetatype]: return DEFAULT_PT_QUANT_TRAIT_TO_OP_DICT - @staticmethod - def model_transformer(model: NNCFNetwork) -> ModelTransformer: - return PTModelTransformer(model) - @staticmethod def target_point(target_type: TargetType, target_node_name: str, port_id: int) -> PTTargetPoint: if NNCFGraphNodeType.INPUT_NODE in target_node_name or target_type == TargetType.POST_LAYER_OPERATION: From d82af3dbdc649d67310456f31dc6f00fc40931c3 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 8 Aug 2023 02:25:36 +0300 Subject: [PATCH 04/45] moveaxis --- nncf/experimental/tensor/functions.py | 49 ++++++++++++------- nncf/experimental/tensor/numpy_functions.py | 6 +++ nncf/experimental/tensor/torch_functions.py | 5 ++ .../template_test_nncf_tensor.py | 8 +++ 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 08afa3956c6..4b0c1b78a11 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -353,11 +353,11 @@ def zeros_like(a: TTensor) -> Tensor: @functools.singledispatch def stack(x: List[TTensor], axis: int = 0) -> Tensor: """ - Stacks a list or deque of NNCFTensors rank-R tensors into one NNCFTensor rank-(R+1) tensor. + Stacks a list or deque of Tensors rank-R tensors into one Tensor rank-(R+1) tensor. - :param x: List or deque of NNCFTensors. + :param x: List or deque of Tensors. :param axis: The axis to stack along. - :return: Stacked NNCFTensor. + :return: Stacked Tensor. """ if isinstance(x, List): unwrapped_x = [i.data for i in x] @@ -371,37 +371,52 @@ def stack(x: List[TTensor], axis: int = 0) -> Tensor: @_tensor_guard def unstack(a: Tensor, axis: int = 0) -> List[Tensor]: """ - Unstack a NNCFTensor into list. + Unstack a Tensor into list. - :param a: NNCFTensor to unstack. + :param a: Tensor to unstack. :param axis: The axis to unstack along. - :return: List of NNCFTensor. + :return: List of Tensor. """ res = unstack(a.data, axis=axis) return [Tensor(i) for i in res] +@functools.singledispatch +@_tensor_guard +def moveaxis(a: Tensor, source: Union[int, List[int]], destination: Union[int, List[int]]) -> Tensor: + """ + Move axes of an array to new positions. + + :param a: The array whose axes should be reordered. + :param source: Original positions of the axes to move. These must be unique. + :param destination: Destination positions for each of the original axes. These must also be unique. + :return: Array with moved axes. + """ + return Tensor(moveaxis(a.data, source, destination)) + + __all__ = [ - "device", - "squeeze", - "flatten", - "amax", - "max", - "amin", - "min", "abs", - "astype", - "reshape", "all", "allclose", + "amax", + "amin", "any", + "astype", "count_nonzero", - "isempty", + "device", + "flatten", "isclose", + "isempty", + "max", "maximum", + "min", "minimum", - "ones_like", "minimum", + "moveaxis", + "ones_like", + "reshape", + "squeeze", "where", "zeros_like", ] diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 196ea9b680e..269739d1f37 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -175,6 +175,7 @@ def _( def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.zeros_like(a) + @functions.stack.register(np.ndarray) @functions.stack.register(np.number) def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: @@ -185,3 +186,8 @@ def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: @functions.unstack.register(np.number) def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: return [np.squeeze(e, axis) for e in np.split(x, x.shape[axis], axis=axis)] + + +@functions.moveaxis.register(np.ndarray) +def _(a: np.ndarray, source: Union[int, List[int]], destination: Union[int, List[int]]) -> np.ndarray: + return np.moveaxis(a, source, destination) diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 7186734d6ec..4dca047ecee 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -172,3 +172,8 @@ def _(x: torch.Tensor, axis: int = 0) -> List[torch.Tensor]: if not list(x.shape): x = x.unsqueeze(0) return [i for i in torch.unbind(x, dim=axis)] + + +@functions.moveaxis.register(torch.Tensor) +def _(a: torch.Tensor, source: Union[int, List[int]], destination: Union[int, List[int]]) -> torch.Tensor: + return torch.moveaxis(a, source, destination) diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index 07d9aa5956f..f3ceaeea1cd 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -634,3 +634,11 @@ def test_fn_stack(self, x, axis, ref): assert isinstance(res, Tensor) assert functions.all(res.data == ref) + + def test_fn_moveaxis(self): + tensor = [[0, 0, 0], [0, 0, 0]] + tensor = Tensor(self.to_tensor(tensor)) + + res = functions.moveaxis(tensor, 0, -1) + + assert res.shape == [3, 2] From 427ffa34acc6f45cf0aa35183bfc195cca251f36 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 8 Aug 2023 21:55:01 +0300 Subject: [PATCH 05/45] mean --- nncf/experimental/tensor/functions.py | 15 +++++++ nncf/experimental/tensor/numpy_functions.py | 6 +++ nncf/experimental/tensor/torch_functions.py | 5 +++ .../template_test_nncf_tensor.py | 41 +++++++++++++++++++ 4 files changed, 67 insertions(+) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 4b0c1b78a11..58e075c8234 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -395,6 +395,20 @@ def moveaxis(a: Tensor, source: Union[int, List[int]], destination: Union[int, L return Tensor(moveaxis(a.data, source, destination)) +@functools.singledispatch +@_tensor_guard +def mean(a: Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) -> Tensor: + """ + Compute the arithmetic mean along the specified axis. + + :param a: Array containing numbers whose mean is desired. + :param axis: Axis or axes along which the means are computed. + :param keepdims: Destination positions for each of the original axes. These must also be unique. + :return: Array with moved axes. + """ + return Tensor(mean(a.data, axis, keepdims)) + + __all__ = [ "abs", "all", @@ -410,6 +424,7 @@ def moveaxis(a: Tensor, source: Union[int, List[int]], destination: Union[int, L "isempty", "max", "maximum", + "mean", "min", "minimum", "minimum", diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 269739d1f37..94d8bac6e65 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -191,3 +191,9 @@ def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: @functions.moveaxis.register(np.ndarray) def _(a: np.ndarray, source: Union[int, List[int]], destination: Union[int, List[int]]) -> np.ndarray: return np.moveaxis(a, source, destination) + + +@functions.mean.register(np.ndarray) +@functions.mean.register(np.number) +def _(a: Union[np.ndarray, np.number], axis: Union[int, List[int]] = None, keepdims: bool = False) -> np.ndarray: + return np.mean(a.data, axis=axis, keepdims=keepdims) diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 4dca047ecee..d3500501d6e 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -177,3 +177,8 @@ def _(x: torch.Tensor, axis: int = 0) -> List[torch.Tensor]: @functions.moveaxis.register(torch.Tensor) def _(a: torch.Tensor, source: Union[int, List[int]], destination: Union[int, List[int]]) -> torch.Tensor: return torch.moveaxis(a, source, destination) + + +@functions.mean.register(torch.Tensor) +def _(a: torch.Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) -> torch.Tensor: + return torch.mean(a.data, axis=axis, keepdims=keepdims) diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index f3ceaeea1cd..7988a5e7dfd 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -642,3 +642,44 @@ def test_fn_moveaxis(self): res = functions.moveaxis(tensor, 0, -1) assert res.shape == [3, 2] + + @pytest.mark.parametrize( + "x, axis, keepdims, ref", + ( + ( + [[0.8, 0.2, 0.2], [0.1, 0.7, 0.1]], + 0, + False, + [0.45, 0.45, 0.15], + ), + ( + [[0.8, 0.2, 0.2], [0.1, 0.7, 0.1]], + 0, + True, + [[0.45, 0.45, 0.15]], + ), + ( + [[0.8, 0.2, 0.2], [0.1, 0.7, 0.1]], + (0, 1), + True, + [[0.35]], + ), + ( + [[0.8, 0.2, 0.2], [0.1, 0.7, 0.1]], + None, + False, + 0.35, + ), + ), + ) + def test_fn_mean(self, x, axis, keepdims, ref): + tensor = Tensor(self.to_tensor(x)) + ref_tensor = self.to_tensor(ref) + + res = functions.mean(tensor, axis, keepdims) + + assert isinstance(res, Tensor) + if isinstance(ref, list): + assert functions.all(functions.isclose(res.data, ref_tensor)) + else: + assert functions.isclose(res.data, ref_tensor) From 4eefd0e3a2bdb630721c800c1305711ff76ef125 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 16 Aug 2023 03:54:22 +0300 Subject: [PATCH 06/45] round --- nncf/experimental/tensor/functions.py | 15 ++++++++++++++ nncf/experimental/tensor/numpy_functions.py | 8 +++++++- nncf/experimental/tensor/torch_functions.py | 7 ++++++- .../template_test_nncf_tensor.py | 20 +++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 58e075c8234..b537581e944 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -409,6 +409,20 @@ def mean(a: Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) return Tensor(mean(a.data, axis, keepdims)) +@functools.singledispatch +@_tensor_guard +def round(a: Tensor, decimals=0) -> Tensor: + """ + Evenly round to the given number of decimals. + + :param a: Input data. + :param decimals: Number of decimal places to round to (default: 0). If decimals is negative, + it specifies the number of positions to the left of the decimal point. + :return: An array of the same type as a, containing the rounded values. + """ + return Tensor(round(a.data, decimals)) + + __all__ = [ "abs", "all", @@ -431,6 +445,7 @@ def mean(a: Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) "moveaxis", "ones_like", "reshape", + "round", "squeeze", "where", "zeros_like", diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 94d8bac6e65..dff7373b8a3 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -196,4 +196,10 @@ def _(a: np.ndarray, source: Union[int, List[int]], destination: Union[int, List @functions.mean.register(np.ndarray) @functions.mean.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Union[int, List[int]] = None, keepdims: bool = False) -> np.ndarray: - return np.mean(a.data, axis=axis, keepdims=keepdims) + return np.mean(a, axis=axis, keepdims=keepdims) + + +@functions.round.register(np.ndarray) +@functions.round.register(np.number) +def _(a: Union[np.ndarray, np.number], decimals: int = 0) -> np.ndarray: + return np.round(a, decimals=decimals) diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index d3500501d6e..596b5402bd9 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -181,4 +181,9 @@ def _(a: torch.Tensor, source: Union[int, List[int]], destination: Union[int, Li @functions.mean.register(torch.Tensor) def _(a: torch.Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) -> torch.Tensor: - return torch.mean(a.data, axis=axis, keepdims=keepdims) + return torch.mean(a, axis=axis, keepdims=keepdims) + + +@functions.round.register(torch.Tensor) +def _(a: torch.Tensor, decimals=0) -> torch.Tensor: + return torch.round(a, decimals=decimals) diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index 7988a5e7dfd..a83de1c7a77 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -683,3 +683,23 @@ def test_fn_mean(self, x, axis, keepdims, ref): assert functions.all(functions.isclose(res.data, ref_tensor)) else: assert functions.isclose(res.data, ref_tensor) + + @pytest.mark.parametrize( + "val, decimals, ref", + ( + (1.1, 0, 1.0), + ([1.1, 0.9], 0, [1.0, 1.0]), + ([1.11, 0.91], 1, [1.1, 0.9]), + ), + ) + def test_fn_round(self, val, decimals, ref): + tensor = Tensor(self.to_tensor(val)) + ref_tensor = self.to_tensor(ref) + + res = functions.round(tensor, decimals) + + assert isinstance(res, Tensor) + if isinstance(ref, list): + assert functions.all(functions.isclose(res.data, ref_tensor)) + else: + assert functions.isclose(res.data, ref_tensor) From 5038dab96a204a5705595bf2fc796ec1e6fd5176 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Fri, 18 Aug 2023 00:09:53 +0300 Subject: [PATCH 07/45] tensor for bc --- nncf/experimental/tensor/numpy_functions.py | 26 +++++ .../onnx/quantization/quantizer_parameters.py | 4 +- nncf/openvino/graph/model_transformer.py | 8 +- .../algorithms/min_max/onnx_backend.py | 2 +- .../algorithms/min_max/openvino_backend.py | 2 +- .../algorithms/min_max/torch_backend.py | 11 +-- nncf/quantization/fake_quantize.py | 95 ++++++++++--------- 7 files changed, 87 insertions(+), 61 deletions(-) diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index dff7373b8a3..eea3860cb94 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -28,78 +28,91 @@ DTYPE_MAP_REV = {v: k for k, v in DTYPE_MAP.items()} +@functions.device.register(np.bool_) @functions.device.register(np.ndarray) @functions.device.register(np.number) def _(a: Union[np.ndarray, np.number]) -> TensorDeviceType: return TensorDeviceType.CPU +@functions.squeeze.register(np.bool_) @functions.squeeze.register(np.ndarray) @functions.squeeze.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.squeeze(a, axis=axis) +@functions.flatten.register(np.bool_) @functions.flatten.register(np.ndarray) @functions.flatten.register(np.number) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return a.flatten() +@functions.max.register(np.bool_) @functions.max.register(np.ndarray) @functions.max.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.max(a, axis=axis) +@functions.amax.register(np.bool_) @functions.amax.register(np.ndarray) @functions.amax.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.amax(a, axis=axis) +@functions.min.register(np.bool_) @functions.min.register(np.ndarray) @functions.min.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.min(a, axis=axis) +@functions.amin.register(np.bool_) @functions.amin.register(np.ndarray) @functions.amin.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.amin(a, axis=axis) +@functions.abs.register(np.bool_) @functions.abs.register(np.ndarray) @functions.abs.register(np.number) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.absolute(a) +@functions.astype.register(np.bool_) @functions.astype.register(np.ndarray) @functions.astype.register(np.number) def _(a: Union[np.ndarray, np.number], dtype: TensorDataType) -> np.ndarray: return a.astype(DTYPE_MAP[dtype]) +@functions.dtype.register(np.bool_) @functions.dtype.register(np.ndarray) @functions.dtype.register(np.number) def _(a: Union[np.ndarray, np.number]) -> TensorDataType: return DTYPE_MAP_REV[np.dtype(a.dtype)] +@functions.reshape.register(np.bool_) @functions.reshape.register(np.ndarray) @functions.reshape.register(np.number) def _(a: Union[np.ndarray, np.number], shape: Union[int, Tuple[int]]) -> np.ndarray: return a.reshape(shape) +@functions.all.register(np.bool_) @functions.all.register(np.ndarray) @functions.all.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: return np.all(a, axis=axis) +@functions.allclose.register(np.bool_) @functions.allclose.register(np.ndarray) @functions.allclose.register(np.number) def _( @@ -112,24 +125,28 @@ def _( return np.allclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) +@functions.any.register(np.bool_) @functions.any.register(np.ndarray) @functions.any.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: return np.any(a, axis=axis) +@functions.count_nonzero.register(np.bool_) @functions.count_nonzero.register(np.ndarray) @functions.count_nonzero.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.count_nonzero(a, axis=axis) +@functions.isempty.register(np.bool_) @functions.isempty.register(np.ndarray) @functions.isempty.register(np.number) def _(a: Union[np.ndarray, np.number]) -> bool: return a.size == 0 +@functions.isclose.register(np.bool_) @functions.isclose.register(np.ndarray) @functions.isclose.register(np.number) def _( @@ -142,24 +159,28 @@ def _( return np.isclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) +@functions.maximum.register(np.bool_) @functions.maximum.register(np.ndarray) @functions.maximum.register(np.number) def _(x1: Union[np.ndarray, np.number], x2: np.ndarray) -> np.ndarray: return np.maximum(x1, x2) +@functions.minimum.register(np.bool_) @functions.minimum.register(np.ndarray) @functions.minimum.register(np.number) def _(x1: Union[np.ndarray, np.number], x2: np.ndarray) -> np.ndarray: return np.minimum(x1, x2) +@functions.ones_like.register(np.bool_) @functions.ones_like.register(np.ndarray) @functions.ones_like.register(np.number) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.ones_like(a) +@functions.where.register(np.bool_) @functions.where.register(np.ndarray) @functions.where.register(np.number) def _( @@ -170,18 +191,21 @@ def _( return np.where(condition, x, y) +@functions.zeros_like.register(np.bool_) @functions.zeros_like.register(np.ndarray) @functions.zeros_like.register(np.number) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.zeros_like(a) +@functions.stack.register(np.bool_) @functions.stack.register(np.ndarray) @functions.stack.register(np.number) def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: return np.stack(x, axis=axis) +@functions.unstack.register(np.bool_) @functions.unstack.register(np.ndarray) @functions.unstack.register(np.number) def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: @@ -193,12 +217,14 @@ def _(a: np.ndarray, source: Union[int, List[int]], destination: Union[int, List return np.moveaxis(a, source, destination) +@functions.mean.register(np.bool_) @functions.mean.register(np.ndarray) @functions.mean.register(np.number) def _(a: Union[np.ndarray, np.number], axis: Union[int, List[int]] = None, keepdims: bool = False) -> np.ndarray: return np.mean(a, axis=axis, keepdims=keepdims) +@functions.round.register(np.bool_) @functions.round.register(np.ndarray) @functions.round.register(np.number) def _(a: Union[np.ndarray, np.number], decimals: int = 0) -> np.ndarray: diff --git a/nncf/onnx/quantization/quantizer_parameters.py b/nncf/onnx/quantization/quantizer_parameters.py index 60cc95bdf6a..b7fcb915fac 100644 --- a/nncf/onnx/quantization/quantizer_parameters.py +++ b/nncf/onnx/quantization/quantizer_parameters.py @@ -53,8 +53,8 @@ def convert_fq_params_to_onnx_params( if levels not in [255, 256]: raise ValueError("Can only export to INT8/UIN8 256-level ONNX Quantize/Dequantize pairs.") - input_low, input_high = parameters.input_low, parameters.input_high - output_low, output_high = parameters.output_low, parameters.output_high + input_low, input_high = parameters.input_low.data, parameters.input_high.data + output_low, output_high = parameters.output_low.data, parameters.output_high.data if not np.allclose(input_high, output_high) or not np.allclose(input_low, output_low): raise ValueError( "ONNX Quantize/Dequantize pairs only support input_high == output_high and input_low == output_low." diff --git a/nncf/openvino/graph/model_transformer.py b/nncf/openvino/graph/model_transformer.py index 4237c5cdeb6..1b0dbfee68b 100644 --- a/nncf/openvino/graph/model_transformer.py +++ b/nncf/openvino/graph/model_transformer.py @@ -262,10 +262,10 @@ def _insert_fake_quantize_op( :param name_to_node_mapping: Mapping from node name to node instance. """ fq_params = transformation.quantizer_parameters - input_low = fq_params.input_low - input_high = fq_params.input_high - output_low = fq_params.output_low - output_high = fq_params.output_high + input_low = fq_params.input_low.data + input_high = fq_params.input_high.data + output_low = fq_params.output_low.data + output_high = fq_params.output_high.data levels = fq_params.levels node_name = transformation.target_point.target_node_name diff --git a/nncf/quantization/algorithms/min_max/onnx_backend.py b/nncf/quantization/algorithms/min_max/onnx_backend.py index e87eca80b36..0b675569d7b 100644 --- a/nncf/quantization/algorithms/min_max/onnx_backend.py +++ b/nncf/quantization/algorithms/min_max/onnx_backend.py @@ -101,7 +101,7 @@ def create_quantizer_insertion_command( quantizer_config: QuantizerConfig, parameters: FakeQuantizeParameters, ): - tensor_type = np.int8 if np.any(parameters.input_low < 0) else np.uint8 + tensor_type = np.int8 if np.any(parameters.input_low.data < 0) else np.uint8 if target_point.is_weight_target_point(): tensor_type = np.int8 # The weight is restricted to have only signed range nncf_input_node_next_nodes = ONNXMinMaxAlgoBackend._get_input_edges_mapping(nncf_graph) diff --git a/nncf/quantization/algorithms/min_max/openvino_backend.py b/nncf/quantization/algorithms/min_max/openvino_backend.py index 628d49a5e37..698ca5a2ff3 100644 --- a/nncf/quantization/algorithms/min_max/openvino_backend.py +++ b/nncf/quantization/algorithms/min_max/openvino_backend.py @@ -137,7 +137,7 @@ def _get_reduction_shape_and_use_abs_max( else: raise NotImplementedError(f"Unsupported target point type {target_point.type}.") - # TODO (l-bat): Disable quantizer propogation through layout changing operations + # TODO (l-bat): Disable quantizer propagation through layout changing operations channel_axis = 1 # OpenVINO activations have channel first layout: [N, C, Z, Y, X] axes = tuple(i for i in range(len(shape)) if i != channel_axis) return axes, use_abs_max diff --git a/nncf/quantization/algorithms/min_max/torch_backend.py b/nncf/quantization/algorithms/min_max/torch_backend.py index e4e34f4ee3c..4f46dd25026 100644 --- a/nncf/quantization/algorithms/min_max/torch_backend.py +++ b/nncf/quantization/algorithms/min_max/torch_backend.py @@ -281,13 +281,12 @@ def _create_quantizer( def _fill_quantizer_parameters(quantizer: BaseQuantizer, parameters: FakeQuantizeParameters) -> None: quantizer.eps = 0 if isinstance(quantizer, AsymmetricQuantizer): - quantizer.input_low = torch.nn.Parameter(torch.from_numpy(parameters.input_low)) - quantizer.input_range = torch.nn.Parameter( - torch.from_numpy(np.array(parameters.input_high - parameters.input_low)) - ) + quantizer.input_low = torch.nn.Parameter(parameters.input_low.data) + range = parameters.input_high - parameters.input_low + quantizer.input_range = torch.nn.Parameter(range.data) else: - quantizer.signed = np.any(parameters.input_low < 0) - quantizer.scale = torch.nn.Parameter(torch.from_numpy(parameters.input_high)) + quantizer.signed = bool(torch.any(parameters.input_low.data < 0)) + quantizer.scale = torch.nn.Parameter(parameters.input_high.data) @staticmethod def _create_quantizer_insertion_command( diff --git a/nncf/quantization/fake_quantize.py b/nncf/quantization/fake_quantize.py index 8093e7a9fbc..e2e8d41578a 100644 --- a/nncf/quantization/fake_quantize.py +++ b/nncf/quantization/fake_quantize.py @@ -21,6 +21,9 @@ from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.experimental.tensor import Tensor +from nncf.experimental.tensor import TensorDataType +from nncf.experimental.tensor import functions @dataclass @@ -35,14 +38,14 @@ class FakeQuantizeParameters: :param levels: Number of quantization levels. """ - input_low: np.ndarray - input_high: np.ndarray - output_low: np.ndarray - output_high: np.ndarray + input_low: Tensor + input_high: Tensor + output_low: Tensor + output_high: Tensor levels: int -def fix_zero_filters_symmetric(max_values: np.ndarray, eps: float = 0.01) -> np.ndarray: +def fix_zero_filters_symmetric(max_values: Tensor, eps: float = 0.01) -> Tensor: """ Fixes zero filters for symmetric quantizer. @@ -50,14 +53,12 @@ def fix_zero_filters_symmetric(max_values: np.ndarray, eps: float = 0.01) -> np. :param eps: Correction coefficient. :return: Fixed the high quant number. """ - max_range = np.max(max_values) - lower_threshold = np.maximum(8e-5, eps * max_range) - return np.maximum(lower_threshold, max_values) + max_range = functions.max(max_values) + lower_threshold = functions.maximum(max_range * eps, 8e-5) + return functions.maximum(lower_threshold, max_values) -def fix_zero_filters_asymmetric( - min_values: np.ndarray, max_values: np.ndarray, eps: float = 1e-8 -) -> Tuple[np.ndarray, np.ndarray]: +def fix_zero_filters_asymmetric(min_values: Tensor, max_values: Tensor, eps: float = 1e-8) -> Tuple[Tensor, Tensor]: """ Fixes zero filters for asymmetric quantizer. @@ -69,20 +70,19 @@ def fix_zero_filters_asymmetric( level_high - fixed the high quant number """ ranges = max_values - min_values - ranges = ranges.flatten() if isinstance(ranges, np.ndarray) else np.array([ranges]) min_correction = 8e-4 - corrections = [ - (np.maximum(eps * rng, rng) - rng) * 0.5 if rng > min_correction else min_correction for rng in ranges - ] - corrections = np.array(corrections).reshape(max_values.shape) + corrections = functions.where( + ranges > min_correction, (functions.maximum(eps * ranges, ranges) - ranges) * 0.5, min_correction + ) + level_low = min_values - corrections level_high = max_values + corrections return level_low, level_high def tune_range( - left_border: np.ndarray, right_border: np.ndarray, num_bits: int, unify_zp: bool = False -) -> Tuple[np.ndarray, np.ndarray]: + left_border: Tensor, right_border: Tensor, num_bits: int, unify_zp: bool = False +) -> Tuple[Tensor, Tensor]: """ Tunes asymmetric quantization range to unify the zero point of all channels if `unify_zp` is True, or sets zero quant precisely to zero value otherwise. @@ -101,22 +101,22 @@ def tune_range( if unify_zp: scale = (right_border - left_border) / level_high zero_point = -left_border / scale - avg_zpts = np.round(np.mean(zero_point)) - qval = np.ones_like(left_border) * avg_zpts + avg_zpts = functions.round(functions.mean(zero_point)) + qval = functions.ones_like(left_border) * avg_zpts else: s = level_high / (right_border - left_border) fval = -left_border * s - qval = np.round(fval) + qval = functions.round(fval) - with np.errstate(invalid="ignore", divide="ignore"): - ra = np.where(qval < level_high, qval / (qval - level_high) * right_border, left_border) - rb = np.where(qval > 0.0, (qval - level_high) / qval * left_border, right_border) + with np.errstate(invalid="ignore", divide="ignore"): # TODO: move to Tensor, to sync with numpy and torch??? + ra = functions.where(qval < level_high, qval / (qval - level_high) * right_border, left_border) + rb = functions.where(qval > 0.0, (qval - level_high) / qval * left_border, right_border) range_a = right_border - ra range_b = rb - left_border - mask = np.where(range_a > range_b, 1.0, 0.0) - inv_mask = np.abs(1.0 - mask) + mask = functions.where(range_a > range_b, 1.0, 0.0) + inv_mask = functions.abs(1.0 - mask) ra = mask * ra + inv_mask * left_border rb = inv_mask * rb + mask * right_border @@ -125,12 +125,12 @@ def tune_range( def symmetric_range( - min_values: np.ndarray, - max_values: np.ndarray, + min_values: Tensor, + max_values: Tensor, levels: int, quantizer_config: QuantizerConfig, q_group: QuantizerGroup, -) -> Tuple[np.ndarray, np.ndarray]: +) -> Tuple[Tensor, Tensor]: """ Calculates the numbers of the low and high quant for the symmetric quantization scheme. @@ -148,21 +148,23 @@ def symmetric_range( else: signed = quantizer_config.signedness_to_force is True level_low = ( - np.zeros_like(level_high) if np.all(min_values >= 0) and not signed else -level_high * levels / (levels - 2) + functions.zeros_like(level_high) + if functions.all(min_values >= 0) and not signed + else -level_high * levels / (levels - 2) ) - level_low = level_low.astype(np.float32) - level_high = level_high.astype(np.float32) + level_low = level_low.astype(TensorDataType.float32) + level_high = level_high.astype(TensorDataType.float32) return level_low, level_high def asymmetric_range( - min_values: np.ndarray, - max_values: np.ndarray, + min_values: Tensor, + max_values: Tensor, quantizer_config: QuantizerConfig, q_group: QuantizerGroup, unify_zp: bool = False, -) -> Tuple[np.ndarray, np.ndarray]: +) -> Tuple[Tensor, Tensor]: """ Calculates the numbers of the low and high quant for the asymmetric quantization scheme. @@ -176,15 +178,15 @@ def asymmetric_range( level_high - the high quant number """ level_low, level_high = fix_zero_filters_asymmetric(min_values, max_values) - level_low = np.where(level_low < 0.0, level_low, 0.0) - level_high = np.where(level_high > 0.0, level_high, 0.0) + level_low = functions.where(level_low < 0.0, level_low, 0.0) + level_high = functions.where(level_high > 0.0, level_high, 0.0) if unify_zp and q_group == QuantizerGroup.ACTIVATIONS: raise NotImplementedError("Unified zero point is not supported for activations.") level_low, level_high = tune_range(level_low, level_high, quantizer_config.num_bits, unify_zp=unify_zp) - level_low = level_low.astype(np.float32) - level_high = level_high.astype(np.float32) + level_low = level_low.astype(TensorDataType.float32) + level_high = level_high.astype(TensorDataType.float32) return level_low, level_high @@ -221,8 +223,8 @@ def calculate_quantizer_parameters( False - the full range is used. :return: Parameters of the FakeQuantize layer. """ - min_values = np.array(statistics.min_values).astype(np.float32) - max_values = np.array(statistics.max_values).astype(np.float32) + min_values = Tensor(statistics.min_values).astype(TensorDataType.float32) + max_values = Tensor(statistics.max_values).astype(TensorDataType.float32) if half_range: input_low, input_high, levels = _calculate_scaled_parameters( @@ -240,21 +242,20 @@ def calculate_quantizer_parameters( input_low, input_high = asymmetric_range(min_values, max_values, quantizer_config, quant_group) if not quantizer_config.per_channel: - input_low = np.squeeze(input_low) - input_high = np.squeeze(input_high) + input_low = functions.squeeze(input_low) + input_high = functions.squeeze(input_high) - input_low, input_high = np.array(input_low), np.array(input_high) output_low, output_high = input_low, input_high return FakeQuantizeParameters(input_low, input_high, output_low, output_high, levels) def _calculate_scaled_parameters( - min_values: np.ndarray, - max_values: np.ndarray, + min_values: Tensor, + max_values: Tensor, quantizer_config: QuantizerConfig, quant_group: QuantizerGroup, narrow_range: bool, -) -> Tuple[np.ndarray, np.ndarray, int]: +) -> Tuple[Tensor, Tensor, int]: """ Calculates FakeQuantize layer attributes scaled to effectively use a half range of the quantization range. From 113a0478ddd374b7742257d7cf837c0e0c0e1c7c Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Fri, 18 Aug 2023 05:33:11 +0300 Subject: [PATCH 08/45] tensor fbc --- nncf/experimental/tensor/functions.py | 30 ++- nncf/experimental/tensor/numpy_functions.py | 115 ++++------- nncf/experimental/tensor/tensor.py | 4 + .../fast_bias_correction/algorithm.py | 40 +++- .../fast_bias_correction/backend.py | 31 --- .../fast_bias_correction/onnx_backend.py | 25 +-- .../fast_bias_correction/openvino_backend.py | 27 +-- .../fast_bias_correction/torch_backend.py | 30 +-- .../native/quantization/test_fake_quantize.py | 186 ++++++++++++++++++ tests/torch/ptq/test_fast_bias_correction.py | 27 +++ 10 files changed, 331 insertions(+), 184 deletions(-) create mode 100644 tests/openvino/native/quantization/test_fake_quantize.py diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index b537581e944..8be677b583e 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -362,7 +362,7 @@ def stack(x: List[TTensor], axis: int = 0) -> Tensor: if isinstance(x, List): unwrapped_x = [i.data for i in x] # singledispatch cannot dispatch function by element in a list - res = stack.registry[type(unwrapped_x[0])](unwrapped_x, axis=axis) + res = stack.dispatch(type(unwrapped_x[0]))(unwrapped_x, axis=axis) return Tensor(res) raise NotImplementedError(f"Function `stack` is not implemented for {type(x)}") @@ -423,6 +423,33 @@ def round(a: Tensor, decimals=0) -> Tensor: return Tensor(round(a.data, decimals)) +@functools.singledispatch +@_tensor_guard +def ndim(a: Tensor) -> int: + """ + Returns the number of dimensions of tensor. + + :param a: Input data. + :return: Number of dimensions. + """ + return Tensor(ndim(a.data)) + + +def mean_per_channel(x: Tensor, axis: int) -> Tensor: + """ + Computes the mean of elements across given channel dimension of Tensor. + + :param x: Tensor to reduce. + :param axis: The channel dimensions to reduce. + :return: Reduced Tensor. + """ + if len(x.shape) < 3: + return mean(x.data, axis=0) + x = moveaxis(x.data, axis, 1) + t = x.reshape(x.shape[0], x.shape[1], -1) + return mean(t, axis=(0, 2)) + + __all__ = [ "abs", "all", @@ -439,6 +466,7 @@ def round(a: Tensor, decimals=0) -> Tensor: "max", "maximum", "mean", + "mean_per_channel", "min", "minimum", "minimum", diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index eea3860cb94..842ff96ee4b 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -28,93 +28,76 @@ DTYPE_MAP_REV = {v: k for k, v in DTYPE_MAP.items()} -@functions.device.register(np.bool_) -@functions.device.register(np.ndarray) -@functions.device.register(np.number) +def registry_numpy_types(registry_fn): + def inner(func): + registry_fn.register(np.ndarray)(func) + registry_fn.register(np.generic)(func) + return func + + return inner + + +@registry_numpy_types(functions.device) def _(a: Union[np.ndarray, np.number]) -> TensorDeviceType: return TensorDeviceType.CPU -@functions.squeeze.register(np.bool_) -@functions.squeeze.register(np.ndarray) -@functions.squeeze.register(np.number) +@registry_numpy_types(functions.squeeze) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.squeeze(a, axis=axis) -@functions.flatten.register(np.bool_) -@functions.flatten.register(np.ndarray) -@functions.flatten.register(np.number) +@registry_numpy_types(functions.flatten) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return a.flatten() -@functions.max.register(np.bool_) -@functions.max.register(np.ndarray) -@functions.max.register(np.number) +@registry_numpy_types(functions.max) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.max(a, axis=axis) -@functions.amax.register(np.bool_) -@functions.amax.register(np.ndarray) -@functions.amax.register(np.number) +@registry_numpy_types(functions.amax) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.amax(a, axis=axis) -@functions.min.register(np.bool_) -@functions.min.register(np.ndarray) -@functions.min.register(np.number) +@registry_numpy_types(functions.min) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.min(a, axis=axis) -@functions.amin.register(np.bool_) -@functions.amin.register(np.ndarray) -@functions.amin.register(np.number) +@registry_numpy_types(functions.amin) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.amin(a, axis=axis) -@functions.abs.register(np.bool_) -@functions.abs.register(np.ndarray) -@functions.abs.register(np.number) +@registry_numpy_types(functions.abs) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.absolute(a) -@functions.astype.register(np.bool_) -@functions.astype.register(np.ndarray) -@functions.astype.register(np.number) +@registry_numpy_types(functions.astype) def _(a: Union[np.ndarray, np.number], dtype: TensorDataType) -> np.ndarray: return a.astype(DTYPE_MAP[dtype]) -@functions.dtype.register(np.bool_) -@functions.dtype.register(np.ndarray) -@functions.dtype.register(np.number) +@registry_numpy_types(functions.dtype) def _(a: Union[np.ndarray, np.number]) -> TensorDataType: return DTYPE_MAP_REV[np.dtype(a.dtype)] -@functions.reshape.register(np.bool_) -@functions.reshape.register(np.ndarray) -@functions.reshape.register(np.number) +@registry_numpy_types(functions.reshape) def _(a: Union[np.ndarray, np.number], shape: Union[int, Tuple[int]]) -> np.ndarray: return a.reshape(shape) -@functions.all.register(np.bool_) -@functions.all.register(np.ndarray) -@functions.all.register(np.number) +@registry_numpy_types(functions.all) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: return np.all(a, axis=axis) -@functions.allclose.register(np.bool_) -@functions.allclose.register(np.ndarray) -@functions.allclose.register(np.number) +@registry_numpy_types(functions.allclose) def _( a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], @@ -125,30 +108,22 @@ def _( return np.allclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) -@functions.any.register(np.bool_) -@functions.any.register(np.ndarray) -@functions.any.register(np.number) +@registry_numpy_types(functions.any) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: return np.any(a, axis=axis) -@functions.count_nonzero.register(np.bool_) -@functions.count_nonzero.register(np.ndarray) -@functions.count_nonzero.register(np.number) +@registry_numpy_types(functions.count_nonzero) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.count_nonzero(a, axis=axis) -@functions.isempty.register(np.bool_) -@functions.isempty.register(np.ndarray) -@functions.isempty.register(np.number) +@registry_numpy_types(functions.isempty) def _(a: Union[np.ndarray, np.number]) -> bool: return a.size == 0 -@functions.isclose.register(np.bool_) -@functions.isclose.register(np.ndarray) -@functions.isclose.register(np.number) +@registry_numpy_types(functions.isclose) def _( a: Union[np.ndarray, np.number], b: np.ndarray, @@ -159,30 +134,22 @@ def _( return np.isclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) -@functions.maximum.register(np.bool_) -@functions.maximum.register(np.ndarray) -@functions.maximum.register(np.number) +@registry_numpy_types(functions.maximum) def _(x1: Union[np.ndarray, np.number], x2: np.ndarray) -> np.ndarray: return np.maximum(x1, x2) -@functions.minimum.register(np.bool_) -@functions.minimum.register(np.ndarray) -@functions.minimum.register(np.number) +@registry_numpy_types(functions.minimum) def _(x1: Union[np.ndarray, np.number], x2: np.ndarray) -> np.ndarray: return np.minimum(x1, x2) -@functions.ones_like.register(np.bool_) -@functions.ones_like.register(np.ndarray) -@functions.ones_like.register(np.number) +@registry_numpy_types(functions.ones_like) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.ones_like(a) -@functions.where.register(np.bool_) -@functions.where.register(np.ndarray) -@functions.where.register(np.number) +@registry_numpy_types(functions.where) def _( condition: Union[np.ndarray, np.number], x: Union[np.ndarray, np.number, float, bool], @@ -191,41 +158,31 @@ def _( return np.where(condition, x, y) -@functions.zeros_like.register(np.bool_) -@functions.zeros_like.register(np.ndarray) -@functions.zeros_like.register(np.number) +@registry_numpy_types(functions.zeros_like) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.zeros_like(a) -@functions.stack.register(np.bool_) -@functions.stack.register(np.ndarray) -@functions.stack.register(np.number) +@registry_numpy_types(functions.stack) def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: return np.stack(x, axis=axis) -@functions.unstack.register(np.bool_) -@functions.unstack.register(np.ndarray) -@functions.unstack.register(np.number) +@registry_numpy_types(functions.unstack) def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: return [np.squeeze(e, axis) for e in np.split(x, x.shape[axis], axis=axis)] -@functions.moveaxis.register(np.ndarray) +@registry_numpy_types(functions.moveaxis) def _(a: np.ndarray, source: Union[int, List[int]], destination: Union[int, List[int]]) -> np.ndarray: return np.moveaxis(a, source, destination) -@functions.mean.register(np.bool_) -@functions.mean.register(np.ndarray) -@functions.mean.register(np.number) +@registry_numpy_types(functions.mean) def _(a: Union[np.ndarray, np.number], axis: Union[int, List[int]] = None, keepdims: bool = False) -> np.ndarray: return np.mean(a, axis=axis, keepdims=keepdims) -@functions.round.register(np.bool_) -@functions.round.register(np.ndarray) -@functions.round.register(np.number) +@registry_numpy_types(functions.round) def _(a: Union[np.ndarray, np.number], decimals: int = 0) -> np.ndarray: return np.round(a, decimals=decimals) diff --git a/nncf/experimental/tensor/tensor.py b/nncf/experimental/tensor/tensor.py index daa8e37aff4..97933d11bc7 100644 --- a/nncf/experimental/tensor/tensor.py +++ b/nncf/experimental/tensor/tensor.py @@ -34,6 +34,10 @@ def data(self) -> TTensor: def shape(self) -> List[int]: return list(self.data.shape) + @property + def ndim(self) -> int: + return self.data.ndim + @property def device(self) -> TensorDeviceType: return _call_function("device", self) diff --git a/nncf/quantization/algorithms/fast_bias_correction/algorithm.py b/nncf/quantization/algorithms/fast_bias_correction/algorithm.py index 4a294b5a0f7..444f181c8d1 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/algorithm.py +++ b/nncf/quantization/algorithms/fast_bias_correction/algorithm.py @@ -9,6 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from math import inf from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union from tqdm import tqdm @@ -26,6 +27,8 @@ from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import get_backend +from nncf.experimental.tensor import Tensor +from nncf.experimental.tensor import functions from nncf.quantization.algorithms.algorithm import Algorithm from nncf.quantization.algorithms.fast_bias_correction.backend import ALGO_BACKENDS @@ -168,9 +171,9 @@ def apply( output_name=sub_output_name, ) - bias_shift = self.reshape_bias_shift(bias_shift, bias_value, channel_axis) + bias_shift = self._reshape_bias_shift(bias_shift, bias_value, channel_axis) updated_bias = bias_value + bias_shift - magnitude = self._backend_entity.get_bias_shift_magnitude(bias_value, updated_bias) + magnitude = self._get_bias_shift_magnitude(bias_value, updated_bias) if magnitude < self.threshold: nncf_logger.debug(f"{node_name} bias would be changed") @@ -181,12 +184,31 @@ def apply( # Create commands of bias correction and apply them to the model. transformation_layout = TransformationLayout() for node, bias_value in node_and_new_bias_value: - transformation_layout.register(self._backend_entity.create_bias_correction_command(node, bias_value, graph)) + transformation_layout.register( + self._backend_entity.create_bias_correction_command(node, bias_value.data, graph) + ) transformed_model = model_transformer.transform(transformation_layout) return transformed_model - def reshape_bias_shift(self, bias_shift: TTensor, bias_value: TTensor, channel_axis: int) -> TTensor: + @staticmethod + def _get_bias_shift_magnitude(current_bias_value: Tensor, updated_bias_value: Tensor) -> float: + """ + Calculates bias shift magnitude based on the current and updated values. + + :param current_bias_value: The original bias value. + :param updated_bias_value: The updated bias value. + :return: Magnitude between original and updated bias values. + """ + bias_shift_magnitude = inf + if functions.count_nonzero(current_bias_value == 0) == 0: + bias_shift_magnitude = functions.max( + functions.abs((updated_bias_value - current_bias_value) / current_bias_value) + ) + return bias_shift_magnitude + + @staticmethod + def _reshape_bias_shift(bias_shift: Tensor, bias_value: Tensor, channel_axis: int) -> TTensor: """ Reshape bias_shift tensor in case of dimensions of bias_value is more then 1. @@ -199,7 +221,7 @@ def reshape_bias_shift(self, bias_shift: TTensor, bias_value: TTensor, channel_a if bias_value.ndim > 1: new_shape = [1] * bias_value.ndim new_shape[channel_axis] = bias_shift.shape[0] - bias_shift = self._backend_entity.reshape_tensor(bias_shift, new_shape) + bias_shift = bias_shift.reshape(new_shape) return bias_shift def _get_fp_inputs(self, statistic_points: StatisticPointsContainer, node_name: str) -> Tuple[List, List]: @@ -223,7 +245,7 @@ def input_filter_func(point): node_name, input_filter_func, self._algorithm_key ): statistics = tensor_collector.get_statistics() - input_fp.extend(statistics.mean_values) + input_fp.extend(Tensor(statistics.mean_values)) input_shape.extend(statistics.shape) return input_fp, input_shape @@ -246,7 +268,7 @@ def output_filter_func(point): for tensor_collector in statistic_points.get_algo_statistics_for_node( node_name, output_filter_func, self._algorithm_key ): - output_fp.extend(tensor_collector.get_statistics().mean_values) + output_fp.extend(Tensor(tensor_collector.get_statistics().mean_values)) return output_fp def _extract_submodel(self, model_transformer: ModelTransformer, node_name: str) -> TModel: @@ -300,8 +322,8 @@ def _get_bias_shift( engine = EngineFactory.create(model) raw_output = engine.infer(input_blob) q_outputs = self._backend_entity.process_model_output(raw_output, output_name) - q_outputs = self._backend_entity.tensor_processor.mean_per_channel(q_outputs, channel_axis).tensor - bias_shift = self._backend_entity.post_process_output_data(output_fp) - q_outputs + q_outputs = functions.mean_per_channel(q_outputs, channel_axis) + bias_shift = functions.stack(output_fp) - q_outputs return bias_shift def get_statistic_points(self, model: TModel, graph: NNCFGraph) -> StatisticPointsContainer: diff --git a/nncf/quantization/algorithms/fast_bias_correction/backend.py b/nncf/quantization/algorithms/fast_bias_correction/backend.py index ca25adc2fb2..251c7c4c3af 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/backend.py @@ -177,37 +177,6 @@ def is_node_with_bias(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: :return: Boolean indicating whether the node has a bias or not. """ - @staticmethod - @abstractmethod - def get_bias_shift_magnitude(current_bias_value: TTensor, updated_bias_value: TTensor) -> float: - """ - Calculates bias shift magnitude based on the current and updated values. - - :param current_bias_value: The original bias value. - :param updated_bias_value: The updated bias value. - :return: Magnitude between original and updated bias values. - """ - - @staticmethod - @abstractmethod - def post_process_output_data(data: List[TTensor]) -> TTensor: - """ - Convert data to backend specific type. - - :param data: List of data. - :return: Converted data. - """ - - @staticmethod - @abstractmethod - def reshape_tensor(data: TTensor, new_shape: List[int]) -> TTensor: - """ - Reshape tensor. - - :param data: Tensor. - :param new_shape: New shape. - """ - @staticmethod @abstractmethod def get_node_names_for_input_output_statistics(node: NNCFNode, nncf_graph: NNCFGraph) -> Tuple[str, str]: diff --git a/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py b/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py index 733018e9bd2..e50b6798bf7 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py @@ -19,6 +19,7 @@ from nncf.common.graph.transformations.commands import TargetType from nncf.common.tensor_statistics.collectors import ReductionShape from nncf.common.utils.backend import BackendType +from nncf.experimental.tensor import Tensor from nncf.onnx.graph.node_utils import get_bias_value from nncf.onnx.graph.node_utils import is_any_weight_quantized from nncf.onnx.graph.node_utils import is_node_with_bias @@ -79,17 +80,16 @@ def get_sub_input_output_names(subgraph: onnx.ModelProto) -> Tuple[str, str]: def create_input_data( shape: Tuple[int], data: List[np.ndarray], input_name: str, channel_axis: int ) -> Dict[str, np.array]: - blob = np.zeros(shape) + blob = np.zeros(shape, dtype=data[0].data.dtype) for j, idx in enumerate(np.ndindex(blob.shape[channel_axis])): index = tuple(slice(None) if i != channel_axis else idx for i in range(blob.ndim)) - blob[index] = data[j] - blob = blob.astype(data[0].dtype) + blob[index] = data[j].data input_data = {input_name: blob} return input_data @staticmethod def get_bias_value(node: NNCFNode, nncf_graph: NNCFGraph, model: onnx.ModelProto) -> np.ndarray: - return get_bias_value(node, model) + return Tensor(get_bias_value(node, model)) @staticmethod def get_activation_port_ids_for_bias_node(node: NNCFNode) -> Tuple[int, int]: @@ -97,7 +97,7 @@ def get_activation_port_ids_for_bias_node(node: NNCFNode) -> Tuple[int, int]: @staticmethod def process_model_output(raw_data: Dict, output_name: str) -> ONNXNNCFTensor: - return ONNXNNCFTensor(raw_data[output_name]) + return Tensor(raw_data[output_name]) @staticmethod def is_quantized_weights(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: @@ -107,21 +107,6 @@ def is_quantized_weights(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: def is_node_with_bias(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: return is_node_with_bias(node) - @staticmethod - def get_bias_shift_magnitude(current_bias_value: np.ndarray, updated_bias_value: np.ndarray) -> float: - bias_shift_magnitude = np.inf - if np.count_nonzero(current_bias_value == 0) == 0: - bias_shift_magnitude = np.max(np.abs((updated_bias_value - current_bias_value) / current_bias_value)) - return bias_shift_magnitude - - @staticmethod - def post_process_output_data(data: List[np.ndarray]) -> np.ndarray: - return np.array(data) - - @staticmethod - def reshape_tensor(data: np.ndarray, new_shape: List[int]) -> np.ndarray: - return data.reshape(new_shape) - @staticmethod def get_node_names_for_input_output_statistics(node: NNCFNode, nncf_graph: NNCFGraph) -> Tuple[str, str]: return node.node_name, node.node_name diff --git a/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py b/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py index f76ef9a0c72..bf50c6a7ae2 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py @@ -20,6 +20,7 @@ from nncf.common.tensor_statistics.collectors import ReductionShape from nncf.common.utils.backend import BackendType from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.experimental.tensor import Tensor from nncf.openvino.graph.metatypes.common import FAKE_QUANTIZE_OPERATIONS from nncf.openvino.graph.node_utils import get_bias_value from nncf.openvino.graph.node_utils import is_node_with_bias @@ -69,19 +70,18 @@ def get_sub_input_output_names(subgraph: ov.Model) -> Tuple[str, str]: @staticmethod def create_input_data( - shape: Tuple[int], data: List[np.ndarray], input_name: str, channel_axis: int + shape: Tuple[int], data: List[Tensor], input_name: str, channel_axis: int ) -> Dict[str, np.ndarray]: - blob = np.zeros(shape) + blob = np.zeros(shape, dtype=data[0].data.dtype) for j, idx in enumerate(np.ndindex(blob.shape[channel_axis])): index = tuple(slice(None) if i != channel_axis else idx for i in range(blob.ndim)) - blob[index] = data[j] - blob = blob.astype(data[0].dtype) + blob[index] = data[j].data input_data = {input_name: blob} return input_data @staticmethod def get_bias_value(node: NNCFNode, nncf_graph: NNCFGraph, model: ov.Model) -> np.ndarray: - return get_bias_value(node, nncf_graph, model) + return Tensor(get_bias_value(node, nncf_graph, model)) @staticmethod def get_activation_port_ids_for_bias_node(node: NNCFNode) -> Tuple[int, int]: @@ -99,27 +99,12 @@ def is_quantized_weights(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: @staticmethod def process_model_output(raw_data: Dict, output_name: str) -> OVNNCFTensor: - return OVNNCFTensor(raw_data[output_name]) + return Tensor(raw_data[output_name]) @staticmethod def is_node_with_bias(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: return is_node_with_bias(node, nncf_graph) - @staticmethod - def get_bias_shift_magnitude(current_bias_value: np.ndarray, updated_bias_value: np.ndarray) -> float: - bias_shift_magnitude = np.inf - if np.count_nonzero(current_bias_value == 0) == 0: - bias_shift_magnitude = np.max(np.abs((updated_bias_value - current_bias_value) / current_bias_value)) - return bias_shift_magnitude - - @staticmethod - def post_process_output_data(data: List[np.ndarray]) -> np.ndarray: - return np.array(data) - - @staticmethod - def reshape_tensor(data: np.ndarray, new_shape: List[int]) -> np.ndarray: - return data.reshape(new_shape) - @staticmethod def get_node_names_for_input_output_statistics(node: NNCFNode, nncf_graph: NNCFGraph) -> Tuple[str, str]: return node.node_name, node.node_name diff --git a/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py b/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py index b7316724db0..5a6b1359f28 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py @@ -20,6 +20,7 @@ from nncf.common.graph.transformations.commands import TargetType from nncf.common.tensor_statistics.collectors import ReductionShape from nncf.common.utils.backend import BackendType +from nncf.experimental.tensor import Tensor from nncf.quantization.algorithms.fast_bias_correction.backend import ALGO_BACKENDS from nncf.quantization.algorithms.fast_bias_correction.backend import FastBiasCorrectionAlgoBackend from nncf.torch.graph.transformations.command_creation import create_bias_correction_command @@ -80,18 +81,16 @@ def get_sub_input_output_names(subgraph: NNCFNetwork) -> Tuple[str, str]: return None, None @staticmethod - def create_input_data( - shape: Tuple[int], data: List[torch.Tensor], input_name: str, channel_axis: int - ) -> torch.Tensor: - blob = torch.zeros(shape, dtype=data[0].dtype) + def create_input_data(shape: Tuple[int], data: List[Tensor], input_name: str, channel_axis: int) -> torch.Tensor: + blob = torch.zeros(shape, dtype=data[0].data.dtype, device=data[0].data.device) for j, idx in enumerate(np.ndindex(blob.shape[channel_axis])): index = tuple(slice(None) if i != channel_axis else idx for i in range(blob.ndim)) - blob[index] = data[j] + blob[index] = data[j].data return blob @staticmethod - def get_bias_value(node: NNCFNode, nncf_graph: NNCFGraph, model: NNCFNetwork) -> np.ndarray: - return get_fused_bias_value(node, model) + def get_bias_value(node: NNCFNode, nncf_graph: NNCFGraph, model: NNCFNetwork) -> Tensor: + return Tensor(get_fused_bias_value(node, model)) @staticmethod def get_activation_port_ids_for_bias_node(node: NNCFNode) -> Tuple[int, int]: @@ -99,7 +98,7 @@ def get_activation_port_ids_for_bias_node(node: NNCFNode) -> Tuple[int, int]: @staticmethod def process_model_output(raw_data: Dict, output_name: str) -> PTNNCFTensor: - return PTNNCFTensor(raw_data) + return Tensor(raw_data) @staticmethod def is_quantized_weights(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: @@ -109,21 +108,6 @@ def is_quantized_weights(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: def is_node_with_bias(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: return is_node_with_fused_bias(node, nncf_graph) - @staticmethod - def get_bias_shift_magnitude(current_bias_value: torch.Tensor, updated_bias_value: torch.Tensor) -> float: - bias_shift_magnitude = torch.inf - if torch.count_nonzero(current_bias_value == 0) == 0: - bias_shift_magnitude = torch.max(torch.abs((updated_bias_value - current_bias_value) / current_bias_value)) - return bias_shift_magnitude - - @staticmethod - def post_process_output_data(data: List[torch.Tensor]) -> torch.Tensor: - return torch.Tensor(data) - - @staticmethod - def reshape_tensor(data: torch.Tensor, new_shape: List[int]) -> torch.Tensor: - return data.reshape(new_shape) - @staticmethod def get_node_names_for_input_output_statistics(node: NNCFNode, nncf_graph: NNCFGraph) -> Tuple[str, str]: input_node_name = node.node_name diff --git a/tests/openvino/native/quantization/test_fake_quantize.py b/tests/openvino/native/quantization/test_fake_quantize.py new file mode 100644 index 00000000000..30187bff378 --- /dev/null +++ b/tests/openvino/native/quantization/test_fake_quantize.py @@ -0,0 +1,186 @@ +# Copyright (c) 2023 Intel Corporation +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import numpy as np +import pytest + +from nncf.common.quantization.structs import QuantizerConfig +from nncf.common.quantization.structs import QuantizerGroup +from nncf.experimental.tensor import Tensor +from nncf.experimental.tensor import functions +from nncf.quantization.fake_quantize import asymmetric_range +from nncf.quantization.fake_quantize import fix_zero_filters_asymmetric +from nncf.quantization.fake_quantize import fix_zero_filters_symmetric +from nncf.quantization.fake_quantize import symmetric_range +from nncf.quantization.fake_quantize import tune_range + + +@pytest.mark.parametrize( + "min_values, max_values, ref_low, ref_high", + ( + (-1.1, 1.0, -1.1, 1.0), + (0.1, 0.1000001, 0.0992, 0.1008001), + ([0.1, -0.1], [0.1000001, 2.0], [0.0992, -0.1], [0.1008001, 2.0]), + ), +) +def test_fix_zero_filters_asymmetric(min_values, max_values, ref_low, ref_high): + level_low, level_high = fix_zero_filters_asymmetric(Tensor(np.array(min_values)), Tensor(np.array(max_values))) + + for val, ref in zip([level_low, level_high], [ref_low, ref_high]): + if isinstance(ref, list): + assert functions.all(functions.isclose(val, ref)), f"{val=}" + else: + assert functions.isclose(val, ref), f"{val=}" + + +@pytest.mark.parametrize( + "max_values, ref", + ( + (1.0, 1.0), + (8e-7, 8e-05), + ([1.0, 0.0], [1.0, 0.01]), + ([[1.0, 0.0], [1.0, 2.0]], [[1.0, 0.02], [1.0, 2.0]]), + ), +) +def test_fix_zero_filters_symmetric(max_values, ref): + res = fix_zero_filters_symmetric(Tensor(np.array(max_values))) + + if isinstance(ref, list): + assert functions.all(functions.isclose(res, ref)) + else: + assert functions.isclose(res, ref) + + +@pytest.mark.parametrize( + "left_border, right_border, unify_zp, ref_ra, ref_rb", + ( + (-1.0, 1.0, True, -1.0078740157480315, 1.0), + (-1.0, 1.0, False, -1.0078740157480315, 1.0), + (-1.0, 1.1, True, -1.0, 1.1074380165289257), + (-1.0, 1.1, False, -1.0, 1.1074380165289257), + ([-1.0, 1.2], [1.0, 2.0], True, [-1.0, 0.66492147], [1.0, 2.0]), + ([-1.0, 1.2], [1.0, 2.0], False, [-1.00787402, 1.19937206], [1.0, 2.0]), + ), +) +def test_tune_range(left_border, right_border, unify_zp, ref_ra, ref_rb): + ra, rb = tune_range( + Tensor(np.array(left_border)), + Tensor(np.array(right_border)), + 8, + unify_zp, + ) + + for val, ref in zip([ra, rb], [ref_ra, ref_rb]): + if isinstance(ref, list): + assert functions.all(functions.isclose(val, ref)) + else: + assert functions.isclose(val, ref) + + +@pytest.mark.parametrize( + "min_values, max_values, levels, quantizer_config, q_group, ref_low, ref_high", + ( + (-1.0, 1.0, 255, QuantizerConfig(8), QuantizerGroup.ACTIVATIONS, -1.0079051, 1.0), + ( + [-1.0, 0.1], + [1.0, 2.0], + 255, + QuantizerConfig(8), + QuantizerGroup.ACTIVATIONS, + [-1.0079051, -2.0158103], + [1.0, 2.0], + ), + ( + [-1.0, 0.1], + [1.0, 2.0], + 255, + QuantizerConfig(8), + QuantizerGroup.WEIGHTS, + [-1.0, -2.0], + [1.0, 2.0], + ), + ( + [-1.0, 0.1], + [1.0, 2.0], + 256, + QuantizerConfig(8, signedness_to_force=True), + QuantizerGroup.ACTIVATIONS, + [-1.007874, -2.015748], + [1.0, 2.0], + ), + ( + [-1.0, 0.1], + [1.0, 2.0], + 256, + QuantizerConfig(8, signedness_to_force=True), + QuantizerGroup.WEIGHTS, + [-1.0, -2.0], + [1.0, 2.0], + ), + ), +) +def test_symmetric_range(min_values, max_values, levels, quantizer_config, q_group, ref_low, ref_high): + level_low, level_high = symmetric_range( + Tensor(np.array(min_values)), + Tensor(np.array(max_values)), + levels, + quantizer_config, + q_group, + ) + for val, ref in zip([level_low, level_high], [ref_low, ref_high]): + if isinstance(ref, list): + assert functions.all(functions.isclose(val, ref)), f"{val=}" + else: + assert functions.isclose(val, ref), f"{val=}" + + +@pytest.mark.parametrize( + "min_values, max_values, quantizer_config, q_group, unify_zp, ref_low, ref_high", + ( + (-1.0, 1.0, QuantizerConfig(8), QuantizerGroup.ACTIVATIONS, False, -1.007874, 1.0), + (-1.0, 1.0, QuantizerConfig(8), QuantizerGroup.WEIGHTS, False, -1.007874, 1.0), + (0.1, 1.0, QuantizerConfig(8), QuantizerGroup.WEIGHTS, True, 0.0, 1.0), + ([-1.0, 0.1], [1.0, 2.0], QuantizerConfig(8), QuantizerGroup.ACTIVATIONS, False, [-1.007874, 0.0], [1.0, 2.0]), + ([-1.0, 0.1], [1.0, 2.0], QuantizerConfig(8), QuantizerGroup.WEIGHTS, False, [-1.007874, 0.0], [1.0, 2.0]), + ( + [-1.0, 0.1], + [1.0, 2.0], + QuantizerConfig(8), + QuantizerGroup.WEIGHTS, + True, + [-1.0, -0.6701571], + [2.984375, 2.0], + ), + ( + [[-1.0], [0.1]], + [[1.0], [2.0]], + QuantizerConfig(8), + QuantizerGroup.ACTIVATIONS, + False, + [[-1.007874], [0.0]], + [[1.0], [2.0]], + ), + ), +) +def test_asymmetric_range(min_values, max_values, quantizer_config, q_group, unify_zp, ref_low, ref_high): + level_low, level_high = asymmetric_range( + Tensor(np.array(min_values)), + Tensor(np.array(max_values)), + quantizer_config, + q_group, + unify_zp, + ) + for val, ref in zip([level_low, level_high], [ref_low, ref_high]): + if isinstance(ref, list): + assert functions.all(functions.isclose(val, ref)), f"{val=}" + else: + assert functions.isclose(val, ref), f"{val=}" diff --git a/tests/torch/ptq/test_fast_bias_correction.py b/tests/torch/ptq/test_fast_bias_correction.py index b713aeb802c..7f5639aaeba 100644 --- a/tests/torch/ptq/test_fast_bias_correction.py +++ b/tests/torch/ptq/test_fast_bias_correction.py @@ -59,3 +59,30 @@ def check_bias(model: NNCFNetwork, ref_bias: list): assert torch.all(torch.isclose(bias_value, ref_bias, atol=0.02)), f"{bias_value} != {ref_bias}" return raise ValueError("Not found node with bias") + + +class TestTorchCudaFBCAlgorithm(TestTorchFBCAlgorithm): + @staticmethod + def list_to_backend_type(data: List) -> torch.Tensor: + return torch.Tensor(data).cuda() + + @staticmethod + def backend_specific_model(model: bool, tmp_dir: str): + return get_nncf_network(model.cuda(), model.INPUT_SIZE) + + @staticmethod + def fn_to_type(tensor): + return torch.Tensor(tensor).cuda() + + @staticmethod + def check_bias(model: NNCFNetwork, ref_bias: list): + ref_bias = torch.Tensor(ref_bias) + nncf_graph = NNCFGraphFactory.create(model) + for node in nncf_graph.get_all_nodes(): + if not is_node_with_fused_bias(node, nncf_graph): + continue + bias_value = get_fused_bias_value(node, model).cpu() + # TODO(AlexanderDokuchaev): return atol=0.0001 after fix 109189 + assert torch.all(torch.isclose(bias_value, ref_bias, atol=0.02)), f"{bias_value} != {ref_bias}" + return + raise ValueError("Not found node with bias") From 519a2e61c01655290d60a9565c14b144bac916f5 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Fri, 18 Aug 2023 19:47:12 +0300 Subject: [PATCH 09/45] linter --- nncf/experimental/tensor/functions.py | 16 ++-------------- nncf/experimental/tensor/tensor.py | 2 +- nncf/experimental/tensor/torch_functions.py | 2 +- .../algorithms/min_max/torch_backend.py | 2 -- .../test_templates/test_fast_bias_correction.py | 2 +- 5 files changed, 5 insertions(+), 19 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 8be677b583e..fc71a26f8cd 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -411,7 +411,7 @@ def mean(a: Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) @functools.singledispatch @_tensor_guard -def round(a: Tensor, decimals=0) -> Tensor: +def round(a: Tensor, decimals=0) -> Tensor: # pylint: disable=redefined-builtin """ Evenly round to the given number of decimals. @@ -423,18 +423,6 @@ def round(a: Tensor, decimals=0) -> Tensor: return Tensor(round(a.data, decimals)) -@functools.singledispatch -@_tensor_guard -def ndim(a: Tensor) -> int: - """ - Returns the number of dimensions of tensor. - - :param a: Input data. - :return: Number of dimensions. - """ - return Tensor(ndim(a.data)) - - def mean_per_channel(x: Tensor, axis: int) -> Tensor: """ Computes the mean of elements across given channel dimension of Tensor. @@ -446,7 +434,7 @@ def mean_per_channel(x: Tensor, axis: int) -> Tensor: if len(x.shape) < 3: return mean(x.data, axis=0) x = moveaxis(x.data, axis, 1) - t = x.reshape(x.shape[0], x.shape[1], -1) + t = x.reshape([x.shape[0], x.shape[1], -1]) return mean(t, axis=(0, 2)) diff --git a/nncf/experimental/tensor/tensor.py b/nncf/experimental/tensor/tensor.py index 97933d11bc7..347e7da39ee 100644 --- a/nncf/experimental/tensor/tensor.py +++ b/nncf/experimental/tensor/tensor.py @@ -142,7 +142,7 @@ def isempty(self) -> "Tensor": def astype(self, dtype: TensorDataType): return _call_function("astype", self, dtype) - def reshape(self, shape: TTensor) -> "Tensor": + def reshape(self, shape: List) -> "Tensor": return _call_function("reshape", self, shape) diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 596b5402bd9..460ed63bc5b 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -171,7 +171,7 @@ def _(x: List[torch.Tensor], axis: int = 0) -> List[torch.Tensor]: def _(x: torch.Tensor, axis: int = 0) -> List[torch.Tensor]: if not list(x.shape): x = x.unsqueeze(0) - return [i for i in torch.unbind(x, dim=axis)] + return torch.unbind(x, dim=axis) @functions.moveaxis.register(torch.Tensor) diff --git a/nncf/quantization/algorithms/min_max/torch_backend.py b/nncf/quantization/algorithms/min_max/torch_backend.py index 4f46dd25026..31e8f3621de 100644 --- a/nncf/quantization/algorithms/min_max/torch_backend.py +++ b/nncf/quantization/algorithms/min_max/torch_backend.py @@ -19,7 +19,6 @@ from nncf.common.graph.graph import NNCFGraph from nncf.common.graph.graph import NNCFNode from nncf.common.graph.layer_attributes import WeightedLayerAttributes -from nncf.common.graph.model_transformer import ModelTransformer from nncf.common.graph.operator_metatypes import OperatorMetatype from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.transformations.commands import TransformationPriority @@ -39,7 +38,6 @@ from nncf.torch.graph.graph import PTTargetPoint from nncf.torch.graph.transformations.commands import PTInsertionCommand from nncf.torch.hardware.config import PTHWConfig -from nncf.torch.model_transformer import PTModelTransformer from nncf.torch.nncf_network import NNCFNetwork from nncf.torch.quantization.default_quantization import DEFAULT_PT_QUANT_TRAIT_TO_OP_DICT from nncf.torch.quantization.init_range import PTRangeInitCollectorParams diff --git a/tests/post_training/test_templates/test_fast_bias_correction.py b/tests/post_training/test_templates/test_fast_bias_correction.py index b972ce851cd..c4ea71d6551 100644 --- a/tests/post_training/test_templates/test_fast_bias_correction.py +++ b/tests/post_training/test_templates/test_fast_bias_correction.py @@ -67,7 +67,7 @@ def test_reshape_bias_shift(self, bias_value: list, bias_shift: list, channel_ax algo = FastBiasCorrection(subset_size=1, inplace_statistics=False) # pylint: disable=protected-access algo._backend_entity = self.get_backend() - new_bias_shift = algo.reshape_bias_shift(bias_shift, bias_value, channel_axis) + new_bias_shift = algo._reshape_bias_shift(bias_shift, bias_value, channel_axis) assert list(new_bias_shift.shape) == ref_shape @staticmethod From 03b1bc16e1de5fa4770ead1c3af5fba01da3e500 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Fri, 18 Aug 2023 21:46:13 +0300 Subject: [PATCH 10/45] fix pt --- nncf/experimental/tensor/tensor.py | 2 +- .../algorithms/min_max/torch_backend.py | 1 - .../test_calculate_quantizer_parameters.py | 9 +- .../template_test_nncf_tensor.py | 12 +-- .../ptq/test_calculation_quantizer_params.py | 85 +++++++++---------- 5 files changed, 54 insertions(+), 55 deletions(-) diff --git a/nncf/experimental/tensor/tensor.py b/nncf/experimental/tensor/tensor.py index 347e7da39ee..d7dbb97ce19 100644 --- a/nncf/experimental/tensor/tensor.py +++ b/nncf/experimental/tensor/tensor.py @@ -32,7 +32,7 @@ def data(self) -> TTensor: @property def shape(self) -> List[int]: - return list(self.data.shape) + return tuple(self.data.shape) @property def ndim(self) -> int: diff --git a/nncf/quantization/algorithms/min_max/torch_backend.py b/nncf/quantization/algorithms/min_max/torch_backend.py index 31e8f3621de..f0c1917b3c5 100644 --- a/nncf/quantization/algorithms/min_max/torch_backend.py +++ b/nncf/quantization/algorithms/min_max/torch_backend.py @@ -11,7 +11,6 @@ from typing import Dict, List, Optional, Set, Tuple, Union -import numpy as np import torch import nncf.torch.graph.operator_metatypes as om diff --git a/tests/post_training/test_templates/test_calculate_quantizer_parameters.py b/tests/post_training/test_templates/test_calculate_quantizer_parameters.py index b9b93153cfc..6c051d3127d 100644 --- a/tests/post_training/test_templates/test_calculate_quantizer_parameters.py +++ b/tests/post_training/test_templates/test_calculate_quantizer_parameters.py @@ -19,6 +19,7 @@ from nncf.common.quantization.structs import QuantizationMode from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup +from nncf.experimental.tensor import functions from nncf.quantization.fake_quantize import FakeQuantizeParameters from nncf.quantization.fake_quantize import calculate_quantizer_parameters from tests.post_training.conftest import FQ_CALCULATED_PARAMETERS_PATH @@ -32,10 +33,10 @@ def compare_fq_parameters(ref_params, params): assert ref_params.input_high.shape == params.input_high.shape assert ref_params.output_low.shape == params.output_low.shape assert ref_params.output_high.shape == params.output_high.shape - assert np.allclose(ref_params.input_low, params.input_low) - assert np.allclose(ref_params.input_high, params.input_high) - assert np.allclose(ref_params.output_low, params.output_low) - assert np.allclose(ref_params.output_high, params.output_high) + assert functions.allclose(ref_params.input_low, params.input_low) + assert functions.allclose(ref_params.input_high, params.input_high) + assert functions.allclose(ref_params.output_low, params.output_low) + assert functions.allclose(ref_params.output_high, params.output_high) def get_test_reference_key(q_group, q_config, narrow_range, hf_range): diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index a83de1c7a77..311bd7c54b7 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -574,13 +574,13 @@ def test_fn_astype(self): def test_reshape(self): tensor = Tensor(self.to_tensor([1, 1])) - assert tensor.shape == [2] - assert tensor.reshape([1, 2]).shape == [1, 2] + assert tensor.shape == (2,) + assert tensor.reshape([1, 2]).shape == (1, 2) def test_fn_reshape(self): tensor = Tensor(self.to_tensor([1, 1])) - assert tensor.shape == [2] - assert functions.reshape(tensor, [1, 2]).shape == [1, 2] + assert tensor.shape == (2,) + assert functions.reshape(tensor, [1, 2]).shape == (1, 2) def test_not_implemented(self): with pytest.raises(NotImplementedError, match="is not implemented for"): @@ -608,7 +608,7 @@ def test_fn_unstack(self, x, axis, ref): res = functions.unstack(tensor, axis=axis) assert isinstance(res, list) - for i in range(len(ref)): + for i, _ in enumerate(ref): assert all(res[i] == ref[i]) @pytest.mark.parametrize( @@ -641,7 +641,7 @@ def test_fn_moveaxis(self): res = functions.moveaxis(tensor, 0, -1) - assert res.shape == [3, 2] + assert res.shape == (3, 2) @pytest.mark.parametrize( "x, axis, keepdims, ref", diff --git a/tests/torch/ptq/test_calculation_quantizer_params.py b/tests/torch/ptq/test_calculation_quantizer_params.py index f98e5137d48..887bc4cd8a2 100644 --- a/tests/torch/ptq/test_calculation_quantizer_params.py +++ b/tests/torch/ptq/test_calculation_quantizer_params.py @@ -25,6 +25,7 @@ from nncf.common.quantization.structs import QuantizationPreset from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup +from nncf.experimental.tensor import Tensor from nncf.quantization.algorithms.min_max.algorithm import MinMaxQuantization from nncf.quantization.algorithms.min_max.torch_backend import PTMinMaxAlgoBackend from nncf.quantization.fake_quantize import FakeQuantizeParameters @@ -54,10 +55,10 @@ class CaseSymParams: SYM_CASES = ( CaseSymParams( fq_params=FakeQuantizeParameters( - np.array(-0.49920455, dtype=np.float32), - np.array(0.49530452, dtype=np.float32), - np.array(-0.49920455, dtype=np.float32), - np.array(0.49530452, dtype=np.float32), + Tensor(torch.tensor(-0.49920455, dtype=torch.float32)), + Tensor(torch.tensor(0.49530452, dtype=torch.float32)), + Tensor(torch.tensor(-0.49920455, dtype=torch.float32)), + Tensor(torch.tensor(0.49530452, dtype=torch.float32)), 256, ), per_channel=False, @@ -66,10 +67,10 @@ class CaseSymParams: ), CaseSymParams( fq_params=FakeQuantizeParameters( - np.array(-0.49530452, dtype=np.float32), - np.array(0.49530452, dtype=np.float32), - np.array(-0.49530452, dtype=np.float32), - np.array(0.49530452, dtype=np.float32), + Tensor(torch.tensor(-0.49530452, dtype=torch.float32)), + Tensor(torch.tensor(0.49530452, dtype=torch.float32)), + Tensor(torch.tensor(-0.49530452, dtype=torch.float32)), + Tensor(torch.tensor(0.49530452, dtype=torch.float32)), 255, ), per_channel=False, @@ -78,27 +79,27 @@ class CaseSymParams: ), CaseSymParams( fq_params=FakeQuantizeParameters( - np.array([-0.4835594, -0.49530452, -0.49221927], dtype=np.float32).reshape(1, 3, 1, 1), - np.array([0.4797816, 0.49920455, 0.48837382], dtype=np.float32).reshape(1, 3, 1, 1), - np.array([-0.4835594, -0.49530452, -0.49221927], dtype=np.float32).reshape(1, 3, 1, 1), - np.array([0.4797816, 0.49920455, 0.48837382], dtype=np.float32).reshape(1, 3, 1, 1), + Tensor(torch.tensor([-0.4835594, -0.49530452, -0.49221927], dtype=torch.float32).reshape(1, 3, 1, 1)), + Tensor(torch.tensor([0.4797816, 0.49920455, 0.48837382], dtype=torch.float32).reshape(1, 3, 1, 1)), + Tensor(torch.tensor([-0.4835594, -0.49530452, -0.49221927], dtype=torch.float32).reshape(1, 3, 1, 1)), + Tensor(torch.tensor([0.4797816, 0.49920455, 0.48837382], dtype=torch.float32).reshape(1, 3, 1, 1)), 256, ), per_channel=True, quant_group=QuantizerGroup.ACTIVATIONS, - ref_scale=np.array([0.4797816, 0.49920455, 0.48837382]).reshape(1, 3, 1, 1), + ref_scale=torch.tensor([0.4797816, 0.49920455, 0.48837382]).reshape(1, 3, 1, 1), ), CaseSymParams( fq_params=FakeQuantizeParameters( - np.array([-0.48837382, -0.49530452], dtype=np.float32).reshape(2, 1, 1, 1), - np.array([0.48837382, 0.49530452], dtype=np.float32).reshape(2, 1, 1, 1), - np.array([-0.48837382, -0.49530452], dtype=np.float32).reshape(2, 1, 1, 1), - np.array([0.48837382, 0.49530452], dtype=np.float32).reshape(2, 1, 1, 1), + Tensor(torch.tensor([-0.48837382, -0.49530452], dtype=torch.float32).reshape(2, 1, 1, 1)), + Tensor(torch.tensor([0.48837382, 0.49530452], dtype=torch.float32).reshape(2, 1, 1, 1)), + Tensor(torch.tensor([-0.48837382, -0.49530452], dtype=torch.float32).reshape(2, 1, 1, 1)), + Tensor(torch.tensor([0.48837382, 0.49530452], dtype=torch.float32).reshape(2, 1, 1, 1)), 255, ), per_channel=True, quant_group=QuantizerGroup.WEIGHTS, - ref_scale=np.array([0.48837382, 0.49530452]).reshape(2, 1, 1, 1), + ref_scale=torch.tensor([0.48837382, 0.49530452]).reshape(2, 1, 1, 1), ), ) @@ -140,10 +141,10 @@ class CaseAsymParams: ASYM_CASES = ( CaseAsymParams( fq_params=FakeQuantizeParameters( - np.array(-0.49530452, dtype=np.float32), - np.array(0.49143496, dtype=np.float32), - np.array(-0.49530452, dtype=np.float32), - np.array(0.49143496, dtype=np.float32), + Tensor(torch.tensor(-0.49530452, dtype=torch.float32)), + Tensor(torch.tensor(0.49143496, dtype=torch.float32)), + Tensor(torch.tensor(-0.49530452, dtype=torch.float32)), + Tensor(torch.tensor(0.49143496, dtype=torch.float32)), 256, ), per_channel=False, @@ -153,10 +154,10 @@ class CaseAsymParams: ), CaseAsymParams( fq_params=FakeQuantizeParameters( - np.array(-0.49530452, dtype=np.float32), - np.array(0.49143496, dtype=np.float32), - np.array(-0.49530452, dtype=np.float32), - np.array(0.49143496, dtype=np.float32), + Tensor(torch.tensor(-0.49530452, dtype=torch.float32)), + Tensor(torch.tensor(0.49143496, dtype=torch.float32)), + Tensor(torch.tensor(-0.49530452, dtype=torch.float32)), + Tensor(torch.tensor(0.49143496, dtype=torch.float32)), 256, ), per_channel=False, @@ -166,29 +167,29 @@ class CaseAsymParams: ), CaseAsymParams( fq_params=FakeQuantizeParameters( - np.array([-0.48051512, -0.49776307, -0.44099426], dtype=np.float32).reshape(1, 3, 1, 1), - np.array([0.4767611, 0.47861832, 0.48837382], dtype=np.float32).reshape(1, 3, 1, 1), - np.array([-0.48051512, -0.49776307, -0.44099426], dtype=np.float32).reshape(1, 3, 1, 1), - np.array([0.4767611, 0.47861832, 0.48837382], dtype=np.float32).reshape(1, 3, 1, 1), + Tensor(torch.tensor([-0.48051512, -0.49776307, -0.44099426], dtype=torch.float32).reshape(1, 3, 1, 1)), + Tensor(torch.tensor([0.4767611, 0.47861832, 0.48837382], dtype=torch.float32).reshape(1, 3, 1, 1)), + Tensor(torch.tensor([-0.48051512, -0.49776307, -0.44099426], dtype=torch.float32).reshape(1, 3, 1, 1)), + Tensor(torch.tensor([0.4767611, 0.47861832, 0.48837382], dtype=torch.float32).reshape(1, 3, 1, 1)), 256, ), per_channel=True, quant_group=QuantizerGroup.ACTIVATIONS, - ref_inp_low=np.array([-0.48051512, -0.49776307, -0.44099426]).reshape(1, 3, 1, 1), - ref_inp_range=np.array([0.9572762, 0.9763814, 0.9293681]).reshape(1, 3, 1, 1), + ref_inp_low=torch.tensor([-0.48051512, -0.49776307, -0.44099426]).reshape(1, 3, 1, 1), + ref_inp_range=torch.tensor([0.9572762, 0.9763814, 0.9293681]).reshape(1, 3, 1, 1), ), CaseAsymParams( fq_params=FakeQuantizeParameters( - np.array([-0.4845584, -0.49583155], dtype=np.float32).reshape(2, 1, 1, 1), - np.array([0.48837382, 0.4767611], dtype=np.float32).reshape(2, 1, 1, 1), - np.array([-0.4845584, -0.49583155], dtype=np.float32).reshape(2, 1, 1, 1), - np.array([0.48837382, 0.4767611], dtype=np.float32).reshape(2, 1, 1, 1), + Tensor(torch.tensor([-0.4845584, -0.49583155], dtype=torch.float32).reshape(2, 1, 1, 1)), + Tensor(torch.tensor([0.48837382, 0.4767611], dtype=torch.float32).reshape(2, 1, 1, 1)), + Tensor(torch.tensor([-0.4845584, -0.49583155], dtype=torch.float32).reshape(2, 1, 1, 1)), + Tensor(torch.tensor([0.48837382, 0.4767611], dtype=torch.float32).reshape(2, 1, 1, 1)), 256, ), per_channel=True, quant_group=QuantizerGroup.WEIGHTS, - ref_inp_low=np.array([-0.4845584, -0.49583155]).reshape(2, 1, 1, 1), - ref_inp_range=np.array([0.97293222, 0.97259265]).reshape(2, 1, 1, 1), + ref_inp_low=torch.tensor([-0.4845584, -0.49583155]).reshape(2, 1, 1, 1), + ref_inp_range=torch.tensor([0.97293222, 0.97259265]).reshape(2, 1, 1, 1), ), ) @@ -270,9 +271,7 @@ def calculate_statistics(data, mode, qgroup, half_range=False): else: max_values = np.amax(data, axes) - statistics = PTMinMaxTensorStatistic( - min_values=torch.from_numpy(np.array(min_values)), max_values=torch.from_numpy(np.array(max_values)) - ) + statistics = PTMinMaxTensorStatistic(min_values=torch.tensor(min_values), max_values=torch.tensor(max_values)) signedness_to_force = True if qgroup == QuantizerGroup.WEIGHTS else None qconfig = QuantizerConfig(num_bits=8, mode=mode, per_channel=per_ch, signedness_to_force=signedness_to_force) narrow_range = get_quantizer_narrow_range(qconfig, qgroup) @@ -343,8 +342,8 @@ def test_quantizer_parameters_export(tmp_path: Path): for name, param in fq_params.items(): assert name in torch_ptq_params - assert np.allclose(param["input_low"], torch_ptq_params[name]["input_low"]) - assert np.allclose(param["input_high"], torch_ptq_params[name]["input_high"]) + assert np.allclose(param["input_low"].data.numpy(), torch_ptq_params[name]["input_low"]) + assert np.allclose(param["input_high"].data.numpy(), torch_ptq_params[name]["input_high"]) class TestFQParams(TemplateTestFQParams): From 82c699bdcde356842bfbb44a21b09b6903eb2a20 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Fri, 18 Aug 2023 23:43:01 +0300 Subject: [PATCH 11/45] linter --- nncf/quantization/algorithms/min_max/torch_backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nncf/quantization/algorithms/min_max/torch_backend.py b/nncf/quantization/algorithms/min_max/torch_backend.py index f0c1917b3c5..f9a736dd3d7 100644 --- a/nncf/quantization/algorithms/min_max/torch_backend.py +++ b/nncf/quantization/algorithms/min_max/torch_backend.py @@ -279,8 +279,8 @@ def _fill_quantizer_parameters(quantizer: BaseQuantizer, parameters: FakeQuantiz quantizer.eps = 0 if isinstance(quantizer, AsymmetricQuantizer): quantizer.input_low = torch.nn.Parameter(parameters.input_low.data) - range = parameters.input_high - parameters.input_low - quantizer.input_range = torch.nn.Parameter(range.data) + input_range = parameters.input_high - parameters.input_low + quantizer.input_range = torch.nn.Parameter(input_range.data) else: quantizer.signed = bool(torch.any(parameters.input_low.data < 0)) quantizer.scale = torch.nn.Parameter(parameters.input_high.data) From 982f5793025eea4e8fa1bb3fc236e946beb029a1 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Sat, 19 Aug 2023 01:06:42 +0300 Subject: [PATCH 12/45] fix --- nncf/openvino/graph/model_transformer.py | 8 ++++---- tests/onnx/quantization/common.py | 13 +++++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/nncf/openvino/graph/model_transformer.py b/nncf/openvino/graph/model_transformer.py index dcf49b0e7f6..a58ac107bb8 100644 --- a/nncf/openvino/graph/model_transformer.py +++ b/nncf/openvino/graph/model_transformer.py @@ -245,10 +245,10 @@ def _convert_to_fp16(data): clip_data = np.clip(data, np.finfo(np.float16).min, np.finfo(np.float16).max) return clip_data.astype(np.float16) - input_low = _convert_to_fp16(fq_params.input_low) - input_high = _convert_to_fp16(fq_params.input_high) - output_low = _convert_to_fp16(fq_params.output_low) - output_high = _convert_to_fp16(fq_params.output_high) + input_low = _convert_to_fp16(fq_params.input_low.data) + input_high = _convert_to_fp16(fq_params.input_high.data) + output_low = _convert_to_fp16(fq_params.output_low.data) + output_high = _convert_to_fp16(fq_params.output_high.data) return input_low, input_high, output_low, output_high @staticmethod diff --git a/tests/onnx/quantization/common.py b/tests/onnx/quantization/common.py index 1d3464882fe..01a916b61a5 100644 --- a/tests/onnx/quantization/common.py +++ b/tests/onnx/quantization/common.py @@ -16,6 +16,7 @@ import onnx from nncf import Dataset +from nncf.experimental.tensor import Tensor from nncf.onnx.graph.nncf_graph_builder import GraphConverter from nncf.onnx.graph.onnx_graph import ONNXGraph from nncf.onnx.statistics.statistics import ONNXMinMaxTensorStatistic @@ -32,10 +33,18 @@ def mock_collect_statistics(mocker): - get_statistics_value = ONNXMinMaxTensorStatistic(min_values=-1, max_values=1) + get_statistics_value = ONNXMinMaxTensorStatistic( + min_values=np.array(-1, dtype=np.float32), max_values=np.array(1, dtype=np.float32) + ) _ = mocker.patch( "nncf.quantization.fake_quantize.calculate_quantizer_parameters", - return_value=FakeQuantizeParameters(np.array(0), np.array(0), np.array(0), np.array(0), 256), + return_value=FakeQuantizeParameters( + Tensor(np.array(0, dtype=np.float32)), + Tensor(np.array(0, dtype=np.float32)), + Tensor(np.array(0, dtype=np.float32)), + Tensor(np.array(0, dtype=np.float32)), + 256, + ), ) _ = mocker.patch( "nncf.common.tensor_statistics.aggregator.StatisticsAggregator.collect_statistics", return_value=None From cf47cbde3ba931051e1d6e5966024cee62c2b46f Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Sat, 19 Aug 2023 01:59:22 +0300 Subject: [PATCH 13/45] fix --- nncf/experimental/tensor/numpy_functions.py | 12 +- .../native/quantization/test_fake_quantize.py | 186 ------------------ .../template_test_nncf_tensor.py | 128 ++++++------ 3 files changed, 73 insertions(+), 253 deletions(-) delete mode 100644 tests/openvino/native/quantization/test_fake_quantize.py diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 842ff96ee4b..75131a4d510 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -28,10 +28,16 @@ DTYPE_MAP_REV = {v: k for k, v in DTYPE_MAP.items()} -def registry_numpy_types(registry_fn): +def registry_numpy_types(singledispatch_fn): + """ + Decorator to register function to singledispatch for numpy classes. + + :param singledispatch_fn: singledispatch function. + """ + def inner(func): - registry_fn.register(np.ndarray)(func) - registry_fn.register(np.generic)(func) + singledispatch_fn.register(np.ndarray)(func) + singledispatch_fn.register(np.generic)(func) return func return inner diff --git a/tests/openvino/native/quantization/test_fake_quantize.py b/tests/openvino/native/quantization/test_fake_quantize.py deleted file mode 100644 index 30187bff378..00000000000 --- a/tests/openvino/native/quantization/test_fake_quantize.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright (c) 2023 Intel Corporation -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import numpy as np -import pytest - -from nncf.common.quantization.structs import QuantizerConfig -from nncf.common.quantization.structs import QuantizerGroup -from nncf.experimental.tensor import Tensor -from nncf.experimental.tensor import functions -from nncf.quantization.fake_quantize import asymmetric_range -from nncf.quantization.fake_quantize import fix_zero_filters_asymmetric -from nncf.quantization.fake_quantize import fix_zero_filters_symmetric -from nncf.quantization.fake_quantize import symmetric_range -from nncf.quantization.fake_quantize import tune_range - - -@pytest.mark.parametrize( - "min_values, max_values, ref_low, ref_high", - ( - (-1.1, 1.0, -1.1, 1.0), - (0.1, 0.1000001, 0.0992, 0.1008001), - ([0.1, -0.1], [0.1000001, 2.0], [0.0992, -0.1], [0.1008001, 2.0]), - ), -) -def test_fix_zero_filters_asymmetric(min_values, max_values, ref_low, ref_high): - level_low, level_high = fix_zero_filters_asymmetric(Tensor(np.array(min_values)), Tensor(np.array(max_values))) - - for val, ref in zip([level_low, level_high], [ref_low, ref_high]): - if isinstance(ref, list): - assert functions.all(functions.isclose(val, ref)), f"{val=}" - else: - assert functions.isclose(val, ref), f"{val=}" - - -@pytest.mark.parametrize( - "max_values, ref", - ( - (1.0, 1.0), - (8e-7, 8e-05), - ([1.0, 0.0], [1.0, 0.01]), - ([[1.0, 0.0], [1.0, 2.0]], [[1.0, 0.02], [1.0, 2.0]]), - ), -) -def test_fix_zero_filters_symmetric(max_values, ref): - res = fix_zero_filters_symmetric(Tensor(np.array(max_values))) - - if isinstance(ref, list): - assert functions.all(functions.isclose(res, ref)) - else: - assert functions.isclose(res, ref) - - -@pytest.mark.parametrize( - "left_border, right_border, unify_zp, ref_ra, ref_rb", - ( - (-1.0, 1.0, True, -1.0078740157480315, 1.0), - (-1.0, 1.0, False, -1.0078740157480315, 1.0), - (-1.0, 1.1, True, -1.0, 1.1074380165289257), - (-1.0, 1.1, False, -1.0, 1.1074380165289257), - ([-1.0, 1.2], [1.0, 2.0], True, [-1.0, 0.66492147], [1.0, 2.0]), - ([-1.0, 1.2], [1.0, 2.0], False, [-1.00787402, 1.19937206], [1.0, 2.0]), - ), -) -def test_tune_range(left_border, right_border, unify_zp, ref_ra, ref_rb): - ra, rb = tune_range( - Tensor(np.array(left_border)), - Tensor(np.array(right_border)), - 8, - unify_zp, - ) - - for val, ref in zip([ra, rb], [ref_ra, ref_rb]): - if isinstance(ref, list): - assert functions.all(functions.isclose(val, ref)) - else: - assert functions.isclose(val, ref) - - -@pytest.mark.parametrize( - "min_values, max_values, levels, quantizer_config, q_group, ref_low, ref_high", - ( - (-1.0, 1.0, 255, QuantizerConfig(8), QuantizerGroup.ACTIVATIONS, -1.0079051, 1.0), - ( - [-1.0, 0.1], - [1.0, 2.0], - 255, - QuantizerConfig(8), - QuantizerGroup.ACTIVATIONS, - [-1.0079051, -2.0158103], - [1.0, 2.0], - ), - ( - [-1.0, 0.1], - [1.0, 2.0], - 255, - QuantizerConfig(8), - QuantizerGroup.WEIGHTS, - [-1.0, -2.0], - [1.0, 2.0], - ), - ( - [-1.0, 0.1], - [1.0, 2.0], - 256, - QuantizerConfig(8, signedness_to_force=True), - QuantizerGroup.ACTIVATIONS, - [-1.007874, -2.015748], - [1.0, 2.0], - ), - ( - [-1.0, 0.1], - [1.0, 2.0], - 256, - QuantizerConfig(8, signedness_to_force=True), - QuantizerGroup.WEIGHTS, - [-1.0, -2.0], - [1.0, 2.0], - ), - ), -) -def test_symmetric_range(min_values, max_values, levels, quantizer_config, q_group, ref_low, ref_high): - level_low, level_high = symmetric_range( - Tensor(np.array(min_values)), - Tensor(np.array(max_values)), - levels, - quantizer_config, - q_group, - ) - for val, ref in zip([level_low, level_high], [ref_low, ref_high]): - if isinstance(ref, list): - assert functions.all(functions.isclose(val, ref)), f"{val=}" - else: - assert functions.isclose(val, ref), f"{val=}" - - -@pytest.mark.parametrize( - "min_values, max_values, quantizer_config, q_group, unify_zp, ref_low, ref_high", - ( - (-1.0, 1.0, QuantizerConfig(8), QuantizerGroup.ACTIVATIONS, False, -1.007874, 1.0), - (-1.0, 1.0, QuantizerConfig(8), QuantizerGroup.WEIGHTS, False, -1.007874, 1.0), - (0.1, 1.0, QuantizerConfig(8), QuantizerGroup.WEIGHTS, True, 0.0, 1.0), - ([-1.0, 0.1], [1.0, 2.0], QuantizerConfig(8), QuantizerGroup.ACTIVATIONS, False, [-1.007874, 0.0], [1.0, 2.0]), - ([-1.0, 0.1], [1.0, 2.0], QuantizerConfig(8), QuantizerGroup.WEIGHTS, False, [-1.007874, 0.0], [1.0, 2.0]), - ( - [-1.0, 0.1], - [1.0, 2.0], - QuantizerConfig(8), - QuantizerGroup.WEIGHTS, - True, - [-1.0, -0.6701571], - [2.984375, 2.0], - ), - ( - [[-1.0], [0.1]], - [[1.0], [2.0]], - QuantizerConfig(8), - QuantizerGroup.ACTIVATIONS, - False, - [[-1.007874], [0.0]], - [[1.0], [2.0]], - ), - ), -) -def test_asymmetric_range(min_values, max_values, quantizer_config, q_group, unify_zp, ref_low, ref_high): - level_low, level_high = asymmetric_range( - Tensor(np.array(min_values)), - Tensor(np.array(max_values)), - quantizer_config, - q_group, - unify_zp, - ) - for val, ref in zip([level_low, level_high], [ref_low, ref_high]): - if isinstance(ref, list): - assert functions.all(functions.isclose(val, ref)), f"{val=}" - else: - assert functions.isclose(val, ref), f"{val=}" diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index 311bd7c54b7..d0a58e51f67 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -157,11 +157,8 @@ def test_squeeze(self, val, axis, ref): nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) res = nncf_tensor.squeeze(axis=axis) - if isinstance(ref, list): - assert functions.all(res == ref_tensor) - else: - assert res == ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -178,11 +175,8 @@ def test_fn_squeeze(self, val, axis, ref): nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) res = functions.squeeze(nncf_tensor, axis=axis) - if isinstance(ref, list): - assert functions.all(res == ref_tensor) - else: - assert res == ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, ref_tensor) @pytest.mark.parametrize( "val,ref", @@ -197,11 +191,8 @@ def test_flatten(self, val, ref): nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) res = nncf_tensor.flatten() - if isinstance(ref, list): - assert all(res.data == ref_tensor) - else: - assert res.data == ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -217,11 +208,8 @@ def test_max(self, val, axis, ref): nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) res = nncf_tensor.max(axis=axis) - if isinstance(ref, list): - assert all(res.data == ref_tensor) - else: - assert res.data == ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -237,11 +225,8 @@ def test_fn_max(self, val, axis, ref): nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) res = functions.max(nncf_tensor, axis=axis) - if isinstance(ref, list): - assert all(res.data == ref_tensor) - else: - assert res.data == ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -257,11 +242,8 @@ def test_fn_amax(self, val, axis, ref): nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) res = functions.amax(nncf_tensor, axis=axis) - if isinstance(ref, list): - assert all(res.data == ref_tensor) - else: - assert res.data == ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -276,11 +258,8 @@ def test_min(self, val, axis, ref): nncf_tensor = Tensor(self.to_tensor(val)) ref_tensor = self.to_tensor(ref) res = nncf_tensor.min(axis=axis) - if isinstance(ref, list): - assert all(res.data == ref_tensor) - else: - assert res.data == ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -295,11 +274,8 @@ def test_fn_min(self, val, axis, ref): nncf_tensor = Tensor(self.to_tensor(val)) ref_tensor = self.to_tensor(ref) res = functions.min(nncf_tensor, axis=axis) - if isinstance(ref, list): - assert all(res.data == ref_tensor) - else: - assert res.data == ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -314,11 +290,8 @@ def test_fn_amin(self, val, axis, ref): nncf_tensor = Tensor(self.to_tensor(val)) ref_tensor = self.to_tensor(ref) res = functions.amin(nncf_tensor, axis=axis) - if isinstance(ref, list): - assert all(res.data == ref_tensor) - else: - assert res.data == ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, ref", @@ -331,11 +304,8 @@ def test_abs(self, val, ref): nncf_tensor = Tensor(self.to_tensor(val)) nncf_ref_tensor = Tensor(self.to_tensor(ref)) res = nncf_tensor.abs() - if isinstance(ref, list): - assert all(res == nncf_ref_tensor) - else: - assert res == nncf_ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, nncf_ref_tensor) @pytest.mark.parametrize( "val, ref", @@ -348,11 +318,8 @@ def test_fn_abs(self, val, ref): nncf_tensor = Tensor(self.to_tensor(val)) nncf_ref_tensor = Tensor(self.to_tensor(ref)) res = functions.abs(nncf_tensor) - if isinstance(ref, list): - assert all(res == nncf_ref_tensor) - else: - assert res == nncf_ref_tensor assert isinstance(res, Tensor) + assert functions.allclose(res, nncf_ref_tensor) def test_getitem(self): arr = [0, 1, 2] @@ -384,11 +351,9 @@ def test_fn_count_nonzero(self, axis, ref): nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) res = functions.count_nonzero(nncf_tensor, axis=axis) - if axis is None: - assert res.data == ref_tensor - else: - assert all(res.data == self.to_tensor(ref)) + assert isinstance(res, Tensor) + assert functions.allclose(res.data, ref_tensor) def test_fn_zeros_like(self): tensor = self.to_tensor([1, 2]) @@ -454,11 +419,8 @@ def test_fn_ones_like(self): def test_fn_all(self, val, axis, ref): tensor = Tensor(self.to_tensor(val)) res = functions.all(tensor, axis=axis) - if isinstance(ref, list): - assert all(res.data == self.to_tensor(ref)) - else: - assert res.data == self.to_tensor(ref) assert isinstance(res, Tensor) + assert functions.allclose(res.data, self.to_tensor(ref)) @pytest.mark.parametrize( "val, axis, ref", @@ -472,11 +434,9 @@ def test_fn_all(self, val, axis, ref): def test_fn_any(self, val, axis, ref): tensor = Tensor(self.to_tensor(val)) res = functions.any(tensor, axis=axis) - if isinstance(ref, list): - assert all(res.data == self.to_tensor(ref)) - else: - assert res == ref + assert isinstance(res, Tensor) + assert functions.allclose(res.data, self.to_tensor(ref)) def test_fn_where(self): tensor = Tensor(self.to_tensor([1, -1])) @@ -679,10 +639,7 @@ def test_fn_mean(self, x, axis, keepdims, ref): res = functions.mean(tensor, axis, keepdims) assert isinstance(res, Tensor) - if isinstance(ref, list): - assert functions.all(functions.isclose(res.data, ref_tensor)) - else: - assert functions.isclose(res.data, ref_tensor) + assert functions.allclose(res.data, ref_tensor) @pytest.mark.parametrize( "val, decimals, ref", @@ -699,7 +656,50 @@ def test_fn_round(self, val, decimals, ref): res = functions.round(tensor, decimals) assert isinstance(res, Tensor) - if isinstance(ref, list): - assert functions.all(functions.isclose(res.data, ref_tensor)) - else: - assert functions.isclose(res.data, ref_tensor) + assert functions.allclose(res.data, ref_tensor) + + @pytest.mark.parametrize( + "val, axis, ref", + ( + (1.1, 0, 1.1), + ( + [[[9.0, 9.0], [0.0, 3.0]], [[5.0, 1.0], [7.0, 1.0]]], + 0, + [5.25, 3.5], + ), + ( + [[[9.0, 9.0], [0.0, 3.0]], [[5.0, 1.0], [7.0, 1.0]]], + 2, + [5.25, 3.5], + ), + ( + [ + [[[9.0, 6.0], [8.0, 5.0]], [[3.0, 9.0], [4.0, 6.0]]], + [[[3.0, 9.0], [9.0, 2.0]], [[2.0, 4.0], [2.0, 5.0]]], + ], + 0, + [6.25, 4.5], + ), + ( + [ + [[[9.0, 6.0], [8.0, 5.0]], [[3.0, 9.0], [4.0, 6.0]]], + [[[3.0, 9.0], [9.0, 2.0]], [[2.0, 4.0], [2.0, 5.0]]], + ], + 1, + [6.375, 4.375], + ), + ( + [ + [[[9.0, 6.0], [8.0, 5.0]], [[3.0, 9.0], [4.0, 6.0]]], + [[[3.0, 9.0], [9.0, 2.0]], [[2.0, 4.0], [2.0, 5.0]]], + ], + -1, + [5.0, 5.75], + ), + ), + ) + def test_fn_mean_per_channel(self, val, axis, ref): + tensor = Tensor(self.to_tensor(val)) + ref_tensor = self.to_tensor(ref) + res = functions.mean_per_channel(tensor, axis) + assert functions.allclose(res.data, ref_tensor), f"{res.data}" From 9cc758224ffdb724f88891d143a63c77887ef5e8 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Sat, 19 Aug 2023 02:16:45 +0300 Subject: [PATCH 14/45] fix --- nncf/experimental/tensor/functions.py | 4 ++-- nncf/experimental/tensor/numpy_functions.py | 2 +- .../test_templates/template_test_nncf_tensor.py | 13 +++++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index fc71a26f8cd..efb8c119ce0 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -432,10 +432,10 @@ def mean_per_channel(x: Tensor, axis: int) -> Tensor: :return: Reduced Tensor. """ if len(x.shape) < 3: - return mean(x.data, axis=0) + return Tensor(mean(x.data, axis=0)) x = moveaxis(x.data, axis, 1) t = x.reshape([x.shape[0], x.shape[1], -1]) - return mean(t, axis=(0, 2)) + return Tensor(mean(t, axis=(0, 2))) __all__ = [ diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 75131a4d510..60bf899250a 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -121,7 +121,7 @@ def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = @registry_numpy_types(functions.count_nonzero) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: - return np.count_nonzero(a, axis=axis) + return np.array(np.count_nonzero(a, axis=axis)) @registry_numpy_types(functions.isempty) diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index d0a58e51f67..6cad8eb69f2 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -343,11 +343,11 @@ def test_iter(self): "axis, ref", ( (None, 3), - (0, [2, 1]), + (0, [2.0, 1.0]), ), ) def test_fn_count_nonzero(self, axis, ref): - tensor = self.to_tensor([[1, 2], [1, 0]]) + tensor = self.to_tensor([[1.0, 2.0], [1.0, 0.0]]) nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) res = functions.count_nonzero(nncf_tensor, axis=axis) @@ -661,7 +661,11 @@ def test_fn_round(self, val, decimals, ref): @pytest.mark.parametrize( "val, axis, ref", ( - (1.1, 0, 1.1), + ( + [[9.0, 9.0], [7.0, 1.0]], + 0, + [8.0, 5.0], + ), ( [[[9.0, 9.0], [0.0, 3.0]], [[5.0, 1.0], [7.0, 1.0]]], 0, @@ -702,4 +706,5 @@ def test_fn_mean_per_channel(self, val, axis, ref): tensor = Tensor(self.to_tensor(val)) ref_tensor = self.to_tensor(ref) res = functions.mean_per_channel(tensor, axis) - assert functions.allclose(res.data, ref_tensor), f"{res.data}" + assert isinstance(res, Tensor) + assert functions.allclose(res, ref_tensor), f"{res.data}" From 840cb6a49b02eaaf3bd10c16e2eac54ad99bb813 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Sat, 19 Aug 2023 04:47:31 +0300 Subject: [PATCH 15/45] fix --- tests/shared/test_templates/template_test_nncf_tensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index 6cad8eb69f2..784ee25b9f8 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -343,7 +343,7 @@ def test_iter(self): "axis, ref", ( (None, 3), - (0, [2.0, 1.0]), + (0, [2, 1]), ), ) def test_fn_count_nonzero(self, axis, ref): From c78668d606a335b9f4bd877afbded3efc8e8a906 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Mon, 4 Sep 2023 19:11:06 +0300 Subject: [PATCH 16/45] fix comments --- nncf/experimental/tensor/README.md | 15 +- nncf/experimental/tensor/functions.py | 104 ++++-------- nncf/experimental/tensor/math_functions.py | 28 +++ nncf/experimental/tensor/numpy_functions.py | 62 +++---- nncf/experimental/tensor/tensor.py | 2 +- nncf/experimental/tensor/torch_functions.py | 68 +++----- .../fast_bias_correction/algorithm.py | 19 +-- .../fast_bias_correction/onnx_backend.py | 6 +- .../fast_bias_correction/openvino_backend.py | 4 +- .../fast_bias_correction/torch_backend.py | 4 +- nncf/quantization/fake_quantize.py | 38 ++--- .../test_calculate_quantizer_parameters.py | 10 +- .../template_test_nncf_tensor.py | 160 +++++------------- 13 files changed, 204 insertions(+), 316 deletions(-) create mode 100644 nncf/experimental/tensor/math_functions.py diff --git a/nncf/experimental/tensor/README.md b/nncf/experimental/tensor/README.md index 09e3dc6a1e0..b94e6488010 100644 --- a/nncf/experimental/tensor/README.md +++ b/nncf/experimental/tensor/README.md @@ -55,16 +55,16 @@ nncf_tensor.max() # Tensor(2) All available functions you can found in [functions.py](functions.py). ```python -from nncf.experimental.tensor import functions -functions.max(nncf_tensor) # Tensor(2) +from nncf.experimental.tensor import functions as fns +fns.max(nncf_tensor) # Tensor(2) ``` **NOTE** A function requires at least one positional argument, which is used to dispatch the function to the appropriate implementation depending on the type of argument. ```python -functions.max(nncf_tensor) # Correct -functions.max(a=nncf_tensor) # TypeError: wrapper requires at least 1 positional argument +fns.max(nncf_tensor) # Correct +fns.max(a=nncf_tensor) # TypeError: wrapper requires at least 1 positional argument ``` ### Loop over Tensor @@ -100,7 +100,7 @@ tensor_a[0:2] # Tensor(array([[1],[2]])) class Tensor: ... def foo(self, arg1: Type) -> "Tensor": - return functions.foo(self, arg1) + return fns.foo(self, arg1) ``` 2. Add function to [function.py](function.py) @@ -127,8 +127,7 @@ tensor_a[0:2] # Tensor(array([[1],[2]])) - [numpy_function.py](numpy_function.py) ```python - @functions.foo.register(np.ndarray) - @functions.foo.register(np.number) + @registry_numpy_types(fns.foo) def _(a: TType, arg1: Type) -> np.ndarray: return np.foo(a, arg1) ``` @@ -136,7 +135,7 @@ tensor_a[0:2] # Tensor(array([[1],[2]])) - [torch_function.py](torch_function.py) ```python - @functions.foo.register(torch.Tensor) + @fns.foo.register(torch.Tensor) def _(a: torch.Tensor, arg1: Type) -> torch.Tensor: return torch.foo(a, arg1) ``` diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index efb8c119ce0..6e8b03c8727 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -48,7 +48,7 @@ def device(a: TTensor) -> TensorDeviceType: @functools.singledispatch @_tensor_guard -def squeeze(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: +def squeeze(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: """ Remove axes of length one from a. @@ -63,7 +63,7 @@ def squeeze(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor @functools.singledispatch @_tensor_guard -def flatten(a: TTensor) -> Tensor: +def flatten(a: TTensor) -> TTensor: """ Return a copy of the tensor collapsed into one dimension. @@ -75,7 +75,7 @@ def flatten(a: TTensor) -> Tensor: @functools.singledispatch @_tensor_guard -def max(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin +def max(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin """ Return the maximum of an array or maximum along an axis. @@ -88,20 +88,7 @@ def max(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # @functools.singledispatch @_tensor_guard -def amax(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin - """ - Return the maximum of an array or maximum along an axis. - - :param a: The input tensor. - :param axis: Axis or axes along which to operate. By default, flattened input is used. - :return: Maximum of a. - """ - return Tensor(amax(a.data, axis)) - - -@functools.singledispatch -@_tensor_guard -def min(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin +def min(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin """ Return the minimum of an array or minimum along an axis. @@ -112,19 +99,6 @@ def min(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # return Tensor(min(a.data, axis)) -@functools.singledispatch -@_tensor_guard -def amin(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin - """ - Return the minimum of an array or minimum along an axis. - - :param a: The input tensor. - :param axis: Axis or axes along which to operate. By default, flattened input is used. - :return: Minimum of a. - """ - return Tensor(amin(a.data, axis)) - - @functools.singledispatch @_tensor_guard def abs(a: TTensor) -> Tensor: # pylint: disable=redefined-builtin @@ -139,7 +113,7 @@ def abs(a: TTensor) -> Tensor: # pylint: disable=redefined-builtin @functools.singledispatch @_tensor_guard -def astype(a: TTensor, data_type: TensorDataType) -> Tensor: +def astype(a: TTensor, data_type: TensorDataType) -> TTensor: """ Copy of the tensor, cast to a specified type. @@ -165,7 +139,7 @@ def dtype(a: TTensor) -> TensorDataType: @functools.singledispatch @_tensor_guard -def reshape(a: TTensor, shape: List[int]) -> Tensor: +def reshape(a: TTensor, shape: List[int]) -> TTensor: """ Gives a new shape to a tensor without changing its data. @@ -178,7 +152,7 @@ def reshape(a: TTensor, shape: List[int]) -> Tensor: @functools.singledispatch @_tensor_guard -def all(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin +def all(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin """ Test whether all tensor elements along a given axis evaluate to True. @@ -191,7 +165,7 @@ def all(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # @functools.singledispatch @_tensor_guard -def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> Tensor: +def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> bool: """ Returns True if two arrays are element-wise equal within a tolerance. @@ -204,20 +178,18 @@ def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, e Defaults to False. :return: True if the two arrays are equal within the given tolerance, otherwise False. """ - return Tensor( - allclose( - a.data, - unwrap_tensor_data(b), - rtol=rtol, - atol=atol, - equal_nan=equal_nan, - ) + return allclose( + a.data, + unwrap_tensor_data(b), + rtol=rtol, + atol=atol, + equal_nan=equal_nan, ) @functools.singledispatch @_tensor_guard -def any(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin +def any(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin """ Test whether any tensor elements along a given axis evaluate to True. @@ -230,7 +202,7 @@ def any(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # @functools.singledispatch @_tensor_guard -def count_nonzero(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: +def count_nonzero(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: """ Counts the number of non-zero values in the tensor input. @@ -244,19 +216,19 @@ def count_nonzero(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> @functools.singledispatch @_tensor_guard -def isempty(a: TTensor) -> Tensor: +def isempty(a: TTensor) -> bool: """ Return True if input tensor is empty. :param a: The input tensor. :return: True if tensor is empty, otherwise False. """ - return Tensor(isempty(a.data)) + return isempty(a.data) @functools.singledispatch @_tensor_guard -def isclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> Tensor: +def isclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> TTensor: """ Returns a boolean array where two arrays are element-wise equal within a tolerance. @@ -282,7 +254,7 @@ def isclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, eq @functools.singledispatch @_tensor_guard -def maximum(x1: TTensor, x2: TTensor) -> Tensor: +def maximum(x1: TTensor, x2: TTensor) -> TTensor: """ Element-wise maximum of tensor elements. @@ -295,7 +267,7 @@ def maximum(x1: TTensor, x2: TTensor) -> Tensor: @functools.singledispatch @_tensor_guard -def minimum(x1: TTensor, x2: TTensor) -> Tensor: +def minimum(x1: TTensor, x2: TTensor) -> TTensor: """ Element-wise minimum of tensor elements. @@ -308,7 +280,7 @@ def minimum(x1: TTensor, x2: TTensor) -> Tensor: @functools.singledispatch @_tensor_guard -def ones_like(a: TTensor) -> Tensor: +def ones_like(a: TTensor) -> TTensor: """ Return a tensor of ones with the same shape and type as a given tensor. @@ -320,7 +292,7 @@ def ones_like(a: TTensor) -> Tensor: @functools.singledispatch @_tensor_guard -def where(condition: TTensor, x: TTensor, y: TTensor) -> Tensor: +def where(condition: TTensor, x: TTensor, y: TTensor) -> TTensor: """ Return elements chosen from x or y depending on condition. @@ -340,7 +312,7 @@ def where(condition: TTensor, x: TTensor, y: TTensor) -> Tensor: @functools.singledispatch @_tensor_guard -def zeros_like(a: TTensor) -> Tensor: +def zeros_like(a: TTensor) -> TTensor: """ Return an tensor of zeros with the same shape and type as a given tensor. @@ -351,7 +323,7 @@ def zeros_like(a: TTensor) -> Tensor: @functools.singledispatch -def stack(x: List[TTensor], axis: int = 0) -> Tensor: +def stack(x: List[TTensor], axis: int = 0) -> TTensor: """ Stacks a list or deque of Tensors rank-R tensors into one Tensor rank-(R+1) tensor. @@ -369,7 +341,7 @@ def stack(x: List[TTensor], axis: int = 0) -> Tensor: @functools.singledispatch @_tensor_guard -def unstack(a: Tensor, axis: int = 0) -> List[Tensor]: +def unstack(a: TTensor, axis: int = 0) -> List[Tensor]: """ Unstack a Tensor into list. @@ -383,7 +355,7 @@ def unstack(a: Tensor, axis: int = 0) -> List[Tensor]: @functools.singledispatch @_tensor_guard -def moveaxis(a: Tensor, source: Union[int, List[int]], destination: Union[int, List[int]]) -> Tensor: +def moveaxis(a: TTensor, source: Union[int, List[int]], destination: Union[int, List[int]]) -> TTensor: """ Move axes of an array to new positions. @@ -397,7 +369,7 @@ def moveaxis(a: Tensor, source: Union[int, List[int]], destination: Union[int, L @functools.singledispatch @_tensor_guard -def mean(a: Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) -> Tensor: +def mean(a: TTensor, axis: Union[int, List[int]] = None, keepdims: bool = False) -> TTensor: """ Compute the arithmetic mean along the specified axis. @@ -411,7 +383,7 @@ def mean(a: Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) @functools.singledispatch @_tensor_guard -def round(a: Tensor, decimals=0) -> Tensor: # pylint: disable=redefined-builtin +def round(a: TTensor, decimals=0) -> TTensor: # pylint: disable=redefined-builtin """ Evenly round to the given number of decimals. @@ -423,27 +395,10 @@ def round(a: Tensor, decimals=0) -> Tensor: # pylint: disable=redefined-builtin return Tensor(round(a.data, decimals)) -def mean_per_channel(x: Tensor, axis: int) -> Tensor: - """ - Computes the mean of elements across given channel dimension of Tensor. - - :param x: Tensor to reduce. - :param axis: The channel dimensions to reduce. - :return: Reduced Tensor. - """ - if len(x.shape) < 3: - return Tensor(mean(x.data, axis=0)) - x = moveaxis(x.data, axis, 1) - t = x.reshape([x.shape[0], x.shape[1], -1]) - return Tensor(mean(t, axis=(0, 2))) - - __all__ = [ "abs", "all", "allclose", - "amax", - "amin", "any", "astype", "count_nonzero", @@ -454,7 +409,6 @@ def mean_per_channel(x: Tensor, axis: int) -> Tensor: "max", "maximum", "mean", - "mean_per_channel", "min", "minimum", "minimum", diff --git a/nncf/experimental/tensor/math_functions.py b/nncf/experimental/tensor/math_functions.py new file mode 100644 index 00000000000..fa23d2cc2df --- /dev/null +++ b/nncf/experimental/tensor/math_functions.py @@ -0,0 +1,28 @@ +# Copyright (c) 2023 Intel Corporation +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from nncf.experimental.tensor import Tensor +from nncf.experimental.tensor import functions as fns + + +def mean_per_channel(x: Tensor, axis: int) -> Tensor: + """ + Computes the mean of elements across given channel dimension of Tensor. + + :param x: Tensor to reduce. + :param axis: The channel dimensions to reduce. + :return: Reduced Tensor. + """ + if len(x.shape) < 3: + return fns.mean(x, axis=0) + x = fns.moveaxis(x, axis, 1) + t = x.reshape([x.shape[0], x.shape[1], -1]) + return fns.mean(t, axis=(0, 2)) diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 60bf899250a..10b6b8f94cf 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -13,7 +13,7 @@ import numpy as np -from nncf.experimental.tensor import functions +from nncf.experimental.tensor import functions as fns from nncf.experimental.tensor.enums import TensorDataType from nncf.experimental.tensor.enums import TensorDeviceType @@ -43,67 +43,57 @@ def inner(func): return inner -@registry_numpy_types(functions.device) +@registry_numpy_types(fns.device) def _(a: Union[np.ndarray, np.number]) -> TensorDeviceType: return TensorDeviceType.CPU -@registry_numpy_types(functions.squeeze) +@registry_numpy_types(fns.squeeze) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.squeeze(a, axis=axis) -@registry_numpy_types(functions.flatten) +@registry_numpy_types(fns.flatten) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return a.flatten() -@registry_numpy_types(functions.max) +@registry_numpy_types(fns.max) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.max(a, axis=axis) -@registry_numpy_types(functions.amax) -def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: - return np.amax(a, axis=axis) - - -@registry_numpy_types(functions.min) +@registry_numpy_types(fns.min) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.min(a, axis=axis) -@registry_numpy_types(functions.amin) -def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: - return np.amin(a, axis=axis) - - -@registry_numpy_types(functions.abs) +@registry_numpy_types(fns.abs) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.absolute(a) -@registry_numpy_types(functions.astype) +@registry_numpy_types(fns.astype) def _(a: Union[np.ndarray, np.number], dtype: TensorDataType) -> np.ndarray: return a.astype(DTYPE_MAP[dtype]) -@registry_numpy_types(functions.dtype) +@registry_numpy_types(fns.dtype) def _(a: Union[np.ndarray, np.number]) -> TensorDataType: return DTYPE_MAP_REV[np.dtype(a.dtype)] -@registry_numpy_types(functions.reshape) +@registry_numpy_types(fns.reshape) def _(a: Union[np.ndarray, np.number], shape: Union[int, Tuple[int]]) -> np.ndarray: return a.reshape(shape) -@registry_numpy_types(functions.all) +@registry_numpy_types(fns.all) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: return np.all(a, axis=axis) -@registry_numpy_types(functions.allclose) +@registry_numpy_types(fns.allclose) def _( a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], @@ -114,22 +104,22 @@ def _( return np.allclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) -@registry_numpy_types(functions.any) +@registry_numpy_types(fns.any) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: return np.any(a, axis=axis) -@registry_numpy_types(functions.count_nonzero) +@registry_numpy_types(fns.count_nonzero) def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.array(np.count_nonzero(a, axis=axis)) -@registry_numpy_types(functions.isempty) +@registry_numpy_types(fns.isempty) def _(a: Union[np.ndarray, np.number]) -> bool: return a.size == 0 -@registry_numpy_types(functions.isclose) +@registry_numpy_types(fns.isclose) def _( a: Union[np.ndarray, np.number], b: np.ndarray, @@ -140,22 +130,22 @@ def _( return np.isclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) -@registry_numpy_types(functions.maximum) +@registry_numpy_types(fns.maximum) def _(x1: Union[np.ndarray, np.number], x2: np.ndarray) -> np.ndarray: return np.maximum(x1, x2) -@registry_numpy_types(functions.minimum) +@registry_numpy_types(fns.minimum) def _(x1: Union[np.ndarray, np.number], x2: np.ndarray) -> np.ndarray: return np.minimum(x1, x2) -@registry_numpy_types(functions.ones_like) +@registry_numpy_types(fns.ones_like) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.ones_like(a) -@registry_numpy_types(functions.where) +@registry_numpy_types(fns.where) def _( condition: Union[np.ndarray, np.number], x: Union[np.ndarray, np.number, float, bool], @@ -164,31 +154,31 @@ def _( return np.where(condition, x, y) -@registry_numpy_types(functions.zeros_like) +@registry_numpy_types(fns.zeros_like) def _(a: Union[np.ndarray, np.number]) -> np.ndarray: return np.zeros_like(a) -@registry_numpy_types(functions.stack) +@registry_numpy_types(fns.stack) def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: return np.stack(x, axis=axis) -@registry_numpy_types(functions.unstack) +@registry_numpy_types(fns.unstack) def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: return [np.squeeze(e, axis) for e in np.split(x, x.shape[axis], axis=axis)] -@registry_numpy_types(functions.moveaxis) +@registry_numpy_types(fns.moveaxis) def _(a: np.ndarray, source: Union[int, List[int]], destination: Union[int, List[int]]) -> np.ndarray: return np.moveaxis(a, source, destination) -@registry_numpy_types(functions.mean) +@registry_numpy_types(fns.mean) def _(a: Union[np.ndarray, np.number], axis: Union[int, List[int]] = None, keepdims: bool = False) -> np.ndarray: return np.mean(a, axis=axis, keepdims=keepdims) -@registry_numpy_types(functions.round) +@registry_numpy_types(fns.round) def _(a: Union[np.ndarray, np.number], decimals: int = 0) -> np.ndarray: return np.round(a, decimals=decimals) diff --git a/nncf/experimental/tensor/tensor.py b/nncf/experimental/tensor/tensor.py index d7dbb97ce19..3ae0cdc76aa 100644 --- a/nncf/experimental/tensor/tensor.py +++ b/nncf/experimental/tensor/tensor.py @@ -136,7 +136,7 @@ def min(self, axis: Optional[TTensor] = None) -> "Tensor": def abs(self) -> "Tensor": return _call_function("abs", self) - def isempty(self) -> "Tensor": + def isempty(self) -> bool: return _call_function("isempty", self) def astype(self, dtype: TensorDataType): diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 460ed63bc5b..e5ec6c7d2ef 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -15,7 +15,7 @@ from nncf.experimental.tensor import TensorDataType from nncf.experimental.tensor import TensorDeviceType -from nncf.experimental.tensor import functions +from nncf.experimental.tensor import functions as fns DTYPE_MAP = { TensorDataType.float16: torch.float16, @@ -28,7 +28,7 @@ DTYPE_MAP_REV = {v: k for k, v in DTYPE_MAP.items()} -@functions.device.register(torch.Tensor) +@fns.device.register(torch.Tensor) def _(a: torch.Tensor) -> TensorDeviceType: DEVICE_MAP = { "cpu": TensorDeviceType.CPU, @@ -37,153 +37,141 @@ def _(a: torch.Tensor) -> TensorDeviceType: return DEVICE_MAP[a.device.type] -@functions.squeeze.register(torch.Tensor) +@fns.squeeze.register(torch.Tensor) def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: if axis is None: return a.squeeze() return a.squeeze(axis) -@functions.flatten.register(torch.Tensor) +@fns.flatten.register(torch.Tensor) def _(a: torch.Tensor) -> torch.Tensor: return a.flatten() -@functions.max.register(torch.Tensor) -def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: - if axis is None: - return torch.max(a) - return torch.max(a, dim=axis).values - - -@functions.amax.register(torch.Tensor) +@fns.max.register(torch.Tensor) def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: + # Analog of numpy.max is torch.amax if axis is None: return torch.amax(a) return torch.amax(a, dim=axis) -@functions.min.register(torch.Tensor) -def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: - if axis is None: - return torch.min(a) - return torch.min(a, dim=axis).values - - -@functions.amin.register(torch.Tensor) +@fns.min.register(torch.Tensor) def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: + # Analog of numpy.min is torch.amin if axis is None: return torch.amin(a) return torch.amin(a, dim=axis) -@functions.abs.register(torch.Tensor) +@fns.abs.register(torch.Tensor) def _(a: torch.Tensor) -> torch.Tensor: return torch.absolute(a) -@functions.astype.register(torch.Tensor) +@fns.astype.register(torch.Tensor) def _(a: torch.Tensor, dtype: TensorDataType) -> torch.Tensor: return a.type(DTYPE_MAP[dtype]) -@functions.dtype.register(torch.Tensor) +@fns.dtype.register(torch.Tensor) def _(a: torch.Tensor) -> TensorDataType: return DTYPE_MAP_REV[a.dtype] -@functions.reshape.register(torch.Tensor) +@fns.reshape.register(torch.Tensor) def _(a: torch.Tensor, shape: List[int]) -> torch.Tensor: return a.reshape(shape) -@functions.all.register(torch.Tensor) +@fns.all.register(torch.Tensor) def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Union[torch.Tensor, bool]: if axis is None: return torch.all(a) return torch.all(a, dim=axis) -@functions.allclose.register(torch.Tensor) +@fns.allclose.register(torch.Tensor) def _(a: torch.Tensor, b: torch.Tensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> bool: return torch.allclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) -@functions.any.register(torch.Tensor) +@fns.any.register(torch.Tensor) def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Union[torch.Tensor, bool]: if axis is None: return torch.any(a) return torch.any(a, dim=axis) -@functions.count_nonzero.register(torch.Tensor) +@fns.count_nonzero.register(torch.Tensor) def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: return torch.count_nonzero(a, dim=axis) -@functions.isempty.register(torch.Tensor) +@fns.isempty.register(torch.Tensor) def _(a: torch.Tensor) -> bool: return a.numel() == 0 -@functions.isclose.register(torch.Tensor) +@fns.isclose.register(torch.Tensor) def _(a: torch.Tensor, b: torch.Tensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False): return torch.isclose(a, b, atol=atol, rtol=rtol, equal_nan=equal_nan) -@functions.maximum.register(torch.Tensor) +@fns.maximum.register(torch.Tensor) def _(x1: torch.Tensor, x2: torch.Tensor) -> torch.Tensor: if not isinstance(x2, torch.Tensor): x2 = torch.tensor(x2, device=x1.data.device) return torch.maximum(x1, x2) -@functions.minimum.register(torch.Tensor) +@fns.minimum.register(torch.Tensor) def _(x1: torch.Tensor, x2: torch.Tensor) -> torch.Tensor: if not isinstance(x2, torch.Tensor): x2 = torch.tensor(x2, device=x1.data.device) return torch.minimum(x1, x2) -@functions.ones_like.register(torch.Tensor) +@fns.ones_like.register(torch.Tensor) def _(a: torch.Tensor) -> torch.Tensor: return torch.ones_like(a) -@functions.where.register(torch.Tensor) +@fns.where.register(torch.Tensor) def _( condition: torch.Tensor, x: Union[torch.Tensor, float, bool], y: Union[torch.Tensor, float, bool] ) -> torch.Tensor: return torch.where(condition, x, y) -@functions.zeros_like.register(torch.Tensor) +@fns.zeros_like.register(torch.Tensor) def _(a: torch.Tensor) -> torch.Tensor: return torch.zeros_like(a) -@functions.stack.register(torch.Tensor) +@fns.stack.register(torch.Tensor) def _(x: List[torch.Tensor], axis: int = 0) -> List[torch.Tensor]: return torch.stack(x, dim=axis) -@functions.unstack.register(torch.Tensor) +@fns.unstack.register(torch.Tensor) def _(x: torch.Tensor, axis: int = 0) -> List[torch.Tensor]: if not list(x.shape): x = x.unsqueeze(0) return torch.unbind(x, dim=axis) -@functions.moveaxis.register(torch.Tensor) +@fns.moveaxis.register(torch.Tensor) def _(a: torch.Tensor, source: Union[int, List[int]], destination: Union[int, List[int]]) -> torch.Tensor: return torch.moveaxis(a, source, destination) -@functions.mean.register(torch.Tensor) +@fns.mean.register(torch.Tensor) def _(a: torch.Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) -> torch.Tensor: return torch.mean(a, axis=axis, keepdims=keepdims) -@functions.round.register(torch.Tensor) +@fns.round.register(torch.Tensor) def _(a: torch.Tensor, decimals=0) -> torch.Tensor: return torch.round(a, decimals=decimals) diff --git a/nncf/quantization/algorithms/fast_bias_correction/algorithm.py b/nncf/quantization/algorithms/fast_bias_correction/algorithm.py index 444f181c8d1..cae9c16d992 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/algorithm.py +++ b/nncf/quantization/algorithms/fast_bias_correction/algorithm.py @@ -28,7 +28,8 @@ from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import get_backend from nncf.experimental.tensor import Tensor -from nncf.experimental.tensor import functions +from nncf.experimental.tensor import functions as fns +from nncf.experimental.tensor import math_functions as mfns from nncf.quantization.algorithms.algorithm import Algorithm from nncf.quantization.algorithms.fast_bias_correction.backend import ALGO_BACKENDS @@ -184,9 +185,7 @@ def apply( # Create commands of bias correction and apply them to the model. transformation_layout = TransformationLayout() for node, bias_value in node_and_new_bias_value: - transformation_layout.register( - self._backend_entity.create_bias_correction_command(node, bias_value.data, graph) - ) + transformation_layout.register(self._backend_entity.create_bias_correction_command(node, bias_value, graph)) transformed_model = model_transformer.transform(transformation_layout) return transformed_model @@ -201,14 +200,12 @@ def _get_bias_shift_magnitude(current_bias_value: Tensor, updated_bias_value: Te :return: Magnitude between original and updated bias values. """ bias_shift_magnitude = inf - if functions.count_nonzero(current_bias_value == 0) == 0: - bias_shift_magnitude = functions.max( - functions.abs((updated_bias_value - current_bias_value) / current_bias_value) - ) + if fns.count_nonzero(current_bias_value == 0) == 0: + bias_shift_magnitude = fns.max(fns.abs((updated_bias_value - current_bias_value) / current_bias_value)) return bias_shift_magnitude @staticmethod - def _reshape_bias_shift(bias_shift: Tensor, bias_value: Tensor, channel_axis: int) -> TTensor: + def _reshape_bias_shift(bias_shift: Tensor, bias_value: Tensor, channel_axis: int) -> Tensor: """ Reshape bias_shift tensor in case of dimensions of bias_value is more then 1. @@ -322,8 +319,8 @@ def _get_bias_shift( engine = EngineFactory.create(model) raw_output = engine.infer(input_blob) q_outputs = self._backend_entity.process_model_output(raw_output, output_name) - q_outputs = functions.mean_per_channel(q_outputs, channel_axis) - bias_shift = functions.stack(output_fp) - q_outputs + q_outputs = mfns.mean_per_channel(q_outputs, channel_axis) + bias_shift = fns.stack(output_fp) - q_outputs return bias_shift def get_statistic_points(self, model: TModel, graph: NNCFGraph) -> StatisticPointsContainer: diff --git a/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py b/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py index e50b6798bf7..79e02ee0ab8 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py @@ -55,9 +55,9 @@ def create_bias_insertion_command(node: NNCFNode) -> ONNXNullBiasInsertionComman @staticmethod def create_bias_correction_command( - node: NNCFNode, bias_value: np.ndarray, nncf_graph: NNCFGraph + node: NNCFNode, bias_value: Tensor, nncf_graph: NNCFGraph ) -> ONNXBiasCorrectionCommand: - return create_bias_correction_command(node, bias_value) + return create_bias_correction_command(node, bias_value.data) @staticmethod def model_extraction_command(inputs: List[str], outputs: List[str]) -> ONNXModelExtractionCommand: @@ -78,7 +78,7 @@ def get_sub_input_output_names(subgraph: onnx.ModelProto) -> Tuple[str, str]: @staticmethod def create_input_data( - shape: Tuple[int], data: List[np.ndarray], input_name: str, channel_axis: int + shape: Tuple[int], data: List[Tensor], input_name: str, channel_axis: int ) -> Dict[str, np.array]: blob = np.zeros(shape, dtype=data[0].data.dtype) for j, idx in enumerate(np.ndindex(blob.shape[channel_axis])): diff --git a/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py b/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py index bf50c6a7ae2..acb494c7009 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py @@ -47,9 +47,9 @@ def target_point(target_type: TargetType, target_node_name: str, port_id: int) - @staticmethod def create_bias_correction_command( - node: NNCFNode, bias_value: np.ndarray, nncf_graph: NNCFGraph + node: NNCFNode, bias_value: Tensor, nncf_graph: NNCFGraph ) -> OVBiasCorrectionCommand: - return OVCommandCreator.create_command_to_update_bias(node, bias_value, nncf_graph) + return OVCommandCreator.create_command_to_update_bias(node, bias_value.data, nncf_graph) @staticmethod def model_extraction_command(inputs: List[str], outputs: List[str]) -> OVModelExtractionCommand: diff --git a/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py b/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py index 5a6b1359f28..cde232ad795 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py @@ -58,9 +58,9 @@ def target_point(target_type: TargetType, target_node_name: str, port_id: int) - @staticmethod def create_bias_correction_command( - node: NNCFNode, bias_value: np.ndarray, nncf_graph: NNCFGraph + node: NNCFNode, bias_value: Tensor, nncf_graph: NNCFGraph ) -> PTBiasCorrectionCommand: - return create_bias_correction_command(node, bias_value) + return create_bias_correction_command(node, bias_value.data) @staticmethod def model_extraction_command(inputs: List[str], outputs: List[str]) -> PTModelExtractionWithFusedBiasCommand: diff --git a/nncf/quantization/fake_quantize.py b/nncf/quantization/fake_quantize.py index e2e8d41578a..37db6dc5ce1 100644 --- a/nncf/quantization/fake_quantize.py +++ b/nncf/quantization/fake_quantize.py @@ -23,7 +23,7 @@ from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.experimental.tensor import Tensor from nncf.experimental.tensor import TensorDataType -from nncf.experimental.tensor import functions +from nncf.experimental.tensor import functions as fns @dataclass @@ -53,9 +53,9 @@ def fix_zero_filters_symmetric(max_values: Tensor, eps: float = 0.01) -> Tensor: :param eps: Correction coefficient. :return: Fixed the high quant number. """ - max_range = functions.max(max_values) - lower_threshold = functions.maximum(max_range * eps, 8e-5) - return functions.maximum(lower_threshold, max_values) + max_range = fns.max(max_values) + lower_threshold = fns.maximum(max_range * eps, 8e-5) + return fns.maximum(lower_threshold, max_values) def fix_zero_filters_asymmetric(min_values: Tensor, max_values: Tensor, eps: float = 1e-8) -> Tuple[Tensor, Tensor]: @@ -71,9 +71,7 @@ def fix_zero_filters_asymmetric(min_values: Tensor, max_values: Tensor, eps: flo """ ranges = max_values - min_values min_correction = 8e-4 - corrections = functions.where( - ranges > min_correction, (functions.maximum(eps * ranges, ranges) - ranges) * 0.5, min_correction - ) + corrections = fns.where(ranges > min_correction, (fns.maximum(eps * ranges, ranges) - ranges) * 0.5, min_correction) level_low = min_values - corrections level_high = max_values + corrections @@ -101,22 +99,22 @@ def tune_range( if unify_zp: scale = (right_border - left_border) / level_high zero_point = -left_border / scale - avg_zpts = functions.round(functions.mean(zero_point)) - qval = functions.ones_like(left_border) * avg_zpts + avg_zpts = fns.round(fns.mean(zero_point)) + qval = fns.ones_like(left_border) * avg_zpts else: s = level_high / (right_border - left_border) fval = -left_border * s - qval = functions.round(fval) + qval = fns.round(fval) with np.errstate(invalid="ignore", divide="ignore"): # TODO: move to Tensor, to sync with numpy and torch??? - ra = functions.where(qval < level_high, qval / (qval - level_high) * right_border, left_border) - rb = functions.where(qval > 0.0, (qval - level_high) / qval * left_border, right_border) + ra = fns.where(qval < level_high, qval / (qval - level_high) * right_border, left_border) + rb = fns.where(qval > 0.0, (qval - level_high) / qval * left_border, right_border) range_a = right_border - ra range_b = rb - left_border - mask = functions.where(range_a > range_b, 1.0, 0.0) - inv_mask = functions.abs(1.0 - mask) + mask = fns.where(range_a > range_b, 1.0, 0.0) + inv_mask = fns.abs(1.0 - mask) ra = mask * ra + inv_mask * left_border rb = inv_mask * rb + mask * right_border @@ -148,8 +146,8 @@ def symmetric_range( else: signed = quantizer_config.signedness_to_force is True level_low = ( - functions.zeros_like(level_high) - if functions.all(min_values >= 0) and not signed + fns.zeros_like(level_high) + if fns.all(min_values >= 0) and not signed else -level_high * levels / (levels - 2) ) @@ -178,8 +176,8 @@ def asymmetric_range( level_high - the high quant number """ level_low, level_high = fix_zero_filters_asymmetric(min_values, max_values) - level_low = functions.where(level_low < 0.0, level_low, 0.0) - level_high = functions.where(level_high > 0.0, level_high, 0.0) + level_low = fns.where(level_low < 0.0, level_low, 0.0) + level_high = fns.where(level_high > 0.0, level_high, 0.0) if unify_zp and q_group == QuantizerGroup.ACTIVATIONS: raise NotImplementedError("Unified zero point is not supported for activations.") @@ -242,8 +240,8 @@ def calculate_quantizer_parameters( input_low, input_high = asymmetric_range(min_values, max_values, quantizer_config, quant_group) if not quantizer_config.per_channel: - input_low = functions.squeeze(input_low) - input_high = functions.squeeze(input_high) + input_low = fns.squeeze(input_low) + input_high = fns.squeeze(input_high) output_low, output_high = input_low, input_high return FakeQuantizeParameters(input_low, input_high, output_low, output_high, levels) diff --git a/tests/post_training/test_templates/test_calculate_quantizer_parameters.py b/tests/post_training/test_templates/test_calculate_quantizer_parameters.py index 6c051d3127d..3e9e8d8b1c1 100644 --- a/tests/post_training/test_templates/test_calculate_quantizer_parameters.py +++ b/tests/post_training/test_templates/test_calculate_quantizer_parameters.py @@ -19,7 +19,7 @@ from nncf.common.quantization.structs import QuantizationMode from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup -from nncf.experimental.tensor import functions +from nncf.experimental.tensor import functions as fns from nncf.quantization.fake_quantize import FakeQuantizeParameters from nncf.quantization.fake_quantize import calculate_quantizer_parameters from tests.post_training.conftest import FQ_CALCULATED_PARAMETERS_PATH @@ -33,10 +33,10 @@ def compare_fq_parameters(ref_params, params): assert ref_params.input_high.shape == params.input_high.shape assert ref_params.output_low.shape == params.output_low.shape assert ref_params.output_high.shape == params.output_high.shape - assert functions.allclose(ref_params.input_low, params.input_low) - assert functions.allclose(ref_params.input_high, params.input_high) - assert functions.allclose(ref_params.output_low, params.output_low) - assert functions.allclose(ref_params.output_high, params.output_high) + assert fns.allclose(ref_params.input_low, params.input_low) + assert fns.allclose(ref_params.input_high, params.input_high) + assert fns.allclose(ref_params.output_low, params.output_low) + assert fns.allclose(ref_params.output_high, params.output_high) def get_test_reference_key(q_group, q_config, narrow_range, hf_range): diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index 784ee25b9f8..7eed20f2222 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -20,7 +20,8 @@ from nncf.experimental.tensor import Tensor from nncf.experimental.tensor import TensorDataType from nncf.experimental.tensor import TensorDeviceType -from nncf.experimental.tensor import functions +from nncf.experimental.tensor import functions as fns +from nncf.experimental.tensor import math_functions as mfns TModel = TypeVar("TModel") TTensor = TypeVar("TTensor") @@ -158,7 +159,7 @@ def test_squeeze(self, val, axis, ref): ref_tensor = self.to_tensor(ref) res = nncf_tensor.squeeze(axis=axis) assert isinstance(res, Tensor) - assert functions.allclose(res, ref_tensor) + assert fns.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -174,9 +175,9 @@ def test_fn_squeeze(self, val, axis, ref): tensor = self.to_tensor(val) nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) - res = functions.squeeze(nncf_tensor, axis=axis) + res = fns.squeeze(nncf_tensor, axis=axis) assert isinstance(res, Tensor) - assert functions.allclose(res, ref_tensor) + assert fns.allclose(res, ref_tensor) @pytest.mark.parametrize( "val,ref", @@ -192,24 +193,7 @@ def test_flatten(self, val, ref): ref_tensor = self.to_tensor(ref) res = nncf_tensor.flatten() assert isinstance(res, Tensor) - assert functions.allclose(res, ref_tensor) - - @pytest.mark.parametrize( - "val, axis, ref", - ( - (1, None, 1), - ([1], None, 1), - ([[[[1], [2]], [[3], [4]]]], None, 4), - ([[1, 2], [3, 4]], 1, [2, 4]), - ), - ) - def test_max(self, val, axis, ref): - tensor = self.to_tensor(val) - nncf_tensor = Tensor(tensor) - ref_tensor = self.to_tensor(ref) - res = nncf_tensor.max(axis=axis) - assert isinstance(res, Tensor) - assert functions.allclose(res, ref_tensor) + assert fns.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -224,26 +208,9 @@ def test_fn_max(self, val, axis, ref): tensor = self.to_tensor(val) nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) - res = functions.max(nncf_tensor, axis=axis) + res = fns.max(nncf_tensor, axis=axis) assert isinstance(res, Tensor) - assert functions.allclose(res, ref_tensor) - - @pytest.mark.parametrize( - "val, axis, ref", - ( - (1, None, 1), - ([1], None, 1), - ([[[[1], [2]], [[3], [4]]]], None, 4), - ([[1, 2], [3, 4]], 1, [2, 4]), - ), - ) - def test_fn_amax(self, val, axis, ref): - tensor = self.to_tensor(val) - nncf_tensor = Tensor(tensor) - ref_tensor = self.to_tensor(ref) - res = functions.amax(nncf_tensor, axis=axis) - assert isinstance(res, Tensor) - assert functions.allclose(res, ref_tensor) + assert fns.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -259,39 +226,7 @@ def test_min(self, val, axis, ref): ref_tensor = self.to_tensor(ref) res = nncf_tensor.min(axis=axis) assert isinstance(res, Tensor) - assert functions.allclose(res, ref_tensor) - - @pytest.mark.parametrize( - "val, axis, ref", - ( - (1, None, 1), - ([1], None, 1), - ([[[[1], [2]], [[3], [4]]]], None, 1), - ([[1, 2], [3, 4]], 1, [1, 3]), - ), - ) - def test_fn_min(self, val, axis, ref): - nncf_tensor = Tensor(self.to_tensor(val)) - ref_tensor = self.to_tensor(ref) - res = functions.min(nncf_tensor, axis=axis) - assert isinstance(res, Tensor) - assert functions.allclose(res, ref_tensor) - - @pytest.mark.parametrize( - "val, axis, ref", - ( - (1, None, 1), - ([1], None, 1), - ([[[[1], [2]], [[3], [4]]]], None, 1), - ([[1, 2], [3, 4]], 1, [1, 3]), - ), - ) - def test_fn_amin(self, val, axis, ref): - nncf_tensor = Tensor(self.to_tensor(val)) - ref_tensor = self.to_tensor(ref) - res = functions.amin(nncf_tensor, axis=axis) - assert isinstance(res, Tensor) - assert functions.allclose(res, ref_tensor) + assert fns.allclose(res, ref_tensor) @pytest.mark.parametrize( "val, ref", @@ -305,7 +240,7 @@ def test_abs(self, val, ref): nncf_ref_tensor = Tensor(self.to_tensor(ref)) res = nncf_tensor.abs() assert isinstance(res, Tensor) - assert functions.allclose(res, nncf_ref_tensor) + assert fns.allclose(res, nncf_ref_tensor) @pytest.mark.parametrize( "val, ref", @@ -317,9 +252,9 @@ def test_abs(self, val, ref): def test_fn_abs(self, val, ref): nncf_tensor = Tensor(self.to_tensor(val)) nncf_ref_tensor = Tensor(self.to_tensor(ref)) - res = functions.abs(nncf_tensor) + res = fns.abs(nncf_tensor) assert isinstance(res, Tensor) - assert functions.allclose(res, nncf_ref_tensor) + assert fns.allclose(res, nncf_ref_tensor) def test_getitem(self): arr = [0, 1, 2] @@ -350,16 +285,16 @@ def test_fn_count_nonzero(self, axis, ref): tensor = self.to_tensor([[1.0, 2.0], [1.0, 0.0]]) nncf_tensor = Tensor(tensor) ref_tensor = self.to_tensor(ref) - res = functions.count_nonzero(nncf_tensor, axis=axis) + res = fns.count_nonzero(nncf_tensor, axis=axis) assert isinstance(res, Tensor) - assert functions.allclose(res.data, ref_tensor) + assert fns.allclose(res.data, ref_tensor) def test_fn_zeros_like(self): tensor = self.to_tensor([1, 2]) nncf_tensor = Tensor(tensor) - res = functions.zeros_like(nncf_tensor) + res = fns.zeros_like(nncf_tensor) assert all(res == Tensor(tensor * 0)) assert isinstance(res, Tensor) @@ -368,7 +303,7 @@ def test_fn_maximum(self): tensor_b = Tensor(self.to_tensor([2, 1])) tensor_ref = self.to_tensor([2, 2]) - res = functions.maximum(tensor_a, tensor_b) + res = fns.maximum(tensor_a, tensor_b) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) @@ -377,7 +312,7 @@ def test_fn_maximum_list(self): tensor_b = [2, 1] tensor_ref = self.to_tensor([2, 2]) - res = functions.maximum(tensor_a, tensor_b) + res = fns.maximum(tensor_a, tensor_b) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) @@ -386,7 +321,7 @@ def test_fn_minimum(self): tensor_b = Tensor(self.to_tensor([2, 1])) tensor_ref = self.to_tensor([1, 1]) - res = functions.minimum(tensor_a, tensor_b) + res = fns.minimum(tensor_a, tensor_b) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) @@ -395,7 +330,7 @@ def test_fn_minimum_list(self): tensor_b = [2, 1] tensor_ref = self.to_tensor([1, 1]) - res = functions.minimum(tensor_a, tensor_b) + res = fns.minimum(tensor_a, tensor_b) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) @@ -403,7 +338,7 @@ def test_fn_ones_like(self): tensor_a = Tensor(self.to_tensor([1, 2])) tensor_ref = self.to_tensor([1, 1]) - res = functions.ones_like(tensor_a) + res = fns.ones_like(tensor_a) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) @@ -418,9 +353,9 @@ def test_fn_ones_like(self): ) def test_fn_all(self, val, axis, ref): tensor = Tensor(self.to_tensor(val)) - res = functions.all(tensor, axis=axis) + res = fns.all(tensor, axis=axis) assert isinstance(res, Tensor) - assert functions.allclose(res.data, self.to_tensor(ref)) + assert fns.allclose(res.data, self.to_tensor(ref)) @pytest.mark.parametrize( "val, axis, ref", @@ -433,15 +368,15 @@ def test_fn_all(self, val, axis, ref): ) def test_fn_any(self, val, axis, ref): tensor = Tensor(self.to_tensor(val)) - res = functions.any(tensor, axis=axis) + res = fns.any(tensor, axis=axis) assert isinstance(res, Tensor) - assert functions.allclose(res.data, self.to_tensor(ref)) + assert fns.allclose(res.data, self.to_tensor(ref)) def test_fn_where(self): tensor = Tensor(self.to_tensor([1, -1])) tensor_ref = self.to_tensor([1, 0]) - res = functions.where(tensor > 0, 1, 0) + res = fns.where(tensor > 0, 1, 0) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) @@ -455,9 +390,9 @@ def test_fn_where(self): ) def test_fn_isempty(self, val, ref): tensor = Tensor(self.to_tensor(val)) - res = functions.isempty(tensor) + res = fns.isempty(tensor) assert res == ref - assert isinstance(res, Tensor) + assert isinstance(res, bool) @pytest.mark.parametrize( "val, ref", @@ -471,7 +406,7 @@ def test_isempty(self, val, ref): tensor = Tensor(self.to_tensor(val)) res = tensor.isempty() assert res == ref - assert isinstance(res, Tensor) + assert isinstance(res, bool) @pytest.mark.parametrize( "x1, x2, rtol, atol, ref", @@ -487,13 +422,12 @@ def test_fn_allclose(self, x1, x2, rtol, atol, ref): tensor1 = Tensor(self.to_tensor(x1)) tensor2 = Tensor(self.to_tensor(x2)) if rtol is not None: - res = functions.allclose(tensor1, tensor2, rtol=rtol) + res = fns.allclose(tensor1, tensor2, rtol=rtol) elif atol is not None: - res = functions.allclose(tensor1, tensor2, atol=atol) + res = fns.allclose(tensor1, tensor2, atol=atol) else: - res = functions.allclose(tensor1, tensor2) + res = fns.allclose(tensor1, tensor2) assert res == ref - assert isinstance(res, Tensor) @pytest.mark.parametrize( "x1, x2, rtol, atol, ref", @@ -508,11 +442,11 @@ def test_fn_isclose(self, x1, x2, rtol, atol, ref): tensor1 = Tensor(self.to_tensor(x1)) tensor2 = Tensor(self.to_tensor(x2)) if rtol is not None: - res = functions.isclose(tensor1, tensor2, rtol=rtol) + res = fns.isclose(tensor1, tensor2, rtol=rtol) elif atol is not None: - res = functions.isclose(tensor1, tensor2, atol=atol) + res = fns.isclose(tensor1, tensor2, atol=atol) else: - res = functions.isclose(tensor1, tensor2) + res = fns.isclose(tensor1, tensor2) assert all(res == self.to_tensor(ref)) assert isinstance(res, Tensor) @@ -528,7 +462,7 @@ def test_astype(self): def test_fn_astype(self): tensor = Tensor(self.to_tensor([1])) - res = functions.astype(tensor, TensorDataType.int8) + res = fns.astype(tensor, TensorDataType.int8) assert isinstance(res, Tensor) assert res.dtype == TensorDataType.int8 @@ -540,11 +474,11 @@ def test_reshape(self): def test_fn_reshape(self): tensor = Tensor(self.to_tensor([1, 1])) assert tensor.shape == (2,) - assert functions.reshape(tensor, [1, 2]).shape == (1, 2) + assert fns.reshape(tensor, [1, 2]).shape == (1, 2) def test_not_implemented(self): with pytest.raises(NotImplementedError, match="is not implemented for"): - functions.device({}, [1, 2]) + fns.device({}, [1, 2]) @pytest.mark.parametrize( "x, axis, ref", @@ -565,7 +499,7 @@ def test_fn_unstack(self, x, axis, ref): tensor = Tensor(self.to_tensor(x)) ref = [self.to_tensor(r) for r in ref] - res = functions.unstack(tensor, axis=axis) + res = fns.unstack(tensor, axis=axis) assert isinstance(res, list) for i, _ in enumerate(ref): @@ -590,16 +524,16 @@ def test_fn_stack(self, x, axis, ref): tensor = [Tensor(self.to_tensor(i)) for i in x] ref = self.to_tensor(ref) - res = functions.stack(tensor, axis=axis) + res = fns.stack(tensor, axis=axis) assert isinstance(res, Tensor) - assert functions.all(res.data == ref) + assert fns.all(res.data == ref) def test_fn_moveaxis(self): tensor = [[0, 0, 0], [0, 0, 0]] tensor = Tensor(self.to_tensor(tensor)) - res = functions.moveaxis(tensor, 0, -1) + res = fns.moveaxis(tensor, 0, -1) assert res.shape == (3, 2) @@ -636,10 +570,10 @@ def test_fn_mean(self, x, axis, keepdims, ref): tensor = Tensor(self.to_tensor(x)) ref_tensor = self.to_tensor(ref) - res = functions.mean(tensor, axis, keepdims) + res = fns.mean(tensor, axis, keepdims) assert isinstance(res, Tensor) - assert functions.allclose(res.data, ref_tensor) + assert fns.allclose(res.data, ref_tensor) @pytest.mark.parametrize( "val, decimals, ref", @@ -653,10 +587,10 @@ def test_fn_round(self, val, decimals, ref): tensor = Tensor(self.to_tensor(val)) ref_tensor = self.to_tensor(ref) - res = functions.round(tensor, decimals) + res = fns.round(tensor, decimals) assert isinstance(res, Tensor) - assert functions.allclose(res.data, ref_tensor) + assert fns.allclose(res.data, ref_tensor) @pytest.mark.parametrize( "val, axis, ref", @@ -705,6 +639,6 @@ def test_fn_round(self, val, decimals, ref): def test_fn_mean_per_channel(self, val, axis, ref): tensor = Tensor(self.to_tensor(val)) ref_tensor = self.to_tensor(ref) - res = functions.mean_per_channel(tensor, axis) + res = mfns.mean_per_channel(tensor, axis) assert isinstance(res, Tensor) - assert functions.allclose(res, ref_tensor), f"{res.data}" + assert fns.allclose(res, ref_tensor), f"{res.data}" From 5d3ebce2d2686483838637333106c206f751d0e4 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 5 Sep 2023 01:28:42 +0300 Subject: [PATCH 17/45] Disable warning on divide operator of numpy --- nncf/experimental/tensor/functions.py | 30 ++++++++++++++++++++- nncf/experimental/tensor/numpy_functions.py | 20 +++++++++++++- nncf/experimental/tensor/tensor.py | 9 ++++--- nncf/experimental/tensor/torch_functions.py | 12 ++++++++- nncf/quantization/fake_quantize.py | 5 ++-- 5 files changed, 66 insertions(+), 10 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 6e8b03c8727..72a7f937781 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -10,7 +10,7 @@ # limitations under the License. import functools -from typing import List, Optional, Tuple, TypeVar, Union +from typing import Callable, List, Optional, Tuple, TypeVar, Union from nncf.experimental.tensor import Tensor from nncf.experimental.tensor import unwrap_tensor_data @@ -395,6 +395,34 @@ def round(a: TTensor, decimals=0) -> TTensor: # pylint: disable=redefined-built return Tensor(round(a.data, decimals)) +@functools.singledispatch +@_tensor_guard +def binary_operator(a: TTensor, b: TTensor, operator_fn=Callable) -> TTensor: + """ + Applies a binary operation to two tensors with disable warnings. + + :param a: The first tensor. + :param b: The second tensor. + :param operator_fn: The binary operation function. + :return: The result of the binary operation. + """ + return Tensor(binary_operator(a.data, unwrap_tensor_data(b), operator_fn)) + + +@functools.singledispatch +@_tensor_guard +def binary_reverse_operator(a: TTensor, b: TTensor, operator_fn=Callable) -> TTensor: + """ + Applies a binary reverse operation to two tensors with disable warnings. + + :param a: The first tensor. + :param b: The second tensor. + :param operator_fn: The binary operation function. + :return: The result of the binary operation. + """ + return Tensor(binary_reverse_operator(a.data, unwrap_tensor_data(b), operator_fn)) + + __all__ = [ "abs", "all", diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 10b6b8f94cf..07f8c757640 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -9,7 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, Optional, Tuple, Union +from typing import Callable, List, Optional, Tuple, Union import numpy as np @@ -182,3 +182,21 @@ def _(a: Union[np.ndarray, np.number], axis: Union[int, List[int]] = None, keepd @registry_numpy_types(fns.round) def _(a: Union[np.ndarray, np.number], decimals: int = 0) -> np.ndarray: return np.round(a, decimals=decimals) + + +@registry_numpy_types(fns.binary_operator) +def binary_operator( + a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], operator_fn=Callable +) -> Union[np.ndarray, np.number]: + # Run operator with disabled warning + with np.errstate(invalid="ignore", divide="ignore"): + return operator_fn(a, b) + + +@registry_numpy_types(fns.binary_reverse_operator) +def binary_reverse_operator( + a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], operator_fn=Callable +) -> Union[np.ndarray, np.number]: + # Run operator with disabled warning + with np.errstate(invalid="ignore", divide="ignore"): + return operator_fn(b, a) diff --git a/nncf/experimental/tensor/tensor.py b/nncf/experimental/tensor/tensor.py index 3ae0cdc76aa..241f9925e4a 100644 --- a/nncf/experimental/tensor/tensor.py +++ b/nncf/experimental/tensor/tensor.py @@ -10,6 +10,7 @@ # limitations under the License. +import operator from typing import Any, List, Optional, Tuple, TypeVar, Union from nncf.experimental.tensor.enums import TensorDataType @@ -85,16 +86,16 @@ def __pow__(self, other: TTensor) -> "Tensor": return Tensor(self.data ** unwrap_tensor_data(other)) def __truediv__(self, other: TTensor) -> "Tensor": - return Tensor(self.data / unwrap_tensor_data(other)) + return _call_function("binary_operator", self, other, operator.truediv) def __rtruediv__(self, other: TTensor) -> "Tensor": - return Tensor(unwrap_tensor_data(other) / self.data) + return _call_function("binary_reverse_operator", self, other, operator.truediv) def __floordiv__(self, other: TTensor) -> "Tensor": - return Tensor(self.data // unwrap_tensor_data(other)) + return _call_function("binary_operator", self, other, operator.floordiv) def __rfloordiv__(self, other: TTensor) -> "Tensor": - return Tensor(unwrap_tensor_data(other) // self.data) + return _call_function("binary_reverse_operator", self, other, operator.floordiv) def __neg__(self) -> "Tensor": return Tensor(-self.data) diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index e5ec6c7d2ef..dd803260262 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -9,7 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, Optional, Tuple, Union +from typing import Callable, List, Optional, Tuple, Union import torch @@ -175,3 +175,13 @@ def _(a: torch.Tensor, axis: Union[int, List[int]] = None, keepdims: bool = Fals @fns.round.register(torch.Tensor) def _(a: torch.Tensor, decimals=0) -> torch.Tensor: return torch.round(a, decimals=decimals) + + +@fns.binary_operator.register(torch.Tensor) +def _(a: torch.Tensor, b: torch.Tensor, operator_fn=Callable) -> torch.Tensor: + return operator_fn(a, b) + + +@fns.binary_reverse_operator.register(torch.Tensor) +def _(a: torch.Tensor, b: torch.Tensor, operator_fn=Callable) -> torch.Tensor: + return operator_fn(b, a) diff --git a/nncf/quantization/fake_quantize.py b/nncf/quantization/fake_quantize.py index 73a49fea62d..74b61523830 100644 --- a/nncf/quantization/fake_quantize.py +++ b/nncf/quantization/fake_quantize.py @@ -106,9 +106,8 @@ def tune_range( fval = -left_border * s qval = fns.round(fval) - with np.errstate(invalid="ignore", divide="ignore"): # TODO: move to Tensor, to sync with numpy and torch??? - ra = fns.where(qval < level_high, qval / (qval - level_high) * right_border, left_border) - rb = fns.where(qval > 0.0, (qval - level_high) / qval * left_border, right_border) + ra = fns.where(qval < level_high, qval / (qval - level_high) * right_border, left_border) + rb = fns.where(qval > 0.0, (qval - level_high) / qval * left_border, right_border) range_a = right_border - ra range_b = rb - left_border From fe3dea19afbce6bd2449a06ea296d5117218d595 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 5 Sep 2023 03:34:38 +0300 Subject: [PATCH 18/45] fix --- nncf/experimental/tensor/functions.py | 4 ++-- nncf/experimental/tensor/numpy_functions.py | 4 ++-- nncf/experimental/tensor/torch_functions.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 72a7f937781..206c7c8fcb2 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -397,7 +397,7 @@ def round(a: TTensor, decimals=0) -> TTensor: # pylint: disable=redefined-built @functools.singledispatch @_tensor_guard -def binary_operator(a: TTensor, b: TTensor, operator_fn=Callable) -> TTensor: +def binary_operator(a: TTensor, b: TTensor, operator_fn: Callable) -> TTensor: """ Applies a binary operation to two tensors with disable warnings. @@ -411,7 +411,7 @@ def binary_operator(a: TTensor, b: TTensor, operator_fn=Callable) -> TTensor: @functools.singledispatch @_tensor_guard -def binary_reverse_operator(a: TTensor, b: TTensor, operator_fn=Callable) -> TTensor: +def binary_reverse_operator(a: TTensor, b: TTensor, operator_fn: Callable) -> TTensor: """ Applies a binary reverse operation to two tensors with disable warnings. diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 07f8c757640..16b194b0034 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -186,7 +186,7 @@ def _(a: Union[np.ndarray, np.number], decimals: int = 0) -> np.ndarray: @registry_numpy_types(fns.binary_operator) def binary_operator( - a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], operator_fn=Callable + a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], operator_fn: Callable ) -> Union[np.ndarray, np.number]: # Run operator with disabled warning with np.errstate(invalid="ignore", divide="ignore"): @@ -195,7 +195,7 @@ def binary_operator( @registry_numpy_types(fns.binary_reverse_operator) def binary_reverse_operator( - a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], operator_fn=Callable + a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], operator_fn: Callable ) -> Union[np.ndarray, np.number]: # Run operator with disabled warning with np.errstate(invalid="ignore", divide="ignore"): diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index dd803260262..9475489f236 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -178,10 +178,10 @@ def _(a: torch.Tensor, decimals=0) -> torch.Tensor: @fns.binary_operator.register(torch.Tensor) -def _(a: torch.Tensor, b: torch.Tensor, operator_fn=Callable) -> torch.Tensor: +def _(a: torch.Tensor, b: torch.Tensor, operator_fn: Callable) -> torch.Tensor: return operator_fn(a, b) @fns.binary_reverse_operator.register(torch.Tensor) -def _(a: torch.Tensor, b: torch.Tensor, operator_fn=Callable) -> torch.Tensor: +def _(a: torch.Tensor, b: torch.Tensor, operator_fn: Callable) -> torch.Tensor: return operator_fn(b, a) From 2d5567b8a623f51150a4b98bc22b4cb505039570 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 5 Sep 2023 07:19:46 +0300 Subject: [PATCH 19/45] fix name --- nncf/experimental/tensor/numpy_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 16b194b0034..4309ee076d1 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -185,7 +185,7 @@ def _(a: Union[np.ndarray, np.number], decimals: int = 0) -> np.ndarray: @registry_numpy_types(fns.binary_operator) -def binary_operator( +def _( a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], operator_fn: Callable ) -> Union[np.ndarray, np.number]: # Run operator with disabled warning @@ -194,7 +194,7 @@ def binary_operator( @registry_numpy_types(fns.binary_reverse_operator) -def binary_reverse_operator( +def _( a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], operator_fn: Callable ) -> Union[np.ndarray, np.number]: # Run operator with disabled warning From e29d56850d14f62aa93f58cdfd43109ff723cd34 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 5 Sep 2023 16:48:06 +0300 Subject: [PATCH 20/45] statistical_functions.py --- .../tensor_statistics/statistical_functions.py} | 0 .../quantization/algorithms/fast_bias_correction/algorithm.py | 4 ++-- tests/shared/test_templates/template_test_nncf_tensor.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename nncf/experimental/{tensor/math_functions.py => common/tensor_statistics/statistical_functions.py} (100%) diff --git a/nncf/experimental/tensor/math_functions.py b/nncf/experimental/common/tensor_statistics/statistical_functions.py similarity index 100% rename from nncf/experimental/tensor/math_functions.py rename to nncf/experimental/common/tensor_statistics/statistical_functions.py diff --git a/nncf/quantization/algorithms/fast_bias_correction/algorithm.py b/nncf/quantization/algorithms/fast_bias_correction/algorithm.py index cae9c16d992..f6d95cd007b 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/algorithm.py +++ b/nncf/quantization/algorithms/fast_bias_correction/algorithm.py @@ -27,9 +27,9 @@ from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import get_backend +from nncf.experimental.common.tensor_statistics import statistical_functions as s_fns from nncf.experimental.tensor import Tensor from nncf.experimental.tensor import functions as fns -from nncf.experimental.tensor import math_functions as mfns from nncf.quantization.algorithms.algorithm import Algorithm from nncf.quantization.algorithms.fast_bias_correction.backend import ALGO_BACKENDS @@ -319,7 +319,7 @@ def _get_bias_shift( engine = EngineFactory.create(model) raw_output = engine.infer(input_blob) q_outputs = self._backend_entity.process_model_output(raw_output, output_name) - q_outputs = mfns.mean_per_channel(q_outputs, channel_axis) + q_outputs = s_fns.mean_per_channel(q_outputs, channel_axis) bias_shift = fns.stack(output_fp) - q_outputs return bias_shift diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index 7eed20f2222..f159d9be351 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -17,11 +17,11 @@ import pytest +from nncf.experimental.common.tensor_statistics import statistical_functions as s_fns from nncf.experimental.tensor import Tensor from nncf.experimental.tensor import TensorDataType from nncf.experimental.tensor import TensorDeviceType from nncf.experimental.tensor import functions as fns -from nncf.experimental.tensor import math_functions as mfns TModel = TypeVar("TModel") TTensor = TypeVar("TTensor") @@ -639,6 +639,6 @@ def test_fn_round(self, val, decimals, ref): def test_fn_mean_per_channel(self, val, axis, ref): tensor = Tensor(self.to_tensor(val)) ref_tensor = self.to_tensor(ref) - res = mfns.mean_per_channel(tensor, axis) + res = s_fns.mean_per_channel(tensor, axis) assert isinstance(res, Tensor) assert fns.allclose(res, ref_tensor), f"{res.data}" From 4816448a29d15fc46ce4387d1c3de8851fcbc4cb Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Fri, 8 Sep 2023 00:54:05 +0300 Subject: [PATCH 21/45] fbc remote tensor processor --- .../algorithms/fast_bias_correction/backend.py | 7 ------- .../algorithms/fast_bias_correction/onnx_backend.py | 10 ++-------- .../fast_bias_correction/openvino_backend.py | 10 ++-------- .../algorithms/fast_bias_correction/torch_backend.py | 8 +------- 4 files changed, 5 insertions(+), 30 deletions(-) diff --git a/nncf/quantization/algorithms/fast_bias_correction/backend.py b/nncf/quantization/algorithms/fast_bias_correction/backend.py index 251c7c4c3af..8b22d2fecf8 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/backend.py @@ -32,13 +32,6 @@ class FastBiasCorrectionAlgoBackend(ABC): - @property - @abstractmethod - def tensor_processor(self): - """ - Returns backend-specific instance of the NNCFCollectorTensorProcessor. - """ - @staticmethod @abstractmethod def target_point(target_type: TargetType, target_node_name: str, port_id: int) -> TargetPoint: diff --git a/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py b/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py index 79e02ee0ab8..86a827d07a0 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py @@ -29,8 +29,6 @@ from nncf.onnx.graph.transformations.commands import ONNXNullBiasInsertionCommand from nncf.onnx.graph.transformations.commands import ONNXTargetPoint from nncf.onnx.statistics.collectors import ONNXMeanStatisticCollector -from nncf.onnx.statistics.collectors import ONNXNNCFCollectorTensorProcessor -from nncf.onnx.tensor import ONNXNNCFTensor from nncf.quantization.algorithms.fast_bias_correction.backend import ALGO_BACKENDS from nncf.quantization.algorithms.fast_bias_correction.backend import FastBiasCorrectionAlgoBackend @@ -41,10 +39,6 @@ class ONNXFastBiasCorrectionAlgoBackend(FastBiasCorrectionAlgoBackend): def types_to_insert_bias(self): return [] - @property - def tensor_processor(self) -> ONNXNNCFCollectorTensorProcessor: - return ONNXNNCFCollectorTensorProcessor() - @staticmethod def target_point(target_type: TargetType, target_node_name: str, port_id: int) -> ONNXTargetPoint: return ONNXTargetPoint(target_type, target_node_name, port_id) @@ -88,7 +82,7 @@ def create_input_data( return input_data @staticmethod - def get_bias_value(node: NNCFNode, nncf_graph: NNCFGraph, model: onnx.ModelProto) -> np.ndarray: + def get_bias_value(node: NNCFNode, nncf_graph: NNCFGraph, model: onnx.ModelProto) -> Tensor: return Tensor(get_bias_value(node, model)) @staticmethod @@ -96,7 +90,7 @@ def get_activation_port_ids_for_bias_node(node: NNCFNode) -> Tuple[int, int]: return 0, 0 @staticmethod - def process_model_output(raw_data: Dict, output_name: str) -> ONNXNNCFTensor: + def process_model_output(raw_data: Dict, output_name: str) -> Tensor: return Tensor(raw_data[output_name]) @staticmethod diff --git a/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py b/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py index 5e02222ee42..e8cb8bec6ba 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py @@ -28,19 +28,13 @@ from nncf.openvino.graph.transformations.commands import OVBiasCorrectionCommand from nncf.openvino.graph.transformations.commands import OVModelExtractionCommand from nncf.openvino.graph.transformations.commands import OVTargetPoint -from nncf.openvino.statistics.collectors import OVNNCFCollectorTensorProcessor from nncf.openvino.statistics.collectors import get_mean_stat_collector -from nncf.openvino.tensor import OVNNCFTensor from nncf.quantization.algorithms.fast_bias_correction.backend import ALGO_BACKENDS from nncf.quantization.algorithms.fast_bias_correction.backend import FastBiasCorrectionAlgoBackend @ALGO_BACKENDS.register(BackendType.OPENVINO) class OVFastBiasCorrectionAlgoBackend(FastBiasCorrectionAlgoBackend): - @property - def tensor_processor(self) -> OVNNCFCollectorTensorProcessor: - return OVNNCFCollectorTensorProcessor - @staticmethod def target_point(target_type: TargetType, target_node_name: str, port_id: int) -> OVTargetPoint: return OVTargetPoint(target_type, target_node_name, port_id) @@ -80,7 +74,7 @@ def create_input_data( return input_data @staticmethod - def get_bias_value(node: NNCFNode, nncf_graph: NNCFGraph, model: ov.Model) -> np.ndarray: + def get_bias_value(node: NNCFNode, nncf_graph: NNCFGraph, model: ov.Model) -> Tensor: return Tensor(get_bias_value(node, nncf_graph, model)) @staticmethod @@ -98,7 +92,7 @@ def is_quantized_weights(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: return weight_node.metatype in FAKE_QUANTIZE_OPERATIONS @staticmethod - def process_model_output(raw_data: Dict, output_name: str) -> OVNNCFTensor: + def process_model_output(raw_data: Dict, output_name: str) -> Tensor: return Tensor(raw_data[output_name]) @staticmethod diff --git a/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py b/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py index cde232ad795..16fd42e1fce 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py @@ -32,9 +32,7 @@ from nncf.torch.model_analyzer import is_node_with_fused_bias from nncf.torch.model_analyzer import is_quantized_weights from nncf.torch.nncf_network import NNCFNetwork -from nncf.torch.tensor import PTNNCFTensor from nncf.torch.tensor_statistics.collectors import PTMeanStatisticCollector -from nncf.torch.tensor_statistics.collectors import PTNNCFCollectorTensorProcessor @ALGO_BACKENDS.register(BackendType.TORCH) @@ -44,10 +42,6 @@ class PTFastBiasCorrectionAlgoBackend(FastBiasCorrectionAlgoBackend): TargetType.POST_LAYER_OPERATION: TargetType.OPERATOR_POST_HOOK, } - @property - def tensor_processor(self) -> PTNNCFCollectorTensorProcessor: - return PTNNCFCollectorTensorProcessor() - @staticmethod def target_point(target_type: TargetType, target_node_name: str, port_id: int) -> PTTargetPoint: if NNCFGraphNodeType.INPUT_NODE in target_node_name or target_type == TargetType.POST_LAYER_OPERATION: @@ -97,7 +91,7 @@ def get_activation_port_ids_for_bias_node(node: NNCFNode) -> Tuple[int, int]: return 0, 0 @staticmethod - def process_model_output(raw_data: Dict, output_name: str) -> PTNNCFTensor: + def process_model_output(raw_data: Dict, output_name: str) -> Tensor: return Tensor(raw_data) @staticmethod From 9f1fe47dd5a7e86ad5b6cff2270219b07a2e2323 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Fri, 8 Sep 2023 01:07:25 +0300 Subject: [PATCH 22/45] del __all__ --- nncf/experimental/tensor/README.md | 6 ++---- nncf/experimental/tensor/functions.py | 27 --------------------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/nncf/experimental/tensor/README.md b/nncf/experimental/tensor/README.md index b94e6488010..d831d55657a 100644 --- a/nncf/experimental/tensor/README.md +++ b/nncf/experimental/tensor/README.md @@ -120,9 +120,7 @@ tensor_a[0:2] # Tensor(array([[1],[2]])) return NotImplemented(f"Function `foo` is not implemented for {type(a)}") ``` -3. Add function name to `__all__` in [function.py](function.py) - -4. Add backend specific implementation of method to: +3. Add backend specific implementation of method to: - [numpy_function.py](numpy_function.py) @@ -140,7 +138,7 @@ tensor_a[0:2] # Tensor(array([[1],[2]])) return torch.foo(a, arg1) ``` -5. Add test of method to [test template](tests/shared/test_templates/template_test_nncf_tensor.py) for Tensor class +4. Add test of method to [test template](tests/shared/test_templates/template_test_nncf_tensor.py) for Tensor class ### Add new backend diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 206c7c8fcb2..4c0bae3c1ff 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -423,33 +423,6 @@ def binary_reverse_operator(a: TTensor, b: TTensor, operator_fn: Callable) -> TT return Tensor(binary_reverse_operator(a.data, unwrap_tensor_data(b), operator_fn)) -__all__ = [ - "abs", - "all", - "allclose", - "any", - "astype", - "count_nonzero", - "device", - "flatten", - "isclose", - "isempty", - "max", - "maximum", - "mean", - "min", - "minimum", - "minimum", - "moveaxis", - "ones_like", - "reshape", - "round", - "squeeze", - "where", - "zeros_like", -] - - def _initialize_backends(): # pylint: disable=unused-import import nncf.experimental.tensor.numpy_functions From 1fde2399886c838cbdd166a5a514ce81e258a52f Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 12 Sep 2023 04:23:03 +0300 Subject: [PATCH 23/45] allclose --- nncf/experimental/tensor/functions.py | 16 +++++++++------- nncf/experimental/tensor/numpy_functions.py | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 4c0bae3c1ff..83883ad208f 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -165,7 +165,7 @@ def all(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: @functools.singledispatch @_tensor_guard -def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> bool: +def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> TTensor: """ Returns True if two arrays are element-wise equal within a tolerance. @@ -178,12 +178,14 @@ def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, e Defaults to False. :return: True if the two arrays are equal within the given tolerance, otherwise False. """ - return allclose( - a.data, - unwrap_tensor_data(b), - rtol=rtol, - atol=atol, - equal_nan=equal_nan, + return Tensor( + allclose( + a.data, + unwrap_tensor_data(b), + rtol=rtol, + atol=atol, + equal_nan=equal_nan, + ) ) diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 4309ee076d1..f436ca0514a 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -126,7 +126,7 @@ def _( rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False, -): +) -> Union[np.ndarray, bool]: return np.isclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) From 93eef069151ba7cc31c3a3fb97287ccfbb2cc4ae Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Fri, 22 Sep 2023 04:58:55 +0300 Subject: [PATCH 24/45] use tensor.functions in tests --- nncf/experimental/tensor/torch_functions.py | 4 ++++ .../test_templates/template_test_nncf_tensor.py | 2 ++ .../ptq/test_calculation_quantizer_params.py | 15 ++++++++------- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 9475489f236..10ad8c567f3 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -94,6 +94,8 @@ def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Union[t @fns.allclose.register(torch.Tensor) def _(a: torch.Tensor, b: torch.Tensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> bool: + if not isinstance(b, torch.Tensor): + b = torch.tensor(b, device=a.device) return torch.allclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) @@ -116,6 +118,8 @@ def _(a: torch.Tensor) -> bool: @fns.isclose.register(torch.Tensor) def _(a: torch.Tensor, b: torch.Tensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False): + if not isinstance(b, torch.Tensor): + b = torch.tensor(b, device=a.device) return torch.isclose(a, b, atol=atol, rtol=rtol, equal_nan=equal_nan) diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index f159d9be351..2212daf4081 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -416,6 +416,7 @@ def test_isempty(self, val, ref): ([0.1], [0.10001], 0.1, None, True), ([0.1], [0.10001], None, 0.1, True), ([0.1], [0.20001], None, 0.1, False), + ([0.1], 0.1, None, None, True), ), ) def test_fn_allclose(self, x1, x2, rtol, atol, ref): @@ -436,6 +437,7 @@ def test_fn_allclose(self, x1, x2, rtol, atol, ref): ([0.1], [0.10001], None, None, [False]), ([0.1], [0.10001], 0.1, None, [True]), ([0.1], [0.10001], None, 0.1, [True]), + ([0.1], 0.1, None, None, [True]), ), ) def test_fn_isclose(self, x1, x2, rtol, atol, ref): diff --git a/tests/torch/ptq/test_calculation_quantizer_params.py b/tests/torch/ptq/test_calculation_quantizer_params.py index 887bc4cd8a2..1412da43cfd 100644 --- a/tests/torch/ptq/test_calculation_quantizer_params.py +++ b/tests/torch/ptq/test_calculation_quantizer_params.py @@ -13,11 +13,11 @@ from pathlib import Path import numpy as np -import onnx import pytest import torch from torch import nn +import onnx from nncf import Dataset from nncf import NNCFConfig from nncf.common.graph.transformations.commands import TargetType @@ -26,6 +26,7 @@ from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup from nncf.experimental.tensor import Tensor +from nncf.experimental.tensor import functions as fn from nncf.quantization.algorithms.min_max.algorithm import MinMaxQuantization from nncf.quantization.algorithms.min_max.torch_backend import PTMinMaxAlgoBackend from nncf.quantization.fake_quantize import FakeQuantizeParameters @@ -105,7 +106,7 @@ class CaseSymParams: @pytest.mark.parametrize("case_to_test", SYM_CASES) -def test_quantizer_params_sym(case_to_test): +def test_quantizer_params_sym(case_to_test: CaseSymParams): per_ch = case_to_test.per_channel fq_params = case_to_test.fq_params quant_group = case_to_test.quant_group @@ -195,7 +196,7 @@ class CaseAsymParams: @pytest.mark.parametrize("case_to_test", ASYM_CASES) -def test_quantizer_params_asym(case_to_test): +def test_quantizer_params_asym(case_to_test: CaseSymParams): per_ch = case_to_test.per_channel fq_params = case_to_test.fq_params quant_group = case_to_test.quant_group @@ -213,8 +214,8 @@ def test_quantizer_params_asym(case_to_test): ) quantizer = PTMinMaxAlgoBackend._create_quantizer(qconfig, scale_shape, fq_params, target_type) assert quantizer.levels == fq_params.levels - assert np.allclose(quantizer.input_low.detach().numpy(), case_to_test.ref_inp_low) - assert np.allclose(quantizer.input_range.detach().numpy(), case_to_test.ref_inp_range) + assert fn.allclose(quantizer.input_low.data, case_to_test.ref_inp_low) + assert fn.allclose(quantizer.input_range.data, case_to_test.ref_inp_range) class LinearTestModel(nn.Module): @@ -342,8 +343,8 @@ def test_quantizer_parameters_export(tmp_path: Path): for name, param in fq_params.items(): assert name in torch_ptq_params - assert np.allclose(param["input_low"].data.numpy(), torch_ptq_params[name]["input_low"]) - assert np.allclose(param["input_high"].data.numpy(), torch_ptq_params[name]["input_high"]) + assert fn.allclose(param["input_low"], torch_ptq_params[name]["input_low"]) + assert fn.allclose(param["input_high"], torch_ptq_params[name]["input_high"]) class TestFQParams(TemplateTestFQParams): From 55a0d8652e266eb49f1cf60dfb217835d1a28d0e Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 26 Sep 2023 16:20:06 +0300 Subject: [PATCH 25/45] - --- nncf/experimental/tensor/README.md | 6 +- nncf/experimental/tensor/functions.py | 10 +- nncf/experimental/tensor/numpy_functions.py | 121 ++++++++++---------- nncf/experimental/tensor/tensor.py | 8 +- nncf/experimental/tensor/torch_functions.py | 4 +- 5 files changed, 75 insertions(+), 74 deletions(-) diff --git a/nncf/experimental/tensor/README.md b/nncf/experimental/tensor/README.md index d831d55657a..1eee526dfeb 100644 --- a/nncf/experimental/tensor/README.md +++ b/nncf/experimental/tensor/README.md @@ -6,7 +6,7 @@ making them more portable and reusable. ## Usage -The main idea is common algorithms should use wrapped tensors and provide to backend-specific function unwrapped tensor. +Common algorithms should use wrapped tensors and provide the unwrapped tensor to the backend-specific function. ### Initialization Tensor @@ -32,6 +32,8 @@ tenor_b = Tensor(np.array([1,2])) tensor_a + tenor_b # Tensor(array([2, 4])) ``` +**NOTE** Division operations for the numpy backend are performed with warnings disabled for the same for all backends. + ### Comparison operators All math operations are overrided to operated with wrapped object and return `Tensor` @@ -125,7 +127,7 @@ tensor_a[0:2] # Tensor(array([[1],[2]])) - [numpy_function.py](numpy_function.py) ```python - @registry_numpy_types(fns.foo) + @_register_numpy_types(fns.foo) def _(a: TType, arg1: Type) -> np.ndarray: return np.foo(a, arg1) ``` diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 83883ad208f..8697966b645 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -343,7 +343,7 @@ def stack(x: List[TTensor], axis: int = 0) -> TTensor: @functools.singledispatch @_tensor_guard -def unstack(a: TTensor, axis: int = 0) -> List[Tensor]: +def unstack(a: TTensor, axis: int = 0) -> List[TTensor]: """ Unstack a Tensor into list. @@ -399,7 +399,7 @@ def round(a: TTensor, decimals=0) -> TTensor: # pylint: disable=redefined-built @functools.singledispatch @_tensor_guard -def binary_operator(a: TTensor, b: TTensor, operator_fn: Callable) -> TTensor: +def binary_op_nowarn(a: TTensor, b: TTensor, operator_fn: Callable) -> TTensor: """ Applies a binary operation to two tensors with disable warnings. @@ -408,12 +408,12 @@ def binary_operator(a: TTensor, b: TTensor, operator_fn: Callable) -> TTensor: :param operator_fn: The binary operation function. :return: The result of the binary operation. """ - return Tensor(binary_operator(a.data, unwrap_tensor_data(b), operator_fn)) + return Tensor(binary_op_nowarn(a.data, unwrap_tensor_data(b), operator_fn)) @functools.singledispatch @_tensor_guard -def binary_reverse_operator(a: TTensor, b: TTensor, operator_fn: Callable) -> TTensor: +def binary_reverse_op_nowarn(a: TTensor, b: TTensor, operator_fn: Callable) -> TTensor: """ Applies a binary reverse operation to two tensors with disable warnings. @@ -422,7 +422,7 @@ def binary_reverse_operator(a: TTensor, b: TTensor, operator_fn: Callable) -> TT :param operator_fn: The binary operation function. :return: The result of the binary operation. """ - return Tensor(binary_reverse_operator(a.data, unwrap_tensor_data(b), operator_fn)) + return Tensor(binary_reverse_op_nowarn(a.data, unwrap_tensor_data(b), operator_fn)) def _initialize_backends(): diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index f436ca0514a..9959ab5dcd4 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -28,7 +28,7 @@ DTYPE_MAP_REV = {v: k for k, v in DTYPE_MAP.items()} -def registry_numpy_types(singledispatch_fn): +def _register_numpy_types(singledispatch_fn): """ Decorator to register function to singledispatch for numpy classes. @@ -43,86 +43,89 @@ def inner(func): return inner -@registry_numpy_types(fns.device) -def _(a: Union[np.ndarray, np.number]) -> TensorDeviceType: +NUMPY_TYPES = Union[np.ndarray, np.generic] + + +@_register_numpy_types(fns.device) +def _(a: NUMPY_TYPES) -> TensorDeviceType: return TensorDeviceType.CPU -@registry_numpy_types(fns.squeeze) -def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: +@_register_numpy_types(fns.squeeze) +def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> NUMPY_TYPES: return np.squeeze(a, axis=axis) -@registry_numpy_types(fns.flatten) -def _(a: Union[np.ndarray, np.number]) -> np.ndarray: +@_register_numpy_types(fns.flatten) +def _(a: NUMPY_TYPES) -> np.ndarray: return a.flatten() -@registry_numpy_types(fns.max) -def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: +@_register_numpy_types(fns.max) +def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.max(a, axis=axis) -@registry_numpy_types(fns.min) -def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: +@_register_numpy_types(fns.min) +def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> NUMPY_TYPES: return np.min(a, axis=axis) -@registry_numpy_types(fns.abs) -def _(a: Union[np.ndarray, np.number]) -> np.ndarray: +@_register_numpy_types(fns.abs) +def _(a: NUMPY_TYPES) -> NUMPY_TYPES: return np.absolute(a) -@registry_numpy_types(fns.astype) -def _(a: Union[np.ndarray, np.number], dtype: TensorDataType) -> np.ndarray: +@_register_numpy_types(fns.astype) +def _(a: NUMPY_TYPES, dtype: TensorDataType) -> NUMPY_TYPES: return a.astype(DTYPE_MAP[dtype]) -@registry_numpy_types(fns.dtype) -def _(a: Union[np.ndarray, np.number]) -> TensorDataType: +@_register_numpy_types(fns.dtype) +def _(a: NUMPY_TYPES) -> TensorDataType: return DTYPE_MAP_REV[np.dtype(a.dtype)] -@registry_numpy_types(fns.reshape) -def _(a: Union[np.ndarray, np.number], shape: Union[int, Tuple[int]]) -> np.ndarray: +@_register_numpy_types(fns.reshape) +def _(a: NUMPY_TYPES, shape: Union[int, Tuple[int]]) -> np.ndarray: return a.reshape(shape) -@registry_numpy_types(fns.all) -def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: +@_register_numpy_types(fns.all) +def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: return np.all(a, axis=axis) -@registry_numpy_types(fns.allclose) +@_register_numpy_types(fns.allclose) def _( - a: Union[np.ndarray, np.number], - b: Union[np.ndarray, np.number], + a: NUMPY_TYPES, + b: NUMPY_TYPES, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False, -) -> bool: +) -> np.ndarray: return np.allclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) -@registry_numpy_types(fns.any) -def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: +@_register_numpy_types(fns.any) +def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: return np.any(a, axis=axis) -@registry_numpy_types(fns.count_nonzero) -def _(a: Union[np.ndarray, np.number], axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: +@_register_numpy_types(fns.count_nonzero) +def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: return np.array(np.count_nonzero(a, axis=axis)) -@registry_numpy_types(fns.isempty) -def _(a: Union[np.ndarray, np.number]) -> bool: +@_register_numpy_types(fns.isempty) +def _(a: NUMPY_TYPES) -> bool: return a.size == 0 -@registry_numpy_types(fns.isclose) +@_register_numpy_types(fns.isclose) def _( - a: Union[np.ndarray, np.number], - b: np.ndarray, + a: NUMPY_TYPES, + b: NUMPY_TYPES, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False, @@ -130,73 +133,69 @@ def _( return np.isclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) -@registry_numpy_types(fns.maximum) -def _(x1: Union[np.ndarray, np.number], x2: np.ndarray) -> np.ndarray: +@_register_numpy_types(fns.maximum) +def _(x1: NUMPY_TYPES, x2: NUMPY_TYPES) -> np.ndarray: return np.maximum(x1, x2) -@registry_numpy_types(fns.minimum) -def _(x1: Union[np.ndarray, np.number], x2: np.ndarray) -> np.ndarray: +@_register_numpy_types(fns.minimum) +def _(x1: NUMPY_TYPES, x2: NUMPY_TYPES) -> np.ndarray: return np.minimum(x1, x2) -@registry_numpy_types(fns.ones_like) -def _(a: Union[np.ndarray, np.number]) -> np.ndarray: +@_register_numpy_types(fns.ones_like) +def _(a: NUMPY_TYPES) -> np.ndarray: return np.ones_like(a) -@registry_numpy_types(fns.where) +@_register_numpy_types(fns.where) def _( - condition: Union[np.ndarray, np.number], + condition: NUMPY_TYPES, x: Union[np.ndarray, np.number, float, bool], y: Union[np.ndarray, float, bool], ) -> np.ndarray: return np.where(condition, x, y) -@registry_numpy_types(fns.zeros_like) -def _(a: Union[np.ndarray, np.number]) -> np.ndarray: +@_register_numpy_types(fns.zeros_like) +def _(a: NUMPY_TYPES) -> np.ndarray: return np.zeros_like(a) -@registry_numpy_types(fns.stack) -def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: +@_register_numpy_types(fns.stack) +def _(x: NUMPY_TYPES, axis: int = 0) -> List[np.ndarray]: return np.stack(x, axis=axis) -@registry_numpy_types(fns.unstack) -def _(x: Union[np.ndarray, np.number], axis: int = 0) -> List[np.ndarray]: +@_register_numpy_types(fns.unstack) +def _(x: NUMPY_TYPES, axis: int = 0) -> List[np.ndarray]: return [np.squeeze(e, axis) for e in np.split(x, x.shape[axis], axis=axis)] -@registry_numpy_types(fns.moveaxis) +@_register_numpy_types(fns.moveaxis) def _(a: np.ndarray, source: Union[int, List[int]], destination: Union[int, List[int]]) -> np.ndarray: return np.moveaxis(a, source, destination) -@registry_numpy_types(fns.mean) -def _(a: Union[np.ndarray, np.number], axis: Union[int, List[int]] = None, keepdims: bool = False) -> np.ndarray: +@_register_numpy_types(fns.mean) +def _(a: NUMPY_TYPES, axis: Union[int, List[int]] = None, keepdims: bool = False) -> np.ndarray: return np.mean(a, axis=axis, keepdims=keepdims) -@registry_numpy_types(fns.round) -def _(a: Union[np.ndarray, np.number], decimals: int = 0) -> np.ndarray: +@_register_numpy_types(fns.round) +def _(a: NUMPY_TYPES, decimals: int = 0) -> np.ndarray: return np.round(a, decimals=decimals) -@registry_numpy_types(fns.binary_operator) -def _( - a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], operator_fn: Callable -) -> Union[np.ndarray, np.number]: +@_register_numpy_types(fns.binary_op_nowarn) +def _(a: NUMPY_TYPES, b: NUMPY_TYPES, operator_fn: Callable) -> NUMPY_TYPES: # Run operator with disabled warning with np.errstate(invalid="ignore", divide="ignore"): return operator_fn(a, b) -@registry_numpy_types(fns.binary_reverse_operator) -def _( - a: Union[np.ndarray, np.number], b: Union[np.ndarray, np.number], operator_fn: Callable -) -> Union[np.ndarray, np.number]: +@_register_numpy_types(fns.binary_reverse_op_nowarn) +def _(a: NUMPY_TYPES, b: NUMPY_TYPES, operator_fn: Callable) -> NUMPY_TYPES: # Run operator with disabled warning with np.errstate(invalid="ignore", divide="ignore"): return operator_fn(b, a) diff --git a/nncf/experimental/tensor/tensor.py b/nncf/experimental/tensor/tensor.py index 241f9925e4a..8e9023de66e 100644 --- a/nncf/experimental/tensor/tensor.py +++ b/nncf/experimental/tensor/tensor.py @@ -86,16 +86,16 @@ def __pow__(self, other: TTensor) -> "Tensor": return Tensor(self.data ** unwrap_tensor_data(other)) def __truediv__(self, other: TTensor) -> "Tensor": - return _call_function("binary_operator", self, other, operator.truediv) + return _call_function("_binary_op_nowarn", self, other, operator.truediv) def __rtruediv__(self, other: TTensor) -> "Tensor": - return _call_function("binary_reverse_operator", self, other, operator.truediv) + return _call_function("_binary_reverse_op_nowarn", self, other, operator.truediv) def __floordiv__(self, other: TTensor) -> "Tensor": - return _call_function("binary_operator", self, other, operator.floordiv) + return _call_function("_binary_op_nowarn", self, other, operator.floordiv) def __rfloordiv__(self, other: TTensor) -> "Tensor": - return _call_function("binary_reverse_operator", self, other, operator.floordiv) + return _call_function("_binary_reverse_op_nowarn", self, other, operator.floordiv) def __neg__(self) -> "Tensor": return Tensor(-self.data) diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 10ad8c567f3..0bd7b64a73a 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -181,11 +181,11 @@ def _(a: torch.Tensor, decimals=0) -> torch.Tensor: return torch.round(a, decimals=decimals) -@fns.binary_operator.register(torch.Tensor) +@fns.binary_op_nowarn.register(torch.Tensor) def _(a: torch.Tensor, b: torch.Tensor, operator_fn: Callable) -> torch.Tensor: return operator_fn(a, b) -@fns.binary_reverse_operator.register(torch.Tensor) +@fns.binary_reverse_op_nowarn.register(torch.Tensor) def _(a: torch.Tensor, b: torch.Tensor, operator_fn: Callable) -> torch.Tensor: return operator_fn(b, a) From cc8d0ed31db89e9b31b6b792a833e1cbb78e84a8 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 26 Sep 2023 16:47:11 +0300 Subject: [PATCH 26/45] typehints --- nncf/experimental/tensor/functions.py | 54 ++++++++++++------------ nncf/experimental/tensor/tensor.py | 60 +++++++++++++-------------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 8697966b645..7bc5e083d1b 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -36,7 +36,7 @@ def wrapper(*args, **kwargs): @functools.singledispatch @_tensor_guard -def device(a: TTensor) -> TensorDeviceType: +def device(a: Tensor) -> TensorDeviceType: """ Return the device of the tensor. @@ -48,7 +48,7 @@ def device(a: TTensor) -> TensorDeviceType: @functools.singledispatch @_tensor_guard -def squeeze(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: +def squeeze(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: """ Remove axes of length one from a. @@ -63,7 +63,7 @@ def squeeze(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTenso @functools.singledispatch @_tensor_guard -def flatten(a: TTensor) -> TTensor: +def flatten(a: Tensor) -> Tensor: """ Return a copy of the tensor collapsed into one dimension. @@ -75,7 +75,7 @@ def flatten(a: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def max(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin +def max(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Return the maximum of an array or maximum along an axis. @@ -88,7 +88,7 @@ def max(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: @functools.singledispatch @_tensor_guard -def min(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin +def min(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Return the minimum of an array or minimum along an axis. @@ -101,7 +101,7 @@ def min(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: @functools.singledispatch @_tensor_guard -def abs(a: TTensor) -> Tensor: # pylint: disable=redefined-builtin +def abs(a: Tensor) -> Tensor: # pylint: disable=redefined-builtin """ Calculate the absolute value element-wise. @@ -113,7 +113,7 @@ def abs(a: TTensor) -> Tensor: # pylint: disable=redefined-builtin @functools.singledispatch @_tensor_guard -def astype(a: TTensor, data_type: TensorDataType) -> TTensor: +def astype(a: Tensor, data_type: TensorDataType) -> Tensor: """ Copy of the tensor, cast to a specified type. @@ -127,7 +127,7 @@ def astype(a: TTensor, data_type: TensorDataType) -> TTensor: @functools.singledispatch @_tensor_guard -def dtype(a: TTensor) -> TensorDataType: +def dtype(a: Tensor) -> TensorDataType: """ Return data type of the tensor. @@ -139,7 +139,7 @@ def dtype(a: TTensor) -> TensorDataType: @functools.singledispatch @_tensor_guard -def reshape(a: TTensor, shape: List[int]) -> TTensor: +def reshape(a: Tensor, shape: List[int]) -> Tensor: """ Gives a new shape to a tensor without changing its data. @@ -152,7 +152,7 @@ def reshape(a: TTensor, shape: List[int]) -> TTensor: @functools.singledispatch @_tensor_guard -def all(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin +def all(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Test whether all tensor elements along a given axis evaluate to True. @@ -165,7 +165,7 @@ def all(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: @functools.singledispatch @_tensor_guard -def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> TTensor: +def allclose(a: Tensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> Tensor: """ Returns True if two arrays are element-wise equal within a tolerance. @@ -191,7 +191,7 @@ def allclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, e @functools.singledispatch @_tensor_guard -def any(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: # pylint: disable=redefined-builtin +def any(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Test whether any tensor elements along a given axis evaluate to True. @@ -204,7 +204,7 @@ def any(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: @functools.singledispatch @_tensor_guard -def count_nonzero(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> TTensor: +def count_nonzero(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: """ Counts the number of non-zero values in the tensor input. @@ -218,7 +218,7 @@ def count_nonzero(a: TTensor, axis: Optional[Union[int, Tuple[int]]] = None) -> @functools.singledispatch @_tensor_guard -def isempty(a: TTensor) -> bool: +def isempty(a: Tensor) -> bool: """ Return True if input tensor is empty. @@ -230,7 +230,7 @@ def isempty(a: TTensor) -> bool: @functools.singledispatch @_tensor_guard -def isclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> TTensor: +def isclose(a: Tensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> Tensor: """ Returns a boolean array where two arrays are element-wise equal within a tolerance. @@ -256,7 +256,7 @@ def isclose(a: TTensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, eq @functools.singledispatch @_tensor_guard -def maximum(x1: TTensor, x2: TTensor) -> TTensor: +def maximum(x1: Tensor, x2: TTensor) -> Tensor: """ Element-wise maximum of tensor elements. @@ -269,7 +269,7 @@ def maximum(x1: TTensor, x2: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def minimum(x1: TTensor, x2: TTensor) -> TTensor: +def minimum(x1: Tensor, x2: TTensor) -> Tensor: """ Element-wise minimum of tensor elements. @@ -282,7 +282,7 @@ def minimum(x1: TTensor, x2: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def ones_like(a: TTensor) -> TTensor: +def ones_like(a: Tensor) -> Tensor: """ Return a tensor of ones with the same shape and type as a given tensor. @@ -294,7 +294,7 @@ def ones_like(a: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def where(condition: TTensor, x: TTensor, y: TTensor) -> TTensor: +def where(condition: Tensor, x: TTensor, y: TTensor) -> Tensor: """ Return elements chosen from x or y depending on condition. @@ -314,7 +314,7 @@ def where(condition: TTensor, x: TTensor, y: TTensor) -> TTensor: @functools.singledispatch @_tensor_guard -def zeros_like(a: TTensor) -> TTensor: +def zeros_like(a: Tensor) -> Tensor: """ Return an tensor of zeros with the same shape and type as a given tensor. @@ -325,7 +325,7 @@ def zeros_like(a: TTensor) -> TTensor: @functools.singledispatch -def stack(x: List[TTensor], axis: int = 0) -> TTensor: +def stack(x: List[Tensor], axis: int = 0) -> Tensor: """ Stacks a list or deque of Tensors rank-R tensors into one Tensor rank-(R+1) tensor. @@ -343,7 +343,7 @@ def stack(x: List[TTensor], axis: int = 0) -> TTensor: @functools.singledispatch @_tensor_guard -def unstack(a: TTensor, axis: int = 0) -> List[TTensor]: +def unstack(a: Tensor, axis: int = 0) -> List[TTensor]: """ Unstack a Tensor into list. @@ -357,7 +357,7 @@ def unstack(a: TTensor, axis: int = 0) -> List[TTensor]: @functools.singledispatch @_tensor_guard -def moveaxis(a: TTensor, source: Union[int, List[int]], destination: Union[int, List[int]]) -> TTensor: +def moveaxis(a: Tensor, source: Union[int, List[int]], destination: Union[int, List[int]]) -> Tensor: """ Move axes of an array to new positions. @@ -371,7 +371,7 @@ def moveaxis(a: TTensor, source: Union[int, List[int]], destination: Union[int, @functools.singledispatch @_tensor_guard -def mean(a: TTensor, axis: Union[int, List[int]] = None, keepdims: bool = False) -> TTensor: +def mean(a: Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) -> Tensor: """ Compute the arithmetic mean along the specified axis. @@ -385,7 +385,7 @@ def mean(a: TTensor, axis: Union[int, List[int]] = None, keepdims: bool = False) @functools.singledispatch @_tensor_guard -def round(a: TTensor, decimals=0) -> TTensor: # pylint: disable=redefined-builtin +def round(a: Tensor, decimals=0) -> Tensor: # pylint: disable=redefined-builtin """ Evenly round to the given number of decimals. @@ -399,7 +399,7 @@ def round(a: TTensor, decimals=0) -> TTensor: # pylint: disable=redefined-built @functools.singledispatch @_tensor_guard -def binary_op_nowarn(a: TTensor, b: TTensor, operator_fn: Callable) -> TTensor: +def binary_op_nowarn(a: Tensor, b: TTensor, operator_fn: Callable) -> Tensor: """ Applies a binary operation to two tensors with disable warnings. @@ -413,7 +413,7 @@ def binary_op_nowarn(a: TTensor, b: TTensor, operator_fn: Callable) -> TTensor: @functools.singledispatch @_tensor_guard -def binary_reverse_op_nowarn(a: TTensor, b: TTensor, operator_fn: Callable) -> TTensor: +def binary_reverse_op_nowarn(a: Tensor, b: TTensor, operator_fn: Callable) -> Tensor: """ Applies a binary reverse operation to two tensors with disable warnings. diff --git a/nncf/experimental/tensor/tensor.py b/nncf/experimental/tensor/tensor.py index 8e9023de66e..b199663e86e 100644 --- a/nncf/experimental/tensor/tensor.py +++ b/nncf/experimental/tensor/tensor.py @@ -8,7 +8,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +from __future__ import annotations import operator from typing import Any, List, Optional, Tuple, TypeVar, Union @@ -53,7 +53,7 @@ def __bool__(self) -> bool: def __iter__(self): return TensorIterator(self.data) - def __getitem__(self, index: int) -> "Tensor": + def __getitem__(self, index: int) -> Tensor: return Tensor(self.data[index]) def __str__(self) -> str: @@ -64,77 +64,77 @@ def __repr__(self) -> str: # built-in operations - def __add__(self, other: TTensor) -> "Tensor": + def __add__(self, other: TTensor) -> Tensor: return Tensor(self.data + unwrap_tensor_data(other)) - def __radd__(self, other: TTensor) -> "Tensor": + def __radd__(self, other: TTensor) -> Tensor: return Tensor(unwrap_tensor_data(other) + self.data) - def __sub__(self, other: TTensor) -> "Tensor": + def __sub__(self, other: TTensor) -> Tensor: return Tensor(self.data - unwrap_tensor_data(other)) - def __rsub__(self, other: TTensor) -> "Tensor": + def __rsub__(self, other: TTensor) -> Tensor: return Tensor(unwrap_tensor_data(other) - self.data) - def __mul__(self, other: TTensor) -> "Tensor": + def __mul__(self, other: TTensor) -> Tensor: return Tensor(self.data * unwrap_tensor_data(other)) - def __rmul__(self, other: TTensor) -> "Tensor": + def __rmul__(self, other: TTensor) -> Tensor: return Tensor(unwrap_tensor_data(other) * self.data) - def __pow__(self, other: TTensor) -> "Tensor": + def __pow__(self, other: TTensor) -> Tensor: return Tensor(self.data ** unwrap_tensor_data(other)) - def __truediv__(self, other: TTensor) -> "Tensor": - return _call_function("_binary_op_nowarn", self, other, operator.truediv) + def __truediv__(self, other: TTensor) -> Tensor: + return _call_function("binary_op_nowarn", self, other, operator.truediv) - def __rtruediv__(self, other: TTensor) -> "Tensor": - return _call_function("_binary_reverse_op_nowarn", self, other, operator.truediv) + def __rtruediv__(self, other: TTensor) -> Tensor: + return _call_function("binary_reverse_op_nowarn", self, other, operator.truediv) - def __floordiv__(self, other: TTensor) -> "Tensor": - return _call_function("_binary_op_nowarn", self, other, operator.floordiv) + def __floordiv__(self, other: TTensor) -> Tensor: + return _call_function("binary_op_nowarn", self, other, operator.floordiv) - def __rfloordiv__(self, other: TTensor) -> "Tensor": - return _call_function("_binary_reverse_op_nowarn", self, other, operator.floordiv) + def __rfloordiv__(self, other: TTensor) -> Tensor: + return _call_function("binary_reverse_op_nowarn", self, other, operator.floordiv) - def __neg__(self) -> "Tensor": + def __neg__(self) -> Tensor: return Tensor(-self.data) # Comparison operators - def __lt__(self, other: TTensor) -> "Tensor": + def __lt__(self, other: TTensor) -> Tensor: return Tensor(self.data < unwrap_tensor_data(other)) - def __le__(self, other: TTensor) -> "Tensor": + def __le__(self, other: TTensor) -> Tensor: return Tensor(self.data <= unwrap_tensor_data(other)) - def __eq__(self, other: TTensor) -> "Tensor": + def __eq__(self, other: TTensor) -> Tensor: return Tensor(self.data == unwrap_tensor_data(other)) - def __ne__(self, other: TTensor) -> "Tensor": + def __ne__(self, other: TTensor) -> Tensor: return Tensor(self.data != unwrap_tensor_data(other)) - def __gt__(self, other: TTensor) -> "Tensor": + def __gt__(self, other: TTensor) -> Tensor: return Tensor(self.data > unwrap_tensor_data(other)) - def __ge__(self, other: TTensor) -> "Tensor": + def __ge__(self, other: TTensor) -> Tensor: return Tensor(self.data >= unwrap_tensor_data(other)) # Tensor functions - def squeeze(self, axis: Optional[Union[int, Tuple[int]]] = None) -> "Tensor": + def squeeze(self, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: return _call_function("squeeze", self, axis) - def flatten(self) -> "Tensor": + def flatten(self) -> Tensor: return _call_function("flatten", self) - def max(self, axis: Optional[TTensor] = None) -> "Tensor": + def max(self, axis: Optional[TTensor] = None) -> Tensor: return _call_function("max", self, axis) - def min(self, axis: Optional[TTensor] = None) -> "Tensor": + def min(self, axis: Optional[TTensor] = None) -> Tensor: return _call_function("min", self, axis) - def abs(self) -> "Tensor": + def abs(self) -> Tensor: return _call_function("abs", self) def isempty(self) -> bool: @@ -143,7 +143,7 @@ def isempty(self) -> bool: def astype(self, dtype: TensorDataType): return _call_function("astype", self, dtype) - def reshape(self, shape: List) -> "Tensor": + def reshape(self, shape: List) -> Tensor: return _call_function("reshape", self, shape) From 5cd62cfe23fa99043f5bd30a0e7c63de5c9c648a Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 26 Sep 2023 19:31:36 +0300 Subject: [PATCH 27/45] typehints --- nncf/experimental/tensor/functions.py | 30 ++++---- nncf/experimental/tensor/numpy_functions.py | 73 ++++++++++--------- nncf/experimental/tensor/tensor.py | 14 ++-- nncf/experimental/tensor/torch_functions.py | 25 ++++--- .../template_test_nncf_tensor.py | 19 ++++- .../ptq/test_calculation_quantizer_params.py | 2 +- 6 files changed, 93 insertions(+), 70 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 7bc5e083d1b..c29fd0e50a1 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -48,7 +48,7 @@ def device(a: Tensor) -> TensorDeviceType: @functools.singledispatch @_tensor_guard -def squeeze(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: +def squeeze(a: Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor: """ Remove axes of length one from a. @@ -75,7 +75,7 @@ def flatten(a: Tensor) -> Tensor: @functools.singledispatch @_tensor_guard -def max(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin +def max(a: Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Return the maximum of an array or maximum along an axis. @@ -88,7 +88,7 @@ def max(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # @functools.singledispatch @_tensor_guard -def min(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin +def min(a: Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Return the minimum of an array or minimum along an axis. @@ -139,7 +139,7 @@ def dtype(a: Tensor) -> TensorDataType: @functools.singledispatch @_tensor_guard -def reshape(a: Tensor, shape: List[int]) -> Tensor: +def reshape(a: Tensor, shape: Tuple[int, ...]) -> Tensor: """ Gives a new shape to a tensor without changing its data. @@ -152,7 +152,7 @@ def reshape(a: Tensor, shape: List[int]) -> Tensor: @functools.singledispatch @_tensor_guard -def all(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin +def all(a: Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Test whether all tensor elements along a given axis evaluate to True. @@ -191,7 +191,7 @@ def allclose(a: Tensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, eq @functools.singledispatch @_tensor_guard -def any(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # pylint: disable=redefined-builtin +def any(a: Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor: # pylint: disable=redefined-builtin """ Test whether any tensor elements along a given axis evaluate to True. @@ -204,7 +204,7 @@ def any(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: # @functools.singledispatch @_tensor_guard -def count_nonzero(a: Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: +def count_nonzero(a: Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor: """ Counts the number of non-zero values in the tensor input. @@ -357,7 +357,7 @@ def unstack(a: Tensor, axis: int = 0) -> List[TTensor]: @functools.singledispatch @_tensor_guard -def moveaxis(a: Tensor, source: Union[int, List[int]], destination: Union[int, List[int]]) -> Tensor: +def moveaxis(a: Tensor, source: Union[int, Tuple[int, ...]], destination: Union[int, Tuple[int, ...]]) -> Tensor: """ Move axes of an array to new positions. @@ -371,7 +371,7 @@ def moveaxis(a: Tensor, source: Union[int, List[int]], destination: Union[int, L @functools.singledispatch @_tensor_guard -def mean(a: Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) -> Tensor: +def mean(a: Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Tensor: """ Compute the arithmetic mean along the specified axis. @@ -399,30 +399,30 @@ def round(a: Tensor, decimals=0) -> Tensor: # pylint: disable=redefined-builtin @functools.singledispatch @_tensor_guard -def binary_op_nowarn(a: Tensor, b: TTensor, operator_fn: Callable) -> Tensor: +def _binary_op_nowarn(a: Tensor, b: TTensor, operator_fn: Callable) -> Tensor: """ - Applies a binary operation to two tensors with disable warnings. + Applies a binary operation with disable warnings. :param a: The first tensor. :param b: The second tensor. :param operator_fn: The binary operation function. :return: The result of the binary operation. """ - return Tensor(binary_op_nowarn(a.data, unwrap_tensor_data(b), operator_fn)) + return Tensor(_binary_op_nowarn(a.data, unwrap_tensor_data(b), operator_fn)) @functools.singledispatch @_tensor_guard -def binary_reverse_op_nowarn(a: Tensor, b: TTensor, operator_fn: Callable) -> Tensor: +def _binary_reverse_op_nowarn(a: Tensor, b: TTensor, operator_fn: Callable) -> Tensor: """ - Applies a binary reverse operation to two tensors with disable warnings. + Applies a binary reverse operation with disable warnings. :param a: The first tensor. :param b: The second tensor. :param operator_fn: The binary operation function. :return: The result of the binary operation. """ - return Tensor(binary_reverse_op_nowarn(a.data, unwrap_tensor_data(b), operator_fn)) + return Tensor(_binary_reverse_op_nowarn(a.data, unwrap_tensor_data(b), operator_fn)) def _initialize_backends(): diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 9959ab5dcd4..0abac90d413 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -43,63 +43,64 @@ def inner(func): return inner -NUMPY_TYPES = Union[np.ndarray, np.generic] - - @_register_numpy_types(fns.device) -def _(a: NUMPY_TYPES) -> TensorDeviceType: +def _(a: Union[np.ndarray, np.generic]) -> TensorDeviceType: return TensorDeviceType.CPU @_register_numpy_types(fns.squeeze) -def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> NUMPY_TYPES: +def _( + a: Union[np.ndarray, np.generic], axis: Optional[Union[int, Tuple[int, ...]]] = None +) -> Union[np.ndarray, np.generic]: return np.squeeze(a, axis=axis) @_register_numpy_types(fns.flatten) -def _(a: NUMPY_TYPES) -> np.ndarray: +def _(a: Union[np.ndarray, np.generic]) -> np.ndarray: return a.flatten() @_register_numpy_types(fns.max) -def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: +def _(a: Union[np.ndarray, np.generic], axis: Optional[Union[int, Tuple[int, ...]]] = None) -> np.ndarray: return np.max(a, axis=axis) @_register_numpy_types(fns.min) -def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> NUMPY_TYPES: +def _( + a: Union[np.ndarray, np.generic], axis: Optional[Union[int, Tuple[int, ...]]] = None +) -> Union[np.ndarray, np.generic]: return np.min(a, axis=axis) @_register_numpy_types(fns.abs) -def _(a: NUMPY_TYPES) -> NUMPY_TYPES: +def _(a: Union[np.ndarray, np.generic]) -> Union[np.ndarray, np.generic]: return np.absolute(a) @_register_numpy_types(fns.astype) -def _(a: NUMPY_TYPES, dtype: TensorDataType) -> NUMPY_TYPES: +def _(a: Union[np.ndarray, np.generic], dtype: TensorDataType) -> Union[np.ndarray, np.generic]: return a.astype(DTYPE_MAP[dtype]) @_register_numpy_types(fns.dtype) -def _(a: NUMPY_TYPES) -> TensorDataType: +def _(a: Union[np.ndarray, np.generic]) -> TensorDataType: return DTYPE_MAP_REV[np.dtype(a.dtype)] @_register_numpy_types(fns.reshape) -def _(a: NUMPY_TYPES, shape: Union[int, Tuple[int]]) -> np.ndarray: +def _(a: Union[np.ndarray, np.generic], shape: Union[int, Tuple[int, ...]]) -> np.ndarray: return a.reshape(shape) @_register_numpy_types(fns.all) -def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: +def _(a: Union[np.ndarray, np.generic], axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Union[np.ndarray, bool]: return np.all(a, axis=axis) @_register_numpy_types(fns.allclose) def _( - a: NUMPY_TYPES, - b: NUMPY_TYPES, + a: Union[np.ndarray, np.generic], + b: Union[np.ndarray, np.generic], rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False, @@ -108,24 +109,24 @@ def _( @_register_numpy_types(fns.any) -def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> Union[np.ndarray, bool]: +def _(a: Union[np.ndarray, np.generic], axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Union[np.ndarray, bool]: return np.any(a, axis=axis) @_register_numpy_types(fns.count_nonzero) -def _(a: NUMPY_TYPES, axis: Optional[Union[int, Tuple[int]]] = None) -> np.ndarray: +def _(a: Union[np.ndarray, np.generic], axis: Optional[Union[int, Tuple[int, ...]]] = None) -> np.ndarray: return np.array(np.count_nonzero(a, axis=axis)) @_register_numpy_types(fns.isempty) -def _(a: NUMPY_TYPES) -> bool: +def _(a: Union[np.ndarray, np.generic]) -> bool: return a.size == 0 @_register_numpy_types(fns.isclose) def _( - a: NUMPY_TYPES, - b: NUMPY_TYPES, + a: Union[np.ndarray, np.generic], + b: Union[np.ndarray, np.generic], rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False, @@ -134,23 +135,23 @@ def _( @_register_numpy_types(fns.maximum) -def _(x1: NUMPY_TYPES, x2: NUMPY_TYPES) -> np.ndarray: +def _(x1: Union[np.ndarray, np.generic], x2: Union[np.ndarray, np.generic]) -> np.ndarray: return np.maximum(x1, x2) @_register_numpy_types(fns.minimum) -def _(x1: NUMPY_TYPES, x2: NUMPY_TYPES) -> np.ndarray: +def _(x1: Union[np.ndarray, np.generic], x2: Union[np.ndarray, np.generic]) -> np.ndarray: return np.minimum(x1, x2) @_register_numpy_types(fns.ones_like) -def _(a: NUMPY_TYPES) -> np.ndarray: +def _(a: Union[np.ndarray, np.generic]) -> np.ndarray: return np.ones_like(a) @_register_numpy_types(fns.where) def _( - condition: NUMPY_TYPES, + condition: Union[np.ndarray, np.generic], x: Union[np.ndarray, np.number, float, bool], y: Union[np.ndarray, float, bool], ) -> np.ndarray: @@ -158,44 +159,48 @@ def _( @_register_numpy_types(fns.zeros_like) -def _(a: NUMPY_TYPES) -> np.ndarray: +def _(a: Union[np.ndarray, np.generic]) -> np.ndarray: return np.zeros_like(a) @_register_numpy_types(fns.stack) -def _(x: NUMPY_TYPES, axis: int = 0) -> List[np.ndarray]: +def _(x: Union[np.ndarray, np.generic], axis: int = 0) -> List[np.ndarray]: return np.stack(x, axis=axis) @_register_numpy_types(fns.unstack) -def _(x: NUMPY_TYPES, axis: int = 0) -> List[np.ndarray]: +def _(x: Union[np.ndarray, np.generic], axis: int = 0) -> List[np.ndarray]: return [np.squeeze(e, axis) for e in np.split(x, x.shape[axis], axis=axis)] @_register_numpy_types(fns.moveaxis) -def _(a: np.ndarray, source: Union[int, List[int]], destination: Union[int, List[int]]) -> np.ndarray: +def _(a: np.ndarray, source: Union[int, Tuple[int, ...]], destination: Union[int, Tuple[int, ...]]) -> np.ndarray: return np.moveaxis(a, source, destination) @_register_numpy_types(fns.mean) -def _(a: NUMPY_TYPES, axis: Union[int, List[int]] = None, keepdims: bool = False) -> np.ndarray: +def _(a: Union[np.ndarray, np.generic], axis: Union[int, Tuple[int, ...]] = None, keepdims: bool = False) -> np.ndarray: return np.mean(a, axis=axis, keepdims=keepdims) @_register_numpy_types(fns.round) -def _(a: NUMPY_TYPES, decimals: int = 0) -> np.ndarray: +def _(a: Union[np.ndarray, np.generic], decimals: int = 0) -> np.ndarray: return np.round(a, decimals=decimals) -@_register_numpy_types(fns.binary_op_nowarn) -def _(a: NUMPY_TYPES, b: NUMPY_TYPES, operator_fn: Callable) -> NUMPY_TYPES: +@_register_numpy_types(fns._binary_op_nowarn) +def _( + a: Union[np.ndarray, np.generic], b: Union[np.ndarray, np.generic], operator_fn: Callable +) -> Union[np.ndarray, np.generic]: # Run operator with disabled warning with np.errstate(invalid="ignore", divide="ignore"): return operator_fn(a, b) -@_register_numpy_types(fns.binary_reverse_op_nowarn) -def _(a: NUMPY_TYPES, b: NUMPY_TYPES, operator_fn: Callable) -> NUMPY_TYPES: +@_register_numpy_types(fns._binary_reverse_op_nowarn) +def _( + a: Union[np.ndarray, np.generic], b: Union[np.ndarray, np.generic], operator_fn: Callable +) -> Union[np.ndarray, np.generic]: # Run operator with disabled warning with np.errstate(invalid="ignore", divide="ignore"): return operator_fn(b, a) diff --git a/nncf/experimental/tensor/tensor.py b/nncf/experimental/tensor/tensor.py index b199663e86e..e4df5c685c7 100644 --- a/nncf/experimental/tensor/tensor.py +++ b/nncf/experimental/tensor/tensor.py @@ -32,7 +32,7 @@ def data(self) -> TTensor: return self._data @property - def shape(self) -> List[int]: + def shape(self) -> Tuple[int, ...]: return tuple(self.data.shape) @property @@ -86,16 +86,16 @@ def __pow__(self, other: TTensor) -> Tensor: return Tensor(self.data ** unwrap_tensor_data(other)) def __truediv__(self, other: TTensor) -> Tensor: - return _call_function("binary_op_nowarn", self, other, operator.truediv) + return _call_function("_binary_op_nowarn", self, other, operator.truediv) def __rtruediv__(self, other: TTensor) -> Tensor: - return _call_function("binary_reverse_op_nowarn", self, other, operator.truediv) + return _call_function("_binary_reverse_op_nowarn", self, other, operator.truediv) def __floordiv__(self, other: TTensor) -> Tensor: - return _call_function("binary_op_nowarn", self, other, operator.floordiv) + return _call_function("_binary_op_nowarn", self, other, operator.floordiv) def __rfloordiv__(self, other: TTensor) -> Tensor: - return _call_function("binary_reverse_op_nowarn", self, other, operator.floordiv) + return _call_function("_binary_reverse_op_nowarn", self, other, operator.floordiv) def __neg__(self) -> Tensor: return Tensor(-self.data) @@ -122,7 +122,7 @@ def __ge__(self, other: TTensor) -> Tensor: # Tensor functions - def squeeze(self, axis: Optional[Union[int, Tuple[int]]] = None) -> Tensor: + def squeeze(self, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor: return _call_function("squeeze", self, axis) def flatten(self) -> Tensor: @@ -143,7 +143,7 @@ def isempty(self) -> bool: def astype(self, dtype: TensorDataType): return _call_function("astype", self, dtype) - def reshape(self, shape: List) -> Tensor: + def reshape(self, shape: Tuple[int, ...]) -> Tensor: return _call_function("reshape", self, shape) diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 0bd7b64a73a..91a5b0b9a40 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -38,9 +38,12 @@ def _(a: torch.Tensor) -> TensorDeviceType: @fns.squeeze.register(torch.Tensor) -def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: +def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> torch.Tensor: if axis is None: return a.squeeze() + if isinstance(axis, Tuple) and any([1 != a.shape[i] for i in axis]): + # Make Numpy behavior, torch.squeeze skips axes that are not equal to one.. + raise ValueError("Cannot select an axis to squeeze out which has size not equal to one") return a.squeeze(axis) @@ -50,7 +53,7 @@ def _(a: torch.Tensor) -> torch.Tensor: @fns.max.register(torch.Tensor) -def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: +def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> torch.Tensor: # Analog of numpy.max is torch.amax if axis is None: return torch.amax(a) @@ -58,7 +61,7 @@ def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.T @fns.min.register(torch.Tensor) -def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: +def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> torch.Tensor: # Analog of numpy.min is torch.amin if axis is None: return torch.amin(a) @@ -81,12 +84,12 @@ def _(a: torch.Tensor) -> TensorDataType: @fns.reshape.register(torch.Tensor) -def _(a: torch.Tensor, shape: List[int]) -> torch.Tensor: +def _(a: torch.Tensor, shape: Tuple[int, ...]) -> torch.Tensor: return a.reshape(shape) @fns.all.register(torch.Tensor) -def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Union[torch.Tensor, bool]: +def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Union[torch.Tensor, bool]: if axis is None: return torch.all(a) return torch.all(a, dim=axis) @@ -100,14 +103,14 @@ def _(a: torch.Tensor, b: torch.Tensor, rtol: float = 1e-05, atol: float = 1e-08 @fns.any.register(torch.Tensor) -def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> Union[torch.Tensor, bool]: +def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Union[torch.Tensor, bool]: if axis is None: return torch.any(a) return torch.any(a, dim=axis) @fns.count_nonzero.register(torch.Tensor) -def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int]]] = None) -> torch.Tensor: +def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> torch.Tensor: return torch.count_nonzero(a, dim=axis) @@ -167,12 +170,12 @@ def _(x: torch.Tensor, axis: int = 0) -> List[torch.Tensor]: @fns.moveaxis.register(torch.Tensor) -def _(a: torch.Tensor, source: Union[int, List[int]], destination: Union[int, List[int]]) -> torch.Tensor: +def _(a: torch.Tensor, source: Union[int, Tuple[int, ...]], destination: Union[int, Tuple[int, ...]]) -> torch.Tensor: return torch.moveaxis(a, source, destination) @fns.mean.register(torch.Tensor) -def _(a: torch.Tensor, axis: Union[int, List[int]] = None, keepdims: bool = False) -> torch.Tensor: +def _(a: torch.Tensor, axis: Union[int, Tuple[int, ...]] = None, keepdims: bool = False) -> torch.Tensor: return torch.mean(a, axis=axis, keepdims=keepdims) @@ -181,11 +184,11 @@ def _(a: torch.Tensor, decimals=0) -> torch.Tensor: return torch.round(a, decimals=decimals) -@fns.binary_op_nowarn.register(torch.Tensor) +@fns._binary_op_nowarn.register(torch.Tensor) def _(a: torch.Tensor, b: torch.Tensor, operator_fn: Callable) -> torch.Tensor: return operator_fn(a, b) -@fns.binary_reverse_op_nowarn.register(torch.Tensor) +@fns._binary_reverse_op_nowarn.register(torch.Tensor) def _(a: torch.Tensor, b: torch.Tensor, operator_fn: Callable) -> torch.Tensor: return operator_fn(b, a) diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index 2212daf4081..2b982380c26 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -151,6 +151,7 @@ def test_comparison_int_rev(self, op_name): ([[[[1], [2]], [[1], [2]]]], None, [[1, 2], [1, 2]]), ([[[[1], [2]], [[1], [2]]]], 0, [[[1], [2]], [[1], [2]]]), ([[[[1], [2]], [[1], [2]]]], -1, [[[1, 2], [1, 2]]]), + ([[[[1], [2]], [[1], [2]]]], (0, 3), [[1, 2], [1, 2]]), ), ) def test_squeeze(self, val, axis, ref): @@ -161,6 +162,20 @@ def test_squeeze(self, val, axis, ref): assert isinstance(res, Tensor) assert fns.allclose(res, ref_tensor) + @pytest.mark.parametrize( + "val, axis, exception_type, exception_match", + ( + ([[[[1], [2]], [[1], [2]]]], (0, 1), ValueError, "not equal to one"), + ([[[[1], [2]], [[1], [2]]]], 42, IndexError, "out of"), + ([[[[1], [2]], [[1], [2]]]], (0, 42), IndexError, "out of"), + ), + ) + def test_squeeze_axis_error(self, val, axis, exception_type, exception_match): + tensor = self.to_tensor(val) + nncf_tensor = Tensor(tensor) + with pytest.raises(exception_type, match=exception_match): + nncf_tensor.squeeze(axis=axis) + @pytest.mark.parametrize( "val, axis, ref", ( @@ -471,12 +486,12 @@ def test_fn_astype(self): def test_reshape(self): tensor = Tensor(self.to_tensor([1, 1])) assert tensor.shape == (2,) - assert tensor.reshape([1, 2]).shape == (1, 2) + assert tensor.reshape((1, 2)).shape == (1, 2) def test_fn_reshape(self): tensor = Tensor(self.to_tensor([1, 1])) assert tensor.shape == (2,) - assert fns.reshape(tensor, [1, 2]).shape == (1, 2) + assert fns.reshape(tensor, (1, 2)).shape == (1, 2) def test_not_implemented(self): with pytest.raises(NotImplementedError, match="is not implemented for"): diff --git a/tests/torch/ptq/test_calculation_quantizer_params.py b/tests/torch/ptq/test_calculation_quantizer_params.py index 1412da43cfd..843fca5bf40 100644 --- a/tests/torch/ptq/test_calculation_quantizer_params.py +++ b/tests/torch/ptq/test_calculation_quantizer_params.py @@ -13,11 +13,11 @@ from pathlib import Path import numpy as np +import onnx import pytest import torch from torch import nn -import onnx from nncf import Dataset from nncf import NNCFConfig from nncf.common.graph.transformations.commands import TargetType From 29f80bd64a8e7bcb6d253082fe8c2b97ba4ddf4a Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Tue, 26 Sep 2023 23:53:37 +0300 Subject: [PATCH 28/45] Fix docstring --- nncf/experimental/tensor/functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index c29fd0e50a1..851460beec1 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -327,9 +327,9 @@ def zeros_like(a: Tensor) -> Tensor: @functools.singledispatch def stack(x: List[Tensor], axis: int = 0) -> Tensor: """ - Stacks a list or deque of Tensors rank-R tensors into one Tensor rank-(R+1) tensor. + Stacks a list of Tensors rank-R tensors into one Tensor rank-(R+1) tensor. - :param x: List or deque of Tensors. + :param x: List of Tensors. :param axis: The axis to stack along. :return: Stacked Tensor. """ From 6c21d17acdc6de3dbe4cf195e8003766e0eeb8ac Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 27 Sep 2023 00:47:01 +0300 Subject: [PATCH 29/45] hints --- nncf/experimental/tensor/tensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nncf/experimental/tensor/tensor.py b/nncf/experimental/tensor/tensor.py index e4df5c685c7..4d706e87b77 100644 --- a/nncf/experimental/tensor/tensor.py +++ b/nncf/experimental/tensor/tensor.py @@ -11,7 +11,7 @@ from __future__ import annotations import operator -from typing import Any, List, Optional, Tuple, TypeVar, Union +from typing import Any, Optional, Tuple, TypeVar, Union from nncf.experimental.tensor.enums import TensorDataType from nncf.experimental.tensor.enums import TensorDeviceType @@ -128,10 +128,10 @@ def squeeze(self, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor: def flatten(self) -> Tensor: return _call_function("flatten", self) - def max(self, axis: Optional[TTensor] = None) -> Tensor: + def max(self, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor: return _call_function("max", self, axis) - def min(self, axis: Optional[TTensor] = None) -> Tensor: + def min(self, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor: return _call_function("min", self, axis) def abs(self) -> Tensor: @@ -140,7 +140,7 @@ def abs(self) -> Tensor: def isempty(self) -> bool: return _call_function("isempty", self) - def astype(self, dtype: TensorDataType): + def astype(self, dtype: TensorDataType) -> Tensor: return _call_function("astype", self, dtype) def reshape(self, shape: Tuple[int, ...]) -> Tensor: From 7bd526526af8e02a43594065b921553b4419d38a Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 27 Sep 2023 01:06:59 +0300 Subject: [PATCH 30/45] float typehint --- nncf/experimental/tensor/functions.py | 22 +++++++------ nncf/experimental/tensor/numpy_functions.py | 16 +++++----- nncf/experimental/tensor/tensor.py | 34 ++++++++++----------- nncf/experimental/tensor/torch_functions.py | 16 ++++++---- 4 files changed, 47 insertions(+), 41 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 851460beec1..83cc8de0d41 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -17,8 +17,6 @@ from nncf.experimental.tensor.enums import TensorDataType from nncf.experimental.tensor.enums import TensorDeviceType -TTensor = TypeVar("TTensor") - def _tensor_guard(func: callable): """ @@ -165,7 +163,9 @@ def all(a: Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Tensor @functools.singledispatch @_tensor_guard -def allclose(a: Tensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> Tensor: +def allclose( + a: Tensor, b: Union[Tensor, float], rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False +) -> Tensor: """ Returns True if two arrays are element-wise equal within a tolerance. @@ -230,7 +230,9 @@ def isempty(a: Tensor) -> bool: @functools.singledispatch @_tensor_guard -def isclose(a: Tensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> Tensor: +def isclose( + a: Tensor, b: Union[Tensor, float], rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False +) -> Tensor: """ Returns a boolean array where two arrays are element-wise equal within a tolerance. @@ -256,7 +258,7 @@ def isclose(a: Tensor, b: TTensor, rtol: float = 1e-05, atol: float = 1e-08, equ @functools.singledispatch @_tensor_guard -def maximum(x1: Tensor, x2: TTensor) -> Tensor: +def maximum(x1: Tensor, x2: Union[Tensor, float]) -> Tensor: """ Element-wise maximum of tensor elements. @@ -269,7 +271,7 @@ def maximum(x1: Tensor, x2: TTensor) -> Tensor: @functools.singledispatch @_tensor_guard -def minimum(x1: Tensor, x2: TTensor) -> Tensor: +def minimum(x1: Tensor, x2: Union[Tensor, float]) -> Tensor: """ Element-wise minimum of tensor elements. @@ -294,7 +296,7 @@ def ones_like(a: Tensor) -> Tensor: @functools.singledispatch @_tensor_guard -def where(condition: Tensor, x: TTensor, y: TTensor) -> Tensor: +def where(condition: Tensor, x: Union[Tensor, float], y: Union[Tensor, float]) -> Tensor: """ Return elements chosen from x or y depending on condition. @@ -343,7 +345,7 @@ def stack(x: List[Tensor], axis: int = 0) -> Tensor: @functools.singledispatch @_tensor_guard -def unstack(a: Tensor, axis: int = 0) -> List[TTensor]: +def unstack(a: Tensor, axis: int = 0) -> List[Tensor]: """ Unstack a Tensor into list. @@ -399,7 +401,7 @@ def round(a: Tensor, decimals=0) -> Tensor: # pylint: disable=redefined-builtin @functools.singledispatch @_tensor_guard -def _binary_op_nowarn(a: Tensor, b: TTensor, operator_fn: Callable) -> Tensor: +def _binary_op_nowarn(a: Tensor, b: Union[Tensor, float], operator_fn: Callable) -> Tensor: """ Applies a binary operation with disable warnings. @@ -413,7 +415,7 @@ def _binary_op_nowarn(a: Tensor, b: TTensor, operator_fn: Callable) -> Tensor: @functools.singledispatch @_tensor_guard -def _binary_reverse_op_nowarn(a: Tensor, b: TTensor, operator_fn: Callable) -> Tensor: +def _binary_reverse_op_nowarn(a: Tensor, b: Union[Tensor, float], operator_fn: Callable) -> Tensor: """ Applies a binary reverse operation with disable warnings. diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 0abac90d413..84ba9128566 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -100,7 +100,7 @@ def _(a: Union[np.ndarray, np.generic], axis: Optional[Union[int, Tuple[int, ... @_register_numpy_types(fns.allclose) def _( a: Union[np.ndarray, np.generic], - b: Union[np.ndarray, np.generic], + b: Union[np.ndarray, np.generic, float], rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False, @@ -126,7 +126,7 @@ def _(a: Union[np.ndarray, np.generic]) -> bool: @_register_numpy_types(fns.isclose) def _( a: Union[np.ndarray, np.generic], - b: Union[np.ndarray, np.generic], + b: Union[np.ndarray, np.generic, float], rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False, @@ -135,12 +135,12 @@ def _( @_register_numpy_types(fns.maximum) -def _(x1: Union[np.ndarray, np.generic], x2: Union[np.ndarray, np.generic]) -> np.ndarray: +def _(x1: Union[np.ndarray, np.generic], x2: Union[np.ndarray, np.generic, float]) -> np.ndarray: return np.maximum(x1, x2) @_register_numpy_types(fns.minimum) -def _(x1: Union[np.ndarray, np.generic], x2: Union[np.ndarray, np.generic]) -> np.ndarray: +def _(x1: Union[np.ndarray, np.generic], x2: Union[np.ndarray, np.generic, float]) -> np.ndarray: return np.minimum(x1, x2) @@ -152,8 +152,8 @@ def _(a: Union[np.ndarray, np.generic]) -> np.ndarray: @_register_numpy_types(fns.where) def _( condition: Union[np.ndarray, np.generic], - x: Union[np.ndarray, np.number, float, bool], - y: Union[np.ndarray, float, bool], + x: Union[np.ndarray, np.generic, float], + y: Union[np.ndarray, np.generic, float], ) -> np.ndarray: return np.where(condition, x, y) @@ -190,7 +190,7 @@ def _(a: Union[np.ndarray, np.generic], decimals: int = 0) -> np.ndarray: @_register_numpy_types(fns._binary_op_nowarn) def _( - a: Union[np.ndarray, np.generic], b: Union[np.ndarray, np.generic], operator_fn: Callable + a: Union[np.ndarray, np.generic], b: Union[np.ndarray, np.generic, float], operator_fn: Callable ) -> Union[np.ndarray, np.generic]: # Run operator with disabled warning with np.errstate(invalid="ignore", divide="ignore"): @@ -199,7 +199,7 @@ def _( @_register_numpy_types(fns._binary_reverse_op_nowarn) def _( - a: Union[np.ndarray, np.generic], b: Union[np.ndarray, np.generic], operator_fn: Callable + a: Union[np.ndarray, np.generic], b: Union[np.ndarray, np.generic, float], operator_fn: Callable ) -> Union[np.ndarray, np.generic]: # Run operator with disabled warning with np.errstate(invalid="ignore", divide="ignore"): diff --git a/nncf/experimental/tensor/tensor.py b/nncf/experimental/tensor/tensor.py index 4d706e87b77..76fd05c4ff1 100644 --- a/nncf/experimental/tensor/tensor.py +++ b/nncf/experimental/tensor/tensor.py @@ -64,37 +64,37 @@ def __repr__(self) -> str: # built-in operations - def __add__(self, other: TTensor) -> Tensor: + def __add__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(self.data + unwrap_tensor_data(other)) - def __radd__(self, other: TTensor) -> Tensor: + def __radd__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(unwrap_tensor_data(other) + self.data) - def __sub__(self, other: TTensor) -> Tensor: + def __sub__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(self.data - unwrap_tensor_data(other)) - def __rsub__(self, other: TTensor) -> Tensor: + def __rsub__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(unwrap_tensor_data(other) - self.data) - def __mul__(self, other: TTensor) -> Tensor: + def __mul__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(self.data * unwrap_tensor_data(other)) - def __rmul__(self, other: TTensor) -> Tensor: + def __rmul__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(unwrap_tensor_data(other) * self.data) - def __pow__(self, other: TTensor) -> Tensor: + def __pow__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(self.data ** unwrap_tensor_data(other)) - def __truediv__(self, other: TTensor) -> Tensor: + def __truediv__(self, other: Union[Tensor, float]) -> Tensor: return _call_function("_binary_op_nowarn", self, other, operator.truediv) - def __rtruediv__(self, other: TTensor) -> Tensor: + def __rtruediv__(self, other: Union[Tensor, float]) -> Tensor: return _call_function("_binary_reverse_op_nowarn", self, other, operator.truediv) - def __floordiv__(self, other: TTensor) -> Tensor: + def __floordiv__(self, other: Union[Tensor, float]) -> Tensor: return _call_function("_binary_op_nowarn", self, other, operator.floordiv) - def __rfloordiv__(self, other: TTensor) -> Tensor: + def __rfloordiv__(self, other: Union[Tensor, float]) -> Tensor: return _call_function("_binary_reverse_op_nowarn", self, other, operator.floordiv) def __neg__(self) -> Tensor: @@ -102,22 +102,22 @@ def __neg__(self) -> Tensor: # Comparison operators - def __lt__(self, other: TTensor) -> Tensor: + def __lt__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(self.data < unwrap_tensor_data(other)) - def __le__(self, other: TTensor) -> Tensor: + def __le__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(self.data <= unwrap_tensor_data(other)) - def __eq__(self, other: TTensor) -> Tensor: + def __eq__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(self.data == unwrap_tensor_data(other)) - def __ne__(self, other: TTensor) -> Tensor: + def __ne__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(self.data != unwrap_tensor_data(other)) - def __gt__(self, other: TTensor) -> Tensor: + def __gt__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(self.data > unwrap_tensor_data(other)) - def __ge__(self, other: TTensor) -> Tensor: + def __ge__(self, other: Union[Tensor, float]) -> Tensor: return Tensor(self.data >= unwrap_tensor_data(other)) # Tensor functions diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 91a5b0b9a40..64129bd015e 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -96,7 +96,9 @@ def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Un @fns.allclose.register(torch.Tensor) -def _(a: torch.Tensor, b: torch.Tensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False) -> bool: +def _( + a: torch.Tensor, b: Union[torch.Tensor, float], rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False +) -> bool: if not isinstance(b, torch.Tensor): b = torch.tensor(b, device=a.device) return torch.allclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan) @@ -120,21 +122,23 @@ def _(a: torch.Tensor) -> bool: @fns.isclose.register(torch.Tensor) -def _(a: torch.Tensor, b: torch.Tensor, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False): +def _( + a: torch.Tensor, b: Union[torch.Tensor, float], rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False +): if not isinstance(b, torch.Tensor): b = torch.tensor(b, device=a.device) return torch.isclose(a, b, atol=atol, rtol=rtol, equal_nan=equal_nan) @fns.maximum.register(torch.Tensor) -def _(x1: torch.Tensor, x2: torch.Tensor) -> torch.Tensor: +def _(x1: torch.Tensor, x2: Union[torch.Tensor, float]) -> torch.Tensor: if not isinstance(x2, torch.Tensor): x2 = torch.tensor(x2, device=x1.data.device) return torch.maximum(x1, x2) @fns.minimum.register(torch.Tensor) -def _(x1: torch.Tensor, x2: torch.Tensor) -> torch.Tensor: +def _(x1: torch.Tensor, x2: Union[torch.Tensor, float]) -> torch.Tensor: if not isinstance(x2, torch.Tensor): x2 = torch.tensor(x2, device=x1.data.device) return torch.minimum(x1, x2) @@ -185,10 +189,10 @@ def _(a: torch.Tensor, decimals=0) -> torch.Tensor: @fns._binary_op_nowarn.register(torch.Tensor) -def _(a: torch.Tensor, b: torch.Tensor, operator_fn: Callable) -> torch.Tensor: +def _(a: torch.Tensor, b: Union[torch.Tensor, float], operator_fn: Callable) -> torch.Tensor: return operator_fn(a, b) @fns._binary_reverse_op_nowarn.register(torch.Tensor) -def _(a: torch.Tensor, b: torch.Tensor, operator_fn: Callable) -> torch.Tensor: +def _(a: torch.Tensor, b: Union[torch.Tensor, float], operator_fn: Callable) -> torch.Tensor: return operator_fn(b, a) From 33bb4403903ff142676daafad02505170adc06ba Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 27 Sep 2023 02:43:58 +0300 Subject: [PATCH 31/45] lint --- nncf/experimental/tensor/functions.py | 2 +- nncf/experimental/tensor/numpy_functions.py | 4 ++-- nncf/experimental/tensor/torch_functions.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 83cc8de0d41..1c6dfe232d9 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -10,7 +10,7 @@ # limitations under the License. import functools -from typing import Callable, List, Optional, Tuple, TypeVar, Union +from typing import Callable, List, Optional, Tuple, Union from nncf.experimental.tensor import Tensor from nncf.experimental.tensor import unwrap_tensor_data diff --git a/nncf/experimental/tensor/numpy_functions.py b/nncf/experimental/tensor/numpy_functions.py index 84ba9128566..b4c515b1da1 100644 --- a/nncf/experimental/tensor/numpy_functions.py +++ b/nncf/experimental/tensor/numpy_functions.py @@ -188,7 +188,7 @@ def _(a: Union[np.ndarray, np.generic], decimals: int = 0) -> np.ndarray: return np.round(a, decimals=decimals) -@_register_numpy_types(fns._binary_op_nowarn) +@_register_numpy_types(fns._binary_op_nowarn) # pylint: disable=protected-access def _( a: Union[np.ndarray, np.generic], b: Union[np.ndarray, np.generic, float], operator_fn: Callable ) -> Union[np.ndarray, np.generic]: @@ -197,7 +197,7 @@ def _( return operator_fn(a, b) -@_register_numpy_types(fns._binary_reverse_op_nowarn) +@_register_numpy_types(fns._binary_reverse_op_nowarn) # pylint: disable=protected-access def _( a: Union[np.ndarray, np.generic], b: Union[np.ndarray, np.generic, float], operator_fn: Callable ) -> Union[np.ndarray, np.generic]: diff --git a/nncf/experimental/tensor/torch_functions.py b/nncf/experimental/tensor/torch_functions.py index 64129bd015e..273d5419781 100644 --- a/nncf/experimental/tensor/torch_functions.py +++ b/nncf/experimental/tensor/torch_functions.py @@ -41,7 +41,7 @@ def _(a: torch.Tensor) -> TensorDeviceType: def _(a: torch.Tensor, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> torch.Tensor: if axis is None: return a.squeeze() - if isinstance(axis, Tuple) and any([1 != a.shape[i] for i in axis]): + if isinstance(axis, Tuple) and any(1 != a.shape[i] for i in axis): # Make Numpy behavior, torch.squeeze skips axes that are not equal to one.. raise ValueError("Cannot select an axis to squeeze out which has size not equal to one") return a.squeeze(axis) @@ -188,11 +188,11 @@ def _(a: torch.Tensor, decimals=0) -> torch.Tensor: return torch.round(a, decimals=decimals) -@fns._binary_op_nowarn.register(torch.Tensor) +@fns._binary_op_nowarn.register(torch.Tensor) # pylint: disable=protected-access def _(a: torch.Tensor, b: Union[torch.Tensor, float], operator_fn: Callable) -> torch.Tensor: return operator_fn(a, b) -@fns._binary_reverse_op_nowarn.register(torch.Tensor) +@fns._binary_reverse_op_nowarn.register(torch.Tensor) # pylint: disable=protected-access def _(a: torch.Tensor, b: Union[torch.Tensor, float], operator_fn: Callable) -> torch.Tensor: return operator_fn(b, a) From 91df596e00b5da7f60b93e0918c01c4e1d137774 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 27 Sep 2023 04:20:35 +0300 Subject: [PATCH 32/45] save device in unify_statistics --- nncf/quantization/algorithms/min_max/torch_backend.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nncf/quantization/algorithms/min_max/torch_backend.py b/nncf/quantization/algorithms/min_max/torch_backend.py index 9e1a6965b96..cd49f81b89b 100644 --- a/nncf/quantization/algorithms/min_max/torch_backend.py +++ b/nncf/quantization/algorithms/min_max/torch_backend.py @@ -134,10 +134,10 @@ def create_quantizer_insertion_command( def unify_statistics(statistics: List[PTMinMaxTensorStatistic]) -> PTMinMaxTensorStatistic: max_values, min_values = [], [] for statistic in statistics: - max_values.append(torch.tensor(statistic.max_values).flatten()) - min_values.append(torch.tensor(statistic.min_values).flatten()) - max_values = torch.max(torch.tensor(max_values)) - min_values = torch.min(torch.tensor(min_values)) + max_values.append(statistic.max_values.flatten()) + min_values.append(statistic.min_values.flatten()) + max_values = torch.amax(torch.stack(max_values), dim=0) + min_values = torch.amin(torch.stack(min_values), dim=0) return PTMinMaxTensorStatistic(min_values=min_values, max_values=max_values) @staticmethod From cfd3f04c5b52c97aad3c2ccca37b0ac2be9d4151 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 27 Sep 2023 17:21:18 +0300 Subject: [PATCH 33/45] mean_per_channel --- .../common/tensor_statistics/statistical_functions.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nncf/experimental/common/tensor_statistics/statistical_functions.py b/nncf/experimental/common/tensor_statistics/statistical_functions.py index fa23d2cc2df..f19476a0400 100644 --- a/nncf/experimental/common/tensor_statistics/statistical_functions.py +++ b/nncf/experimental/common/tensor_statistics/statistical_functions.py @@ -23,6 +23,8 @@ def mean_per_channel(x: Tensor, axis: int) -> Tensor: """ if len(x.shape) < 3: return fns.mean(x, axis=0) - x = fns.moveaxis(x, axis, 1) - t = x.reshape([x.shape[0], x.shape[1], -1]) - return fns.mean(t, axis=(0, 2)) + pos_axis = axis + x.ndim if axis < 0 else axis + if not 0 <= pos_axis < x.ndim: + raise ValueError(f"axis {axis} is out of bounds for array of dimension {x.ndim}") + axis = tuple(i for i in range(x.ndim) if i != pos_axis) + return fns.mean(x, axis=axis) From 11c6cba7ed7559db4ef6f815bdc39535e5852b22 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 27 Sep 2023 17:50:40 +0300 Subject: [PATCH 34/45] _dispatch_list --- nncf/experimental/tensor/functions.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 1c6dfe232d9..5cd670c8f03 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -337,9 +337,7 @@ def stack(x: List[Tensor], axis: int = 0) -> Tensor: """ if isinstance(x, List): unwrapped_x = [i.data for i in x] - # singledispatch cannot dispatch function by element in a list - res = stack.dispatch(type(unwrapped_x[0]))(unwrapped_x, axis=axis) - return Tensor(res) + return Tensor(_dispatch_list(stack, unwrapped_x, axis=axis)) raise NotImplementedError(f"Function `stack` is not implemented for {type(x)}") @@ -427,6 +425,16 @@ def _binary_reverse_op_nowarn(a: Tensor, b: Union[Tensor, float], operator_fn: C return Tensor(_binary_reverse_op_nowarn(a.data, unwrap_tensor_data(b), operator_fn)) +def _dispatch_list(fn: "functools._SingleDispatchCallable", *args, **kwargs): + """ + Dispatches the function to the appropriate implementation based on the type of the first element in the list. + + :param fn: A function wrapped by `functools.singledispatch`. + :return: The result value of the function call. + """ + return fn.dispatch(type(args[0][0]))(*args, **kwargs) + + def _initialize_backends(): # pylint: disable=unused-import import nncf.experimental.tensor.numpy_functions From 63d7a166486fd353158f14aedd56c6dcd0f8b463 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 27 Sep 2023 18:02:38 +0300 Subject: [PATCH 35/45] test_fn_mean_per_channel_incorrect_axis --- .../common/tensor_statistics/statistical_functions.py | 2 +- tests/shared/test_templates/template_test_nncf_tensor.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/nncf/experimental/common/tensor_statistics/statistical_functions.py b/nncf/experimental/common/tensor_statistics/statistical_functions.py index f19476a0400..24fc115a058 100644 --- a/nncf/experimental/common/tensor_statistics/statistical_functions.py +++ b/nncf/experimental/common/tensor_statistics/statistical_functions.py @@ -24,7 +24,7 @@ def mean_per_channel(x: Tensor, axis: int) -> Tensor: if len(x.shape) < 3: return fns.mean(x, axis=0) pos_axis = axis + x.ndim if axis < 0 else axis - if not 0 <= pos_axis < x.ndim: + if pos_axis < 0 or pos_axis >= x.ndim: raise ValueError(f"axis {axis} is out of bounds for array of dimension {x.ndim}") axis = tuple(i for i in range(x.ndim) if i != pos_axis) return fns.mean(x, axis=axis) diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index 2b982380c26..87e7ffecff9 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -659,3 +659,9 @@ def test_fn_mean_per_channel(self, val, axis, ref): res = s_fns.mean_per_channel(tensor, axis) assert isinstance(res, Tensor) assert fns.allclose(res, ref_tensor), f"{res.data}" + + @pytest.mark.parametrize("axis", (3, 4, -4, -5)) + def test_fn_mean_per_channel_incorrect_axis(self, axis): + tensor = Tensor(self.to_tensor([[[9.0, 9.0], [0.0, 3.0]], [[5.0, 1.0], [7.0, 1.0]]])) + with pytest.raises(ValueError, match="is out of bounds for array of dimension"): + s_fns.mean_per_channel(tensor, axis) From 6d62e44430b5caf5ff1b992c3a3a021449966596 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 27 Sep 2023 18:19:03 +0300 Subject: [PATCH 36/45] _dispatch_list in readme --- nncf/experimental/tensor/README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/nncf/experimental/tensor/README.md b/nncf/experimental/tensor/README.md index 1eee526dfeb..15fd586ca51 100644 --- a/nncf/experimental/tensor/README.md +++ b/nncf/experimental/tensor/README.md @@ -122,9 +122,19 @@ tensor_a[0:2] # Tensor(array([[1],[2]])) return NotImplemented(f"Function `foo` is not implemented for {type(a)}") ``` + **NOTE** For the case when the first argument has type `List[Tensor]`, use the `_dispatch_list` function. This function dispatches function by first element in the first argument. + ```python + @functools.singledispatch + def foo(x: List[Tensor], axis: int = 0) -> Tensor: + if isinstance(x, List): + unwrapped_x = [i.data for i in x] + return Tensor(_dispatch_list(foo, unwrapped_x, axis=axis)) + raise NotImplementedError(f"Function `foo` is not implemented for {type(x)}") + ``` + 3. Add backend specific implementation of method to: - - [numpy_function.py](numpy_function.py) + - [numpy_function.py](numpy_functions.py) ```python @_register_numpy_types(fns.foo) @@ -132,7 +142,7 @@ tensor_a[0:2] # Tensor(array([[1],[2]])) return np.foo(a, arg1) ``` - - [torch_function.py](torch_function.py) + - [torch_function.py](torch_functions.py) ```python @fns.foo.register(torch.Tensor) @@ -140,7 +150,7 @@ tensor_a[0:2] # Tensor(array([[1],[2]])) return torch.foo(a, arg1) ``` -4. Add test of method to [test template](tests/shared/test_templates/template_test_nncf_tensor.py) for Tensor class +4. Add test of method to [test template](../../../tests/shared/test_templates/template_test_nncf_tensor.py) for Tensor class ### Add new backend From 97673087235161c1d648de74743954fa6c6f9bdb Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 27 Sep 2023 18:40:16 +0300 Subject: [PATCH 37/45] lint --- nncf/experimental/tensor/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/nncf/experimental/tensor/README.md b/nncf/experimental/tensor/README.md index 15fd586ca51..ea8ae2168c9 100644 --- a/nncf/experimental/tensor/README.md +++ b/nncf/experimental/tensor/README.md @@ -123,6 +123,7 @@ tensor_a[0:2] # Tensor(array([[1],[2]])) ``` **NOTE** For the case when the first argument has type `List[Tensor]`, use the `_dispatch_list` function. This function dispatches function by first element in the first argument. + ```python @functools.singledispatch def foo(x: List[Tensor], axis: int = 0) -> Tensor: From 35ee9a44d5f873f7d38ec3c4ad0904962eb77857 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Thu, 28 Sep 2023 17:13:04 +0300 Subject: [PATCH 38/45] Add check to device in tests --- .../template_test_nncf_tensor.py | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/tests/shared/test_templates/template_test_nncf_tensor.py b/tests/shared/test_templates/template_test_nncf_tensor.py index 87e7ffecff9..461deb14fce 100644 --- a/tests/shared/test_templates/template_test_nncf_tensor.py +++ b/tests/shared/test_templates/template_test_nncf_tensor.py @@ -69,6 +69,7 @@ def test_operators_tensor(self, op_name): assert res.dtype == res_nncf.data.dtype assert all(res == res_nncf.data) assert isinstance(res_nncf, Tensor) + assert res_nncf.device == nncf_tensor_a.device @pytest.mark.parametrize("op_name", OPERATOR_MAP.keys()) def test_operators_int(self, op_name): @@ -84,6 +85,7 @@ def test_operators_int(self, op_name): assert res.dtype == res_nncf.data.dtype assert all(res == res_nncf.data) assert isinstance(res_nncf, Tensor) + assert res_nncf.device == nncf_tensor_a.device @pytest.mark.parametrize("op_name", ("add", "sub", "mul", "truediv", "floordiv")) def test_operators_int_rev(self, op_name): @@ -99,6 +101,7 @@ def test_operators_int_rev(self, op_name): assert res.dtype == res_nncf.data.dtype assert all(res == res_nncf.data) assert isinstance(res_nncf, Tensor) + assert res_nncf.device == nncf_tensor_a.device @pytest.mark.parametrize("op_name", COMPARISON_OPERATOR_MAP.keys()) def test_comparison_tensor(self, op_name): @@ -161,6 +164,7 @@ def test_squeeze(self, val, axis, ref): res = nncf_tensor.squeeze(axis=axis) assert isinstance(res, Tensor) assert fns.allclose(res, ref_tensor) + assert res.device == nncf_tensor.device @pytest.mark.parametrize( "val, axis, exception_type, exception_match", @@ -193,6 +197,7 @@ def test_fn_squeeze(self, val, axis, ref): res = fns.squeeze(nncf_tensor, axis=axis) assert isinstance(res, Tensor) assert fns.allclose(res, ref_tensor) + assert res.device == nncf_tensor.device @pytest.mark.parametrize( "val,ref", @@ -209,6 +214,7 @@ def test_flatten(self, val, ref): res = nncf_tensor.flatten() assert isinstance(res, Tensor) assert fns.allclose(res, ref_tensor) + assert res.device == nncf_tensor.device @pytest.mark.parametrize( "val, axis, ref", @@ -226,6 +232,7 @@ def test_fn_max(self, val, axis, ref): res = fns.max(nncf_tensor, axis=axis) assert isinstance(res, Tensor) assert fns.allclose(res, ref_tensor) + assert res.device == nncf_tensor.device @pytest.mark.parametrize( "val, axis, ref", @@ -242,6 +249,7 @@ def test_min(self, val, axis, ref): res = nncf_tensor.min(axis=axis) assert isinstance(res, Tensor) assert fns.allclose(res, ref_tensor) + assert res.device == nncf_tensor.device @pytest.mark.parametrize( "val, ref", @@ -256,6 +264,7 @@ def test_abs(self, val, ref): res = nncf_tensor.abs() assert isinstance(res, Tensor) assert fns.allclose(res, nncf_ref_tensor) + assert res.device == nncf_tensor.device @pytest.mark.parametrize( "val, ref", @@ -270,6 +279,7 @@ def test_fn_abs(self, val, ref): res = fns.abs(nncf_tensor) assert isinstance(res, Tensor) assert fns.allclose(res, nncf_ref_tensor) + assert res.device == nncf_tensor.device def test_getitem(self): arr = [0, 1, 2] @@ -277,6 +287,7 @@ def test_getitem(self): res = nncf_tensor[1] assert res == 1 assert isinstance(res, Tensor) + assert res.device == nncf_tensor.device def test_iter(self): arr = [0, 1, 2] @@ -304,6 +315,7 @@ def test_fn_count_nonzero(self, axis, ref): assert isinstance(res, Tensor) assert fns.allclose(res.data, ref_tensor) + assert res.device == nncf_tensor.device def test_fn_zeros_like(self): tensor = self.to_tensor([1, 2]) @@ -312,6 +324,7 @@ def test_fn_zeros_like(self): res = fns.zeros_like(nncf_tensor) assert all(res == Tensor(tensor * 0)) assert isinstance(res, Tensor) + assert res.device == nncf_tensor.device def test_fn_maximum(self): tensor_a = Tensor(self.to_tensor([1, 2])) @@ -321,6 +334,7 @@ def test_fn_maximum(self): res = fns.maximum(tensor_a, tensor_b) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) + assert res.device == tensor_a.device def test_fn_maximum_list(self): tensor_a = Tensor(self.to_tensor([1, 2])) @@ -330,6 +344,7 @@ def test_fn_maximum_list(self): res = fns.maximum(tensor_a, tensor_b) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) + assert res.device == tensor_a.device def test_fn_minimum(self): tensor_a = Tensor(self.to_tensor([1, 2])) @@ -339,6 +354,7 @@ def test_fn_minimum(self): res = fns.minimum(tensor_a, tensor_b) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) + assert res.device == tensor_a.device def test_fn_minimum_list(self): tensor_a = Tensor(self.to_tensor([1, 2])) @@ -348,6 +364,7 @@ def test_fn_minimum_list(self): res = fns.minimum(tensor_a, tensor_b) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) + assert res.device == tensor_a.device def test_fn_ones_like(self): tensor_a = Tensor(self.to_tensor([1, 2])) @@ -356,6 +373,7 @@ def test_fn_ones_like(self): res = fns.ones_like(tensor_a) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) + assert res.device == tensor_a.device @pytest.mark.parametrize( "val, axis, ref", @@ -371,6 +389,7 @@ def test_fn_all(self, val, axis, ref): res = fns.all(tensor, axis=axis) assert isinstance(res, Tensor) assert fns.allclose(res.data, self.to_tensor(ref)) + assert res.device == tensor.device @pytest.mark.parametrize( "val, axis, ref", @@ -387,6 +406,7 @@ def test_fn_any(self, val, axis, ref): assert isinstance(res, Tensor) assert fns.allclose(res.data, self.to_tensor(ref)) + assert res.device == tensor.device def test_fn_where(self): tensor = Tensor(self.to_tensor([1, -1])) @@ -394,6 +414,7 @@ def test_fn_where(self): res = fns.where(tensor > 0, 1, 0) assert all(res.data == tensor_ref) assert isinstance(res, Tensor) + assert res.device == tensor.device @pytest.mark.parametrize( "val, ref", @@ -476,6 +497,7 @@ def test_astype(self): res = tensor.astype(TensorDataType.int8) assert isinstance(res, Tensor) assert res.dtype == TensorDataType.int8 + assert res.device == tensor.device def test_fn_astype(self): tensor = Tensor(self.to_tensor([1])) @@ -485,13 +507,17 @@ def test_fn_astype(self): def test_reshape(self): tensor = Tensor(self.to_tensor([1, 1])) + res = tensor.reshape((1, 2)) assert tensor.shape == (2,) - assert tensor.reshape((1, 2)).shape == (1, 2) + assert res.shape == (1, 2) + assert res.device == tensor.device def test_fn_reshape(self): tensor = Tensor(self.to_tensor([1, 1])) + res = fns.reshape(tensor, (1, 2)) assert tensor.shape == (2,) - assert fns.reshape(tensor, (1, 2)).shape == (1, 2) + assert res.shape == (1, 2) + assert res.device == tensor.device def test_not_implemented(self): with pytest.raises(NotImplementedError, match="is not implemented for"): @@ -521,6 +547,7 @@ def test_fn_unstack(self, x, axis, ref): assert isinstance(res, list) for i, _ in enumerate(ref): assert all(res[i] == ref[i]) + assert res[i].device == tensor.device @pytest.mark.parametrize( "x, axis, ref", @@ -538,13 +565,14 @@ def test_fn_unstack(self, x, axis, ref): ), ) def test_fn_stack(self, x, axis, ref): - tensor = [Tensor(self.to_tensor(i)) for i in x] + list_tensor = [Tensor(self.to_tensor(i)) for i in x] ref = self.to_tensor(ref) - res = fns.stack(tensor, axis=axis) + res = fns.stack(list_tensor, axis=axis) assert isinstance(res, Tensor) assert fns.all(res.data == ref) + assert res.device == list_tensor[0].device def test_fn_moveaxis(self): tensor = [[0, 0, 0], [0, 0, 0]] @@ -591,6 +619,7 @@ def test_fn_mean(self, x, axis, keepdims, ref): assert isinstance(res, Tensor) assert fns.allclose(res.data, ref_tensor) + assert res.device == tensor.device @pytest.mark.parametrize( "val, decimals, ref", @@ -608,6 +637,7 @@ def test_fn_round(self, val, decimals, ref): assert isinstance(res, Tensor) assert fns.allclose(res.data, ref_tensor) + assert res.device == tensor.device @pytest.mark.parametrize( "val, axis, ref", @@ -659,6 +689,7 @@ def test_fn_mean_per_channel(self, val, axis, ref): res = s_fns.mean_per_channel(tensor, axis) assert isinstance(res, Tensor) assert fns.allclose(res, ref_tensor), f"{res.data}" + assert res.device == tensor.device @pytest.mark.parametrize("axis", (3, 4, -4, -5)) def test_fn_mean_per_channel_incorrect_axis(self, axis): From 19640adb3f88c700b321a4b64053d9c83810eff7 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Mon, 2 Oct 2023 19:44:18 +0300 Subject: [PATCH 39/45] functions to tensor namespace --- nncf/experimental/tensor/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nncf/experimental/tensor/__init__.py b/nncf/experimental/tensor/__init__.py index 96c8cb31f82..75ef8294615 100644 --- a/nncf/experimental/tensor/__init__.py +++ b/nncf/experimental/tensor/__init__.py @@ -12,5 +12,6 @@ from nncf.experimental.tensor.enums import TensorBackendType from nncf.experimental.tensor.enums import TensorDataType from nncf.experimental.tensor.enums import TensorDeviceType +from nncf.experimental.tensor.functions import * from nncf.experimental.tensor.tensor import Tensor from nncf.experimental.tensor.tensor import unwrap_tensor_data From 3ac85232cf17665c310acf2e4bb656db15315fb0 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Mon, 2 Oct 2023 19:45:05 +0300 Subject: [PATCH 40/45] update import mean_per_channel --- .../quantization/algorithms/fast_bias_correction/algorithm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nncf/quantization/algorithms/fast_bias_correction/algorithm.py b/nncf/quantization/algorithms/fast_bias_correction/algorithm.py index 78a05fc26bf..3ca368671aa 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/algorithm.py +++ b/nncf/quantization/algorithms/fast_bias_correction/algorithm.py @@ -26,7 +26,7 @@ from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import get_backend -from nncf.experimental.common.tensor_statistics import statistical_functions as s_fns +from nncf.experimental.common.tensor_statistics.statistical_functions import mean_per_channel from nncf.experimental.tensor import Tensor from nncf.experimental.tensor import functions as fns from nncf.quantization.algorithms.algorithm import Algorithm @@ -318,7 +318,7 @@ def _get_bias_shift( engine = EngineFactory.create(model) raw_output = engine.infer(input_blob) q_outputs = self._backend_entity.process_model_output(raw_output, output_name) - q_outputs = s_fns.mean_per_channel(q_outputs, channel_axis) + q_outputs = mean_per_channel(q_outputs, channel_axis) bias_shift = fns.stack(output_fp) - q_outputs return bias_shift From 94ed9a14ed951f981d2d71b50a4d2a524fd2bc64 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Mon, 2 Oct 2023 19:49:58 +0300 Subject: [PATCH 41/45] fix --- nncf/experimental/tensor/functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 5cd670c8f03..6e83c57bfe3 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -12,10 +12,10 @@ import functools from typing import Callable, List, Optional, Tuple, Union -from nncf.experimental.tensor import Tensor -from nncf.experimental.tensor import unwrap_tensor_data from nncf.experimental.tensor.enums import TensorDataType from nncf.experimental.tensor.enums import TensorDeviceType +from nncf.experimental.tensor.tensor import Tensor +from nncf.experimental.tensor.tensor import unwrap_tensor_data def _tensor_guard(func: callable): From 4090048859fdd8e3b784c1e016c2a2d57595bede Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Mon, 2 Oct 2023 21:35:24 +0300 Subject: [PATCH 42/45] linter --- nncf/experimental/tensor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nncf/experimental/tensor/__init__.py b/nncf/experimental/tensor/__init__.py index 75ef8294615..a410a7f0acd 100644 --- a/nncf/experimental/tensor/__init__.py +++ b/nncf/experimental/tensor/__init__.py @@ -12,6 +12,6 @@ from nncf.experimental.tensor.enums import TensorBackendType from nncf.experimental.tensor.enums import TensorDataType from nncf.experimental.tensor.enums import TensorDeviceType -from nncf.experimental.tensor.functions import * +from nncf.experimental.tensor.functions import * # pylint: disable=redefined-builtin from nncf.experimental.tensor.tensor import Tensor from nncf.experimental.tensor.tensor import unwrap_tensor_data From 1feb2f53f2d13a47b5c253f2d4164b21e0999f72 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Wed, 4 Oct 2023 16:54:36 +0300 Subject: [PATCH 43/45] - --- nncf/experimental/tensor/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nncf/experimental/tensor/__init__.py b/nncf/experimental/tensor/__init__.py index a410a7f0acd..96c8cb31f82 100644 --- a/nncf/experimental/tensor/__init__.py +++ b/nncf/experimental/tensor/__init__.py @@ -12,6 +12,5 @@ from nncf.experimental.tensor.enums import TensorBackendType from nncf.experimental.tensor.enums import TensorDataType from nncf.experimental.tensor.enums import TensorDeviceType -from nncf.experimental.tensor.functions import * # pylint: disable=redefined-builtin from nncf.experimental.tensor.tensor import Tensor from nncf.experimental.tensor.tensor import unwrap_tensor_data From 43eeba0e499cdf733b8e9042a856fbf6cec65af7 Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Fri, 6 Oct 2023 02:31:46 +0300 Subject: [PATCH 44/45] dispath_list --- nncf/experimental/tensor/functions.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/nncf/experimental/tensor/functions.py b/nncf/experimental/tensor/functions.py index 6e83c57bfe3..33e55c1ef50 100644 --- a/nncf/experimental/tensor/functions.py +++ b/nncf/experimental/tensor/functions.py @@ -336,8 +336,7 @@ def stack(x: List[Tensor], axis: int = 0) -> Tensor: :return: Stacked Tensor. """ if isinstance(x, List): - unwrapped_x = [i.data for i in x] - return Tensor(_dispatch_list(stack, unwrapped_x, axis=axis)) + return Tensor(_dispatch_list(stack, x, axis=axis)) raise NotImplementedError(f"Function `stack` is not implemented for {type(x)}") @@ -425,14 +424,16 @@ def _binary_reverse_op_nowarn(a: Tensor, b: Union[Tensor, float], operator_fn: C return Tensor(_binary_reverse_op_nowarn(a.data, unwrap_tensor_data(b), operator_fn)) -def _dispatch_list(fn: "functools._SingleDispatchCallable", *args, **kwargs): +def _dispatch_list(fn: "functools._SingleDispatchCallable", tensor_list: List[Tensor], *args, **kwargs): """ - Dispatches the function to the appropriate implementation based on the type of the first element in the list. + Dispatches the function to the type of the wrapped data of the first element in tensor_list. :param fn: A function wrapped by `functools.singledispatch`. + :param tensor_list: List of Tensors. :return: The result value of the function call. """ - return fn.dispatch(type(args[0][0]))(*args, **kwargs) + unwrapped_list = [i.data for i in tensor_list] + return fn.dispatch(type(unwrapped_list[0]))(unwrapped_list, *args, **kwargs) def _initialize_backends(): From d5de2a4b273470ecdef530d124a9f95f7f1711ee Mon Sep 17 00:00:00 2001 From: Alexander Dokuchaev Date: Mon, 9 Oct 2023 14:06:12 +0300 Subject: [PATCH 45/45] typehint --- nncf/quantization/algorithms/fast_bias_correction/backend.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nncf/quantization/algorithms/fast_bias_correction/backend.py b/nncf/quantization/algorithms/fast_bias_correction/backend.py index 91d3010ad12..6dde985c5d4 100644 --- a/nncf/quantization/algorithms/fast_bias_correction/backend.py +++ b/nncf/quantization/algorithms/fast_bias_correction/backend.py @@ -23,6 +23,7 @@ from nncf.common.tensor import NNCFTensor from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase from nncf.common.utils.registry import Registry +from nncf.experimental.tensor import Tensor TModel = TypeVar("TModel") TTensor = TypeVar("TTensor") @@ -113,7 +114,7 @@ def create_input_data( @staticmethod @abstractmethod - def get_bias_value(node: NNCFNode, nncf_graph: NNCFGraph, model: TModel) -> np.ndarray: + def get_bias_value(node: NNCFNode, nncf_graph: NNCFGraph, model: TModel) -> Tensor: """ Returns bias value in the NumPy format of provided node. @@ -149,7 +150,7 @@ def is_quantized_weights(node: NNCFNode, nncf_graph: NNCFGraph) -> bool: @staticmethod @abstractmethod - def process_model_output(raw_data: OutputType, output_name: str) -> NNCFTensor: + def process_model_output(raw_data: OutputType, output_name: str) -> Tensor: """ Returns backend-specific processed output from the model.