From ccf014b5ab4b48eb50f83206c0abf0b61021e0b9 Mon Sep 17 00:00:00 2001 From: dlyakhov Date: Thu, 27 Nov 2025 16:11:40 +0100 Subject: [PATCH] [Collectors] Remove unused legacy collector, move experimental collectors to common --- .../quantization/initialization/range.py | 2 +- .../common/tensor_statistics/aggregator.py | 2 +- .../common/tensor_statistics/collectors.py | 1382 ++++++++++++++--- .../common/tensor_statistics/reduction.py | 74 - .../tensor_statistics/statistic_point.py | 2 +- .../statistical_functions.py | 1 - .../common/tensor_statistics/statistics.py | 315 +++- .../common/tensor_statistics/__init__.py | 10 - .../common/tensor_statistics/collectors.py | 1256 --------------- .../common/tensor_statistics/statistics.py | 299 ---- .../torch/fx/statistics/aggregator.py | 4 +- src/nncf/onnx/statistics/aggregator.py | 4 +- src/nncf/onnx/statistics/collectors.py | 18 +- src/nncf/openvino/statistics/aggregator.py | 6 +- src/nncf/openvino/statistics/collectors.py | 38 +- .../algorithms/bias_correction/algorithm.py | 2 +- .../algorithms/bias_correction/backend.py | 10 +- .../bias_correction/onnx_backend.py | 2 +- .../bias_correction/openvino_backend.py | 2 +- .../bias_correction/torch_fx_backend.py | 2 +- .../algorithms/channel_alignment/backend.py | 6 +- .../channel_alignment/openvino_backend.py | 13 +- .../fast_bias_correction/algorithm.py | 2 +- .../fast_bias_correction/backend.py | 6 +- .../fast_bias_correction/onnx_backend.py | 2 +- .../fast_bias_correction/openvino_backend.py | 2 +- .../fast_bias_correction/torch_backend.py | 2 +- .../fast_bias_correction/torch_fx_backend.py | 2 +- .../algorithms/layerwise/backend.py | 6 +- .../algorithms/layerwise/openvino_backend.py | 4 +- .../algorithms/min_max/algorithm.py | 8 +- .../algorithms/min_max/backend.py | 2 +- .../algorithms/min_max/onnx_backend.py | 4 +- .../algorithms/min_max/openvino_backend.py | 2 +- .../algorithms/min_max/torch_backend.py | 4 +- .../algorithms/min_max/torch_fx_backend.py | 4 +- .../algorithms/smooth_quant/algorithm.py | 8 +- .../algorithms/smooth_quant/backend.py | 6 +- .../algorithms/smooth_quant/onnx_backend.py | 2 +- .../weight_compression/activation_stats.py | 2 +- .../weight_compression/algorithm.py | 2 +- .../algorithms/weight_compression/awq.py | 2 +- .../algorithms/weight_compression/backend.py | 25 +- .../weight_compression/lora_correction.py | 2 +- .../weight_compression/onnx_backend.py | 10 +- .../weight_compression/openvino_backend.py | 14 +- .../weight_compression/scale_estimation.py | 2 +- .../weight_compression/torch_backend.py | 10 +- .../weight_compression/torch_fx_backend.py | 10 +- src/nncf/quantization/fake_quantize.py | 2 +- .../function_hook/statistics/aggregator.py | 4 +- src/nncf/torch/quantization/algo.py | 4 +- src/nncf/torch/quantization/init_range.py | 14 +- src/nncf/torch/statistics/aggregator.py | 2 +- src/nncf/torch/tensor_statistics/algo.py | 9 +- .../torch/tensor_statistics/collectors.py | 42 +- .../torch/tensor_statistics/statistics.py | 8 +- .../test_reducers_and_aggregators.py | 40 +- .../test_statistic_collector.py | 20 +- tests/common/test_statistics_aggregator.py | 6 +- .../test_tensor_collector_batch_size.py | 4 +- .../template_test_nncf_tensor.py | 2 +- .../test_calculate_quantizer_parameters.py | 2 +- .../test_templates/test_channel_alignment.py | 6 +- .../test_templates/test_ptq_params.py | 14 +- .../test_templates/test_quantizer_config.py | 16 +- .../test_templates/test_smooth_quant.py | 4 +- .../test_templates/test_statistics_caching.py | 4 +- .../test_weights_compression_backends.py | 14 +- tests/onnx/quantization/common.py | 4 +- .../test_reducers_and_aggregators.py | 18 +- .../test_tensor_collector_batch_size.py | 8 +- tests/onnx/test_statistics_aggregator.py | 2 +- .../test_reducers_and_aggregators.py | 2 +- .../quantization/test_weights_compression.py | 2 +- .../native/test_statistic_collector.py | 2 +- .../native/test_statistics_aggregator.py | 2 +- .../test_tensor_collector_batch_size.py | 6 +- .../test_tensor_statistics.py | 12 +- tests/torch/test_statistics_aggregator.py | 2 +- .../test_calculation_quantizer_params.py | 2 +- .../test_reducers_and_aggregators.py | 20 +- .../quantization/test_statistic_collector.py | 2 +- .../test_tensor_collector_batch_size.py | 8 +- .../fx/test_calculation_quantizer_params.py | 2 +- tests/torch2/fx/test_statistics_aggregator.py | 2 +- 86 files changed, 1655 insertions(+), 2253 deletions(-) delete mode 100644 src/nncf/common/tensor_statistics/reduction.py rename src/nncf/{experimental => }/common/tensor_statistics/statistical_functions.py (99%) delete mode 100644 src/nncf/experimental/common/tensor_statistics/__init__.py delete mode 100644 src/nncf/experimental/common/tensor_statistics/collectors.py delete mode 100644 src/nncf/experimental/common/tensor_statistics/statistics.py rename tests/common/{experimental => }/test_reducers_and_aggregators.py (94%) rename tests/common/{experimental => }/test_statistic_collector.py (95%) rename tests/common/{experimental => }/test_tensor_collector_batch_size.py (95%) diff --git a/src/nncf/common/quantization/initialization/range.py b/src/nncf/common/quantization/initialization/range.py index 4e93309d857..2bb4fc2f663 100644 --- a/src/nncf/common/quantization/initialization/range.py +++ b/src/nncf/common/quantization/initialization/range.py @@ -16,9 +16,9 @@ from nncf.common.initialization.dataloader import NNCFDataLoader from nncf.common.quantization.structs import QuantizationScheme from nncf.common.quantization.structs import QuantizerGroup +from nncf.common.tensor_statistics.collectors import AggregationAxes from nncf.common.tensor_statistics.collectors import ReductionAxes from nncf.config.schemata.defaults import NUM_INIT_SAMPLES -from nncf.experimental.common.tensor_statistics.collectors import AggregationAxes class RangeInitConfig: diff --git a/src/nncf/common/tensor_statistics/aggregator.py b/src/nncf/common/tensor_statistics/aggregator.py index d8b902ae3ca..91a7f23f428 100644 --- a/src/nncf/common/tensor_statistics/aggregator.py +++ b/src/nncf/common/tensor_statistics/aggregator.py @@ -23,11 +23,11 @@ from nncf.common.logging import nncf_logger from nncf.common.logging.track_progress import track from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer +from nncf.common.tensor_statistics.statistics import TensorStatistic from nncf.common.tensor_statistics.statistics_serializer import dump_statistics from nncf.common.tensor_statistics.statistics_serializer import load_statistics from nncf.common.utils.backend import BackendType from nncf.data.dataset import Dataset -from nncf.experimental.common.tensor_statistics.statistics import TensorStatistic from nncf.tensor import Tensor TensorType = TypeVar("TensorType") diff --git a/src/nncf/common/tensor_statistics/collectors.py b/src/nncf/common/tensor_statistics/collectors.py index c93f014250a..5a8aba9ba23 100644 --- a/src/nncf/common/tensor_statistics/collectors.py +++ b/src/nncf/common/tensor_statistics/collectors.py @@ -9,370 +9,1246 @@ # See the License for the specific language governing permissions and # limitations under the License. +# This file includes code from PyTorch project https://github.com/pytorch/pytorch/blob/v2.8.0/torch/ao/quantization/observer.py +# The original license is: BSD-3-Clause, https://github.com/pytorch/pytorch/blob/main/LICENSE + from abc import ABC from abc import abstractmethod +from collections import defaultdict from collections import deque -from typing import Any, Optional, Union, cast - -import numpy as np -from numpy.typing import NDArray +from copy import deepcopy +from enum import Enum +from typing import Any, Optional, TypeVar, Union -from nncf.common.tensor import NNCFTensor +import nncf.tensor +import nncf.tensor.functions as fns from nncf.common.tensor import TensorType -from nncf.common.tensor_statistics.reduction import get_per_channel_history - +from nncf.common.tensor_statistics.statistical_functions import mean_per_channel +from nncf.common.tensor_statistics.statistics import MedianMADTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import TensorStatistic +from nncf.quantization.range_estimator import AggregatorType +from nncf.quantization.range_estimator import StatisticsType +from nncf.tensor import Tensor +from nncf.tensor.definitions import TensorDataType + +InplaceInsertionFNType = TypeVar("InplaceInsertionFNType") ReductionAxes = tuple[int, ...] +AggregationAxes = tuple[int, ...] +Axes = tuple[int, ...] + + +class AxesMode(Enum): + """ + Represents different strategies for handling tensor axes. + + :param REDUCTION: Indicates that the specified axes should be reduced during an operation. + :param KEEP: Indicates that the specified axes should be preserved and not reduced during + an operation. + """ + + REDUCTION = "reduction" + KEEP = "keep" + + +def determine_reduction_axes( + ndim: int, axes: Optional[Axes] = None, axes_mode: AxesMode = AxesMode.REDUCTION +) -> ReductionAxes: + """ + Determines the set of axes along which a reduction operation should be performed + based on the specified axes mode. + + :param ndim: The number of dimensions in the input tensor. + :param axes: The axes specified for the reduction operation. If `None`, all axes + are considered (i.e., `tuple(range(ndim))`). + + :param axes_mode: Defines how the specified axes are interpreted: + - `AxesMode.REDUCTION`: the given axes will be reduced. + - `AxesMode.KEEP`: all axes except the specified ones will be reduced. + :return: The resolved set of axes along which the reduction operation should be performed. + """ + if axes is None: + return tuple(range(ndim)) + + if axes_mode == AxesMode.REDUCTION: + return axes + + all_axes = tuple(range(ndim)) + if len(all_axes) > 1: + # Ensure that all axes have positive values + keep_axes = tuple(all_axes[i] for i in axes) + return tuple(set(all_axes) - set(keep_axes)) + return () -class TensorStatisticCollectorBase(ABC): - """Collector estimate statistics at the quantization point based on the provided reduction shape.""" +class TensorReducerBase(ABC): + """ + Tensor reducer is a callable object that reduces tensors according to + the specified rule. Could handle tensors inplace or out of place. + """ - def __init__(self, reduction_shape: Optional[ReductionAxes] = None, num_samples: Optional[int] = None): + def __init__( + self, + axes: Optional[Axes] = None, + axes_mode: AxesMode = AxesMode.REDUCTION, + inplace: bool = False, + ): """ - Initializes Tensor Statistic Collector + :param axes: The axes along which the reduction operation should be applied. + If `None`, the operation will be applied to all axes (i.e., `tuple(range(tensor.ndim))`). + :param axes_mode: Determines how the specified `axes` are treated during the operation. + Use `AxesMode.REDUCTION` to reduce over the given axes, or `AxesMode.KEEP` to preserve them. + :param inplace: Whether should be calculated inplace or out of place. + """ + self._axes = axes + self._axes_mode = axes_mode + self._inplace = inplace + self._keepdims = True + + @property + def inplace(self): + return self._inplace - :param reduction_shape: Shape that defines tensor dimensions to reduce. - :param num_samples: Maximum number of samples to collect. + @property + def output_port_id(self) -> int: """ - self._reduction_shape = reduction_shape - self._enabled = True - self._collected_samples = 0 + Port id of the last node of the reducer subgraph if statistic is inplace. + Port id of the reducer output return node if statistic is not inplace. + """ + return 0 + + @property + def name(self): + return self.__class__.__name__ + str(self.__hash__()) + + @abstractmethod + def _reduce_out_of_place(self, x: list[TensorType]) -> list[TensorType]: + """ + Specifies the reduction rule. + + :param x: Tensor to register. + """ + + def get_inplace_fn(self) -> Optional[InplaceInsertionFNType]: + """ + Returns correspondent inplace operation builder if inplace operations are available in backend. + + :return: Inplace operation builder if possible else None. + """ + return None + + def __call__(self, x: list[Tensor]): + if any(t.isempty() for t in x): + return None + + if self.inplace: + return x + + return self._reduce_out_of_place(x) + + def __eq__(self, __o: object) -> bool: + return ( + isinstance(__o, self.__class__) + and self._axes == __o._axes + and self._axes_mode == __o._axes_mode + and self._inplace == __o.inplace + ) + + def __hash__(self) -> int: + return hash((self.__class__.__name__, self.inplace, self._axes, self._axes_mode)) + + +class AggregatorBase: + """ + Aggregator is designed to receive (register) calculated statistics and aggregate them. + """ + + def __init__( + self, + aggregation_axes: Optional[AggregationAxes] = None, + num_samples: Optional[int] = None, + window_size: Optional[int] = None, + ): + """ + :param aggregation_axes: Axes along which to operate. + Registered statistics are stacked along zero axis, + axes >=1 correspond to received statistic axes shifted left by 1. + :param num_samples: Maximum number of samples to collect. Aggregator + skips tensor registration if tensor registration was called num_samples times before. + Aggregator never skips registration if num_samples is None. + :param window_size: Number of samples from the end of the list of collected samples to aggregate. + Aggregates all available collected statistics in case parameter is None. + """ + self._aggregation_axes = (0,) if aggregation_axes is None else aggregation_axes + self._keepdims = True self._num_samples = num_samples + self._collected_samples = 0 + self._window_size = window_size + self._container = deque(maxlen=window_size) @property - def num_samples(self) -> Union[int, None]: + def num_samples(self) -> int: return self._num_samples - def register_input(self, x: TensorType) -> TensorType: - """Registers input tensor""" - if not self._enabled: - return x + def register_reduced_input(self, x: TensorType): if self._num_samples is not None and self._collected_samples >= self._num_samples: - return x - if self._reduction_shape is None: - self._reduction_shape = tuple(range(len(cast(NNCFTensor, x).shape))) - self._register_input(x) + return + self._register_reduced_input_impl(x) self._collected_samples += 1 - return x @abstractmethod - def _register_input(self, x: TensorType) -> None: - pass + def _register_reduced_input_impl(self, x: TensorType) -> None: + """ + Registers incoming tensor in tensor aggregator. + + :param x: Tensor to register. + """ + + def aggregate(self) -> Any: + """ + Aggregates collected tensors and returns aggregated result. + In case no tensors were collected returns None. - def get_statistics(self) -> Any: - """Returns collected statistics, if present.""" - if self._collected_samples == 0: - raise StatisticsNotCollectedError() - return self._get_statistics() + :return: Aggregated result. + """ + if self._collected_samples: + return self._aggregate_impl() + return None @abstractmethod - def _get_statistics(self) -> Any: - pass + def _aggregate_impl(self) -> Any: + """ + Aggregates collected tensors and returns aggregated result. + + :return: Aggregated result. + """ + + def reset(self): + self._collected_samples = 0 + self._container = [] + + def __eq__(self, __o: object) -> bool: + return isinstance(__o, self.__class__) and self._num_samples == __o.num_samples + + def __hash__(self) -> int: + return hash(self.__class__.__name__) + + +class TensorCollector: + """ + Calculates statistics at given tensors according to registered statistic branches. + Statistic branch consists of one reducer and one aggregator instance. TensorCollector + applies a reducer on a correspondent inputs and then passes the one of the reduced tensors + chosen by output port id to a correspondent aggregator for each registered statistic branch. + Receives tensors by `register_input` method. Aggregated values as a TensorStatistic instance or + a dict could be collected by `get_statistics` call. + """ + + def __init__(self, statistic_container: Optional[type[TensorStatistic]] = None) -> None: + self._reducers: set[TensorReducerBase] = set() + self._aggregators: dict[tuple[int, int, int], AggregatorBase] = {} + self._stat_container_kwargs_map: dict[str, tuple[int, int, int]] = {} + self._stat_container = statistic_container + self.enable() + self.clear_cache() + + @property + def num_samples(self) -> Optional[int]: + output = None + for aggregator in self._aggregators.values(): + if aggregator.num_samples and output: + output = max(output, aggregator.num_samples) + else: + output = aggregator.num_samples + return output + + @property + def enabled(self) -> bool: + return self._enabled - def enable(self) -> None: + @property + def reducers(self): + return self._reducers.copy() + + @property + def aggregators(self): + return self._aggregators.copy() + + def enable(self): self._enabled = True - def disable(self) -> None: + def disable(self): self._enabled = False - def reset(self) -> None: - """Resets all the statistics in the collector.""" - self._collected_samples = 0 - self._reset() + def register_statistic_branch( + self, + container_key: str, + reducer: TensorReducerBase, + aggregator: AggregatorBase, + reducer_output_port_id: int = 0, + ) -> None: + """ + Registers statistic collection branch for a container key. Correspondent input will be reduced + by given reducer and reduced value will be registered and aggregated by given aggregator. + Passed container key should be unique for the TensorCollector instance. + Passed aggregator instance should never be used twice for one TensorCollector instance. + + :param container_key: Container key to pass aggregated statistic to. + :param reducer: TensorReducer instance for the statistic collection branch. + :param aggregator: TensorAggregator instance for the statistic collection branch. + :reducer_output_port_id: Reducer target output port id. + """ + if container_key in self._stat_container_kwargs_map: + msg = f"Two different statistic branches for one container key {container_key} are encountered" + raise nncf.InternalError(msg) + if any(aggr is aggregator for aggr in self._aggregators.values()): + msg = f"One aggregator instance {aggregator} for different branches is encountered" + raise nncf.InternalError(msg) - @abstractmethod - def _reset(self) -> None: - pass + self._reducers.add(reducer) + key = (hash(reducer), reducer_output_port_id, hash(aggregator)) - def collected_samples(self) -> int: - return self._collected_samples + if key not in self._aggregators: + self._aggregators[key] = aggregator + self._stat_container_kwargs_map[container_key] = key + def register_inputs(self, inputs: dict[int, list[Tensor]]) -> None: + """ + Registers given input in TensorCollector. -class StatisticsNotCollectedError(Exception): - """Raised when the statistics are not collected but requested.""" + :param inputs: Tensor inputs in format of dict where keys + are reducer names and values are correspondent input tensors + """ + if not self.enabled: + return + reduced_inputs = {} + for reducer in self._reducers: + reducer_hash = hash(reducer) + input_ = inputs[reducer_hash] + reduced_input = reducer(input_) + if reduced_input is not None: + reduced_inputs[reducer_hash] = reduced_input + + for ( + (reducer_hash, reducer_port_id, _), + aggregator, + ) in self._aggregators.items(): + if reducer_hash in reduced_inputs: + aggregator.register_reduced_input(reduced_inputs[reducer_hash][reducer_port_id]) + + def register_input_for_all_reducers(self, input_: Tensor) -> None: + """ + Registers given input_ in each available statistic collection branch. + :param input_: Tensor input to register. + """ + self.register_inputs({hash(reducer): [input_] for reducer in self._reducers}) + + def _aggregate(self) -> None: + result = {} + for ( + key, + aggregator, + ) in self._aggregators.items(): + val = aggregator.aggregate() + result[key] = val + return result + + def set_cache(self, statistics: TensorStatistic) -> None: + """ + Sets cached statistics from given config and disable TensorCollector. + :param statistics: TensorStatistic. + """ + self._cached_statistics = statistics + self.reset() + self.disable() -class OnlineTensorStatisticCollector(TensorStatisticCollectorBase): - """Base class for collectors that collects statistics in online regime, without storing the data.""" + def create_statistics_container(self, config: dict[str, Any]) -> TensorStatistic: + """ + Returns a TensorStatistic instance with aggregated values. + :param config: Aggregated values. + :return: TensorStatistic instance. + """ + if not self._stat_container: # TODO(kshpv): need to remove an ability to return a Dict. + return config + return self._stat_container.from_config(config) -class OfflineTensorStatisticCollector(TensorStatisticCollectorBase): - """Collects statistics in offline regime by storing the data and aggregating it afterwards.""" + def clear_cache(self) -> None: + """ + Clears the cached statistics and enables TensorCollector. + """ + self._cached_statistics = None - def __init__( - self, reduction_shape: Optional[ReductionAxes] = None, num_samples: int = None, window_size: int = None - ): - super().__init__(reduction_shape, num_samples) - self._samples: deque[int] = deque(maxlen=window_size) + def get_statistics(self) -> TensorStatistic: + """ + Returns aggregated values in format of a TensorStatistic instance or + a dict. - def _reset(self) -> None: - self._samples.clear() + :return: Aggregated values. + """ + if self._cached_statistics is not None: + return deepcopy(self._cached_statistics) + aggregated_values = self._aggregate() + statistics_config = {} + for container_key, branch_key in self._stat_container_kwargs_map.items(): + statistics_config[container_key] = aggregated_values[branch_key] + return self.create_statistics_container(statistics_config) -class MinMaxStatisticCollector(OnlineTensorStatisticCollector): - """Collector estimates min of minimum values and max of maximum values.""" + def replace_aggregator(self, key: tuple[int, int, int], aggregator: AggregatorBase) -> None: + """ + Friend method that replaces aggregator instance on equivalent one. + Key should be valid for for given aggregator and a statistic branch + with key should be present in TensorCollector. - def __init__(self, use_abs_max: bool, reduction_shape: ReductionAxes, num_samples: int = None): - super().__init__(reduction_shape, num_samples) - self._use_abs_max = use_abs_max - self._tensor_processor = self._get_processor() + :param key: Statistic branch key. + :param aggregator: Aggregator instance to replace existing instance by given key. + """ + assert key in self._aggregators + assert key[2] == hash(aggregator) + self._aggregators[key] = aggregator - self._min_values = None - self._max_values = None + def reset(self): + for aggregator in self._aggregators.values(): + aggregator.reset() @staticmethod - @abstractmethod - def _get_processor() -> Any: - pass + def get_tensor_collector_inputs( + outputs: dict[str, Tensor], output_info: list[tuple[int, list[str]]] + ) -> dict[int, list[Tensor]]: + """ + Static method that converts all model outputs and collected output_info + to a layout required for `register_inputs` method. This method is not a part of + `register_inputs` to avoid all inputs passing to `TensorCollector.register_inputs` method. - def _register_input_common(self, x: NNCFTensor) -> None: - min_reduced = self._tensor_processor.reduce_min(x, self._reduction_shape) - if self._use_abs_max: - x = self._tensor_processor.abs(x) - max_reduced = self._tensor_processor.reduce_max(x, self._reduction_shape) + :param outputs: Target model outputs. + :param output_info: Output info collected by a `TensorCollector.get_output_info` method. + :returns: Model outputs in a format required by `TensorCollector.register_inputs` method. + """ + target_inputs = {} + for reducer, names in output_info: + target_inputs[reducer] = [outputs[name] for name in names] + return target_inputs - if self._min_values is None: - self._min_values = min_reduced - else: - self._min_values = self._tensor_processor.min(min_reduced, self._min_values) - if self._max_values is None: - self._max_values = max_reduced - else: - self._max_values = self._tensor_processor.max(max_reduced, self._max_values) +class MergedTensorCollector(TensorCollector): + """ + Tensor collector that merge several tensor collectors in one. + Statistics collected by a merged tensor collector automatically available + in all tensor collectors that were merged by the merged tensor collector. + This works because merged tensor collectors share tensor aggregators instances with + the merged tensor collector. + """ - def _reset(self) -> None: - self._min_values = None - self._max_values = None + def __init__(self, tensor_collectors: list[TensorCollector]) -> None: + """ + :param tensor_collectors: Tensor collectors to merge. + """ + super().__init__() + aggregators: dict[tuple[int, int, int], list[tuple[TensorCollector, AggregatorBase]]] = defaultdict(list) + for tensor_collector in tensor_collectors: + if not tensor_collector.enabled: + continue + self._reducers.update(tensor_collector.reducers) + for key, aggregator in tensor_collector.aggregators.items(): + aggregators[key].append((tensor_collector, aggregator)) + for key, aggregators_to_merge in aggregators.items(): + _, unique_aggregator = aggregators_to_merge[0] + for tensor_collector, _ in aggregators_to_merge[1:]: + tensor_collector.replace_aggregator(key, unique_aggregator) + self._aggregators[key] = unique_aggregator + + +################################################## +# Reducers +################################################## + + +class RawReducer(TensorReducerBase): + def __init__(self): + super().__init__(inplace=False) + + def get_inplace_fn(self) -> Optional[InplaceInsertionFNType]: + return None + + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + return x + + +class ShapeReducer(TensorReducerBase): + def __init__(self, inplace: bool = False): + super().__init__(inplace=inplace) + + def _reduce_out_of_place(self, x: list[TensorType]) -> list[TensorType]: + # Return as tensor for consistency, because in-place reducer returns a tensor + return [fns.tensor(x[0].shape, backend=x[0].backend, dtype=TensorDataType.int32, device=x[0].device)] + + def get_inplace_fn(self) -> Optional[InplaceInsertionFNType]: + return None + + +class MinReducer(TensorReducerBase): + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + x = x[0] + reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) + return [fns.min(x, reduction_axes, keepdims=self._keepdims)] -class MinMaxOfflineStatisticCollectorBase(OfflineTensorStatisticCollector): - """ - Base class for collectors that aggregate statistics - from minimum and maximum values of tensors. - """ +class MaxReducer(TensorReducerBase): + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + x = x[0] + reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) + return [fns.max(x, reduction_axes, keepdims=self._keepdims)] + + +class AbsMaxReducer(TensorReducerBase): + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + x = fns.abs(x[0]) + reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) + return [fns.max(x, reduction_axes, keepdims=self._keepdims)] + + +class MeanReducer(TensorReducerBase): + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + x = x[0] + reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) + return [fns.mean(x, reduction_axes, keepdims=self._keepdims)] + + +class MeanVarianceReducer(TensorReducerBase): + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + x = x[0] + reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) + variance = fns.var(x, reduction_axes) + return [fns.mean(variance)] + + +class MaxVarianceReducer(TensorReducerBase): + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + x = x[0] + reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) + variance = fns.var(x, reduction_axes) + return [fns.max(variance)] + + +class MeanAbsMaxReducer(TensorReducerBase): + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + x = fns.abs(x[0]) + reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) + abs_max = fns.max(x, reduction_axes, keepdims=self._keepdims) + return [fns.mean(abs_max)] + + +class QuantileReducerBase(TensorReducerBase): def __init__( self, - use_per_sample_stats: bool, - use_abs_max: bool, - reduction_shape: ReductionAxes, - num_samples: int = None, - window_size: int = None, + axes: Optional[Axes] = None, + axes_mode: AxesMode = AxesMode.REDUCTION, + quantile: Optional[Union[float, tuple[float]]] = None, + inplace: bool = False, ): - super().__init__(reduction_shape, num_samples) - self._use_per_sample_stats = use_per_sample_stats - self._use_abs_max = use_abs_max - self._tensor_processor = self._get_processor() + super().__init__(axes, axes_mode, False) + self._quantile = (0.01, 0.99) if quantile is None else quantile - self._all_min_values: deque[int] = deque(maxlen=window_size) - self._all_max_values: deque[int] = deque(maxlen=window_size) + def __eq__(self, __o: object) -> bool: + return super().__eq__(__o) and self._quantile == __o._quantile - @staticmethod - @abstractmethod - def _get_processor() -> Any: - pass + def __hash__(self) -> int: + return hash((self.__class__.__name__, self.inplace, self._axes, self._axes_mode, tuple(self._quantile))) - def _register_input_common(self, x: NNCFTensor) -> None: - min_reduced = self._tensor_processor.reduce_min(x, self._reduction_shape) - if self._use_abs_max: - x = self._tensor_processor.abs(x) - max_reduced = self._tensor_processor.reduce_max(x, self._reduction_shape) - if self._use_per_sample_stats: - self._all_min_values.extend(self._tensor_processor.unstack(min_reduced)) - self._all_max_values.extend(self._tensor_processor.unstack(max_reduced)) - else: - self._all_min_values.append(min_reduced) - self._all_max_values.append(max_reduced) +class QuantileReducer(QuantileReducerBase): + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + x = x[0] + reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) + return fns.quantile(x, self._quantile, reduction_axes, keepdims=self._keepdims) - @abstractmethod - def _min_aggregate(self) -> None: - pass - @abstractmethod - def _max_aggregate(self) -> None: - pass +class AbsQuantileReducer(QuantileReducerBase): + def __init__( + self, + axes: Optional[Axes] = None, + axes_mode: AxesMode = AxesMode.REDUCTION, + quantile: Optional[Union[float, tuple[float]]] = None, + inplace: bool = False, + ): + quantile = (0.99,) if quantile is None else quantile + super().__init__(axes, axes_mode, quantile) + + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + x = fns.abs(x[0]) + reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) + return fns.quantile(x, self._quantile, reduction_axes, keepdims=self._keepdims) + + +class BatchMeanReducer(TensorReducerBase): + def __init__(self, inplace: bool = False): + super().__init__(None, inplace) + + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + return [fns.mean(x[0], axis=0, keepdims=True)] + + +class MeanPerChReducer(TensorReducerBase): + def __init__(self, channel_axis: int = 1, inplace: bool = False): + super().__init__(inplace=inplace) + self._channel_axis = channel_axis + + def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: + return [mean_per_channel(x[0], self._channel_axis)] + + def __eq__(self, __o: object) -> bool: + return super().__eq__(__o) and self._channel_axis == __o._channel_axis + + def __hash__(self) -> int: + return hash((self.__class__.__name__, self.inplace, self._axes, self._axes_mode, self._channel_axis)) + - def _reset(self) -> None: - self._all_min_values.clear() - self._all_max_values.clear() +################################################## +# Aggregators +################################################## -class MixedMinMaxStatisticCollector(MinMaxOfflineStatisticCollectorBase): +class NoopAggregator(AggregatorBase): + def __init__(self, num_samples: Optional[int], return_first: bool = False): + """ + Creates an aggregator that only accumulates data without any additional processing. + :param num_samples: The number of samples to collect. If None, all samples are collected. + :param return_first: If True, the first collected sample is returned on aggregate call. + If False, all collected samples are returned as a list. + """ + if return_first and num_samples is not None and num_samples != 1: + msg = "NoopAggregator with return_first=True should not have num_samples > 1" + raise nncf.InternalError(msg) + super().__init__(None, num_samples=1 if return_first else num_samples) + self._return_first = return_first + + def _register_reduced_input_impl(self, x: TensorType) -> None: + self._container.append(x) + + def _aggregate_impl(self): + return self._container[0] if self._return_first else self._container + + +class OnlineAggregatorBase(AggregatorBase, ABC): """ - Collector aggregates (min or mean) of minimum values and (max or mean) of maximum values. + Base class for aggregators which are using aggregation function fn with following property: + fn([x1, x2, x3]) == fn([fn([x1, x2]), x3]) where x1, x2, x3 are samples to aggregate. + Online aggregation fn([fn([x1, x2]), x3]) allows to keep memory stamp low as only + one sample is stored during statistic collection. """ - def __init__( - self, - use_per_sample_stats: bool, - use_abs_max: bool, - use_means_of_mins: bool, - use_means_of_maxs: bool, - reduction_shape: ReductionAxes, - num_samples: int = None, - window_size: int = None, - ): - super().__init__(use_per_sample_stats, use_abs_max, reduction_shape, num_samples, window_size) - self._use_means_of_mins = use_means_of_mins - self._use_means_of_maxs = use_means_of_maxs + def _register_reduced_input_impl(self, x: Tensor) -> None: + online_aggregation_axes = tuple(dim - 1 for dim in self._aggregation_axes if dim != 0) + if online_aggregation_axes: + reduced = self._aggregation_fn(x, axis=online_aggregation_axes, keepdims=self._keepdims) + else: + reduced = x + if 0 in self._aggregation_axes: + stacked_tensors = fns.stack([reduced, *self._container], axis=0) + aggregated = self._aggregation_fn(stacked_tensors, axis=0, keepdims=self._keepdims) + aggregated = fns.squeeze(aggregated, 0) + self._container = [aggregated] + else: + self._container.append(reduced) + + def _aggregate_impl(self) -> Tensor: + if 0 in self._aggregation_axes and self._keepdims: + return self._container[0] + return fns.stack(self._container) + + @abstractmethod + def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: + pass + + +class MinAggregator(OnlineAggregatorBase): + def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: + return fns.min(stacked_value, axis=axis, keepdims=keepdims) - def _min_aggregate(self) -> Any: - stacked_min = self._tensor_processor.stack(self._all_min_values) - if self._use_means_of_mins: - return self._tensor_processor.mean(stacked_min, axis=0) - return self._tensor_processor.reduce_min(stacked_min, axis=0) - def _max_aggregate(self) -> Any: - stacked_max = self._tensor_processor.stack(self._all_max_values) - if self._use_means_of_maxs: - return self._tensor_processor.mean(stacked_max, axis=0) - return self._tensor_processor.reduce_max(stacked_max, axis=0) +class MaxAggregator(OnlineAggregatorBase): + def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: + return fns.max(stacked_value, axis=axis, keepdims=keepdims) -class MeanMinMaxStatisticCollector(MinMaxOfflineStatisticCollectorBase): +def _move_axes_flatten_cat( + tensor_list: list[Tensor], aggregation_axes: tuple[int, ...] +) -> tuple[Tensor, tuple[int, ...]]: """ - Collector aggregates mean of minimum values and mean of maximum values. + Moves aggregation axes to the beginning of the tensor shape for each tensor from the list, flattens + and concatenates them in 0 dimension. Computes target shape for the processed tensor + after an aggregation function is applied to it. Target shape preserves original order + of dimensions and replaces aggregated dimensions by 1. + + :param tensor_list: Tensor list to process. + :param aggregation_axes: Aggregation axes to move, flatten and concatenate. + :return: Tuple of the processed tensor and + target shape for the processed tensor after an aggregation function is applied to it. """ + tensor_shape = list(tensor_list[0].shape) - def _min_aggregate(self) -> Any: - stacked_min = self._tensor_processor.stack(self._all_min_values) - return self._tensor_processor.mean(stacked_min, axis=0) + # Transpose dims to move aggregation axes forward + transpose_dims = list(range(len(tensor_shape))) + for idx, axis in enumerate(aggregation_axes): + transpose_dims[axis], transpose_dims[idx] = transpose_dims[idx], transpose_dims[axis] - def _max_aggregate(self) -> Any: - stacked_max = self._tensor_processor.stack(self._all_max_values) - return self._tensor_processor.mean(stacked_max, axis=0) + # Shape to flatten aggregation axes + reshape_shape = [-1] + [tensor_shape[dim] for dim in transpose_dims][len(aggregation_axes) :] + reshaped_tensors = [] + for tensor in tensor_list: + transposed_t = fns.transpose(tensor, transpose_dims) + reshaped_tensors.append(fns.reshape(transposed_t, reshape_shape)) -class MeanStatisticCollector(OfflineTensorStatisticCollector): + shape_after_aggregation = tuple(1 if idx in aggregation_axes else dim for idx, dim in enumerate(tensor_shape)) + return fns.concatenate(reshaped_tensors, axis=0), shape_after_aggregation + + +class OfflineAggregatorBase(AggregatorBase, ABC): """ - Collector that aggregates statistics as mean along a pre-assigned axis. + Base class for aggregators which are using aggregation function fn which + does not fulfill property fn([x1, x2, x3]) == fn([fn([x1, x2]), x3]) + where x1, x2, x3 are samples to aggregate. Child aggregators collect + all samples in a container and aggregate them in one step. """ - def __init__(self, channel_axis: int, num_samples: Optional[int] = None, window_size: Optional[int] = None) -> None: - """ - :param channel_axis: The main axis for the reduction while statistics collection. - :param num_samples: Optional parameter for statistic collection that regulates - the number of samples that will be processed. - :param window_size: Optional maximum length for the statistic collection - """ - super().__init__(num_samples=num_samples) - self._channel_axis = channel_axis - self._tensor_processor = self._get_processor() - self._all_values: deque[int] = deque(maxlen=window_size) - self._all_shapes: deque[int] = deque(maxlen=window_size) + def _register_reduced_input_impl(self, x: TensorType) -> None: + self._container.append(x) + + def _aggregate_impl(self) -> Tensor: + # Case when all registered tensors have identical shape + if all(self._container[0].shape == x.shape for x in self._container): + stacked_value = fns.stack(self._container) + aggregated = self._aggregation_fn(stacked_value, axis=self._aggregation_axes, keepdims=self._keepdims) + return fns.squeeze(aggregated, 0) + online_axes = tuple(x - 1 for x in self._aggregation_axes if x > 0) + + # Case when some registered tensors have different shapes and + # 0 is present in the aggregation axes + if 0 in self._aggregation_axes: + stacked_value, shape_after_aggregation = _move_axes_flatten_cat(self._container, online_axes) + aggregated = self._aggregation_fn(stacked_value, axis=0, keepdims=False) + if self._keepdims: + aggregated = fns.reshape(aggregated, shape_after_aggregation) + return aggregated + + # Case when some registered tensors have different shapes and + # 0 is not present in the aggregation axes + ret_val = [] + for tensor in self._container: + ret_val.append(self._aggregation_fn(tensor, axis=online_axes, keepdims=self._keepdims)) + return fns.stack(ret_val, axis=0) - @staticmethod @abstractmethod - def _get_processor() -> Any: + def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: pass - def _register_input_common(self, x: NNCFTensor) -> None: - if self._channel_axis == 0: - self._all_values.append(self._tensor_processor.batch_mean(x)) - else: - self._all_values.append(self._tensor_processor.mean_per_channel(x, self._channel_axis)) - self._all_shapes.append(cast(int, x.shape)) - def _reset(self) -> None: - self._all_values.clear() - self._all_shapes.clear() +class MeanAggregator(OfflineAggregatorBase): + def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: + return fns.mean(stacked_value, axis=axis, keepdims=keepdims) - def _mean_aggregate(self) -> Any: - all_values_stack = self._tensor_processor.stack(self._all_values) - return self._tensor_processor.mean(all_values_stack, 0) - def _shape(self) -> Any: - return self._all_shapes[0] +class MedianAggregator(OfflineAggregatorBase): + def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: + return fns.median(stacked_value, axis=axis, keepdims=keepdims) -class RawStatisticCollector(OfflineTensorStatisticCollector): - """ - Collects tensor samples, where each tensor represented in raw format. - Each sample stays available for usage in further stages of the algorithm. - """ - - def __init__(self, num_samples: Optional[int] = None) -> None: - """ - :param num_samples: Optional parameter for statistic collection that regulates - the number of samples that will be processed. - """ - super().__init__(num_samples=num_samples) - self._all_values: list[int] = [] +class NoOutliersAggregatorBase(OfflineAggregatorBase, ABC): + def __init__( + self, + aggregation_axes: Optional[AggregationAxes] = None, + num_samples: Optional[int] = None, + window_size=None, + quantile: float = 0.01, + ): + super().__init__(aggregation_axes=aggregation_axes, num_samples=num_samples) + self._window_size = window_size + self._container = deque(maxlen=window_size) + self._quantile = quantile + + def _aggregation_fn(self, stacked_value: Tensor, axis: int, keepdims: bool) -> Tensor: + low_values, high_values = fns.quantile(stacked_value, q=(self._quantile, 1 - self._quantile), axis=axis) + outliers_mask = fns.logical_or(stacked_value < low_values, high_values < stacked_value) + aggregated = self._masked_aggregation_fn( + stacked_samples=stacked_value, + mask=outliers_mask, + axis=axis, + keepdims=keepdims, + ) + return aggregated - @staticmethod @abstractmethod - def _get_processor() -> Any: + def _masked_aggregation_fn( + self, stacked_samples: Tensor, mask: Tensor, axis: AggregationAxes, keepdims: bool + ) -> Tensor: pass - def _register_input_common(self, x: NNCFTensor) -> None: - self._all_values.append(cast(int, x.tensor)) + def __eq__(self, __o: object) -> bool: + return super().__eq__(__o) and self._quantile == __o._quantile - def _reset(self) -> None: - self._all_values.clear() + def __hash__(self) -> int: + return hash((self.__class__.__name__, self._quantile)) -class MedianMADStatisticCollector(OfflineTensorStatisticCollector): - """ - Collector estimates median and median absolute deviation (MAD). - """ +class MeanNoOutliersAggregator(NoOutliersAggregatorBase): + def _masked_aggregation_fn( + self, stacked_samples: Tensor, mask: Tensor, axis: AggregationAxes, keepdims: bool + ) -> Tensor: + return fns.masked_mean(stacked_samples, axis=axis, mask=mask, keepdims=keepdims) + - def _prepare_statistics(self) -> tuple[NDArray[Any], NDArray[Any]]: - per_channel_history = get_per_channel_history( - self._samples, cast(list[int], self._reduction_shape), discard_zeros=True +class MedianNoOutliersAggregator(NoOutliersAggregatorBase): + def _masked_aggregation_fn( + self, stacked_samples: Tensor, mask: Tensor, axis: AggregationAxes, keepdims: bool + ) -> Tensor: + return fns.masked_median(stacked_samples, axis=axis, mask=mask, keepdims=keepdims) + + +class MedianAbsoluteDeviationAggregator(AggregatorBase): + def __init__( + self, + aggregation_axes: Optional[AggregationAxes] = None, + num_samples: Optional[int] = None, + window_size=None, + ): + super().__init__( + aggregation_axes=aggregation_axes, + num_samples=num_samples, + window_size=window_size, ) - per_channel_median = [np.median(channel_hist) for channel_hist in per_channel_history] - per_channel_mad = [] - for idx, median in enumerate(per_channel_median): - per_channel_mad.append(np.median(abs(per_channel_history[idx] - median))) - numpy_median = np.asarray(per_channel_median) - numpy_mad = np.asarray(per_channel_mad) - return numpy_median, numpy_mad + if 0 not in self._aggregation_axes: + msg = "Aggregation without 0 dim is not supported yet for MedianAbsoluteDeviationAggregator" + raise NotImplementedError(msg) + def _register_reduced_input_impl(self, x: TensorType) -> None: + return self._container.append(x) -class PercentileStatisticCollector(OfflineTensorStatisticCollector): - """ - Collector estimates percentile values of all data history. - """ + def _aggregate_impl(self) -> dict[str, Tensor]: + stacked_val, shape_after_aggregation = _move_axes_flatten_cat( + self._container, [x - 1 for x in self._aggregation_axes if x > 0] + ) + + mask = fns.abs(stacked_val) < fns.finfo(stacked_val).eps + median_per_ch = fns.masked_median(stacked_val, mask=mask, axis=0, keepdims=True) + + mad_values = fns.median( + fns.abs(stacked_val - median_per_ch), + axis=0, + keepdims=False, + ) + if self._keepdims: + median_per_ch = fns.reshape(median_per_ch, shape_after_aggregation) + mad_values = fns.reshape(mad_values, shape_after_aggregation) + else: + median_per_ch = fns.squeeze(median_per_ch, 0) + return { + MedianMADTensorStatistic.MEDIAN_VALUES_STAT: median_per_ch, + MedianMADTensorStatistic.MAD_VALUES_STAT: mad_values, + } + +class PercentileAggregator(AggregatorBase): def __init__( self, percentiles_to_collect: list[float], - reduction_shape: Optional[ReductionAxes] = None, - num_samples: int = None, - window_size: int = None, + aggregation_axes: Optional[AggregationAxes] = None, + num_samples: Optional[int] = None, + window_size=None, ): - super().__init__(reduction_shape, num_samples, window_size) + super().__init__(aggregation_axes=aggregation_axes, num_samples=num_samples) + if 0 not in self._aggregation_axes: + msg = "Aggregation without 0 dim is not supported yet for PercentileAggregator" + raise NotImplementedError(msg) self._percentiles_to_collect = percentiles_to_collect + self._window_size = window_size + self._container = deque(maxlen=window_size) + + def _register_reduced_input_impl(self, x: TensorType) -> None: + return self._container.append(x) + + def _aggregate_impl(self) -> dict[float, Tensor]: + stacked_val, shape_after_aggregation = _move_axes_flatten_cat( + self._container, [x - 1 for x in self._aggregation_axes if x > 0] + ) + + percentiles = fns.percentile(stacked_val, self._percentiles_to_collect, axis=0, keepdims=False) + retval = {} + for idx, percentile in enumerate(self._percentiles_to_collect): + value = percentiles[idx] + if self._keepdims: + value = fns.reshape(value, shape_after_aggregation) + retval[percentile] = value + return retval + + +class HAWQAggregator(AggregatorBase): + def __init__(self, num_samples: Optional[int] = None): + super().__init__(num_samples=num_samples) + self._container = Tensor(0.0) + + def _register_reduced_input_impl(self, x: TensorType) -> None: + trace = fns.sum(fns.multiply(x, x)) + # NOTE: average trace?? divide by number of diagonal elements + # TODO: revise this formula as possibly it is with an error; adopted from previous HAWQ implementation + self._container = (self._container + trace) / x.size - def _prepare_statistics(self) -> dict[float, Any]: - per_channel_history = get_per_channel_history(self._samples, cast(list[int], self._reduction_shape)) - percentile_vs_values_dict = {} - for pc in self._percentiles_to_collect: - per_channel_percentiles = [np.percentile(channel_hist, pc) for channel_hist in per_channel_history] - numpy_percentiles = np.asarray(per_channel_percentiles) - percentile_vs_values_dict[pc] = numpy_percentiles - return percentile_vs_values_dict + def _aggregate_impl(self) -> Tensor: + return self._container * 2 / self._collected_samples -class MeanPercentileStatisticCollector(OfflineTensorStatisticCollector): +class HistogramAggregator(AggregatorBase): """ - Collector estimates percentile values per step and then averages the results. + NNCF implementation of the torch.ao.quantization.observer.HistogramObserver. + Intended to be combined with a single RawReducer. + The aggregator records the running histogram of the input tensor values along with + min/max values. Only the reduction_axis==None is supported. + + The min and max are computed as follows: + + 1. Create the histogram of the incoming inputs. + The histogram is computed continuously, and the ranges per bin change + with every new tensor observed. + 2. Search the distribution in the histogram for optimal min/max values. + The search for the min/max values ensures the minimization of the + quantization error with respect to the floating point model. """ + histogram: Tensor + min_val: Optional[float] + max_val: Optional[float] + def __init__( self, - percentiles_to_collect: list[float], - reduction_shape: Optional[ReductionAxes] = None, - num_samples: int = None, - window_size: int = None, - ): - super().__init__(reduction_shape, num_samples, window_size) - self._all_pct_values: dict[float, Any] = {} - for pc in percentiles_to_collect: - self._all_pct_values[pc] = deque(maxlen=window_size) - - def _reset(self) -> None: - for val in self._all_pct_values.values(): - val.clear() + bins: int = 2048, + dist_nbits: int = 8, + num_samples: Optional[int] = None, + window_size: Optional[int] = None, + ) -> None: + """ + :param bins: Number of bins to use for the histogram + :param dist_nbits: Target quantization number of bits to calculate the quantization error. + :param num_samples: Maximum number of samples to collect. Aggregator + skips tensor registration if tensor registration was called num_samples times before. + Aggregator never skips registration if num_samples is None. + """ + super().__init__(num_samples=num_samples, window_size=window_size) + self.bins = bins + self.min_val = None + self.max_val = None + self.dst_nbins = 2**dist_nbits + self.upsample_rate = 16 # used to reduce quantization errors when upscaling histogram + + def _get_norm(self, delta_begin: Tensor, delta_end: Tensor, density: Tensor) -> Tensor: + """ + Compute the L2 norm of the values uniformaly distributed between + delta_begin and delta_end. + + norm = density * (integral_{begin, end} x^2) + = density * (end^3 - begin^3) / 3 + + :param delta_begin: Start of the integral interval. + :param delta_end: End of the integral interval. + :param density: Density of the elements in the histogram. + :return: The norm of the values uniformaly distributed between delta_begin and delta_end. + """ + norm = (delta_end * delta_end * delta_end - delta_begin * delta_begin * delta_begin) / 3 + return density * norm + + def _compute_quantization_error(self, next_start_bin: int, next_end_bin: int, bin_width: float) -> float: + """ + Computes the L2 norm of quantization error when mapping histogram bins into a reduced set of + quantization bins using the specified range [next_start_bin, next_end_bin]. + + :param next_start_bin: The index of the first source histogram bin included + in the quantization range. + :param next_end_bin: The index of the last source histogram bin included + in the quantization range. + :param bin_width: The width of a single source histogram bin. + :return: A scalar float value representing the total quantization error + for the given bin range. + """ + dst_bin_width = bin_width * (next_end_bin - next_start_bin + 1) / self.dst_nbins + if dst_bin_width == 0.0: + return 0.0 + + src_bin = fns.arange(0, self.bins, backend=self.histogram.backend, device=self.histogram.device) + # distances from the beginning of first dst_bin to the beginning and + # end of src_bin + src_bin_begin = (src_bin - next_start_bin) * bin_width + src_bin_end = src_bin_begin + bin_width + + # which dst_bins the beginning and end of src_bin belong to? + dst_bin_of_begin = fns.clip(fns.floor(src_bin_begin / dst_bin_width), 0, self.dst_nbins - 1) + dst_bin_of_begin_center = (dst_bin_of_begin + 0.5) * dst_bin_width + + dst_bin_of_end = fns.clip( + fns.floor(src_bin_end / dst_bin_width), + 0, + self.dst_nbins - 1, + ) + density = self.histogram / bin_width + + norm = fns.zeros((self.bins,), backend=self.histogram.backend, device=self.histogram.device) + + delta_begin = src_bin_begin - dst_bin_of_begin_center + delta_end = dst_bin_width / 2 + norm += self._get_norm( + delta_begin, + (fns.zeros((self.bins,), backend=self.histogram.backend, device=self.histogram.device) + 1) * delta_end, + density, + ) + + norm += (dst_bin_of_end - dst_bin_of_begin - 1) * self._get_norm(-dst_bin_width / 2, dst_bin_width / 2, density) + + dst_bin_of_end_center = dst_bin_of_end * dst_bin_width + dst_bin_width / 2 + + delta_begin = -dst_bin_width / 2 + delta_end = src_bin_end - dst_bin_of_end_center + norm += self._get_norm(delta_begin, delta_end, density) + + return fns.sum(norm).item() + + def _non_linear_param_search(self) -> tuple[float, float]: + """ + An approximation for L2 error minimization for selecting min/max. + By selecting new min/max, we filter out outliers in input distribution. + This follows the implementation of NormMinimization::NonlinearQuantizationParamsSearch in + caffe2/quantization/server/norm_minimization.cc + By selecting new min/max, we filter out outliers in input distribution. + + :return: An approximation for L2 error minimization for selecting min/max. + """ + assert self.histogram.shape[0] == self.bins, "bins mismatch" + bin_width = (self.max_val - self.min_val) / self.bins + + # cumulative sum + total = fns.sum(self.histogram).item() + cSum = fns.cumsum(self.histogram, axis=0) + + stepsize = 1e-5 # granularity + alpha = 0.0 # lower bound + beta = 1.0 # upper bound + start_bin = 0 + end_bin = self.bins - 1 + norm_min = float("inf") + + while alpha < beta: + # Find the next step + next_alpha = alpha + stepsize + next_beta = beta - stepsize + + # find the left and right bins between the quantile bounds + left = start_bin + right = end_bin + while left < end_bin and cSum[left] < next_alpha * total: + left = left + 1 + while right > start_bin and cSum[right] > next_beta * total: + right = right - 1 + + # decide the next move + next_start_bin = start_bin + next_end_bin = end_bin + if (left - start_bin) > (end_bin - right): + # move the start bin + next_start_bin = left + alpha = next_alpha + else: + # move the end bin + next_end_bin = right + beta = next_beta + + if next_start_bin == start_bin and next_end_bin == end_bin: + continue + + # calculate the quantization error using next_start_bin and next_end_bin + norm = self._compute_quantization_error(next_start_bin, next_end_bin, bin_width) + + if norm > norm_min: + break + norm_min = norm + start_bin = next_start_bin + end_bin = next_end_bin + + new_min = self.min_val + bin_width * start_bin + new_max = self.min_val + bin_width * (end_bin + 1) + return new_min, new_max + + def _upscale_histogram( + self, + histogram: Tensor, + orig_min: float, + orig_max: float, + update_min: float, + update_max: float, + ) -> Tensor: + """ + Updates the histogram into a more fine-coarsed histogram to reduce bin quantization errors. + + :param histogram: The input histogram tensor of size bins. + :param orig_min: The lower boundary of the original histogram range. + :param orig_max: The upper boundary of the original histogram range. + :param update_min: The lower boundary of the updated histogram range. + :param update_max: The upper boundary of the updated histogram range. + :return: A histogram tensor of size bins aligned with the updated range [update_min, update_max]. + """ + histogram = fns.repeat(histogram, self.upsample_rate) / self.upsample_rate + bin_size = (orig_max - orig_min) / (self.bins * self.upsample_rate) + mid_points_histogram = ( + fns.linspace( + orig_min, + orig_max, + self.bins * self.upsample_rate + 1, + backend=self.histogram.backend, + device=self.histogram.device, + )[:-1] + + 0.5 * bin_size + ) + boundaries_new_histogram = fns.linspace( + update_min, update_max, self.bins + 1, backend=self.histogram.backend, device=self.histogram.device + ) + # this maps the mid-poits of the histogram to the new histogram's space + bucket_assignments = fns.searchsorted(boundaries_new_histogram, mid_points_histogram, side="right") - 1 + # this then maps the histogram mid-points in the new space, weighted by the original histogram's values + # this is just the old histogram in the new histogram's space + + # In case due to numerical issues the values land higher/lower than the maximum/minimum + bucket_assignments[bucket_assignments >= self.bins] = self.bins - 1 + bucket_assignments[bucket_assignments < 0] = 0 + + update_histogram = fns.bincount(bucket_assignments, weights=histogram, minlength=self.bins) + return update_histogram + + def _combine_histograms( + self, + orig_hist: Tensor, + orig_min: float, + orig_max: float, + update_hist: Tensor, + update_min: float, + update_max: float, + ) -> Tensor: + """ + Combines the original histogram with an updated histogram, aligning both + to the target range [update_min, update_max]. + + :param orig_hist: The original histogram tensor of size bins. + :param orig_min: The lower boundary of the original histogram range. + :param orig_max: The upper boundary of the original histogram range. + :param update_hist: The histogram tensor of size bins in the updated range. + :param update_min: The lower boundary of the updated histogram range. + :param update_max: The upper boundary of the updated histogram range. + :return: A histogram tensor of size bins representing the combined + distribution in the updated range. + """ + # If the new min and max are the same as the current min and max, + # we can just add the new histogram to the original histogram + if update_min == orig_min and update_max == orig_max: + return orig_hist + update_hist + + # If the orig hist only has one value (i.e., the min and max are the same) + # we can just add it into new histogram + if orig_min == orig_max: + bin_value = fns.sum(orig_hist) + transformed_orig_hist = ( + fns.histogram( + fns.tensor( + orig_min, + backend=self.histogram.backend, + device=self.histogram.device, + dtype=TensorDataType.float32, + ), + bins=self.bins, + range=(update_min, update_max), + ) + * bin_value + ) + return transformed_orig_hist + update_hist + + # We assume the update_hist is already in the target range, we will map the orig_max to it + assert update_min <= orig_min + assert update_max >= orig_max + + # Now we need to turn the old_histogram, into the range of the new histogram + transformed_orig_hist = self._upscale_histogram( + orig_hist, + orig_min, + orig_max, + update_min, + update_max, + ) + + return update_hist + transformed_orig_hist + + def reset_histogram(self, x: Tensor, min_val: float, max_val: float) -> None: + """ + Resets and initializes the histogram based on the provided tensor and range. + + :param x: The input tensor used to build the histogram. + :param min_val: The scalar tensor specifying the lower boundary of the histogram range. + :param max_val: The scalar tensor specifying the upper boundary of the histogram range. + """ + self.min_val = min_val + self.max_val = max_val + self.histogram = fns.histogram(x, self.bins, range=(min_val, max_val)) + + def _register_reduced_input_impl(self, x: Tensor) -> None: + x_min, x_max = fns.min(x).item(), fns.max(x).item() + + current_min = self.min_val + current_max = self.max_val + + is_uninitialized = self.min_val is None or self.max_val is None + if is_uninitialized: + self.reset_histogram(x, x_min, x_max) + return + + update_min, update_max = x_min, x_max + new_min = min(current_min, update_min) + new_max = max(current_max, update_max) + + update_histogram = fns.histogram(x, self.bins, range=(new_min, new_max)) + + self.histogram = self._combine_histograms( + self.histogram, + current_min, + current_max, + update_histogram, + new_min, + new_max, + ) + self.min_val = new_min + self.max_val = new_max + + def _aggregate_impl(self) -> dict[str, Tensor]: + min_, max_ = self._non_linear_param_search() + return { + MinMaxTensorStatistic.MIN_STAT: fns.tensor( + min_, backend=self.histogram.backend, device=self.histogram.device, dtype=TensorDataType.float32 + ), + MinMaxTensorStatistic.MAX_STAT: fns.tensor( + max_, backend=self.histogram.backend, device=self.histogram.device, dtype=TensorDataType.float32 + ), + } + + +REDUCERS_MAP = { + StatisticsType.MIN: MinReducer, + StatisticsType.MAX: MaxReducer, + StatisticsType.ABS_MAX: AbsMaxReducer, + StatisticsType.MEAN: MeanReducer, + StatisticsType.QUANTILE: QuantileReducer, + StatisticsType.ABS_QUANTILE: AbsQuantileReducer, + StatisticsType.RAW: RawReducer, +} +AGGREGATORS_MAP = { + AggregatorType.MIN: MinAggregator, + AggregatorType.MAX: MaxAggregator, + AggregatorType.MEAN: MeanAggregator, + AggregatorType.MEAN_NO_OUTLIERS: MeanNoOutliersAggregator, + AggregatorType.MEDIAN: MedianAggregator, + AggregatorType.MEDIAN_NO_OUTLIERS: MedianNoOutliersAggregator, +} diff --git a/src/nncf/common/tensor_statistics/reduction.py b/src/nncf/common/tensor_statistics/reduction.py deleted file mode 100644 index afe19609d86..00000000000 --- a/src/nncf/common/tensor_statistics/reduction.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) 2025 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 collections import deque -from typing import Any - -import numpy as np -from numpy.typing import NDArray - - -def get_channel_count_and_dim_idx(scale_shape: list[int]) -> tuple[int, int]: - channel_dim_idx = 0 - channel_count = 1 - for dim_idx, dim in enumerate(scale_shape): - if dim != 1: - channel_dim_idx = dim_idx - channel_count = dim - return channel_count, channel_dim_idx - - -def split_into_channels(input_: NDArray[Any], scale_shape: list[int]) -> list[NDArray[Any]]: - channel_count, channel_dim_idx = get_channel_count_and_dim_idx(scale_shape) - channel_first_tensor = np.moveaxis(input_, channel_dim_idx, 0) - if channel_count == 1: - return [channel_first_tensor] - - ret_list = [] - for i in range(channel_count): - ret_list.append(channel_first_tensor[i, ...]) - return ret_list - - -def get_per_channel_history( - raw_input_history: deque[Any], scale_shape: list[int], discard_zeros: bool = False -) -> list[Any]: - channel_count, _ = get_channel_count_and_dim_idx(scale_shape) - per_channel_history: list[Any] = [None for i in range(channel_count)] - for _ in range(len(raw_input_history)): - entry = raw_input_history.popleft() - split = split_into_channels(entry, scale_shape) - for i in range(channel_count): - flat_channel_split = split[i].flatten() - - if discard_zeros: - # For post-RELU quantizers exact zeros may prevail and lead to - # zero mean and MAD - discard them - flat_channel_split = flat_channel_split[flat_channel_split != 0] - - if per_channel_history[i] is None: - per_channel_history[i] = flat_channel_split - else: - per_channel_history[i] = np.concatenate([per_channel_history[i], flat_channel_split]) - raw_input_history.append(entry) - return per_channel_history - - -def np_percentile_reduce_like(input_: NDArray[Any], ref_tensor_shape: tuple[int], q: float) -> NDArray[Any]: - numel = np.prod(ref_tensor_shape) - if numel == 1: - return np.array([np.percentile(input_, q)]) - tmp = input_ - for dim_idx, dim in enumerate(ref_tensor_shape): - if dim == 1: - numpy_tmp = np.percentile(tmp, q, axis=dim_idx, keepdims=True) - tmp = numpy_tmp - return tmp diff --git a/src/nncf/common/tensor_statistics/statistic_point.py b/src/nncf/common/tensor_statistics/statistic_point.py index d4a62b640ad..10280c6090e 100644 --- a/src/nncf/common/tensor_statistics/statistic_point.py +++ b/src/nncf/common/tensor_statistics/statistic_point.py @@ -13,7 +13,7 @@ from typing import Any, Callable, Generator, Optional, cast from nncf.common.graph.transformations.commands import TargetPoint -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorCollector class StatisticPoint: diff --git a/src/nncf/experimental/common/tensor_statistics/statistical_functions.py b/src/nncf/common/tensor_statistics/statistical_functions.py similarity index 99% rename from src/nncf/experimental/common/tensor_statistics/statistical_functions.py rename to src/nncf/common/tensor_statistics/statistical_functions.py index d75e60ab188..c6e81344265 100644 --- a/src/nncf/experimental/common/tensor_statistics/statistical_functions.py +++ b/src/nncf/common/tensor_statistics/statistical_functions.py @@ -8,7 +8,6 @@ # 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 typing import Optional from nncf.tensor import Tensor diff --git a/src/nncf/common/tensor_statistics/statistics.py b/src/nncf/common/tensor_statistics/statistics.py index cbc9b153a78..103fa90b2d6 100644 --- a/src/nncf/common/tensor_statistics/statistics.py +++ b/src/nncf/common/tensor_statistics/statistics.py @@ -9,114 +9,291 @@ # See the License for the specific language governing permissions and # limitations under the License. -from abc import ABC -from abc import abstractmethod +from __future__ import annotations + from collections import Counter -from typing import Any, TypeVar +from dataclasses import dataclass +from dataclasses import fields +from typing import Any, ClassVar +import nncf from nncf.tensor import Tensor +from nncf.tensor import TensorDataType from nncf.tensor import functions as fns -TensorType = TypeVar("TensorType") - -class TensorStatistic(ABC): +@dataclass +class TensorStatistic: """Base class that stores statistic data""" - TENSOR_STATISTIC_OUTPUT_KEY = "tensor_statistic_output" + TENSOR_STATISTIC_OUTPUT_KEY: ClassVar[str] = "tensor_statistic_output" - @staticmethod - def tensor_eq(tensor1: Tensor, tensor2: Tensor, rtol: float = 1e-6) -> bool: - return fns.allclose(tensor1, tensor2, rtol=rtol) + def get_data(self, is_serialized: bool = False) -> dict[str, Any]: + """ + Retrieves the data of the tensor statistics. If `is_serialized` is True, + the data is prepared for serialization by including only Tensor instances. - @abstractmethod - def __eq__(self, other: Any) -> bool: - pass + :param is_serialized: If True, the data is prepared for serialization by + including only Tensor instances. + :return: Dictionary with keys and their associated data. If `is_serialized` + is True, the dictionary will contain only Tensor instances. + """ + if is_serialized: + return self._get_serialized_data() # Dict[str, Tensor] + return {field.name: getattr(self, field.name) for field in fields(self)} + + def _get_serialized_data(self) -> dict[str, Tensor]: + """ + Prepares the data for serialization by including only Tensor instances. + + :return: Dictionary with data for serialization. + """ + serialized_data = {} + for field in fields(self): + key = field.name + value = getattr(self, key) + if isinstance(value, Tensor): + serialized_data[key] = value + else: + msg = f"Unsupported type of value: {type(value)}" + raise nncf.InternalError(msg) + return serialized_data + + def load_data(self, data: dict[str, Tensor]) -> None: + """ + Loads the data from the serialized data. + + :param data: Data to load. + """ + for key in (field.name for field in fields(self)): + setattr(self, key, data[key]) + + @classmethod + def from_config(cls, config: dict[str, Any]) -> TensorStatistic: + args = {key: config[key] for key in (field.name for field in fields(cls))} + return cls(**args) +@dataclass class MinMaxTensorStatistic(TensorStatistic): - MIN_STAT = "min_values" - MAX_STAT = "max_values" + MIN_STAT: ClassVar[str] = "min_values" + MAX_STAT: ClassVar[str] = "max_values" + MIN_MAX_STAT: ClassVar[str] = "min_max_values" - def __init__(self, min_values: Tensor, max_values: Tensor): - self.min_values = min_values - self.max_values = max_values + min_values: Tensor + max_values: Tensor - def __eq__(self, other: Any) -> bool: - if not isinstance(other, MinMaxTensorStatistic): - return False - return self.tensor_eq(self.min_values, other.min_values) and self.tensor_eq(self.max_values, other.max_values) + def __eq__(self, other: TensorStatistic): + if isinstance(other, MinMaxTensorStatistic): + return fns.allclose(self.min_values, other.min_values) and fns.allclose(self.max_values, other.max_values) + return False + @classmethod + def from_config(cls, config: dict[str, Any]) -> TensorStatistic: + if cls.MIN_MAX_STAT in config: + # Build MinMaxTensorStatistic for aggregators which + # outputs both min and max values from a single aggregator instance. + return cls(**config[cls.MIN_MAX_STAT]) + return cls(min_values=config[cls.MIN_STAT], max_values=config[cls.MAX_STAT]) + +@dataclass +class AbsMaxTensorStatistic(TensorStatistic): + ABS_MAX_STAT: ClassVar[str] = "abs_max" + + abs_max: Tensor + + def __eq__(self, other: TensorStatistic): + if isinstance(other, AbsMaxTensorStatistic): + return fns.allclose(self.abs_max, other.abs_max) + return False + + +@dataclass(init=False) class MeanTensorStatistic(TensorStatistic): - MEAN_STAT = "mean_values" - SHAPE_STAT = "shape" + MEAN_STAT: ClassVar[str] = "mean_values" + SHAPE_STAT: ClassVar[str] = "shape" - """ - Base class for the statistics that collects as mean per-axis - """ + mean_values: Tensor + shape: tuple[int, ...] - def __init__(self, mean_values: Tensor, shape: Tensor): - """ - :param mean_values: Collected mean per-axis values. - :param shape: The shape of the collected statistics. - """ + def __init__(self, mean_values: Tensor, shape: Tensor) -> None: self.mean_values = mean_values - self.shape = shape + self.shape = tuple(shape.tolist()) - def __eq__(self, other: Any) -> bool: - if not isinstance(other, MeanTensorStatistic): - return False - return self.tensor_eq(self.mean_values, other.mean_values) and self.tensor_eq(self.shape, other.shape) + def __eq__(self, other: TensorStatistic): + if isinstance(other, MeanTensorStatistic): + return self.shape == other.shape and fns.allclose(self.mean_values, other.mean_values) + return False + + def _get_serialized_data(self) -> dict[str, Tensor]: + backend = self.mean_values.backend + device = self.mean_values.device + return { + self.MEAN_STAT: self.mean_values, + self.SHAPE_STAT: fns.tensor(self.shape, backend=backend, dtype=TensorDataType.int32, device=device), + } + + def load_data(self, loaded_data: dict[str, Tensor]) -> None: + self.mean_values = loaded_data[self.MEAN_STAT] + self.shape = tuple(loaded_data[self.SHAPE_STAT].tolist()) +@dataclass class MedianMADTensorStatistic(TensorStatistic): - MEDIAN_VALUES_STAT = "median_values" - MAD_VALUES_STAT = "mad_values" + MEDIAN_VALUES_STAT: ClassVar[str] = "median_values" + MAD_VALUES_STAT: ClassVar[str] = "mad_values" - def __init__(self, median_values: Tensor, mad_values: Tensor): - self.median_values = median_values - self.mad_values = mad_values + median_values: Tensor + mad_values: Tensor - def __eq__(self, other: Any) -> bool: - if not isinstance(other, MedianMADTensorStatistic): - return False - return self.tensor_eq(self.median_values, other.median_values) and self.tensor_eq( - self.mad_values, other.mad_values + def __eq__(self, other: TensorStatistic): + if isinstance(other, MedianMADTensorStatistic): + return fns.allclose(self.median_values, other.median_values) and fns.allclose( + self.mad_values, other.mad_values + ) + return False + + @classmethod + def from_config(cls, config: dict[str, Any]) -> TensorStatistic: + return cls( + median_values=config[cls.TENSOR_STATISTIC_OUTPUT_KEY][cls.MEDIAN_VALUES_STAT], + mad_values=config[cls.TENSOR_STATISTIC_OUTPUT_KEY][cls.MAD_VALUES_STAT], ) +@dataclass class PercentileTensorStatistic(TensorStatistic): - PERCENTILE_VS_VALUE_DICT = "percentile_vs_values_dict" + PERCENTILE_VS_VALUE_DICT: ClassVar[str] = "percentile_vs_values_dict" - def __init__(self, percentile_vs_values_dict: dict[float, Any]): - self.percentile_vs_values_dict = percentile_vs_values_dict + percentile_vs_values_dict: dict[str, Tensor] - def __eq__(self, other: Any, rtol: float = 1e-9) -> bool: - if not isinstance(other, PercentileTensorStatistic): - return False - if Counter(self.percentile_vs_values_dict.keys()) != Counter(other.percentile_vs_values_dict.keys()): - return False - for pct in self.percentile_vs_values_dict: - if not self.tensor_eq(self.percentile_vs_values_dict[pct], other.percentile_vs_values_dict[pct]): + def __eq__(self, other: TensorStatistic): + if isinstance(other, PercentileTensorStatistic): + if Counter(self.percentile_vs_values_dict.keys()) != Counter(other.percentile_vs_values_dict.keys()): return False - return True + for pct in self.percentile_vs_values_dict: + if not fns.allclose(self.percentile_vs_values_dict[pct], other.percentile_vs_values_dict[pct]): + return False + return True + return False + + @classmethod + def from_config(cls, config: dict[str, Any]) -> TensorStatistic: + if cls.TENSOR_STATISTIC_OUTPUT_KEY in config: + percentile_vs_values_dict = config[cls.TENSOR_STATISTIC_OUTPUT_KEY] + else: + percentile_vs_values_dict = {} + for (_, percentile), value in config.items(): + percentile_vs_values_dict[percentile] = value + return cls(percentile_vs_values_dict=percentile_vs_values_dict) + + def _get_serialized_data(self) -> dict[str, Tensor]: + return self.PERCENTILE_VS_VALUE_DICT + def load_data(self, loaded_data: dict[str, Tensor]) -> None: + self.percentile_vs_values_dict = loaded_data + +@dataclass class RawTensorStatistic(TensorStatistic): - VALUES_STATS = "values" + VALUES_STATS: ClassVar[str] = "values" - """ - Base class for the raw statistics, without any aggregation. - """ + values: Tensor + + def __eq__(self, other: RawTensorStatistic) -> bool: + if isinstance(other, RawTensorStatistic): + return fns.allclose(self.values, other.values) + return False + + +@dataclass +class HessianTensorStatistic(TensorStatistic): + HESSIAN_INPUT_ACTIVATION_STATS: ClassVar[str] = "hessian" + + hessian: Tensor + + def __eq__(self, other: TensorStatistic): + if isinstance(other, HessianTensorStatistic): + return fns.allclose(self.hessian, other.hessian) + return False + + +@dataclass +class MeanVarianceTensorStatistic(TensorStatistic): + MEAN_VARIANCE_STAT: ClassVar[str] = "mean_variance" + + mean_variance: Tensor + + def __eq__(self, other: TensorStatistic): + if isinstance(other, MeanVarianceTensorStatistic): + return fns.allclose(self.mean_variance, other.mean_variance) + return False - def __init__(self, values: Tensor): - """ - :param values: Collected raw values. - """ - self.values = values + +@dataclass +class MaxVarianceTensorStatistic(TensorStatistic): + MAX_VARIANCE_STAT: ClassVar[str] = "max_variance" + + max_variance: Tensor + + def __eq__(self, other: TensorStatistic): + if isinstance(other, MaxVarianceTensorStatistic): + return fns.allclose(self.max_variance, other.max_variance) + return False + + +@dataclass +class MeanMagnitudeTensorStatistic(TensorStatistic): + MEAN_MAGNITUDE_STAT: ClassVar[str] = "mean_magnitude" + + mean_magnitude: Tensor + + def __eq__(self, other: TensorStatistic): + if isinstance(other, MeanMagnitudeTensorStatistic): + return fns.allclose(self.mean_magnitude, other.mean_magnitude) + return False + + +@dataclass +class WCTensorStatistic(TensorStatistic): + MEAN_STAT = "mean_values" + SHAPE_STAT = "shape_values" + + mean_values: list[Tensor] + shape_values: list[tuple[Tensor]] def __eq__(self, other: Any) -> bool: - if not isinstance(other, RawTensorStatistic): + shapes_equal = all(self.shapes[i] == other.shapes[i] for i in range(len(self.mean_values))) + if not shapes_equal: return False - return self.tensor_eq(self.values, other.values) + mean_values_equal = all( + self.tensor_eq(self.mean_values[i], other.mean_values[i]) for i in range(len(self.mean_values)) + ) + return mean_values_equal + + def _get_serialized_data(self) -> dict[str, Tensor]: + backend = self.mean_values[0].backend + device = self.mean_values[0].device + return { + self.MEAN_STAT: fns.stack(self.mean_values), + self.SHAPE_STAT: fns.tensor( + self.shape_values, + backend=backend, + dtype=TensorDataType.int32, + device=device, + ), + } + + def load_data(self, loaded_data: dict[str, Tensor]) -> None: + self.shape_values = [tuple(shape) for shape in loaded_data[self.SHAPE_STAT]] + self.mean_values = [it for it in loaded_data[self.MEAN_STAT]] + + @classmethod + def from_config(cls, config: dict[str, Any]) -> TensorStatistic: + mean_values, shape_values = None, None + if cls.MEAN_STAT in config and config[cls.MEAN_STAT] is not None: + mean_values = [fns.squeeze(it) for it in config[cls.MEAN_STAT]] + if cls.SHAPE_STAT in config and config[cls.SHAPE_STAT] is not None: + shape_values = [tuple(it.tolist()) for it in config[cls.SHAPE_STAT]] + return cls(mean_values=mean_values, shape_values=shape_values) diff --git a/src/nncf/experimental/common/tensor_statistics/__init__.py b/src/nncf/experimental/common/tensor_statistics/__init__.py deleted file mode 100644 index e5a42efc0ef..00000000000 --- a/src/nncf/experimental/common/tensor_statistics/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2025 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. diff --git a/src/nncf/experimental/common/tensor_statistics/collectors.py b/src/nncf/experimental/common/tensor_statistics/collectors.py deleted file mode 100644 index f783c8e5267..00000000000 --- a/src/nncf/experimental/common/tensor_statistics/collectors.py +++ /dev/null @@ -1,1256 +0,0 @@ -# Copyright (c) 2025 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. - -# This file includes code from PyTorch project https://github.com/pytorch/pytorch/blob/v2.8.0/torch/ao/quantization/observer.py -# The original license is: BSD-3-Clause, https://github.com/pytorch/pytorch/blob/main/LICENSE - -from abc import ABC -from abc import abstractmethod -from collections import defaultdict -from collections import deque -from copy import deepcopy -from enum import Enum -from typing import Any, Optional, TypeVar, Union - -import nncf -import nncf.tensor -import nncf.tensor.functions as fns -from nncf.common.tensor import TensorType -from nncf.common.tensor_statistics.collectors import ReductionAxes -from nncf.experimental.common.tensor_statistics.statistical_functions import mean_per_channel -from nncf.experimental.common.tensor_statistics.statistics import MedianMADTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import TensorStatistic -from nncf.quantization.range_estimator import AggregatorType -from nncf.quantization.range_estimator import StatisticsType -from nncf.tensor import Tensor -from nncf.tensor.definitions import TensorDataType - -InplaceInsertionFNType = TypeVar("InplaceInsertionFNType") -AggregationAxes = tuple[int, ...] -Axes = tuple[int, ...] - - -class AxesMode(Enum): - """ - Represents different strategies for handling tensor axes. - - :param REDUCTION: Indicates that the specified axes should be reduced during an operation. - :param KEEP: Indicates that the specified axes should be preserved and not reduced during - an operation. - """ - - REDUCTION = "reduction" - KEEP = "keep" - - -def determine_reduction_axes( - ndim: int, axes: Optional[Axes] = None, axes_mode: AxesMode = AxesMode.REDUCTION -) -> ReductionAxes: - """ - Determines the set of axes along which a reduction operation should be performed - based on the specified axes mode. - - :param ndim: The number of dimensions in the input tensor. - :param axes: The axes specified for the reduction operation. If `None`, all axes - are considered (i.e., `tuple(range(ndim))`). - - :param axes_mode: Defines how the specified axes are interpreted: - - `AxesMode.REDUCTION`: the given axes will be reduced. - - `AxesMode.KEEP`: all axes except the specified ones will be reduced. - :return: The resolved set of axes along which the reduction operation should be performed. - """ - if axes is None: - return tuple(range(ndim)) - - if axes_mode == AxesMode.REDUCTION: - return axes - - all_axes = tuple(range(ndim)) - if len(all_axes) > 1: - # Ensure that all axes have positive values - keep_axes = tuple(all_axes[i] for i in axes) - return tuple(set(all_axes) - set(keep_axes)) - return () - - -class TensorReducerBase(ABC): - """ - Tensor reducer is a callable object that reduces tensors according to - the specified rule. Could handle tensors inplace or out of place. - """ - - def __init__( - self, - axes: Optional[Axes] = None, - axes_mode: AxesMode = AxesMode.REDUCTION, - inplace: bool = False, - ): - """ - :param axes: The axes along which the reduction operation should be applied. - If `None`, the operation will be applied to all axes (i.e., `tuple(range(tensor.ndim))`). - :param axes_mode: Determines how the specified `axes` are treated during the operation. - Use `AxesMode.REDUCTION` to reduce over the given axes, or `AxesMode.KEEP` to preserve them. - :param inplace: Whether should be calculated inplace or out of place. - """ - self._axes = axes - self._axes_mode = axes_mode - self._inplace = inplace - self._keepdims = True - - @property - def inplace(self): - return self._inplace - - @property - def output_port_id(self) -> int: - """ - Port id of the last node of the reducer subgraph if statistic is inplace. - Port id of the reducer output return node if statistic is not inplace. - """ - return 0 - - @property - def name(self): - return self.__class__.__name__ + str(self.__hash__()) - - @abstractmethod - def _reduce_out_of_place(self, x: list[TensorType]) -> list[TensorType]: - """ - Specifies the reduction rule. - - :param x: Tensor to register. - """ - - def get_inplace_fn(self) -> Optional[InplaceInsertionFNType]: - """ - Returns correspondent inplace operation builder if inplace operations are available in backend. - - :return: Inplace operation builder if possible else None. - """ - return None - - def __call__(self, x: list[Tensor]): - if any(t.isempty() for t in x): - return None - - if self.inplace: - return x - - return self._reduce_out_of_place(x) - - def __eq__(self, __o: object) -> bool: - return ( - isinstance(__o, self.__class__) - and self._axes == __o._axes - and self._axes_mode == __o._axes_mode - and self._inplace == __o.inplace - ) - - def __hash__(self) -> int: - return hash((self.__class__.__name__, self.inplace, self._axes, self._axes_mode)) - - -class AggregatorBase: - """ - Aggregator is designed to receive (register) calculated statistics and aggregate them. - """ - - def __init__( - self, - aggregation_axes: Optional[AggregationAxes] = None, - num_samples: Optional[int] = None, - window_size: Optional[int] = None, - ): - """ - :param aggregation_axes: Axes along which to operate. - Registered statistics are stacked along zero axis, - axes >=1 correspond to received statistic axes shifted left by 1. - :param num_samples: Maximum number of samples to collect. Aggregator - skips tensor registration if tensor registration was called num_samples times before. - Aggregator never skips registration if num_samples is None. - :param window_size: Number of samples from the end of the list of collected samples to aggregate. - Aggregates all available collected statistics in case parameter is None. - """ - self._aggregation_axes = (0,) if aggregation_axes is None else aggregation_axes - self._keepdims = True - self._num_samples = num_samples - self._collected_samples = 0 - self._window_size = window_size - self._container = deque(maxlen=window_size) - - @property - def num_samples(self) -> int: - return self._num_samples - - def register_reduced_input(self, x: TensorType): - if self._num_samples is not None and self._collected_samples >= self._num_samples: - return - self._register_reduced_input_impl(x) - self._collected_samples += 1 - - @abstractmethod - def _register_reduced_input_impl(self, x: TensorType) -> None: - """ - Registers incoming tensor in tensor aggregator. - - :param x: Tensor to register. - """ - - def aggregate(self) -> Any: - """ - Aggregates collected tensors and returns aggregated result. - In case no tensors were collected returns None. - - :return: Aggregated result. - """ - if self._collected_samples: - return self._aggregate_impl() - return None - - @abstractmethod - def _aggregate_impl(self) -> Any: - """ - Aggregates collected tensors and returns aggregated result. - - :return: Aggregated result. - """ - - def reset(self): - self._collected_samples = 0 - self._container = [] - - def __eq__(self, __o: object) -> bool: - return isinstance(__o, self.__class__) and self._num_samples == __o.num_samples - - def __hash__(self) -> int: - return hash(self.__class__.__name__) - - -class TensorCollector: - """ - Calculates statistics at given tensors according to registered statistic branches. - Statistic branch consists of one reducer and one aggregator instance. TensorCollector - applies a reducer on a correspondent inputs and then passes the one of the reduced tensors - chosen by output port id to a correspondent aggregator for each registered statistic branch. - Receives tensors by `register_input` method. Aggregated values as a TensorStatistic instance or - a dict could be collected by `get_statistics` call. - """ - - def __init__(self, statistic_container: Optional[type[TensorStatistic]] = None) -> None: - self._reducers: set[TensorReducerBase] = set() - self._aggregators: dict[tuple[int, int, int], AggregatorBase] = {} - self._stat_container_kwargs_map: dict[str, tuple[int, int, int]] = {} - self._stat_container = statistic_container - self.enable() - self.clear_cache() - - @property - def num_samples(self) -> Optional[int]: - output = None - for aggregator in self._aggregators.values(): - if aggregator.num_samples and output: - output = max(output, aggregator.num_samples) - else: - output = aggregator.num_samples - return output - - @property - def enabled(self) -> bool: - return self._enabled - - @property - def reducers(self): - return self._reducers.copy() - - @property - def aggregators(self): - return self._aggregators.copy() - - def enable(self): - self._enabled = True - - def disable(self): - self._enabled = False - - def register_statistic_branch( - self, - container_key: str, - reducer: TensorReducerBase, - aggregator: AggregatorBase, - reducer_output_port_id: int = 0, - ) -> None: - """ - Registers statistic collection branch for a container key. Correspondent input will be reduced - by given reducer and reduced value will be registered and aggregated by given aggregator. - Passed container key should be unique for the TensorCollector instance. - Passed aggregator instance should never be used twice for one TensorCollector instance. - - :param container_key: Container key to pass aggregated statistic to. - :param reducer: TensorReducer instance for the statistic collection branch. - :param aggregator: TensorAggregator instance for the statistic collection branch. - :reducer_output_port_id: Reducer target output port id. - """ - if container_key in self._stat_container_kwargs_map: - msg = f"Two different statistic branches for one container key {container_key} are encountered" - raise nncf.InternalError(msg) - if any(aggr is aggregator for aggr in self._aggregators.values()): - msg = f"One aggregator instance {aggregator} for different branches is encountered" - raise nncf.InternalError(msg) - - self._reducers.add(reducer) - key = (hash(reducer), reducer_output_port_id, hash(aggregator)) - - if key not in self._aggregators: - self._aggregators[key] = aggregator - self._stat_container_kwargs_map[container_key] = key - - def register_inputs(self, inputs: dict[int, list[Tensor]]) -> None: - """ - Registers given input in TensorCollector. - - :param inputs: Tensor inputs in format of dict where keys - are reducer names and values are correspondent input tensors - """ - if not self.enabled: - return - reduced_inputs = {} - for reducer in self._reducers: - reducer_hash = hash(reducer) - input_ = inputs[reducer_hash] - reduced_input = reducer(input_) - if reduced_input is not None: - reduced_inputs[reducer_hash] = reduced_input - - for ( - (reducer_hash, reducer_port_id, _), - aggregator, - ) in self._aggregators.items(): - if reducer_hash in reduced_inputs: - aggregator.register_reduced_input(reduced_inputs[reducer_hash][reducer_port_id]) - - def register_input_for_all_reducers(self, input_: Tensor) -> None: - """ - Registers given input_ in each available statistic collection branch. - - :param input_: Tensor input to register. - """ - self.register_inputs({hash(reducer): [input_] for reducer in self._reducers}) - - def _aggregate(self) -> None: - result = {} - for ( - key, - aggregator, - ) in self._aggregators.items(): - val = aggregator.aggregate() - result[key] = val - return result - - def set_cache(self, statistics: TensorStatistic) -> None: - """ - Sets cached statistics from given config and disable TensorCollector. - :param statistics: TensorStatistic. - """ - self._cached_statistics = statistics - self.reset() - self.disable() - - def create_statistics_container(self, config: dict[str, Any]) -> TensorStatistic: - """ - Returns a TensorStatistic instance with aggregated values. - - :param config: Aggregated values. - :return: TensorStatistic instance. - """ - if not self._stat_container: # TODO(kshpv): need to remove an ability to return a Dict. - return config - return self._stat_container.from_config(config) - - def clear_cache(self) -> None: - """ - Clears the cached statistics and enables TensorCollector. - """ - self._cached_statistics = None - - def get_statistics(self) -> TensorStatistic: - """ - Returns aggregated values in format of a TensorStatistic instance or - a dict. - - :return: Aggregated values. - """ - if self._cached_statistics is not None: - return deepcopy(self._cached_statistics) - - aggregated_values = self._aggregate() - statistics_config = {} - for container_key, branch_key in self._stat_container_kwargs_map.items(): - statistics_config[container_key] = aggregated_values[branch_key] - return self.create_statistics_container(statistics_config) - - def replace_aggregator(self, key: tuple[int, int, int], aggregator: AggregatorBase) -> None: - """ - Friend method that replaces aggregator instance on equivalent one. - Key should be valid for for given aggregator and a statistic branch - with key should be present in TensorCollector. - - :param key: Statistic branch key. - :param aggregator: Aggregator instance to replace existing instance by given key. - """ - assert key in self._aggregators - assert key[2] == hash(aggregator) - self._aggregators[key] = aggregator - - def reset(self): - for aggregator in self._aggregators.values(): - aggregator.reset() - - @staticmethod - def get_tensor_collector_inputs( - outputs: dict[str, Tensor], output_info: list[tuple[int, list[str]]] - ) -> dict[int, list[Tensor]]: - """ - Static method that converts all model outputs and collected output_info - to a layout required for `register_inputs` method. This method is not a part of - `register_inputs` to avoid all inputs passing to `TensorCollector.register_inputs` method. - - :param outputs: Target model outputs. - :param output_info: Output info collected by a `TensorCollector.get_output_info` method. - :returns: Model outputs in a format required by `TensorCollector.register_inputs` method. - """ - target_inputs = {} - for reducer, names in output_info: - target_inputs[reducer] = [outputs[name] for name in names] - return target_inputs - - -class MergedTensorCollector(TensorCollector): - """ - Tensor collector that merge several tensor collectors in one. - Statistics collected by a merged tensor collector automatically available - in all tensor collectors that were merged by the merged tensor collector. - This works because merged tensor collectors share tensor aggregators instances with - the merged tensor collector. - """ - - def __init__(self, tensor_collectors: list[TensorCollector]) -> None: - """ - :param tensor_collectors: Tensor collectors to merge. - """ - super().__init__() - aggregators: dict[tuple[int, int, int], list[tuple[TensorCollector, AggregatorBase]]] = defaultdict(list) - for tensor_collector in tensor_collectors: - if not tensor_collector.enabled: - continue - self._reducers.update(tensor_collector.reducers) - for key, aggregator in tensor_collector.aggregators.items(): - aggregators[key].append((tensor_collector, aggregator)) - - for key, aggregators_to_merge in aggregators.items(): - _, unique_aggregator = aggregators_to_merge[0] - for tensor_collector, _ in aggregators_to_merge[1:]: - tensor_collector.replace_aggregator(key, unique_aggregator) - self._aggregators[key] = unique_aggregator - - -################################################## -# Reducers -################################################## - - -class RawReducer(TensorReducerBase): - def __init__(self): - super().__init__(inplace=False) - - def get_inplace_fn(self) -> Optional[InplaceInsertionFNType]: - return None - - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - return x - - -class ShapeReducer(TensorReducerBase): - def __init__(self, inplace: bool = False): - super().__init__(inplace=inplace) - - def _reduce_out_of_place(self, x: list[TensorType]) -> list[TensorType]: - # Return as tensor for consistency, because in-place reducer returns a tensor - return [fns.tensor(x[0].shape, backend=x[0].backend, dtype=TensorDataType.int32, device=x[0].device)] - - def get_inplace_fn(self) -> Optional[InplaceInsertionFNType]: - return None - - -class MinReducer(TensorReducerBase): - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - x = x[0] - reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) - return [fns.min(x, reduction_axes, keepdims=self._keepdims)] - - -class MaxReducer(TensorReducerBase): - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - x = x[0] - reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) - return [fns.max(x, reduction_axes, keepdims=self._keepdims)] - - -class AbsMaxReducer(TensorReducerBase): - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - x = fns.abs(x[0]) - reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) - return [fns.max(x, reduction_axes, keepdims=self._keepdims)] - - -class MeanReducer(TensorReducerBase): - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - x = x[0] - reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) - return [fns.mean(x, reduction_axes, keepdims=self._keepdims)] - - -class MeanVarianceReducer(TensorReducerBase): - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - x = x[0] - reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) - variance = fns.var(x, reduction_axes) - return [fns.mean(variance)] - - -class MaxVarianceReducer(TensorReducerBase): - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - x = x[0] - reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) - variance = fns.var(x, reduction_axes) - return [fns.max(variance)] - - -class MeanAbsMaxReducer(TensorReducerBase): - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - x = fns.abs(x[0]) - reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) - abs_max = fns.max(x, reduction_axes, keepdims=self._keepdims) - return [fns.mean(abs_max)] - - -class QuantileReducerBase(TensorReducerBase): - def __init__( - self, - axes: Optional[Axes] = None, - axes_mode: AxesMode = AxesMode.REDUCTION, - quantile: Optional[Union[float, tuple[float]]] = None, - inplace: bool = False, - ): - super().__init__(axes, axes_mode, False) - self._quantile = (0.01, 0.99) if quantile is None else quantile - - def __eq__(self, __o: object) -> bool: - return super().__eq__(__o) and self._quantile == __o._quantile - - def __hash__(self) -> int: - return hash((self.__class__.__name__, self.inplace, self._axes, self._axes_mode, tuple(self._quantile))) - - -class QuantileReducer(QuantileReducerBase): - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - x = x[0] - reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) - return fns.quantile(x, self._quantile, reduction_axes, keepdims=self._keepdims) - - -class AbsQuantileReducer(QuantileReducerBase): - def __init__( - self, - axes: Optional[Axes] = None, - axes_mode: AxesMode = AxesMode.REDUCTION, - quantile: Optional[Union[float, tuple[float]]] = None, - inplace: bool = False, - ): - quantile = (0.99,) if quantile is None else quantile - super().__init__(axes, axes_mode, quantile) - - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - x = fns.abs(x[0]) - reduction_axes = determine_reduction_axes(x.ndim, self._axes, self._axes_mode) - return fns.quantile(x, self._quantile, reduction_axes, keepdims=self._keepdims) - - -class BatchMeanReducer(TensorReducerBase): - def __init__(self, inplace: bool = False): - super().__init__(None, inplace) - - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - return [fns.mean(x[0], axis=0, keepdims=True)] - - -class MeanPerChReducer(TensorReducerBase): - def __init__(self, channel_axis: int = 1, inplace: bool = False): - super().__init__(inplace=inplace) - self._channel_axis = channel_axis - - def _reduce_out_of_place(self, x: list[Tensor]) -> list[Tensor]: - return [mean_per_channel(x[0], self._channel_axis)] - - def __eq__(self, __o: object) -> bool: - return super().__eq__(__o) and self._channel_axis == __o._channel_axis - - def __hash__(self) -> int: - return hash((self.__class__.__name__, self.inplace, self._axes, self._axes_mode, self._channel_axis)) - - -################################################## -# Aggregators -################################################## - - -class NoopAggregator(AggregatorBase): - def __init__(self, num_samples: Optional[int], return_first: bool = False): - """ - Creates an aggregator that only accumulates data without any additional processing. - :param num_samples: The number of samples to collect. If None, all samples are collected. - :param return_first: If True, the first collected sample is returned on aggregate call. - If False, all collected samples are returned as a list. - """ - if return_first and num_samples is not None and num_samples != 1: - msg = "NoopAggregator with return_first=True should not have num_samples > 1" - raise nncf.InternalError(msg) - super().__init__(None, num_samples=1 if return_first else num_samples) - self._return_first = return_first - - def _register_reduced_input_impl(self, x: TensorType) -> None: - self._container.append(x) - - def _aggregate_impl(self): - return self._container[0] if self._return_first else self._container - - -class OnlineAggregatorBase(AggregatorBase, ABC): - """ - Base class for aggregators which are using aggregation function fn with following property: - fn([x1, x2, x3]) == fn([fn([x1, x2]), x3]) where x1, x2, x3 are samples to aggregate. - Online aggregation fn([fn([x1, x2]), x3]) allows to keep memory stamp low as only - one sample is stored during statistic collection. - """ - - def _register_reduced_input_impl(self, x: Tensor) -> None: - online_aggregation_axes = tuple(dim - 1 for dim in self._aggregation_axes if dim != 0) - if online_aggregation_axes: - reduced = self._aggregation_fn(x, axis=online_aggregation_axes, keepdims=self._keepdims) - else: - reduced = x - if 0 in self._aggregation_axes: - stacked_tensors = fns.stack([reduced, *self._container], axis=0) - aggregated = self._aggregation_fn(stacked_tensors, axis=0, keepdims=self._keepdims) - aggregated = fns.squeeze(aggregated, 0) - self._container = [aggregated] - else: - self._container.append(reduced) - - def _aggregate_impl(self) -> Tensor: - if 0 in self._aggregation_axes and self._keepdims: - return self._container[0] - return fns.stack(self._container) - - @abstractmethod - def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: - pass - - -class MinAggregator(OnlineAggregatorBase): - def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: - return fns.min(stacked_value, axis=axis, keepdims=keepdims) - - -class MaxAggregator(OnlineAggregatorBase): - def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: - return fns.max(stacked_value, axis=axis, keepdims=keepdims) - - -class OfflineAggregatorBase(AggregatorBase, ABC): - """ - Base class for aggregators which are using aggregation function fn which - does not fulfill property fn([x1, x2, x3]) == fn([fn([x1, x2]), x3]) - where x1, x2, x3 are samples to aggregate. Child aggregators collect - all samples in a container and aggregate them in one step. - """ - - def _register_reduced_input_impl(self, x: TensorType) -> None: - self._container.append(x) - - def _aggregate_impl(self) -> Tensor: - # Case when all registered tensors have identical shape - if all(self._container[0].shape == x.shape for x in self._container): - stacked_value = fns.stack(self._container) - aggregated = self._aggregation_fn(stacked_value, axis=self._aggregation_axes, keepdims=self._keepdims) - return fns.squeeze(aggregated, 0) - online_axes = tuple(x - 1 for x in self._aggregation_axes if x > 0) - - # Case when some registered tensors have different shapes and - # 0 is present in the aggregation axes - if 0 in self._aggregation_axes: - stacked_value, shape_after_aggregation = _move_axes_flatten_cat(self._container, online_axes) - aggregated = self._aggregation_fn(stacked_value, axis=0, keepdims=False) - if self._keepdims: - aggregated = fns.reshape(aggregated, shape_after_aggregation) - return aggregated - - # Case when some registered tensors have different shapes and - # 0 is not present in the aggregation axes - ret_val = [] - for tensor in self._container: - ret_val.append(self._aggregation_fn(tensor, axis=online_axes, keepdims=self._keepdims)) - return fns.stack(ret_val, axis=0) - - @abstractmethod - def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: - pass - - -class MeanAggregator(OfflineAggregatorBase): - def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: - return fns.mean(stacked_value, axis=axis, keepdims=keepdims) - - -class MedianAggregator(OfflineAggregatorBase): - def _aggregation_fn(self, stacked_value: Tensor, axis: AggregationAxes, keepdims: bool) -> Tensor: - return fns.median(stacked_value, axis=axis, keepdims=keepdims) - - -class NoOutliersAggregatorBase(OfflineAggregatorBase, ABC): - def __init__( - self, - aggregation_axes: Optional[AggregationAxes] = None, - num_samples: Optional[int] = None, - window_size=None, - quantile: float = 0.01, - ): - super().__init__(aggregation_axes=aggregation_axes, num_samples=num_samples) - self._window_size = window_size - self._container = deque(maxlen=window_size) - self._quantile = quantile - - def _aggregation_fn(self, stacked_value: Tensor, axis: int, keepdims: bool) -> Tensor: - low_values, high_values = fns.quantile(stacked_value, q=(self._quantile, 1 - self._quantile), axis=axis) - outliers_mask = fns.logical_or(stacked_value < low_values, high_values < stacked_value) - aggregated = self._masked_aggregation_fn( - stacked_samples=stacked_value, - mask=outliers_mask, - axis=axis, - keepdims=keepdims, - ) - return aggregated - - @abstractmethod - def _masked_aggregation_fn( - self, stacked_samples: Tensor, mask: Tensor, axis: AggregationAxes, keepdims: bool - ) -> Tensor: - pass - - def __eq__(self, __o: object) -> bool: - return super().__eq__(__o) and self._quantile == __o._quantile - - def __hash__(self) -> int: - return hash((self.__class__.__name__, self._quantile)) - - -class MeanNoOutliersAggregator(NoOutliersAggregatorBase): - def _masked_aggregation_fn( - self, stacked_samples: Tensor, mask: Tensor, axis: AggregationAxes, keepdims: bool - ) -> Tensor: - return fns.masked_mean(stacked_samples, axis=axis, mask=mask, keepdims=keepdims) - - -class MedianNoOutliersAggregator(NoOutliersAggregatorBase): - def _masked_aggregation_fn( - self, stacked_samples: Tensor, mask: Tensor, axis: AggregationAxes, keepdims: bool - ) -> Tensor: - return fns.masked_median(stacked_samples, axis=axis, mask=mask, keepdims=keepdims) - - -class MedianAbsoluteDeviationAggregator(AggregatorBase): - def __init__( - self, - aggregation_axes: Optional[AggregationAxes] = None, - num_samples: Optional[int] = None, - window_size=None, - ): - super().__init__( - aggregation_axes=aggregation_axes, - num_samples=num_samples, - window_size=window_size, - ) - if 0 not in self._aggregation_axes: - msg = "Aggregation without 0 dim is not supported yet for MedianAbsoluteDeviationAggregator" - raise NotImplementedError(msg) - - def _register_reduced_input_impl(self, x: TensorType) -> None: - return self._container.append(x) - - def _aggregate_impl(self) -> dict[str, Tensor]: - stacked_val, shape_after_aggregation = _move_axes_flatten_cat( - self._container, [x - 1 for x in self._aggregation_axes if x > 0] - ) - - mask = fns.abs(stacked_val) < fns.finfo(stacked_val).eps - median_per_ch = fns.masked_median(stacked_val, mask=mask, axis=0, keepdims=True) - - mad_values = fns.median( - fns.abs(stacked_val - median_per_ch), - axis=0, - keepdims=False, - ) - if self._keepdims: - median_per_ch = fns.reshape(median_per_ch, shape_after_aggregation) - mad_values = fns.reshape(mad_values, shape_after_aggregation) - else: - median_per_ch = fns.squeeze(median_per_ch, 0) - return { - MedianMADTensorStatistic.MEDIAN_VALUES_STAT: median_per_ch, - MedianMADTensorStatistic.MAD_VALUES_STAT: mad_values, - } - - -class PercentileAggregator(AggregatorBase): - def __init__( - self, - percentiles_to_collect: list[float], - aggregation_axes: Optional[AggregationAxes] = None, - num_samples: Optional[int] = None, - window_size=None, - ): - super().__init__(aggregation_axes=aggregation_axes, num_samples=num_samples) - if 0 not in self._aggregation_axes: - msg = "Aggregation without 0 dim is not supported yet for PercentileAggregator" - raise NotImplementedError(msg) - self._percentiles_to_collect = percentiles_to_collect - self._window_size = window_size - self._container = deque(maxlen=window_size) - - def _register_reduced_input_impl(self, x: TensorType) -> None: - return self._container.append(x) - - def _aggregate_impl(self) -> dict[float, Tensor]: - stacked_val, shape_after_aggregation = _move_axes_flatten_cat( - self._container, [x - 1 for x in self._aggregation_axes if x > 0] - ) - - percentiles = fns.percentile(stacked_val, self._percentiles_to_collect, axis=0, keepdims=False) - retval = {} - for idx, percentile in enumerate(self._percentiles_to_collect): - value = percentiles[idx] - if self._keepdims: - value = fns.reshape(value, shape_after_aggregation) - retval[percentile] = value - return retval - - -class HAWQAggregator(AggregatorBase): - def __init__(self, num_samples: Optional[int] = None): - super().__init__(num_samples=num_samples) - self._container = Tensor(0.0) - - def _register_reduced_input_impl(self, x: TensorType) -> None: - trace = fns.sum(fns.multiply(x, x)) - # NOTE: average trace?? divide by number of diagonal elements - # TODO: revise this formula as possibly it is with an error; adopted from previous HAWQ implementation - self._container = (self._container + trace) / x.size - - def _aggregate_impl(self) -> Tensor: - return self._container * 2 / self._collected_samples - - -def _move_axes_flatten_cat( - tensor_list: list[Tensor], aggregation_axes: tuple[int, ...] -) -> tuple[Tensor, tuple[int, ...]]: - """ - Moves aggregation axes to the beginning of the tensor shape for each tensor from the list, flattens - and concatenates them in 0 dimension. Computes target shape for the processed tensor - after an aggregation function is applied to it. Target shape preserves original order - of dimensions and replaces aggregated dimensions by 1. - - :param tensor_list: Tensor list to process. - :param aggregation_axes: Aggregation axes to move, flatten and concatenate. - :return: Tuple of the processed tensor and - target shape for the processed tensor after an aggregation function is applied to it. - """ - tensor_shape = list(tensor_list[0].shape) - - # Transpose dims to move aggregation axes forward - transpose_dims = list(range(len(tensor_shape))) - for idx, axis in enumerate(aggregation_axes): - transpose_dims[axis], transpose_dims[idx] = transpose_dims[idx], transpose_dims[axis] - - # Shape to flatten aggregation axes - reshape_shape = [-1] + [tensor_shape[dim] for dim in transpose_dims][len(aggregation_axes) :] - - reshaped_tensors = [] - for tensor in tensor_list: - transposed_t = fns.transpose(tensor, transpose_dims) - reshaped_tensors.append(fns.reshape(transposed_t, reshape_shape)) - - shape_after_aggregation = tuple(1 if idx in aggregation_axes else dim for idx, dim in enumerate(tensor_shape)) - return fns.concatenate(reshaped_tensors, axis=0), shape_after_aggregation - - -class HistogramAggregator(AggregatorBase): - """ - NNCF implementation of the torch.ao.quantization.observer.HistogramObserver. - Intended to be combined with a single RawReducer. - The aggregator records the running histogram of the input tensor values along with - min/max values. Only the reduction_axis==None is supported. - - The min and max are computed as follows: - - 1. Create the histogram of the incoming inputs. - The histogram is computed continuously, and the ranges per bin change - with every new tensor observed. - 2. Search the distribution in the histogram for optimal min/max values. - The search for the min/max values ensures the minimization of the - quantization error with respect to the floating point model. - """ - - histogram: Tensor - min_val: Optional[float] - max_val: Optional[float] - - def __init__( - self, - bins: int = 2048, - dist_nbits: int = 8, - num_samples: Optional[int] = None, - window_size: Optional[int] = None, - ) -> None: - """ - :param bins: Number of bins to use for the histogram - :param dist_nbits: Target quantization number of bits to calculate the quantization error. - :param num_samples: Maximum number of samples to collect. Aggregator - skips tensor registration if tensor registration was called num_samples times before. - Aggregator never skips registration if num_samples is None. - """ - super().__init__(num_samples=num_samples, window_size=window_size) - self.bins = bins - self.min_val = None - self.max_val = None - self.dst_nbins = 2**dist_nbits - self.upsample_rate = 16 # used to reduce quantization errors when upscaling histogram - - def _get_norm(self, delta_begin: Tensor, delta_end: Tensor, density: Tensor) -> Tensor: - """ - Compute the L2 norm of the values uniformaly distributed between - delta_begin and delta_end. - - norm = density * (integral_{begin, end} x^2) - = density * (end^3 - begin^3) / 3 - - :param delta_begin: Start of the integral interval. - :param delta_end: End of the integral interval. - :param density: Density of the elements in the histogram. - :return: The norm of the values uniformaly distributed between delta_begin and delta_end. - """ - norm = (delta_end * delta_end * delta_end - delta_begin * delta_begin * delta_begin) / 3 - return density * norm - - def _compute_quantization_error(self, next_start_bin: int, next_end_bin: int, bin_width: float) -> float: - """ - Computes the L2 norm of quantization error when mapping histogram bins into a reduced set of - quantization bins using the specified range [next_start_bin, next_end_bin]. - - :param next_start_bin: The index of the first source histogram bin included - in the quantization range. - :param next_end_bin: The index of the last source histogram bin included - in the quantization range. - :param bin_width: The width of a single source histogram bin. - :return: A scalar float value representing the total quantization error - for the given bin range. - """ - dst_bin_width = bin_width * (next_end_bin - next_start_bin + 1) / self.dst_nbins - if dst_bin_width == 0.0: - return 0.0 - - src_bin = fns.arange(0, self.bins, backend=self.histogram.backend, device=self.histogram.device) - # distances from the beginning of first dst_bin to the beginning and - # end of src_bin - src_bin_begin = (src_bin - next_start_bin) * bin_width - src_bin_end = src_bin_begin + bin_width - - # which dst_bins the beginning and end of src_bin belong to? - dst_bin_of_begin = fns.clip(fns.floor(src_bin_begin / dst_bin_width), 0, self.dst_nbins - 1) - dst_bin_of_begin_center = (dst_bin_of_begin + 0.5) * dst_bin_width - - dst_bin_of_end = fns.clip( - fns.floor(src_bin_end / dst_bin_width), - 0, - self.dst_nbins - 1, - ) - density = self.histogram / bin_width - - norm = fns.zeros((self.bins,), backend=self.histogram.backend, device=self.histogram.device) - - delta_begin = src_bin_begin - dst_bin_of_begin_center - delta_end = dst_bin_width / 2 - norm += self._get_norm( - delta_begin, - (fns.zeros((self.bins,), backend=self.histogram.backend, device=self.histogram.device) + 1) * delta_end, - density, - ) - - norm += (dst_bin_of_end - dst_bin_of_begin - 1) * self._get_norm(-dst_bin_width / 2, dst_bin_width / 2, density) - - dst_bin_of_end_center = dst_bin_of_end * dst_bin_width + dst_bin_width / 2 - - delta_begin = -dst_bin_width / 2 - delta_end = src_bin_end - dst_bin_of_end_center - norm += self._get_norm(delta_begin, delta_end, density) - - return fns.sum(norm).item() - - def _non_linear_param_search(self) -> tuple[float, float]: - """ - An approximation for L2 error minimization for selecting min/max. - By selecting new min/max, we filter out outliers in input distribution. - This follows the implementation of NormMinimization::NonlinearQuantizationParamsSearch in - caffe2/quantization/server/norm_minimization.cc - By selecting new min/max, we filter out outliers in input distribution. - - :return: An approximation for L2 error minimization for selecting min/max. - """ - assert self.histogram.shape[0] == self.bins, "bins mismatch" - bin_width = (self.max_val - self.min_val) / self.bins - - # cumulative sum - total = fns.sum(self.histogram).item() - cSum = fns.cumsum(self.histogram, axis=0) - - stepsize = 1e-5 # granularity - alpha = 0.0 # lower bound - beta = 1.0 # upper bound - start_bin = 0 - end_bin = self.bins - 1 - norm_min = float("inf") - - while alpha < beta: - # Find the next step - next_alpha = alpha + stepsize - next_beta = beta - stepsize - - # find the left and right bins between the quantile bounds - left = start_bin - right = end_bin - while left < end_bin and cSum[left] < next_alpha * total: - left = left + 1 - while right > start_bin and cSum[right] > next_beta * total: - right = right - 1 - - # decide the next move - next_start_bin = start_bin - next_end_bin = end_bin - if (left - start_bin) > (end_bin - right): - # move the start bin - next_start_bin = left - alpha = next_alpha - else: - # move the end bin - next_end_bin = right - beta = next_beta - - if next_start_bin == start_bin and next_end_bin == end_bin: - continue - - # calculate the quantization error using next_start_bin and next_end_bin - norm = self._compute_quantization_error(next_start_bin, next_end_bin, bin_width) - - if norm > norm_min: - break - norm_min = norm - start_bin = next_start_bin - end_bin = next_end_bin - - new_min = self.min_val + bin_width * start_bin - new_max = self.min_val + bin_width * (end_bin + 1) - return new_min, new_max - - def _upscale_histogram( - self, - histogram: Tensor, - orig_min: float, - orig_max: float, - update_min: float, - update_max: float, - ) -> Tensor: - """ - Updates the histogram into a more fine-coarsed histogram to reduce bin quantization errors. - - :param histogram: The input histogram tensor of size bins. - :param orig_min: The lower boundary of the original histogram range. - :param orig_max: The upper boundary of the original histogram range. - :param update_min: The lower boundary of the updated histogram range. - :param update_max: The upper boundary of the updated histogram range. - :return: A histogram tensor of size bins aligned with the updated range [update_min, update_max]. - """ - histogram = fns.repeat(histogram, self.upsample_rate) / self.upsample_rate - bin_size = (orig_max - orig_min) / (self.bins * self.upsample_rate) - mid_points_histogram = ( - fns.linspace( - orig_min, - orig_max, - self.bins * self.upsample_rate + 1, - backend=self.histogram.backend, - device=self.histogram.device, - )[:-1] - + 0.5 * bin_size - ) - boundaries_new_histogram = fns.linspace( - update_min, update_max, self.bins + 1, backend=self.histogram.backend, device=self.histogram.device - ) - # this maps the mid-poits of the histogram to the new histogram's space - bucket_assignments = fns.searchsorted(boundaries_new_histogram, mid_points_histogram, side="right") - 1 - # this then maps the histogram mid-points in the new space, weighted by the original histogram's values - # this is just the old histogram in the new histogram's space - - # In case due to numerical issues the values land higher/lower than the maximum/minimum - bucket_assignments[bucket_assignments >= self.bins] = self.bins - 1 - bucket_assignments[bucket_assignments < 0] = 0 - - update_histogram = fns.bincount(bucket_assignments, weights=histogram, minlength=self.bins) - return update_histogram - - def _combine_histograms( - self, - orig_hist: Tensor, - orig_min: float, - orig_max: float, - update_hist: Tensor, - update_min: float, - update_max: float, - ) -> Tensor: - """ - Combines the original histogram with an updated histogram, aligning both - to the target range [update_min, update_max]. - - :param orig_hist: The original histogram tensor of size bins. - :param orig_min: The lower boundary of the original histogram range. - :param orig_max: The upper boundary of the original histogram range. - :param update_hist: The histogram tensor of size bins in the updated range. - :param update_min: The lower boundary of the updated histogram range. - :param update_max: The upper boundary of the updated histogram range. - :return: A histogram tensor of size bins representing the combined - distribution in the updated range. - """ - # If the new min and max are the same as the current min and max, - # we can just add the new histogram to the original histogram - if update_min == orig_min and update_max == orig_max: - return orig_hist + update_hist - - # If the orig hist only has one value (i.e., the min and max are the same) - # we can just add it into new histogram - if orig_min == orig_max: - bin_value = fns.sum(orig_hist) - transformed_orig_hist = ( - fns.histogram( - fns.tensor( - orig_min, - backend=self.histogram.backend, - device=self.histogram.device, - dtype=TensorDataType.float32, - ), - bins=self.bins, - range=(update_min, update_max), - ) - * bin_value - ) - return transformed_orig_hist + update_hist - - # We assume the update_hist is already in the target range, we will map the orig_max to it - assert update_min <= orig_min - assert update_max >= orig_max - - # Now we need to turn the old_histogram, into the range of the new histogram - transformed_orig_hist = self._upscale_histogram( - orig_hist, - orig_min, - orig_max, - update_min, - update_max, - ) - - return update_hist + transformed_orig_hist - - def reset_histogram(self, x: Tensor, min_val: float, max_val: float) -> None: - """ - Resets and initializes the histogram based on the provided tensor and range. - - :param x: The input tensor used to build the histogram. - :param min_val: The scalar tensor specifying the lower boundary of the histogram range. - :param max_val: The scalar tensor specifying the upper boundary of the histogram range. - """ - self.min_val = min_val - self.max_val = max_val - self.histogram = fns.histogram(x, self.bins, range=(min_val, max_val)) - - def _register_reduced_input_impl(self, x: Tensor) -> None: - x_min, x_max = fns.min(x).item(), fns.max(x).item() - - current_min = self.min_val - current_max = self.max_val - - is_uninitialized = self.min_val is None or self.max_val is None - if is_uninitialized: - self.reset_histogram(x, x_min, x_max) - return - - update_min, update_max = x_min, x_max - new_min = min(current_min, update_min) - new_max = max(current_max, update_max) - - update_histogram = fns.histogram(x, self.bins, range=(new_min, new_max)) - - self.histogram = self._combine_histograms( - self.histogram, - current_min, - current_max, - update_histogram, - new_min, - new_max, - ) - self.min_val = new_min - self.max_val = new_max - - def _aggregate_impl(self) -> dict[str, Tensor]: - min_, max_ = self._non_linear_param_search() - return { - MinMaxTensorStatistic.MIN_STAT: fns.tensor( - min_, backend=self.histogram.backend, device=self.histogram.device, dtype=TensorDataType.float32 - ), - MinMaxTensorStatistic.MAX_STAT: fns.tensor( - max_, backend=self.histogram.backend, device=self.histogram.device, dtype=TensorDataType.float32 - ), - } - - -REDUCERS_MAP = { - StatisticsType.MIN: MinReducer, - StatisticsType.MAX: MaxReducer, - StatisticsType.ABS_MAX: AbsMaxReducer, - StatisticsType.MEAN: MeanReducer, - StatisticsType.QUANTILE: QuantileReducer, - StatisticsType.ABS_QUANTILE: AbsQuantileReducer, - StatisticsType.RAW: RawReducer, -} - -AGGREGATORS_MAP = { - AggregatorType.MIN: MinAggregator, - AggregatorType.MAX: MaxAggregator, - AggregatorType.MEAN: MeanAggregator, - AggregatorType.MEAN_NO_OUTLIERS: MeanNoOutliersAggregator, - AggregatorType.MEDIAN: MedianAggregator, - AggregatorType.MEDIAN_NO_OUTLIERS: MedianNoOutliersAggregator, -} diff --git a/src/nncf/experimental/common/tensor_statistics/statistics.py b/src/nncf/experimental/common/tensor_statistics/statistics.py deleted file mode 100644 index 103fa90b2d6..00000000000 --- a/src/nncf/experimental/common/tensor_statistics/statistics.py +++ /dev/null @@ -1,299 +0,0 @@ -# Copyright (c) 2025 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 __future__ import annotations - -from collections import Counter -from dataclasses import dataclass -from dataclasses import fields -from typing import Any, ClassVar - -import nncf -from nncf.tensor import Tensor -from nncf.tensor import TensorDataType -from nncf.tensor import functions as fns - - -@dataclass -class TensorStatistic: - """Base class that stores statistic data""" - - TENSOR_STATISTIC_OUTPUT_KEY: ClassVar[str] = "tensor_statistic_output" - - def get_data(self, is_serialized: bool = False) -> dict[str, Any]: - """ - Retrieves the data of the tensor statistics. If `is_serialized` is True, - the data is prepared for serialization by including only Tensor instances. - - :param is_serialized: If True, the data is prepared for serialization by - including only Tensor instances. - :return: Dictionary with keys and their associated data. If `is_serialized` - is True, the dictionary will contain only Tensor instances. - """ - if is_serialized: - return self._get_serialized_data() # Dict[str, Tensor] - return {field.name: getattr(self, field.name) for field in fields(self)} - - def _get_serialized_data(self) -> dict[str, Tensor]: - """ - Prepares the data for serialization by including only Tensor instances. - - :return: Dictionary with data for serialization. - """ - serialized_data = {} - for field in fields(self): - key = field.name - value = getattr(self, key) - if isinstance(value, Tensor): - serialized_data[key] = value - else: - msg = f"Unsupported type of value: {type(value)}" - raise nncf.InternalError(msg) - return serialized_data - - def load_data(self, data: dict[str, Tensor]) -> None: - """ - Loads the data from the serialized data. - - :param data: Data to load. - """ - for key in (field.name for field in fields(self)): - setattr(self, key, data[key]) - - @classmethod - def from_config(cls, config: dict[str, Any]) -> TensorStatistic: - args = {key: config[key] for key in (field.name for field in fields(cls))} - return cls(**args) - - -@dataclass -class MinMaxTensorStatistic(TensorStatistic): - MIN_STAT: ClassVar[str] = "min_values" - MAX_STAT: ClassVar[str] = "max_values" - MIN_MAX_STAT: ClassVar[str] = "min_max_values" - - min_values: Tensor - max_values: Tensor - - def __eq__(self, other: TensorStatistic): - if isinstance(other, MinMaxTensorStatistic): - return fns.allclose(self.min_values, other.min_values) and fns.allclose(self.max_values, other.max_values) - return False - - @classmethod - def from_config(cls, config: dict[str, Any]) -> TensorStatistic: - if cls.MIN_MAX_STAT in config: - # Build MinMaxTensorStatistic for aggregators which - # outputs both min and max values from a single aggregator instance. - return cls(**config[cls.MIN_MAX_STAT]) - return cls(min_values=config[cls.MIN_STAT], max_values=config[cls.MAX_STAT]) - - -@dataclass -class AbsMaxTensorStatistic(TensorStatistic): - ABS_MAX_STAT: ClassVar[str] = "abs_max" - - abs_max: Tensor - - def __eq__(self, other: TensorStatistic): - if isinstance(other, AbsMaxTensorStatistic): - return fns.allclose(self.abs_max, other.abs_max) - return False - - -@dataclass(init=False) -class MeanTensorStatistic(TensorStatistic): - MEAN_STAT: ClassVar[str] = "mean_values" - SHAPE_STAT: ClassVar[str] = "shape" - - mean_values: Tensor - shape: tuple[int, ...] - - def __init__(self, mean_values: Tensor, shape: Tensor) -> None: - self.mean_values = mean_values - self.shape = tuple(shape.tolist()) - - def __eq__(self, other: TensorStatistic): - if isinstance(other, MeanTensorStatistic): - return self.shape == other.shape and fns.allclose(self.mean_values, other.mean_values) - return False - - def _get_serialized_data(self) -> dict[str, Tensor]: - backend = self.mean_values.backend - device = self.mean_values.device - return { - self.MEAN_STAT: self.mean_values, - self.SHAPE_STAT: fns.tensor(self.shape, backend=backend, dtype=TensorDataType.int32, device=device), - } - - def load_data(self, loaded_data: dict[str, Tensor]) -> None: - self.mean_values = loaded_data[self.MEAN_STAT] - self.shape = tuple(loaded_data[self.SHAPE_STAT].tolist()) - - -@dataclass -class MedianMADTensorStatistic(TensorStatistic): - MEDIAN_VALUES_STAT: ClassVar[str] = "median_values" - MAD_VALUES_STAT: ClassVar[str] = "mad_values" - - median_values: Tensor - mad_values: Tensor - - def __eq__(self, other: TensorStatistic): - if isinstance(other, MedianMADTensorStatistic): - return fns.allclose(self.median_values, other.median_values) and fns.allclose( - self.mad_values, other.mad_values - ) - return False - - @classmethod - def from_config(cls, config: dict[str, Any]) -> TensorStatistic: - return cls( - median_values=config[cls.TENSOR_STATISTIC_OUTPUT_KEY][cls.MEDIAN_VALUES_STAT], - mad_values=config[cls.TENSOR_STATISTIC_OUTPUT_KEY][cls.MAD_VALUES_STAT], - ) - - -@dataclass -class PercentileTensorStatistic(TensorStatistic): - PERCENTILE_VS_VALUE_DICT: ClassVar[str] = "percentile_vs_values_dict" - - percentile_vs_values_dict: dict[str, Tensor] - - def __eq__(self, other: TensorStatistic): - if isinstance(other, PercentileTensorStatistic): - if Counter(self.percentile_vs_values_dict.keys()) != Counter(other.percentile_vs_values_dict.keys()): - return False - for pct in self.percentile_vs_values_dict: - if not fns.allclose(self.percentile_vs_values_dict[pct], other.percentile_vs_values_dict[pct]): - return False - return True - return False - - @classmethod - def from_config(cls, config: dict[str, Any]) -> TensorStatistic: - if cls.TENSOR_STATISTIC_OUTPUT_KEY in config: - percentile_vs_values_dict = config[cls.TENSOR_STATISTIC_OUTPUT_KEY] - else: - percentile_vs_values_dict = {} - for (_, percentile), value in config.items(): - percentile_vs_values_dict[percentile] = value - return cls(percentile_vs_values_dict=percentile_vs_values_dict) - - def _get_serialized_data(self) -> dict[str, Tensor]: - return self.PERCENTILE_VS_VALUE_DICT - - def load_data(self, loaded_data: dict[str, Tensor]) -> None: - self.percentile_vs_values_dict = loaded_data - - -@dataclass -class RawTensorStatistic(TensorStatistic): - VALUES_STATS: ClassVar[str] = "values" - - values: Tensor - - def __eq__(self, other: RawTensorStatistic) -> bool: - if isinstance(other, RawTensorStatistic): - return fns.allclose(self.values, other.values) - return False - - -@dataclass -class HessianTensorStatistic(TensorStatistic): - HESSIAN_INPUT_ACTIVATION_STATS: ClassVar[str] = "hessian" - - hessian: Tensor - - def __eq__(self, other: TensorStatistic): - if isinstance(other, HessianTensorStatistic): - return fns.allclose(self.hessian, other.hessian) - return False - - -@dataclass -class MeanVarianceTensorStatistic(TensorStatistic): - MEAN_VARIANCE_STAT: ClassVar[str] = "mean_variance" - - mean_variance: Tensor - - def __eq__(self, other: TensorStatistic): - if isinstance(other, MeanVarianceTensorStatistic): - return fns.allclose(self.mean_variance, other.mean_variance) - return False - - -@dataclass -class MaxVarianceTensorStatistic(TensorStatistic): - MAX_VARIANCE_STAT: ClassVar[str] = "max_variance" - - max_variance: Tensor - - def __eq__(self, other: TensorStatistic): - if isinstance(other, MaxVarianceTensorStatistic): - return fns.allclose(self.max_variance, other.max_variance) - return False - - -@dataclass -class MeanMagnitudeTensorStatistic(TensorStatistic): - MEAN_MAGNITUDE_STAT: ClassVar[str] = "mean_magnitude" - - mean_magnitude: Tensor - - def __eq__(self, other: TensorStatistic): - if isinstance(other, MeanMagnitudeTensorStatistic): - return fns.allclose(self.mean_magnitude, other.mean_magnitude) - return False - - -@dataclass -class WCTensorStatistic(TensorStatistic): - MEAN_STAT = "mean_values" - SHAPE_STAT = "shape_values" - - mean_values: list[Tensor] - shape_values: list[tuple[Tensor]] - - def __eq__(self, other: Any) -> bool: - shapes_equal = all(self.shapes[i] == other.shapes[i] for i in range(len(self.mean_values))) - if not shapes_equal: - return False - mean_values_equal = all( - self.tensor_eq(self.mean_values[i], other.mean_values[i]) for i in range(len(self.mean_values)) - ) - return mean_values_equal - - def _get_serialized_data(self) -> dict[str, Tensor]: - backend = self.mean_values[0].backend - device = self.mean_values[0].device - return { - self.MEAN_STAT: fns.stack(self.mean_values), - self.SHAPE_STAT: fns.tensor( - self.shape_values, - backend=backend, - dtype=TensorDataType.int32, - device=device, - ), - } - - def load_data(self, loaded_data: dict[str, Tensor]) -> None: - self.shape_values = [tuple(shape) for shape in loaded_data[self.SHAPE_STAT]] - self.mean_values = [it for it in loaded_data[self.MEAN_STAT]] - - @classmethod - def from_config(cls, config: dict[str, Any]) -> TensorStatistic: - mean_values, shape_values = None, None - if cls.MEAN_STAT in config and config[cls.MEAN_STAT] is not None: - mean_values = [fns.squeeze(it) for it in config[cls.MEAN_STAT]] - if cls.SHAPE_STAT in config and config[cls.SHAPE_STAT] is not None: - shape_values = [tuple(it.tolist()) for it in config[cls.SHAPE_STAT]] - return cls(mean_values=mean_values, shape_values=shape_values) diff --git a/src/nncf/experimental/torch/fx/statistics/aggregator.py b/src/nncf/experimental/torch/fx/statistics/aggregator.py index 4988292f664..f5d2f922ff1 100644 --- a/src/nncf/experimental/torch/fx/statistics/aggregator.py +++ b/src/nncf/experimental/torch/fx/statistics/aggregator.py @@ -19,9 +19,9 @@ from nncf.common.graph.transformations.layout import TransformationLayout from nncf.common.tensor_statistics.aggregator import StatisticPointsContainer from nncf.common.tensor_statistics.aggregator import StatisticsAggregator +from nncf.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.statistics import TensorStatistic from nncf.common.utils.backend import BackendType -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import TensorStatistic from nncf.experimental.torch.fx.commands import FXApplyTransformationCommand from nncf.experimental.torch.fx.transformations import leaf_module_insertion_transformation_builder from nncf.tensor import Tensor diff --git a/src/nncf/onnx/statistics/aggregator.py b/src/nncf/onnx/statistics/aggregator.py index 3b6d6ec96ff..6c2b2da8acc 100644 --- a/src/nncf/onnx/statistics/aggregator.py +++ b/src/nncf/onnx/statistics/aggregator.py @@ -18,10 +18,10 @@ from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.transformations.layout import TransformationLayout from nncf.common.tensor_statistics.aggregator import StatisticsAggregator +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer +from nncf.common.tensor_statistics.statistics import TensorStatistic from nncf.common.utils.backend import BackendType -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import TensorStatistic from nncf.onnx.graph.node_utils import get_input_edge from nncf.onnx.graph.node_utils import get_input_edges_mapping from nncf.onnx.graph.onnx_helper import get_name_to_node_map diff --git a/src/nncf/onnx/statistics/collectors.py b/src/nncf/onnx/statistics/collectors.py index bb86da78b47..e97ce8c69c2 100644 --- a/src/nncf/onnx/statistics/collectors.py +++ b/src/nncf/onnx/statistics/collectors.py @@ -11,15 +11,15 @@ from typing import Optional -from nncf.experimental.common.tensor_statistics.collectors import BatchMeanReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAggregator -from nncf.experimental.common.tensor_statistics.collectors import MeanPerChReducer -from nncf.experimental.common.tensor_statistics.collectors import NoopAggregator -from nncf.experimental.common.tensor_statistics.collectors import RawReducer -from nncf.experimental.common.tensor_statistics.collectors import ShapeReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import MeanTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import RawTensorStatistic +from nncf.common.tensor_statistics.collectors import BatchMeanReducer +from nncf.common.tensor_statistics.collectors import MeanAggregator +from nncf.common.tensor_statistics.collectors import MeanPerChReducer +from nncf.common.tensor_statistics.collectors import NoopAggregator +from nncf.common.tensor_statistics.collectors import RawReducer +from nncf.common.tensor_statistics.collectors import ShapeReducer +from nncf.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.statistics import MeanTensorStatistic +from nncf.common.tensor_statistics.statistics import RawTensorStatistic def get_mean_statistic_collector( diff --git a/src/nncf/openvino/statistics/aggregator.py b/src/nncf/openvino/statistics/aggregator.py index cad1fb0b470..3c9b0c1d339 100644 --- a/src/nncf/openvino/statistics/aggregator.py +++ b/src/nncf/openvino/statistics/aggregator.py @@ -18,12 +18,12 @@ from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.transformations.layout import TransformationLayout from nncf.common.tensor_statistics.aggregator import StatisticsAggregator +from nncf.common.tensor_statistics.collectors import MergedTensorCollector +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer +from nncf.common.tensor_statistics.statistics import TensorStatistic from nncf.common.utils.backend import BackendType -from nncf.experimental.common.tensor_statistics.collectors import MergedTensorCollector -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import TensorStatistic from nncf.openvino.graph.node_utils import get_ov_model_reduce_node_name from nncf.openvino.graph.node_utils import get_reducer_output_node_names from nncf.openvino.graph.transformations.commands import OVInplaceFnInsertionCommand diff --git a/src/nncf/openvino/statistics/collectors.py b/src/nncf/openvino/statistics/collectors.py index 35e01a55e99..365f0b7f772 100644 --- a/src/nncf/openvino/statistics/collectors.py +++ b/src/nncf/openvino/statistics/collectors.py @@ -11,25 +11,25 @@ from typing import Optional -from nncf.experimental.common.tensor_statistics.collectors import AbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import AbsQuantileReducer -from nncf.experimental.common.tensor_statistics.collectors import BatchMeanReducer -from nncf.experimental.common.tensor_statistics.collectors import InplaceInsertionFNType -from nncf.experimental.common.tensor_statistics.collectors import MaxReducer -from nncf.experimental.common.tensor_statistics.collectors import MaxVarianceReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAggregator -from nncf.experimental.common.tensor_statistics.collectors import MeanPerChReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanVarianceReducer -from nncf.experimental.common.tensor_statistics.collectors import MinReducer -from nncf.experimental.common.tensor_statistics.collectors import NoopAggregator -from nncf.experimental.common.tensor_statistics.collectors import QuantileReducer -from nncf.experimental.common.tensor_statistics.collectors import RawReducer -from nncf.experimental.common.tensor_statistics.collectors import ShapeReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import MeanTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import RawTensorStatistic +from nncf.common.tensor_statistics.collectors import AbsMaxReducer +from nncf.common.tensor_statistics.collectors import AbsQuantileReducer +from nncf.common.tensor_statistics.collectors import BatchMeanReducer +from nncf.common.tensor_statistics.collectors import InplaceInsertionFNType +from nncf.common.tensor_statistics.collectors import MaxReducer +from nncf.common.tensor_statistics.collectors import MaxVarianceReducer +from nncf.common.tensor_statistics.collectors import MeanAbsMaxReducer +from nncf.common.tensor_statistics.collectors import MeanAggregator +from nncf.common.tensor_statistics.collectors import MeanPerChReducer +from nncf.common.tensor_statistics.collectors import MeanReducer +from nncf.common.tensor_statistics.collectors import MeanVarianceReducer +from nncf.common.tensor_statistics.collectors import MinReducer +from nncf.common.tensor_statistics.collectors import NoopAggregator +from nncf.common.tensor_statistics.collectors import QuantileReducer +from nncf.common.tensor_statistics.collectors import RawReducer +from nncf.common.tensor_statistics.collectors import ShapeReducer +from nncf.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.statistics import MeanTensorStatistic +from nncf.common.tensor_statistics.statistics import RawTensorStatistic from nncf.openvino.graph.node_utils import get_inplace_batch_mean_op from nncf.openvino.graph.node_utils import get_inplace_max_op from nncf.openvino.graph.node_utils import get_inplace_max_var_op diff --git a/src/nncf/quantization/algorithms/bias_correction/algorithm.py b/src/nncf/quantization/algorithms/bias_correction/algorithm.py index 96a90f5d348..9363f88d067 100644 --- a/src/nncf/quantization/algorithms/bias_correction/algorithm.py +++ b/src/nncf/quantization/algorithms/bias_correction/algorithm.py @@ -27,10 +27,10 @@ from nncf.common.logging.track_progress import track from nncf.common.tensor_statistics.statistic_point import StatisticPoint from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer +from nncf.common.tensor_statistics.statistical_functions import mean_per_channel from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import copy_model from nncf.common.utils.backend import get_backend -from nncf.experimental.common.tensor_statistics.statistical_functions import mean_per_channel from nncf.quantization.algorithms.algorithm import Algorithm from nncf.tensor import Tensor from nncf.tensor import functions as fns diff --git a/src/nncf/quantization/algorithms/bias_correction/backend.py b/src/nncf/quantization/algorithms/bias_correction/backend.py index a2b5818f9e8..6bbb60d857c 100644 --- a/src/nncf/quantization/algorithms/bias_correction/backend.py +++ b/src/nncf/quantization/algorithms/bias_correction/backend.py @@ -21,7 +21,7 @@ from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.transformations.commands import TransformationCommand from nncf.common.tensor import NNCFTensor -from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.tensor import Tensor TModel = TypeVar("TModel") @@ -85,7 +85,7 @@ def mean_statistic_collector( inplace: bool, num_samples: Optional[int] = None, window_size: Optional[int] = None, - ) -> TensorStatisticCollectorBase: + ) -> TensorCollector: """ Returns backend-specific mean statistic collector. @@ -93,18 +93,18 @@ def mean_statistic_collector( :param inplace: Whether to calculate statistic inplace or not. :param num_samples: Maximum number of samples to collect. :param window_size: The maximum size of the samples queue. - :return: Backend-specific TensorStatisticCollectorBase for the statistics calculation. + :return: Backend-specific TensorCollector for the statistics calculation. """ @staticmethod @abstractmethod - def raw_statistic_collector(num_samples: Optional[int] = None) -> TensorStatisticCollectorBase: + def raw_statistic_collector(num_samples: Optional[int] = None) -> TensorCollector: """ Returns backend-specific raw statistic collector. This statistic collector is used for raw data calculation, without aggregating. :param num_samples: Maximum number of samples to collect. - :return: Backend-specific TensorStatisticCollectorBase for the statistics calculation. + :return: Backend-specific TensorCollector for the statistics calculation. """ @staticmethod diff --git a/src/nncf/quantization/algorithms/bias_correction/onnx_backend.py b/src/nncf/quantization/algorithms/bias_correction/onnx_backend.py index 398c65da311..33566723539 100644 --- a/src/nncf/quantization/algorithms/bias_correction/onnx_backend.py +++ b/src/nncf/quantization/algorithms/bias_correction/onnx_backend.py @@ -16,7 +16,7 @@ from nncf.common.graph import NNCFGraph from nncf.common.graph import NNCFNode from nncf.common.graph.transformations.commands import TargetType -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.onnx.graph.model_utils import remove_fq_from_inputs from nncf.onnx.graph.node_utils import get_bias_value from nncf.onnx.graph.node_utils import is_any_weight_quantized diff --git a/src/nncf/quantization/algorithms/bias_correction/openvino_backend.py b/src/nncf/quantization/algorithms/bias_correction/openvino_backend.py index 680f19e6b2e..72cec286195 100644 --- a/src/nncf/quantization/algorithms/bias_correction/openvino_backend.py +++ b/src/nncf/quantization/algorithms/bias_correction/openvino_backend.py @@ -16,7 +16,7 @@ from nncf.common.graph import NNCFGraph from nncf.common.graph import NNCFNode from nncf.common.graph.transformations.commands import TargetType -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.openvino.graph.metatypes.groups import FAKE_QUANTIZE_OPERATIONS from nncf.openvino.graph.model_utils import remove_fq_from_inputs from nncf.openvino.graph.node_utils import get_bias_value diff --git a/src/nncf/quantization/algorithms/bias_correction/torch_fx_backend.py b/src/nncf/quantization/algorithms/bias_correction/torch_fx_backend.py index 8bfc70b08f0..23faf490889 100644 --- a/src/nncf/quantization/algorithms/bias_correction/torch_fx_backend.py +++ b/src/nncf/quantization/algorithms/bias_correction/torch_fx_backend.py @@ -17,7 +17,7 @@ from nncf.common.graph import NNCFGraph from nncf.common.graph import NNCFNode from nncf.common.graph.transformations.commands import TargetType -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.experimental.torch.fx.commands import FXApplyTransformationCommand from nncf.experimental.torch.fx.model_utils import get_target_point from nncf.experimental.torch.fx.model_utils import remove_fq_from_inputs diff --git a/src/nncf/quantization/algorithms/channel_alignment/backend.py b/src/nncf/quantization/algorithms/channel_alignment/backend.py index eff238cf582..ed9c449d130 100644 --- a/src/nncf/quantization/algorithms/channel_alignment/backend.py +++ b/src/nncf/quantization/algorithms/channel_alignment/backend.py @@ -20,7 +20,7 @@ from nncf.common.graph.layer_attributes import ConvolutionLayerAttributes from nncf.common.graph.transformations.commands import TargetPoint from nncf.common.graph.transformations.commands import TargetType -from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase +from nncf.common.tensor_statistics.collectors import TensorCollector TModel = TypeVar("TModel") @@ -95,9 +95,7 @@ def get_weights_port_ids_for_node(node: NNCFNode) -> tuple[int, int]: @staticmethod @abstractmethod - def get_statistic_collector( - reduction_axes, q: float, num_samples: int, inplace: bool - ) -> TensorStatisticCollectorBase: + def get_statistic_collector(reduction_axes, q: float, num_samples: int, inplace: bool) -> TensorCollector: """ Get backend-specific tensor collector that collects medians of minimal and maximal quantiles. diff --git a/src/nncf/quantization/algorithms/channel_alignment/openvino_backend.py b/src/nncf/quantization/algorithms/channel_alignment/openvino_backend.py index fcfc57c12bb..e20e848ec63 100644 --- a/src/nncf/quantization/algorithms/channel_alignment/openvino_backend.py +++ b/src/nncf/quantization/algorithms/channel_alignment/openvino_backend.py @@ -19,11 +19,10 @@ from nncf.common.graph import NNCFNode from nncf.common.graph.layer_attributes import ConvolutionLayerAttributes from nncf.common.graph.transformations.commands import TargetType -from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase -from nncf.experimental.common.tensor_statistics.collectors import AxesMode -from nncf.experimental.common.tensor_statistics.collectors import MedianAggregator -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.collectors import AxesMode +from nncf.common.tensor_statistics.collectors import MedianAggregator +from nncf.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.openvino.graph import node_utils from nncf.openvino.graph.layout import OVLayoutElem from nncf.openvino.graph.layout import get_conv_weights_layout_from_node @@ -78,9 +77,7 @@ def get_add_metatypes(): return [OVAddMetatype, OVSubtractMetatype] @staticmethod - def get_statistic_collector( - reduction_axes, q: float, num_samples: int, inplace: bool - ) -> TensorStatisticCollectorBase: + def get_statistic_collector(reduction_axes, q: float, num_samples: int, inplace: bool) -> TensorCollector: tensor_collector = TensorCollector(MinMaxTensorStatistic) quantile_reducer = OVQuantileReducer(reduction_axes, AxesMode.REDUCTION, (q, 1 - q), inplace) diff --git a/src/nncf/quantization/algorithms/fast_bias_correction/algorithm.py b/src/nncf/quantization/algorithms/fast_bias_correction/algorithm.py index 286ee423c92..5c35f67a5de 100644 --- a/src/nncf/quantization/algorithms/fast_bias_correction/algorithm.py +++ b/src/nncf/quantization/algorithms/fast_bias_correction/algorithm.py @@ -24,9 +24,9 @@ from nncf.common.logging.track_progress import track from nncf.common.tensor_statistics.statistic_point import StatisticPoint from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer +from nncf.common.tensor_statistics.statistical_functions import mean_per_channel from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import get_backend -from nncf.experimental.common.tensor_statistics.statistical_functions import mean_per_channel from nncf.quantization.algorithms.algorithm import Algorithm from nncf.tensor import Tensor from nncf.tensor import functions as fns diff --git a/src/nncf/quantization/algorithms/fast_bias_correction/backend.py b/src/nncf/quantization/algorithms/fast_bias_correction/backend.py index eca0d84fa4b..dfff3b9c254 100644 --- a/src/nncf/quantization/algorithms/fast_bias_correction/backend.py +++ b/src/nncf/quantization/algorithms/fast_bias_correction/backend.py @@ -20,7 +20,7 @@ from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.transformations.commands import TransformationCommand from nncf.common.graph.transformations.layout import TransformationLayout -from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.tensor import Tensor TModel = TypeVar("TModel") @@ -77,7 +77,7 @@ def mean_statistic_collector( inplace: bool, num_samples: Optional[int] = None, window_size: Optional[int] = None, - ) -> TensorStatisticCollectorBase: + ) -> TensorCollector: """ Returns backend-specific mean statistic collector. @@ -85,7 +85,7 @@ def mean_statistic_collector( :param inplace: Whether to calculate statistic inplace or not. :param num_samples: Maximum number of samples to collect. :param window_size: The maximum size of the samples queue. - :return: Backend-specific TensorStatisticCollectorBase for the statistics calculation. + :return: Backend-specific TensorCollector for the statistics calculation. """ @staticmethod diff --git a/src/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py b/src/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py index 2447d5b8509..ca96983ed99 100644 --- a/src/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py +++ b/src/nncf/quantization/algorithms/fast_bias_correction/onnx_backend.py @@ -18,7 +18,7 @@ from nncf.common.graph import NNCFGraph from nncf.common.graph import NNCFNode from nncf.common.graph.transformations.commands import TargetType -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.onnx.graph.metatypes.groups import OPERATIONS_WITH_BIAS_REDUCED from nncf.onnx.graph.node_utils import get_act_quantization_axis from nncf.onnx.graph.node_utils import get_bias_value diff --git a/src/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py b/src/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py index 0fc0501056b..237f0d9a4ef 100644 --- a/src/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py +++ b/src/nncf/quantization/algorithms/fast_bias_correction/openvino_backend.py @@ -17,7 +17,7 @@ from nncf.common.graph import NNCFGraph from nncf.common.graph import NNCFNode from nncf.common.graph.transformations.commands import TargetType -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.openvino.graph.metatypes.groups import FAKE_QUANTIZE_OPERATIONS from nncf.openvino.graph.metatypes.groups import OPERATIONS_WITH_BIAS_REDUCED from nncf.openvino.graph.model_builder import OVModelBuilder diff --git a/src/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py b/src/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py index 1a77ab88365..84a16484901 100644 --- a/src/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py +++ b/src/nncf/quantization/algorithms/fast_bias_correction/torch_backend.py @@ -19,7 +19,7 @@ from nncf.common.graph.definitions import NNCFGraphNodeType from nncf.common.graph.model_transformer import ModelTransformer from nncf.common.graph.transformations.commands import TargetType -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.quantization.algorithms.fast_bias_correction.backend import FastBiasCorrectionAlgoBackend from nncf.tensor import Tensor from nncf.torch.function_hook.extractor import extract_model diff --git a/src/nncf/quantization/algorithms/fast_bias_correction/torch_fx_backend.py b/src/nncf/quantization/algorithms/fast_bias_correction/torch_fx_backend.py index c3518bc50e6..dff472ebd3f 100644 --- a/src/nncf/quantization/algorithms/fast_bias_correction/torch_fx_backend.py +++ b/src/nncf/quantization/algorithms/fast_bias_correction/torch_fx_backend.py @@ -18,7 +18,7 @@ from nncf.common.graph import NNCFGraph from nncf.common.graph import NNCFNode from nncf.common.graph.transformations.commands import TargetType -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.experimental.torch.fx.commands import FXApplyTransformationCommand from nncf.experimental.torch.fx.model_utils import get_target_point from nncf.experimental.torch.fx.node_utils import get_bias_value diff --git a/src/nncf/quantization/algorithms/layerwise/backend.py b/src/nncf/quantization/algorithms/layerwise/backend.py index 05e801906e4..bd77aa6f8f7 100644 --- a/src/nncf/quantization/algorithms/layerwise/backend.py +++ b/src/nncf/quantization/algorithms/layerwise/backend.py @@ -16,7 +16,7 @@ from nncf.common.graph import NNCFGraph from nncf.common.graph.transformations.commands import TargetPoint from nncf.common.graph.transformations.commands import TargetType -from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.data.dataset import Dataset from nncf.quantization.algorithms.layerwise.iterator import LayerwiseIterator from nncf.quantization.algorithms.layerwise.scheduler import LayerwiseStep @@ -67,11 +67,11 @@ def target_point(target_type: TargetType, target_node_name: str, port_id: int) - @staticmethod @abstractmethod - def raw_statistic_collector(num_samples: Optional[int] = None) -> TensorStatisticCollectorBase: + def raw_statistic_collector(num_samples: Optional[int] = None) -> TensorCollector: """ Returns backend-specific raw statistic collector. This statistic collector is used for raw data calculation, without aggregating. :param num_samples: Maximum number of samples to collect. - :return: Backend-specific TensorStatisticCollectorBase for the statistics calculation. + :return: Backend-specific TensorCollector for the statistics calculation. """ diff --git a/src/nncf/quantization/algorithms/layerwise/openvino_backend.py b/src/nncf/quantization/algorithms/layerwise/openvino_backend.py index a53dea2adf7..11861727905 100644 --- a/src/nncf/quantization/algorithms/layerwise/openvino_backend.py +++ b/src/nncf/quantization/algorithms/layerwise/openvino_backend.py @@ -15,7 +15,7 @@ from nncf.common.graph import NNCFGraph from nncf.common.graph.transformations.commands import TargetType -from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.data.dataset import Dataset from nncf.openvino.graph.transformations.commands import OVTargetPoint from nncf.openvino.statistics.collectors import get_raw_stat_collector @@ -43,5 +43,5 @@ def target_point(target_type: TargetType, target_node_name: str, port_id: int) - return OVTargetPoint(target_type, target_node_name, port_id) @staticmethod - def raw_statistic_collector(num_samples: Optional[int] = None) -> TensorStatisticCollectorBase: + def raw_statistic_collector(num_samples: Optional[int] = None) -> TensorCollector: return get_raw_stat_collector(num_samples) diff --git a/src/nncf/quantization/algorithms/min_max/algorithm.py b/src/nncf/quantization/algorithms/min_max/algorithm.py index 5ff7c06f3b3..19d1446e557 100644 --- a/src/nncf/quantization/algorithms/min_max/algorithm.py +++ b/src/nncf/quantization/algorithms/min_max/algorithm.py @@ -45,14 +45,14 @@ from nncf.common.quantization.structs import QuantizationScheme from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup +from nncf.common.tensor_statistics.collectors import AGGREGATORS_MAP +from nncf.common.tensor_statistics.collectors import HistogramAggregator +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import get_backend -from nncf.experimental.common.tensor_statistics.collectors import AGGREGATORS_MAP -from nncf.experimental.common.tensor_statistics.collectors import HistogramAggregator -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.parameters import ModelType from nncf.parameters import QuantizationMode from nncf.parameters import TargetDevice diff --git a/src/nncf/quantization/algorithms/min_max/backend.py b/src/nncf/quantization/algorithms/min_max/backend.py index 3fb5f136775..2b593075138 100644 --- a/src/nncf/quantization/algorithms/min_max/backend.py +++ b/src/nncf/quantization/algorithms/min_max/backend.py @@ -22,7 +22,7 @@ from nncf.common.hardware.config import HWConfig from nncf.common.quantization.quantizer_propagation.structs import QuantizationTrait from nncf.common.quantization.structs import QuantizerConfig -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.parameters import ModelType from nncf.parameters import TargetDevice from nncf.quantization.fake_quantize import FakeConvertParameters diff --git a/src/nncf/quantization/algorithms/min_max/onnx_backend.py b/src/nncf/quantization/algorithms/min_max/onnx_backend.py index 96f534c726e..a8da804608c 100644 --- a/src/nncf/quantization/algorithms/min_max/onnx_backend.py +++ b/src/nncf/quantization/algorithms/min_max/onnx_backend.py @@ -22,8 +22,8 @@ from nncf.common.hardware.config import HWConfig from nncf.common.quantization.quantizer_propagation.structs import QuantizationTrait from nncf.common.quantization.structs import QuantizerConfig -from nncf.experimental.common.tensor_statistics.collectors import REDUCERS_MAP -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase +from nncf.common.tensor_statistics.collectors import REDUCERS_MAP +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.onnx.graph.metatypes import onnx_metatypes as om from nncf.onnx.graph.metatypes.groups import ELEMENTWISE_OPERATIONS from nncf.onnx.graph.metatypes.groups import MATMUL_METATYPES diff --git a/src/nncf/quantization/algorithms/min_max/openvino_backend.py b/src/nncf/quantization/algorithms/min_max/openvino_backend.py index 4d4dd617bdb..da20153d0f2 100644 --- a/src/nncf/quantization/algorithms/min_max/openvino_backend.py +++ b/src/nncf/quantization/algorithms/min_max/openvino_backend.py @@ -17,7 +17,7 @@ from nncf.common.graph.transformations.commands import TargetType from nncf.common.hardware.config import HWConfig from nncf.common.quantization.structs import QuantizerConfig -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.openvino.graph.layer_attributes import OVLayerAttributes from nncf.openvino.graph.metatypes import openvino_metatypes as om from nncf.openvino.graph.metatypes.groups import ELEMENTWISE_OPERATIONS diff --git a/src/nncf/quantization/algorithms/min_max/torch_backend.py b/src/nncf/quantization/algorithms/min_max/torch_backend.py index ba7b34b02c6..ad513ac6d67 100644 --- a/src/nncf/quantization/algorithms/min_max/torch_backend.py +++ b/src/nncf/quantization/algorithms/min_max/torch_backend.py @@ -24,8 +24,8 @@ from nncf.common.hardware.config import HWConfig from nncf.common.quantization.quantizer_propagation.structs import QuantizationTrait from nncf.common.quantization.structs import QuantizerConfig -from nncf.experimental.common.tensor_statistics.collectors import REDUCERS_MAP -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase +from nncf.common.tensor_statistics.collectors import REDUCERS_MAP +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.parameters import ModelType from nncf.parameters import TargetDevice from nncf.quantization.algorithms.min_max.backend import MinMaxAlgoBackend diff --git a/src/nncf/quantization/algorithms/min_max/torch_fx_backend.py b/src/nncf/quantization/algorithms/min_max/torch_fx_backend.py index eb14a764c8f..2e748687525 100644 --- a/src/nncf/quantization/algorithms/min_max/torch_fx_backend.py +++ b/src/nncf/quantization/algorithms/min_max/torch_fx_backend.py @@ -26,8 +26,8 @@ from nncf.common.quantization.structs import QuantizationScheme from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import TypedQuantizerConfig -from nncf.experimental.common.tensor_statistics.collectors import REDUCERS_MAP -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase +from nncf.common.tensor_statistics.collectors import REDUCERS_MAP +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.experimental.torch.fx.commands import FXApplyTransformationCommand from nncf.experimental.torch.fx.model_utils import get_target_point from nncf.experimental.torch.fx.transformations import qdq_insertion_transformation_builder diff --git a/src/nncf/quantization/algorithms/smooth_quant/algorithm.py b/src/nncf/quantization/algorithms/smooth_quant/algorithm.py index 4205aae4f5d..abf8a9e17df 100644 --- a/src/nncf/quantization/algorithms/smooth_quant/algorithm.py +++ b/src/nncf/quantization/algorithms/smooth_quant/algorithm.py @@ -23,14 +23,14 @@ from nncf.common.graph.transformations.layout import TransformationLayout from nncf.common.logging import nncf_logger from nncf.common.logging.track_progress import track +from nncf.common.tensor_statistics.collectors import AxesMode +from nncf.common.tensor_statistics.collectors import MaxAggregator +from nncf.common.tensor_statistics.collectors import NoopAggregator +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint 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.collectors import AxesMode -from nncf.experimental.common.tensor_statistics.collectors import MaxAggregator -from nncf.experimental.common.tensor_statistics.collectors import NoopAggregator -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector from nncf.quantization.algorithms.algorithm import Algorithm from nncf.tensor import Tensor from nncf.tensor import functions as fns diff --git a/src/nncf/quantization/algorithms/smooth_quant/backend.py b/src/nncf/quantization/algorithms/smooth_quant/backend.py index 0555e1e8747..375eb86fe1f 100644 --- a/src/nncf/quantization/algorithms/smooth_quant/backend.py +++ b/src/nncf/quantization/algorithms/smooth_quant/backend.py @@ -20,10 +20,10 @@ from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.transformations.commands import TransformationCommand from nncf.common.graph.utils import get_reduction_axes +from nncf.common.tensor_statistics.collectors import AbsMaxReducer +from nncf.common.tensor_statistics.collectors import AxesMode +from nncf.common.tensor_statistics.collectors import ShapeReducer from nncf.common.tensor_statistics.statistic_point import StatisticPoint -from nncf.experimental.common.tensor_statistics.collectors import AbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import AxesMode -from nncf.experimental.common.tensor_statistics.collectors import ShapeReducer from nncf.tensor import Tensor TModel = TypeVar("TModel") diff --git a/src/nncf/quantization/algorithms/smooth_quant/onnx_backend.py b/src/nncf/quantization/algorithms/smooth_quant/onnx_backend.py index b88bd694a3c..9c3a0d513d6 100644 --- a/src/nncf/quantization/algorithms/smooth_quant/onnx_backend.py +++ b/src/nncf/quantization/algorithms/smooth_quant/onnx_backend.py @@ -20,8 +20,8 @@ from nncf.common.graph import NNCFNode from nncf.common.graph.operator_metatypes import OperatorMetatype from nncf.common.graph.transformations.commands import TargetType +from nncf.common.tensor_statistics.collectors import AxesMode from nncf.common.tensor_statistics.statistic_point import StatisticPoint -from nncf.experimental.common.tensor_statistics.collectors import AxesMode from nncf.onnx.graph.metatypes.groups import MATMUL_METATYPES from nncf.onnx.graph.metatypes.groups import OPERATIONS_WITH_WEIGHTS from nncf.onnx.graph.metatypes.groups import QUANTIZE_AGNOSTIC_OPERATIONS diff --git a/src/nncf/quantization/algorithms/weight_compression/activation_stats.py b/src/nncf/quantization/algorithms/weight_compression/activation_stats.py index 7dcb1db30cd..13e855dda8a 100644 --- a/src/nncf/quantization/algorithms/weight_compression/activation_stats.py +++ b/src/nncf/quantization/algorithms/weight_compression/activation_stats.py @@ -12,7 +12,7 @@ from functools import reduce from operator import mul -from nncf.experimental.common.tensor_statistics.statistics import WCTensorStatistic +from nncf.common.tensor_statistics.statistics import WCTensorStatistic from nncf.tensor import Tensor from nncf.tensor import functions as fns diff --git a/src/nncf/quantization/algorithms/weight_compression/algorithm.py b/src/nncf/quantization/algorithms/weight_compression/algorithm.py index d2dfca84d58..bd75aea0bf5 100644 --- a/src/nncf/quantization/algorithms/weight_compression/algorithm.py +++ b/src/nncf/quantization/algorithms/weight_compression/algorithm.py @@ -30,10 +30,10 @@ from nncf.common.scopes import should_consider_scope from nncf.common.tensor_statistics.statistic_point import StatisticPoint from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer +from nncf.common.tensor_statistics.statistics import WCTensorStatistic from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import get_backend from nncf.common.utils.helpers import create_table -from nncf.experimental.common.tensor_statistics.statistics import WCTensorStatistic from nncf.parameters import BackupMode from nncf.parameters import CompressionFormat from nncf.parameters import CompressWeightsMode diff --git a/src/nncf/quantization/algorithms/weight_compression/awq.py b/src/nncf/quantization/algorithms/weight_compression/awq.py index 508ad57060d..a78212f37e8 100644 --- a/src/nncf/quantization/algorithms/weight_compression/awq.py +++ b/src/nncf/quantization/algorithms/weight_compression/awq.py @@ -22,9 +22,9 @@ from nncf.common.graph.transformations.layout import TransformationLayout from nncf.common.logging.track_progress import track from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer +from nncf.common.tensor_statistics.statistics import WCTensorStatistic from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import get_backend -from nncf.experimental.common.tensor_statistics.statistics import WCTensorStatistic from nncf.quantization.algorithms.algorithm import Algorithm from nncf.quantization.algorithms.weight_compression.activation_stats import process_stats from nncf.quantization.algorithms.weight_compression.backend import WeightCompressionAlgoBackend diff --git a/src/nncf/quantization/algorithms/weight_compression/backend.py b/src/nncf/quantization/algorithms/weight_compression/backend.py index d429b127152..e71c9fff067 100644 --- a/src/nncf/quantization/algorithms/weight_compression/backend.py +++ b/src/nncf/quantization/algorithms/weight_compression/backend.py @@ -19,19 +19,18 @@ from nncf.common.graph.patterns.patterns import GraphPattern from nncf.common.graph.transformations.commands import TargetPoint from nncf.common.graph.transformations.commands import TargetType -from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase +from nncf.common.tensor_statistics.collectors import HAWQAggregator +from nncf.common.tensor_statistics.collectors import MaxVarianceReducer +from nncf.common.tensor_statistics.collectors import MeanAbsMaxReducer +from nncf.common.tensor_statistics.collectors import MeanAggregator +from nncf.common.tensor_statistics.collectors import MeanVarianceReducer +from nncf.common.tensor_statistics.collectors import RawReducer +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint -from nncf.experimental.common.tensor_statistics.collectors import HAWQAggregator -from nncf.experimental.common.tensor_statistics.collectors import MaxVarianceReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAggregator -from nncf.experimental.common.tensor_statistics.collectors import MeanVarianceReducer -from nncf.experimental.common.tensor_statistics.collectors import RawReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import HessianTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MaxVarianceTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MeanMagnitudeTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MeanVarianceTensorStatistic +from nncf.common.tensor_statistics.statistics import HessianTensorStatistic +from nncf.common.tensor_statistics.statistics import MaxVarianceTensorStatistic +from nncf.common.tensor_statistics.statistics import MeanMagnitudeTensorStatistic +from nncf.common.tensor_statistics.statistics import MeanVarianceTensorStatistic from nncf.parameters import CompressionFormat from nncf.quantization.advanced_parameters import AdvancedCompressionParameters from nncf.quantization.algorithms.weight_compression.config import WeightCompressionParameters @@ -220,7 +219,7 @@ def target_point(target_type: TargetType, target_node_name: str, port_id: int) - @abstractmethod def mean_statistic_collector( self, reduction_axes: tuple[int], subset_size: Optional[int] = None - ) -> TensorStatisticCollectorBase: + ) -> TensorCollector: """ Return mean statistic collector diff --git a/src/nncf/quantization/algorithms/weight_compression/lora_correction.py b/src/nncf/quantization/algorithms/weight_compression/lora_correction.py index 0fe478dfab5..0df60e13b2f 100644 --- a/src/nncf/quantization/algorithms/weight_compression/lora_correction.py +++ b/src/nncf/quantization/algorithms/weight_compression/lora_correction.py @@ -15,10 +15,10 @@ import nncf from nncf.common.logging import nncf_logger +from nncf.common.tensor_statistics.statistics import WCTensorStatistic from nncf.common.utils.debug import DEBUG_LOG_DIR from nncf.common.utils.debug import is_debug from nncf.common.utils.decorators import skip_if_dependency_unavailable -from nncf.experimental.common.tensor_statistics.statistics import WCTensorStatistic from nncf.parameters import CompressWeightsMode from nncf.quantization.advanced_parameters import AdvancedLoraCorrectionParameters from nncf.quantization.algorithms.weight_compression.activation_stats import process_stats diff --git a/src/nncf/quantization/algorithms/weight_compression/onnx_backend.py b/src/nncf/quantization/algorithms/weight_compression/onnx_backend.py index 735ba9a2a3e..911eddf725c 100644 --- a/src/nncf/quantization/algorithms/weight_compression/onnx_backend.py +++ b/src/nncf/quantization/algorithms/weight_compression/onnx_backend.py @@ -25,12 +25,12 @@ from nncf.common.graph.patterns.patterns import GraphPattern from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.utils import get_reduction_axes +from nncf.common.tensor_statistics.collectors import MeanReducer +from nncf.common.tensor_statistics.collectors import NoopAggregator +from nncf.common.tensor_statistics.collectors import ShapeReducer +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint -from nncf.experimental.common.tensor_statistics.collectors import MeanReducer -from nncf.experimental.common.tensor_statistics.collectors import NoopAggregator -from nncf.experimental.common.tensor_statistics.collectors import ShapeReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import WCTensorStatistic +from nncf.common.tensor_statistics.statistics import WCTensorStatistic from nncf.onnx.graph.metatypes import onnx_metatypes from nncf.onnx.graph.metatypes.groups import ATOMIC_ACTIVATIONS_OPERATIONS from nncf.onnx.graph.metatypes.groups import CONVOLUTION_METATYPES diff --git a/src/nncf/quantization/algorithms/weight_compression/openvino_backend.py b/src/nncf/quantization/algorithms/weight_compression/openvino_backend.py index 3ec241b36c6..8d9e75078cc 100644 --- a/src/nncf/quantization/algorithms/weight_compression/openvino_backend.py +++ b/src/nncf/quantization/algorithms/weight_compression/openvino_backend.py @@ -20,15 +20,15 @@ from nncf.common.graph.patterns.patterns import GraphPattern from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.utils import get_reduction_axes +from nncf.common.tensor_statistics.collectors import MeanAggregator +from nncf.common.tensor_statistics.collectors import NoopAggregator +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint +from nncf.common.tensor_statistics.statistics import MaxVarianceTensorStatistic +from nncf.common.tensor_statistics.statistics import MeanMagnitudeTensorStatistic +from nncf.common.tensor_statistics.statistics import MeanVarianceTensorStatistic +from nncf.common.tensor_statistics.statistics import WCTensorStatistic from nncf.common.utils.caching import disable_results_caching -from nncf.experimental.common.tensor_statistics.collectors import MeanAggregator -from nncf.experimental.common.tensor_statistics.collectors import NoopAggregator -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import MaxVarianceTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MeanMagnitudeTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MeanVarianceTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import WCTensorStatistic from nncf.openvino.graph.metatypes import openvino_metatypes as om from nncf.openvino.graph.metatypes.groups import ATOMIC_ACTIVATIONS_OPERATIONS from nncf.openvino.graph.model_transformer import OVModelTransformer diff --git a/src/nncf/quantization/algorithms/weight_compression/scale_estimation.py b/src/nncf/quantization/algorithms/weight_compression/scale_estimation.py index 4ad557b9868..167ba77a995 100644 --- a/src/nncf/quantization/algorithms/weight_compression/scale_estimation.py +++ b/src/nncf/quantization/algorithms/weight_compression/scale_estimation.py @@ -15,9 +15,9 @@ import nncf from nncf.common.graph.graph import NNCFGraph from nncf.common.logging.track_progress import track +from nncf.common.tensor_statistics.statistics import WCTensorStatistic from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import get_backend -from nncf.experimental.common.tensor_statistics.statistics import WCTensorStatistic from nncf.quantization.algorithms.weight_compression.activation_stats import process_stats from nncf.quantization.algorithms.weight_compression.backend import WeightCompressionAlgoBackend from nncf.quantization.algorithms.weight_compression.config import WeightCompressionConfig diff --git a/src/nncf/quantization/algorithms/weight_compression/torch_backend.py b/src/nncf/quantization/algorithms/weight_compression/torch_backend.py index a153e3e85f1..351434ddc37 100644 --- a/src/nncf/quantization/algorithms/weight_compression/torch_backend.py +++ b/src/nncf/quantization/algorithms/weight_compression/torch_backend.py @@ -23,12 +23,12 @@ from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.transformations.layout import TransformationLayout from nncf.common.quantization.structs import QuantizationScheme +from nncf.common.tensor_statistics.collectors import MeanReducer +from nncf.common.tensor_statistics.collectors import NoopAggregator +from nncf.common.tensor_statistics.collectors import ShapeReducer +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint -from nncf.experimental.common.tensor_statistics.collectors import MeanReducer -from nncf.experimental.common.tensor_statistics.collectors import NoopAggregator -from nncf.experimental.common.tensor_statistics.collectors import ShapeReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import WCTensorStatistic +from nncf.common.tensor_statistics.statistics import WCTensorStatistic from nncf.parameters import CompressionFormat from nncf.parameters import CompressWeightsMode from nncf.quantization.advanced_parameters import AdvancedCompressionParameters diff --git a/src/nncf/quantization/algorithms/weight_compression/torch_fx_backend.py b/src/nncf/quantization/algorithms/weight_compression/torch_fx_backend.py index f1c1a49a269..064611738c0 100644 --- a/src/nncf/quantization/algorithms/weight_compression/torch_fx_backend.py +++ b/src/nncf/quantization/algorithms/weight_compression/torch_fx_backend.py @@ -23,12 +23,12 @@ from nncf.common.graph.patterns.patterns import GraphPattern from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.transformations.layout import TransformationLayout +from nncf.common.tensor_statistics.collectors import MeanReducer +from nncf.common.tensor_statistics.collectors import NoopAggregator +from nncf.common.tensor_statistics.collectors import ShapeReducer +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint -from nncf.experimental.common.tensor_statistics.collectors import MeanReducer -from nncf.experimental.common.tensor_statistics.collectors import NoopAggregator -from nncf.experimental.common.tensor_statistics.collectors import ShapeReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import WCTensorStatistic +from nncf.common.tensor_statistics.statistics import WCTensorStatistic from nncf.experimental.torch.fx.commands import FXApplyTransformationCommand from nncf.experimental.torch.fx.model_transformer import FXModelTransformer from nncf.experimental.torch.fx.node_utils import get_graph_node_by_name diff --git a/src/nncf/quantization/fake_quantize.py b/src/nncf/quantization/fake_quantize.py index 6d6ee971203..756d83a6f82 100644 --- a/src/nncf/quantization/fake_quantize.py +++ b/src/nncf/quantization/fake_quantize.py @@ -19,7 +19,7 @@ from nncf.common.quantization.structs import QuantizationScheme as QuantizationMode from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.quantization.advanced_parameters import FP8Type from nncf.tensor import Tensor from nncf.tensor import TensorDataType diff --git a/src/nncf/torch/function_hook/statistics/aggregator.py b/src/nncf/torch/function_hook/statistics/aggregator.py index d74ced72c25..f4fb2ed9cba 100644 --- a/src/nncf/torch/function_hook/statistics/aggregator.py +++ b/src/nncf/torch/function_hook/statistics/aggregator.py @@ -19,11 +19,11 @@ from nncf.common.graph.transformations.commands import TargetPoint from nncf.common.graph.transformations.layout import TransformationLayout from nncf.common.tensor_statistics.aggregator import StatisticsAggregator +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer +from nncf.common.tensor_statistics.statistics import TensorStatistic from nncf.common.utils.backend import BackendType from nncf.data.dataset import Dataset -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import TensorStatistic from nncf.tensor import Tensor from nncf.torch.function_hook.commands import PT2InsertionCommand from nncf.torch.function_hook.hook_storage import RemovableHookHandle diff --git a/src/nncf/torch/quantization/algo.py b/src/nncf/torch/quantization/algo.py index 6526ea7606d..7fec1db838c 100644 --- a/src/nncf/torch/quantization/algo.py +++ b/src/nncf/torch/quantization/algo.py @@ -60,6 +60,8 @@ from nncf.common.schedulers import BaseCompressionScheduler from nncf.common.statistics import NNCFStatistics from nncf.common.tensor_statistics.collectors import ReductionAxes +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import TensorStatistic from nncf.common.utils.api_marker import api from nncf.common.utils.backend import BackendType from nncf.common.utils.backend import copy_model @@ -75,8 +77,6 @@ from nncf.config.schemata.defaults import QUANTIZATION_PRESET from nncf.config.schemata.defaults import QUANTIZE_INPUTS from nncf.config.schemata.defaults import QUANTIZE_OUTPUTS -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import TensorStatistic from nncf.parameters import StripFormat from nncf.torch.algo_selector import PT_COMPRESSION_ALGORITHMS from nncf.torch.algo_selector import ZeroCompressionLoss diff --git a/src/nncf/torch/quantization/init_range.py b/src/nncf/torch/quantization/init_range.py index 52496b5ce88..56ed47a3148 100644 --- a/src/nncf/torch/quantization/init_range.py +++ b/src/nncf/torch/quantization/init_range.py @@ -31,10 +31,10 @@ from nncf.common.quantization.structs import QuantizerId from nncf.common.quantization.structs import WeightQuantizerId from nncf.common.scopes import should_consider_scope +from nncf.common.tensor_statistics.collectors import AggregationAxes from nncf.common.tensor_statistics.collectors import ReductionAxes -from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.config.schemata.algo.quantization import RANGE_INIT_TYPES_VS_DESCRIPTIONS -from nncf.experimental.common.tensor_statistics.collectors import AggregationAxes from nncf.torch.graph.graph import PTNNCFGraph from nncf.torch.initialization import DataLoaderBaseRunner from nncf.torch.nncf_network import NNCFNetwork @@ -117,7 +117,7 @@ class StatCollectorGenerator: @staticmethod def generate_collectors_for_range_init_statistics_collection( target_model_graph: PTNNCFGraph, quantizer_setup: QuantizerSetupBase, range_init_params: PTRangeInitParams - ) -> dict[TensorStatisticObservationPoint, dict[ReductionAxes, TensorStatisticCollectorBase]]: + ) -> dict[TensorStatisticObservationPoint, dict[ReductionAxes, TensorCollector]]: retval = {} for qp in quantizer_setup.quantization_points.values(): init_config = range_init_params.get_init_config_for_quantization_point(qp) @@ -150,7 +150,7 @@ def generate_stat_collector_for_range_init_config( scale_shape: ReductionAxes = None, collector_params: PTRangeInitCollectorParams = None, num_samples_to_collect_override: int = None, - ) -> TensorStatisticCollectorBase: + ) -> TensorCollector: num_samples = init_config.num_init_samples if num_samples_to_collect_override is not None: num_samples = num_samples_to_collect_override @@ -262,14 +262,12 @@ def __init__( self.modules_to_init = modules_to_init_vs_init_configs self.progressbar_description = "Range parameters initialization" - self.collectors_and_modules_to_init: dict[str, tuple[TensorStatisticCollectorBase, BaseQuantizer]] = ( - OrderedDict() - ) + self.collectors_and_modules_to_init: dict[str, tuple[TensorCollector, BaseQuantizer]] = OrderedDict() self.hook_handles = [] self.batch_size = batch_size def _get_fwd_hook( - self, collector: TensorStatisticCollectorBase + self, collector: TensorCollector ) -> Callable[["torch.Module", torch.Tensor, torch.Tensor], torch.Tensor]: hook = create_register_input_hook(collector=collector) diff --git a/src/nncf/torch/statistics/aggregator.py b/src/nncf/torch/statistics/aggregator.py index a206bc6b19b..4b81f2bf825 100644 --- a/src/nncf/torch/statistics/aggregator.py +++ b/src/nncf/torch/statistics/aggregator.py @@ -18,8 +18,8 @@ from nncf.common.graph.transformations.layout import TransformationLayout from nncf.common.tensor_statistics.aggregator import StatisticPointsContainer from nncf.common.tensor_statistics.aggregator import StatisticsAggregator +from nncf.common.tensor_statistics.statistics import TensorStatistic from nncf.common.utils.backend import BackendType -from nncf.experimental.common.tensor_statistics.statistics import TensorStatistic from nncf.tensor import Tensor from nncf.torch.graph.transformations.commands import PTInsertionCommand from nncf.torch.graph.transformations.commands import PTTargetPoint diff --git a/src/nncf/torch/tensor_statistics/algo.py b/src/nncf/torch/tensor_statistics/algo.py index 88deb0a1543..908fd14ee59 100644 --- a/src/nncf/torch/tensor_statistics/algo.py +++ b/src/nncf/torch/tensor_statistics/algo.py @@ -16,9 +16,8 @@ from nncf.common.schedulers import StubCompressionScheduler from nncf.common.statistics import NNCFStatistics from nncf.common.tensor_statistics.collectors import ReductionAxes -from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.config import NNCFConfig -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector from nncf.tensor import Tensor from nncf.torch.algo_selector import ZeroCompressionLoss from nncf.torch.compression_method_api import PTCompressionAlgorithmBuilder @@ -71,7 +70,7 @@ class TensorStatisticsCollectionBuilder(PTCompressionAlgorithmBuilder): def __init__( self, config: NNCFConfig, - observation_points_vs_collectors: dict[TensorStatisticObservationPoint, TensorStatisticCollectorBase], + observation_points_vs_collectors: dict[TensorStatisticObservationPoint, TensorCollector], ): super().__init__(config) self._observation_points_vs_collectors = observation_points_vs_collectors @@ -107,9 +106,7 @@ def _get_algo_specific_config_section(self) -> dict: class TensorStatisticsCollectionController(PTCompressionAlgorithmController): - def __init__( - self, target_model: NNCFNetwork, ip_vs_collector_dict: dict[PTTargetPoint, TensorStatisticCollectorBase] - ): + def __init__(self, target_model: NNCFNetwork, ip_vs_collector_dict: dict[PTTargetPoint, TensorCollector]): super().__init__(target_model) self.ip_vs_collector_dict = ip_vs_collector_dict self._scheduler = StubCompressionScheduler() diff --git a/src/nncf/torch/tensor_statistics/collectors.py b/src/nncf/torch/tensor_statistics/collectors.py index 9fe723f8339..6eb96e87390 100644 --- a/src/nncf/torch/tensor_statistics/collectors.py +++ b/src/nncf/torch/tensor_statistics/collectors.py @@ -14,27 +14,27 @@ import numpy as np -from nncf.experimental.common.tensor_statistics.collectors import AbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import AggregatorBase -from nncf.experimental.common.tensor_statistics.collectors import BatchMeanReducer -from nncf.experimental.common.tensor_statistics.collectors import MaxAggregator -from nncf.experimental.common.tensor_statistics.collectors import MaxReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAggregator -from nncf.experimental.common.tensor_statistics.collectors import MeanPerChReducer -from nncf.experimental.common.tensor_statistics.collectors import MedianAbsoluteDeviationAggregator -from nncf.experimental.common.tensor_statistics.collectors import MinAggregator -from nncf.experimental.common.tensor_statistics.collectors import MinReducer -from nncf.experimental.common.tensor_statistics.collectors import NoopAggregator -from nncf.experimental.common.tensor_statistics.collectors import PercentileAggregator -from nncf.experimental.common.tensor_statistics.collectors import QuantileReducer -from nncf.experimental.common.tensor_statistics.collectors import RawReducer -from nncf.experimental.common.tensor_statistics.collectors import ShapeReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import MeanTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MedianMADTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import PercentileTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import RawTensorStatistic +from nncf.common.tensor_statistics.collectors import AbsMaxReducer +from nncf.common.tensor_statistics.collectors import AggregatorBase +from nncf.common.tensor_statistics.collectors import BatchMeanReducer +from nncf.common.tensor_statistics.collectors import MaxAggregator +from nncf.common.tensor_statistics.collectors import MaxReducer +from nncf.common.tensor_statistics.collectors import MeanAggregator +from nncf.common.tensor_statistics.collectors import MeanPerChReducer +from nncf.common.tensor_statistics.collectors import MedianAbsoluteDeviationAggregator +from nncf.common.tensor_statistics.collectors import MinAggregator +from nncf.common.tensor_statistics.collectors import MinReducer +from nncf.common.tensor_statistics.collectors import NoopAggregator +from nncf.common.tensor_statistics.collectors import PercentileAggregator +from nncf.common.tensor_statistics.collectors import QuantileReducer +from nncf.common.tensor_statistics.collectors import RawReducer +from nncf.common.tensor_statistics.collectors import ShapeReducer +from nncf.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.statistics import MeanTensorStatistic +from nncf.common.tensor_statistics.statistics import MedianMADTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import PercentileTensorStatistic +from nncf.common.tensor_statistics.statistics import RawTensorStatistic from nncf.tensor import Tensor diff --git a/src/nncf/torch/tensor_statistics/statistics.py b/src/nncf/torch/tensor_statistics/statistics.py index 833360736a9..b5290851a47 100644 --- a/src/nncf/torch/tensor_statistics/statistics.py +++ b/src/nncf/torch/tensor_statistics/statistics.py @@ -10,10 +10,10 @@ # limitations under the License. -from nncf.experimental.common.tensor_statistics.statistics import MedianMADTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import PercentileTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import TensorStatistic +from nncf.common.tensor_statistics.statistics import MedianMADTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import PercentileTensorStatistic +from nncf.common.tensor_statistics.statistics import TensorStatistic def pt_convert_stat_to_min_max_tensor_stat(statistic: TensorStatistic) -> MinMaxTensorStatistic: diff --git a/tests/common/experimental/test_reducers_and_aggregators.py b/tests/common/test_reducers_and_aggregators.py similarity index 94% rename from tests/common/experimental/test_reducers_and_aggregators.py rename to tests/common/test_reducers_and_aggregators.py index c6027432c5b..55fdf13b1f0 100644 --- a/tests/common/experimental/test_reducers_and_aggregators.py +++ b/tests/common/test_reducers_and_aggregators.py @@ -22,26 +22,26 @@ import nncf from nncf.common.graph.layer_attributes import Dtype from nncf.common.tensor import NNCFTensor -from nncf.experimental.common.tensor_statistics.collectors import AggregationAxes -from nncf.experimental.common.tensor_statistics.collectors import AxesMode -from nncf.experimental.common.tensor_statistics.collectors import HAWQAggregator -from nncf.experimental.common.tensor_statistics.collectors import HistogramAggregator -from nncf.experimental.common.tensor_statistics.collectors import MaxAggregator -from nncf.experimental.common.tensor_statistics.collectors import MaxVarianceReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAggregator -from nncf.experimental.common.tensor_statistics.collectors import MeanNoOutliersAggregator -from nncf.experimental.common.tensor_statistics.collectors import MeanVarianceReducer -from nncf.experimental.common.tensor_statistics.collectors import MedianAbsoluteDeviationAggregator -from nncf.experimental.common.tensor_statistics.collectors import MedianAggregator -from nncf.experimental.common.tensor_statistics.collectors import MedianNoOutliersAggregator -from nncf.experimental.common.tensor_statistics.collectors import MinAggregator -from nncf.experimental.common.tensor_statistics.collectors import NoopAggregator -from nncf.experimental.common.tensor_statistics.collectors import PercentileAggregator -from nncf.experimental.common.tensor_statistics.collectors import RawReducer -from nncf.experimental.common.tensor_statistics.collectors import ShapeReducer -from nncf.experimental.common.tensor_statistics.collectors import determine_reduction_axes -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.collectors import AggregationAxes +from nncf.common.tensor_statistics.collectors import AxesMode +from nncf.common.tensor_statistics.collectors import HAWQAggregator +from nncf.common.tensor_statistics.collectors import HistogramAggregator +from nncf.common.tensor_statistics.collectors import MaxAggregator +from nncf.common.tensor_statistics.collectors import MaxVarianceReducer +from nncf.common.tensor_statistics.collectors import MeanAbsMaxReducer +from nncf.common.tensor_statistics.collectors import MeanAggregator +from nncf.common.tensor_statistics.collectors import MeanNoOutliersAggregator +from nncf.common.tensor_statistics.collectors import MeanVarianceReducer +from nncf.common.tensor_statistics.collectors import MedianAbsoluteDeviationAggregator +from nncf.common.tensor_statistics.collectors import MedianAggregator +from nncf.common.tensor_statistics.collectors import MedianNoOutliersAggregator +from nncf.common.tensor_statistics.collectors import MinAggregator +from nncf.common.tensor_statistics.collectors import NoopAggregator +from nncf.common.tensor_statistics.collectors import PercentileAggregator +from nncf.common.tensor_statistics.collectors import RawReducer +from nncf.common.tensor_statistics.collectors import ShapeReducer +from nncf.common.tensor_statistics.collectors import determine_reduction_axes +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.tensor import Tensor from nncf.tensor import functions as fns diff --git a/tests/common/experimental/test_statistic_collector.py b/tests/common/test_statistic_collector.py similarity index 95% rename from tests/common/experimental/test_statistic_collector.py rename to tests/common/test_statistic_collector.py index 1e54fde2e05..f16bc81b6c2 100644 --- a/tests/common/experimental/test_statistic_collector.py +++ b/tests/common/test_statistic_collector.py @@ -17,16 +17,16 @@ import nncf from nncf.common.tensor import NNCFTensor -from nncf.experimental.common.tensor_statistics.collectors import AggregatorBase -from nncf.experimental.common.tensor_statistics.collectors import MergedTensorCollector -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase -from nncf.experimental.common.tensor_statistics.collectors import TensorType -from nncf.experimental.common.tensor_statistics.statistics import MeanTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MedianMADTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import PercentileTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import RawTensorStatistic +from nncf.common.tensor_statistics.collectors import AggregatorBase +from nncf.common.tensor_statistics.collectors import MergedTensorCollector +from nncf.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorReducerBase +from nncf.common.tensor_statistics.collectors import TensorType +from nncf.common.tensor_statistics.statistics import MeanTensorStatistic +from nncf.common.tensor_statistics.statistics import MedianMADTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import PercentileTensorStatistic +from nncf.common.tensor_statistics.statistics import RawTensorStatistic from nncf.tensor import Tensor diff --git a/tests/common/test_statistics_aggregator.py b/tests/common/test_statistics_aggregator.py index 91fe12e8e0b..d2bc98d33c6 100644 --- a/tests/common/test_statistics_aggregator.py +++ b/tests/common/test_statistics_aggregator.py @@ -25,11 +25,11 @@ from nncf.common.quantization.structs import QuantizationScheme as QuantizationMode from nncf.common.quantization.structs import QuantizerConfig from nncf.common.tensor_statistics.aggregator import EMPTY_DATASET_ERROR +from nncf.common.tensor_statistics.collectors import NoopAggregator +from nncf.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.common.tensor_statistics.statistic_point import StatisticPoint from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer -from nncf.experimental.common.tensor_statistics.collectors import NoopAggregator -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase from nncf.quantization.algorithms.bias_correction.backend import BiasCorrectionAlgoBackend from nncf.quantization.algorithms.fast_bias_correction.backend import FastBiasCorrectionAlgoBackend from nncf.quantization.algorithms.min_max.algorithm import MinMaxQuantization diff --git a/tests/common/experimental/test_tensor_collector_batch_size.py b/tests/common/test_tensor_collector_batch_size.py similarity index 95% rename from tests/common/experimental/test_tensor_collector_batch_size.py rename to tests/common/test_tensor_collector_batch_size.py index f91b876a8a5..42e63d49e15 100644 --- a/tests/common/experimental/test_tensor_collector_batch_size.py +++ b/tests/common/test_tensor_collector_batch_size.py @@ -15,8 +15,8 @@ import pytest from nncf.common.graph.utils import get_reduction_axes -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.tensor import Tensor from nncf.tensor import functions as fns diff --git a/tests/cross_fw/test_templates/template_test_nncf_tensor.py b/tests/cross_fw/test_templates/template_test_nncf_tensor.py index 14494e8efd7..cac78273ac0 100644 --- a/tests/cross_fw/test_templates/template_test_nncf_tensor.py +++ b/tests/cross_fw/test_templates/template_test_nncf_tensor.py @@ -21,7 +21,7 @@ import nncf import nncf.tensor.functions as fns -from nncf.experimental.common.tensor_statistics import statistical_functions as s_fns +from nncf.common.tensor_statistics import statistical_functions as s_fns from nncf.tensor import Tensor from nncf.tensor import TensorDataType from nncf.tensor import TensorDeviceType diff --git a/tests/cross_fw/test_templates/test_calculate_quantizer_parameters.py b/tests/cross_fw/test_templates/test_calculate_quantizer_parameters.py index e7bc9db9b86..4da4bf788ec 100644 --- a/tests/cross_fw/test_templates/test_calculate_quantizer_parameters.py +++ b/tests/cross_fw/test_templates/test_calculate_quantizer_parameters.py @@ -21,7 +21,7 @@ from nncf.common.quantization.structs import QuantizationScheme as QuantizationMode from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.quantization.fake_quantize import FakeQuantizeParameters from nncf.quantization.fake_quantize import calculate_quantizer_parameters from nncf.tensor import functions as fns diff --git a/tests/cross_fw/test_templates/test_channel_alignment.py b/tests/cross_fw/test_templates/test_channel_alignment.py index bc5ca86daae..0e91eb64f29 100644 --- a/tests/cross_fw/test_templates/test_channel_alignment.py +++ b/tests/cross_fw/test_templates/test_channel_alignment.py @@ -21,12 +21,12 @@ from nncf.common.graph.model_transformer import ModelTransformer from nncf.common.graph.transformations.commands import TargetType from nncf.common.graph.transformations.commands import TransformationType +from nncf.common.tensor_statistics.collectors import MedianAggregator +from nncf.common.tensor_statistics.collectors import QuantileReducer +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic -from nncf.experimental.common.tensor_statistics.collectors import MedianAggregator -from nncf.experimental.common.tensor_statistics.collectors import QuantileReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector from nncf.quantization.algorithms.channel_alignment.algorithm import ChannelAlignment from nncf.quantization.algorithms.channel_alignment.backend import ChannelAlignmentAlgoBackend from nncf.quantization.algorithms.channel_alignment.backend import LayoutDescriptor diff --git a/tests/cross_fw/test_templates/test_ptq_params.py b/tests/cross_fw/test_templates/test_ptq_params.py index 4467d2b3fa7..8c871b5fd06 100644 --- a/tests/cross_fw/test_templates/test_ptq_params.py +++ b/tests/cross_fw/test_templates/test_ptq_params.py @@ -24,15 +24,15 @@ from nncf.common.quantization.structs import QuantizationScheme as QuantizationMode from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup +from nncf.common.tensor_statistics.collectors import HistogramAggregator +from nncf.common.tensor_statistics.collectors import MaxAggregator +from nncf.common.tensor_statistics.collectors import MeanAggregator +from nncf.common.tensor_statistics.collectors import MinAggregator +from nncf.common.tensor_statistics.collectors import RawReducer +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer -from nncf.experimental.common.tensor_statistics.collectors import HistogramAggregator -from nncf.experimental.common.tensor_statistics.collectors import MaxAggregator -from nncf.experimental.common.tensor_statistics.collectors import MeanAggregator -from nncf.experimental.common.tensor_statistics.collectors import MinAggregator -from nncf.experimental.common.tensor_statistics.collectors import RawReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.parameters import ModelType from nncf.quantization.advanced_parameters import OverflowFix from nncf.quantization.algorithms.min_max.algorithm import MinMaxQuantization diff --git a/tests/cross_fw/test_templates/test_quantizer_config.py b/tests/cross_fw/test_templates/test_quantizer_config.py index 71c5867560a..875ecfe3eb5 100644 --- a/tests/cross_fw/test_templates/test_quantizer_config.py +++ b/tests/cross_fw/test_templates/test_quantizer_config.py @@ -28,15 +28,15 @@ from nncf.common.quantization.structs import QuantizationScheme as QuantizationMode from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup +from nncf.common.tensor_statistics.collectors import AbsMaxReducer +from nncf.common.tensor_statistics.collectors import MaxAggregator +from nncf.common.tensor_statistics.collectors import MaxReducer +from nncf.common.tensor_statistics.collectors import MeanAggregator +from nncf.common.tensor_statistics.collectors import MinAggregator +from nncf.common.tensor_statistics.collectors import MinReducer from nncf.common.tensor_statistics.collectors import ReductionAxes -from nncf.experimental.common.tensor_statistics.collectors import AbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import MaxAggregator -from nncf.experimental.common.tensor_statistics.collectors import MaxReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAggregator -from nncf.experimental.common.tensor_statistics.collectors import MinAggregator -from nncf.experimental.common.tensor_statistics.collectors import MinReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase +from nncf.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.parameters import TargetDevice from nncf.quantization.advanced_parameters import QuantizationParameters from nncf.quantization.algorithms.min_max.algorithm import MinMaxQuantization diff --git a/tests/cross_fw/test_templates/test_smooth_quant.py b/tests/cross_fw/test_templates/test_smooth_quant.py index b3b54351372..1008ad00d42 100644 --- a/tests/cross_fw/test_templates/test_smooth_quant.py +++ b/tests/cross_fw/test_templates/test_smooth_quant.py @@ -19,8 +19,8 @@ from nncf.common.factory import NNCFGraphFactory from nncf.common.factory import StatisticsAggregatorFactory from nncf.common.graph.graph import NNCFNode -from nncf.experimental.common.tensor_statistics.collectors import AbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import ShapeReducer +from nncf.common.tensor_statistics.collectors import AbsMaxReducer +from nncf.common.tensor_statistics.collectors import ShapeReducer from nncf.parameters import ModelType from nncf.quantization.advanced_parameters import AdvancedQuantizationParameters from nncf.quantization.advanced_parameters import AdvancedSmoothQuantParameters diff --git a/tests/cross_fw/test_templates/test_statistics_caching.py b/tests/cross_fw/test_templates/test_statistics_caching.py index abdc8cf7ae3..4e4ab0c3658 100644 --- a/tests/cross_fw/test_templates/test_statistics_caching.py +++ b/tests/cross_fw/test_templates/test_statistics_caching.py @@ -13,10 +13,10 @@ from nncf.common.graph.transformations.commands import TargetPoint from nncf.common.graph.transformations.commands import TargetType +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.common.tensor_statistics.statistic_point import StatisticPoint from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.tensor import Tensor diff --git a/tests/cross_fw/test_templates/test_weights_compression_backends.py b/tests/cross_fw/test_templates/test_weights_compression_backends.py index a465e2ffa49..9affb2168c0 100644 --- a/tests/cross_fw/test_templates/test_weights_compression_backends.py +++ b/tests/cross_fw/test_templates/test_weights_compression_backends.py @@ -13,13 +13,13 @@ import pytest -from nncf.experimental.common.tensor_statistics.collectors import HAWQAggregator -from nncf.experimental.common.tensor_statistics.collectors import MaxVarianceReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanAggregator -from nncf.experimental.common.tensor_statistics.collectors import MeanVarianceReducer -from nncf.experimental.common.tensor_statistics.collectors import RawReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import HAWQAggregator +from nncf.common.tensor_statistics.collectors import MaxVarianceReducer +from nncf.common.tensor_statistics.collectors import MeanAbsMaxReducer +from nncf.common.tensor_statistics.collectors import MeanAggregator +from nncf.common.tensor_statistics.collectors import MeanVarianceReducer +from nncf.common.tensor_statistics.collectors import RawReducer +from nncf.common.tensor_statistics.collectors import TensorCollector class TemplateTestMixedPrecisionAlgoBackend: diff --git a/tests/onnx/quantization/common.py b/tests/onnx/quantization/common.py index 7ba0cc36d91..8591992017a 100644 --- a/tests/onnx/quantization/common.py +++ b/tests/onnx/quantization/common.py @@ -16,7 +16,7 @@ import onnx from nncf import Dataset -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.onnx.graph.nncf_graph_builder import GraphConverter from nncf.onnx.graph.onnx_helper import get_edge_dtype from nncf.onnx.graph.onnx_helper import get_edge_info_mapping @@ -53,7 +53,7 @@ def mock_collect_statistics(mocker): "nncf.common.tensor_statistics.aggregator.StatisticsAggregator.collect_statistics", return_value=None ) _ = mocker.patch( - "nncf.experimental.common.tensor_statistics.collectors.TensorCollector.get_statistics", + "nncf.common.tensor_statistics.collectors.TensorCollector.get_statistics", return_value=get_statistics_value, ) diff --git a/tests/onnx/quantization/test_reducers_and_aggregators.py b/tests/onnx/quantization/test_reducers_and_aggregators.py index e44439bc026..4ecb28ea3ae 100644 --- a/tests/onnx/quantization/test_reducers_and_aggregators.py +++ b/tests/onnx/quantization/test_reducers_and_aggregators.py @@ -15,16 +15,16 @@ import pytest from nncf.common.graph.layer_attributes import Dtype -from nncf.experimental.common.tensor_statistics.collectors import AbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import AbsQuantileReducer -from nncf.experimental.common.tensor_statistics.collectors import BatchMeanReducer -from nncf.experimental.common.tensor_statistics.collectors import MaxReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanPerChReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanReducer -from nncf.experimental.common.tensor_statistics.collectors import MinReducer -from nncf.experimental.common.tensor_statistics.collectors import QuantileReducer +from nncf.common.tensor_statistics.collectors import AbsMaxReducer +from nncf.common.tensor_statistics.collectors import AbsQuantileReducer +from nncf.common.tensor_statistics.collectors import BatchMeanReducer +from nncf.common.tensor_statistics.collectors import MaxReducer +from nncf.common.tensor_statistics.collectors import MeanPerChReducer +from nncf.common.tensor_statistics.collectors import MeanReducer +from nncf.common.tensor_statistics.collectors import MinReducer +from nncf.common.tensor_statistics.collectors import QuantileReducer from nncf.tensor import Tensor -from tests.common.experimental.test_reducers_and_aggregators import TemplateTestReducersAggregators +from tests.common.test_reducers_and_aggregators import TemplateTestReducersAggregators class TestReducersAggregators(TemplateTestReducersAggregators): diff --git a/tests/onnx/quantization/test_tensor_collector_batch_size.py b/tests/onnx/quantization/test_tensor_collector_batch_size.py index 500426a4eb5..2f306904929 100644 --- a/tests/onnx/quantization/test_tensor_collector_batch_size.py +++ b/tests/onnx/quantization/test_tensor_collector_batch_size.py @@ -12,10 +12,10 @@ import numpy as np import pytest -from nncf.experimental.common.tensor_statistics.collectors import AGGREGATORS_MAP -from nncf.experimental.common.tensor_statistics.collectors import REDUCERS_MAP -from nncf.experimental.common.tensor_statistics.collectors import RawReducer -from tests.common.experimental.test_tensor_collector_batch_size import TemplateTestTensorCollectorBatchSize +from nncf.common.tensor_statistics.collectors import AGGREGATORS_MAP +from nncf.common.tensor_statistics.collectors import REDUCERS_MAP +from nncf.common.tensor_statistics.collectors import RawReducer +from tests.common.test_tensor_collector_batch_size import TemplateTestTensorCollectorBatchSize class TestTensorCollectorBatchSize(TemplateTestTensorCollectorBatchSize): diff --git a/tests/onnx/test_statistics_aggregator.py b/tests/onnx/test_statistics_aggregator.py index 17105b1baf5..cafa52c5aa5 100644 --- a/tests/onnx/test_statistics_aggregator.py +++ b/tests/onnx/test_statistics_aggregator.py @@ -15,7 +15,7 @@ from nncf import Dataset from nncf.common.graph.transformations.commands import TargetType -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.onnx.graph.transformations.commands import ONNXTargetPoint from nncf.onnx.statistics.aggregator import ONNXStatisticsAggregator from nncf.quantization.algorithms.bias_correction.onnx_backend import ONNXBiasCorrectionAlgoBackend diff --git a/tests/openvino/native/quantization/test_reducers_and_aggregators.py b/tests/openvino/native/quantization/test_reducers_and_aggregators.py index a47773ff82f..f8793cfb53f 100644 --- a/tests/openvino/native/quantization/test_reducers_and_aggregators.py +++ b/tests/openvino/native/quantization/test_reducers_and_aggregators.py @@ -31,7 +31,7 @@ from nncf.openvino.statistics.collectors import OVQuantileReducer from nncf.openvino.statistics.collectors import OVShapeReducer from nncf.tensor import Tensor -from tests.common.experimental.test_reducers_and_aggregators import TemplateTestReducersAggregators +from tests.common.test_reducers_and_aggregators import TemplateTestReducersAggregators class TestReducersAggregators(TemplateTestReducersAggregators): diff --git a/tests/openvino/native/quantization/test_weights_compression.py b/tests/openvino/native/quantization/test_weights_compression.py index 5a38bd3a79b..4d8a6662d04 100644 --- a/tests/openvino/native/quantization/test_weights_compression.py +++ b/tests/openvino/native/quantization/test_weights_compression.py @@ -27,10 +27,10 @@ from nncf import CompressWeightsMode from nncf import SensitivityMetric from nncf.common.factory import NNCFGraphFactory +from nncf.common.tensor_statistics.collectors import AggregatorBase from nncf.common.utils.debug import nncf_debug from nncf.common.utils.helpers import set_env_variable from nncf.data.dataset import Dataset -from nncf.experimental.common.tensor_statistics.collectors import AggregatorBase from nncf.openvino.graph.model_transformer import OVModelTransformer from nncf.openvino.graph.node_utils import get_const_value_as_numpy_tensor from nncf.openvino.optimized_functions import astype diff --git a/tests/openvino/native/test_statistic_collector.py b/tests/openvino/native/test_statistic_collector.py index f248048d9cb..beb15aca948 100644 --- a/tests/openvino/native/test_statistic_collector.py +++ b/tests/openvino/native/test_statistic_collector.py @@ -13,7 +13,7 @@ import numpy as np from nncf.tensor import Tensor -from tests.common.experimental.test_statistic_collector import TemplateTestStatisticCollector +from tests.common.test_statistic_collector import TemplateTestStatisticCollector class TestOVStatisticCollector(TemplateTestStatisticCollector): diff --git a/tests/openvino/native/test_statistics_aggregator.py b/tests/openvino/native/test_statistics_aggregator.py index 9613bcd470d..849aba477e6 100644 --- a/tests/openvino/native/test_statistics_aggregator.py +++ b/tests/openvino/native/test_statistics_aggregator.py @@ -18,7 +18,7 @@ from nncf import Dataset from nncf.common.graph.transformations.commands import TargetPoint from nncf.common.graph.transformations.commands import TargetType -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.openvino.graph.transformations.commands import OVTargetPoint from nncf.openvino.statistics.aggregator import OVStatisticsAggregator from nncf.openvino.statistics.collectors import OV_REDUCERS_MAP diff --git a/tests/openvino/native/test_tensor_collector_batch_size.py b/tests/openvino/native/test_tensor_collector_batch_size.py index c65ce58a152..b403acb7d30 100644 --- a/tests/openvino/native/test_tensor_collector_batch_size.py +++ b/tests/openvino/native/test_tensor_collector_batch_size.py @@ -12,10 +12,10 @@ import numpy as np import pytest -from nncf.experimental.common.tensor_statistics.collectors import AGGREGATORS_MAP -from nncf.experimental.common.tensor_statistics.collectors import RawReducer +from nncf.common.tensor_statistics.collectors import AGGREGATORS_MAP +from nncf.common.tensor_statistics.collectors import RawReducer from nncf.openvino.statistics.collectors import OV_REDUCERS_MAP -from tests.common.experimental.test_tensor_collector_batch_size import TemplateTestTensorCollectorBatchSize +from tests.common.test_tensor_collector_batch_size import TemplateTestTensorCollectorBatchSize class TestTensorCollectorBatchSize(TemplateTestTensorCollectorBatchSize): diff --git a/tests/torch/tensor_statistics/test_tensor_statistics.py b/tests/torch/tensor_statistics/test_tensor_statistics.py index 2f247701b48..f1aa9bccfd8 100644 --- a/tests/torch/tensor_statistics/test_tensor_statistics.py +++ b/tests/torch/tensor_statistics/test_tensor_statistics.py @@ -15,11 +15,11 @@ import torch from nncf.common.tensor_statistics.collectors import ReductionAxes -from nncf.common.tensor_statistics.collectors import TensorStatisticCollectorBase +from nncf.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.statistics import MedianMADTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import PercentileTensorStatistic from nncf.common.tensor_statistics.statistics import TensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MedianMADTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic -from nncf.experimental.common.tensor_statistics.statistics import PercentileTensorStatistic from nncf.tensor import Tensor from nncf.tensor import functions as fns from nncf.torch.tensor_statistics.collectors import get_mean_percentile_statistic_collector @@ -111,7 +111,7 @@ class TestCollectedStatistics: ) def test_collected_statistics_with_shape_convert( self, - collector: type[TensorStatisticCollectorBase], + collector: type[TensorCollector], reduction_axes_vs_ref_statistic: dict[tuple[ReductionAxes, ReductionAxes], TensorStatistic], ): for shapes in reduction_axes_vs_ref_statistic: @@ -202,7 +202,7 @@ def test_collected_statistics_with_shape_convert( ) def test_collected_statistics( self, - collector: type[TensorStatisticCollectorBase], + collector: type[TensorCollector], reduction_axes_vs_ref_statistic: dict[ReductionAxes, TensorStatistic], ): for reduction_axes in reduction_axes_vs_ref_statistic: diff --git a/tests/torch/test_statistics_aggregator.py b/tests/torch/test_statistics_aggregator.py index 291f960319e..693c3744015 100644 --- a/tests/torch/test_statistics_aggregator.py +++ b/tests/torch/test_statistics_aggregator.py @@ -23,8 +23,8 @@ from nncf.common.graph.transformations.layout import TransformationLayout from nncf.common.quantization.structs import QuantizationScheme as QuantizationMode from nncf.common.quantization.structs import QuantizerConfig +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.common.tensor_statistics.statistic_point import StatisticPointsContainer -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase from nncf.quantization.algorithms.fast_bias_correction.torch_backend import PTFastBiasCorrectionAlgoBackend from nncf.quantization.algorithms.min_max.torch_backend import PTMinMaxAlgoBackend from nncf.quantization.range_estimator import RangeEstimatorParametersSet diff --git a/tests/torch2/function_hook/quantization/test_calculation_quantizer_params.py b/tests/torch2/function_hook/quantization/test_calculation_quantizer_params.py index 4f991517cc9..21103309a2d 100644 --- a/tests/torch2/function_hook/quantization/test_calculation_quantizer_params.py +++ b/tests/torch2/function_hook/quantization/test_calculation_quantizer_params.py @@ -24,7 +24,7 @@ from nncf.common.quantization.structs import QuantizationScheme as QuantizationMode from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic 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 diff --git a/tests/torch2/function_hook/quantization/test_reducers_and_aggregators.py b/tests/torch2/function_hook/quantization/test_reducers_and_aggregators.py index 927fbb6d0dd..bb56affdd5d 100644 --- a/tests/torch2/function_hook/quantization/test_reducers_and_aggregators.py +++ b/tests/torch2/function_hook/quantization/test_reducers_and_aggregators.py @@ -18,19 +18,19 @@ import nncf from nncf.common.graph.layer_attributes import Dtype -from nncf.experimental.common.tensor_statistics.collectors import AbsMaxReducer -from nncf.experimental.common.tensor_statistics.collectors import AbsQuantileReducer -from nncf.experimental.common.tensor_statistics.collectors import BatchMeanReducer -from nncf.experimental.common.tensor_statistics.collectors import MaxReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanPerChReducer -from nncf.experimental.common.tensor_statistics.collectors import MeanReducer -from nncf.experimental.common.tensor_statistics.collectors import MinReducer -from nncf.experimental.common.tensor_statistics.collectors import QuantileReducer -from nncf.experimental.common.tensor_statistics.collectors import TensorCollector +from nncf.common.tensor_statistics.collectors import AbsMaxReducer +from nncf.common.tensor_statistics.collectors import AbsQuantileReducer +from nncf.common.tensor_statistics.collectors import BatchMeanReducer +from nncf.common.tensor_statistics.collectors import MaxReducer +from nncf.common.tensor_statistics.collectors import MeanPerChReducer +from nncf.common.tensor_statistics.collectors import MeanReducer +from nncf.common.tensor_statistics.collectors import MinReducer +from nncf.common.tensor_statistics.collectors import QuantileReducer +from nncf.common.tensor_statistics.collectors import TensorCollector from nncf.tensor import Tensor from nncf.tensor import functions as fns from nncf.torch.tensor_statistics.algo import create_register_input_hook -from tests.common.experimental.test_reducers_and_aggregators import TemplateTestReducersAggregators +from tests.common.test_reducers_and_aggregators import TemplateTestReducersAggregators class BaseTestReducersAggregators(TemplateTestReducersAggregators, ABC): diff --git a/tests/torch2/function_hook/quantization/test_statistic_collector.py b/tests/torch2/function_hook/quantization/test_statistic_collector.py index 2f6f5f6a5b3..d26febec4cc 100644 --- a/tests/torch2/function_hook/quantization/test_statistic_collector.py +++ b/tests/torch2/function_hook/quantization/test_statistic_collector.py @@ -14,7 +14,7 @@ import torch from nncf.tensor import Tensor -from tests.common.experimental.test_statistic_collector import TemplateTestStatisticCollector +from tests.common.test_statistic_collector import TemplateTestStatisticCollector class TestPTStatisticCollector(TemplateTestStatisticCollector): diff --git a/tests/torch2/function_hook/quantization/test_tensor_collector_batch_size.py b/tests/torch2/function_hook/quantization/test_tensor_collector_batch_size.py index 1222e06413d..3f83879cd08 100644 --- a/tests/torch2/function_hook/quantization/test_tensor_collector_batch_size.py +++ b/tests/torch2/function_hook/quantization/test_tensor_collector_batch_size.py @@ -13,10 +13,10 @@ import pytest import torch -from nncf.experimental.common.tensor_statistics.collectors import AGGREGATORS_MAP -from nncf.experimental.common.tensor_statistics.collectors import REDUCERS_MAP -from nncf.experimental.common.tensor_statistics.collectors import RawReducer -from tests.common.experimental.test_tensor_collector_batch_size import TemplateTestTensorCollectorBatchSize +from nncf.common.tensor_statistics.collectors import AGGREGATORS_MAP +from nncf.common.tensor_statistics.collectors import REDUCERS_MAP +from nncf.common.tensor_statistics.collectors import RawReducer +from tests.common.test_tensor_collector_batch_size import TemplateTestTensorCollectorBatchSize class TestTensorCollectorBatchSize(TemplateTestTensorCollectorBatchSize): diff --git a/tests/torch2/fx/test_calculation_quantizer_params.py b/tests/torch2/fx/test_calculation_quantizer_params.py index 384c9e0e75e..a78a7bcaf88 100644 --- a/tests/torch2/fx/test_calculation_quantizer_params.py +++ b/tests/torch2/fx/test_calculation_quantizer_params.py @@ -22,7 +22,7 @@ from nncf.common.quantization.structs import QuantizerConfig from nncf.common.quantization.structs import QuantizerGroup from nncf.common.quantization.structs import TypedQuantizerConfig -from nncf.experimental.common.tensor_statistics.statistics import MinMaxTensorStatistic +from nncf.common.tensor_statistics.statistics import MinMaxTensorStatistic from nncf.quantization.algorithms.min_max.torch_fx_backend import FXMinMaxAlgoBackend from nncf.quantization.fake_quantize import FakeQuantizeParameters from nncf.quantization.fake_quantize import calculate_quantizer_parameters diff --git a/tests/torch2/fx/test_statistics_aggregator.py b/tests/torch2/fx/test_statistics_aggregator.py index f1022da473f..96720cd76a3 100644 --- a/tests/torch2/fx/test_statistics_aggregator.py +++ b/tests/torch2/fx/test_statistics_aggregator.py @@ -17,7 +17,7 @@ from nncf import Dataset from nncf.common.graph.transformations.commands import TargetType -from nncf.experimental.common.tensor_statistics.collectors import TensorReducerBase +from nncf.common.tensor_statistics.collectors import TensorReducerBase from nncf.experimental.torch.fx.statistics.aggregator import FXStatisticsAggregator from nncf.quantization.algorithms.fast_bias_correction.torch_fx_backend import FXFastBiasCorrectionAlgoBackend from nncf.quantization.algorithms.min_max.torch_fx_backend import FXMinMaxAlgoBackend