Skip to content

Conversation

@andrey-churkin
Copy link
Contributor

@andrey-churkin andrey-churkin commented Oct 13, 2025

Changes

  • This PR introduces a new axes and axes_mode parameters for TensorReducerBase. These parameters have the following meaning:

    • 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))).
    • 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.

    These parameters are used to calculate the reduction axes (determine_reduction_axes() method) during statistic collection, allowing us to avoid requiring the actual tensor shape (actually only number of dimensions ndim is required) before inference.

  • Modifies the SmoothQuant algorithm to use the axes and axes_mode parameters for the ONNX backend instead of relying on the tensor shape from the NNCF graph, as this shape isn't always available.

Related tickets

Ref: 173880, Ref: 174334

Tests

  • Build post_training_quantization # 735 (# 739)
  • tests/onnx/test_nncf_graph_builder.py::test_unknown_shape

@andrey-churkin andrey-churkin requested a review from a team as a code owner October 13, 2025 07:07
Comment on lines +157 to +164

@staticmethod
def get_abs_max_reducer_cls() -> type[OVAbsMaxReducer]:
return OVAbsMaxReducer

@staticmethod
def get_shape_reducer_cls() -> type[OVShapeReducer]:
return OVShapeReducer
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We add the get_abs_max_reducer_cls() and get_shape_reducer_cls() methods here because the OpenVINO backend uses the OVAbsMaxReducer and OVShapeReducer classes instead of AbsMaxReducer and ShapeReducer to enable in-place statistic collection.

@andrey-churkin andrey-churkin changed the title SQ [ONNX][SmoothQuant] Introduce a new keep_axes parameter Oct 15, 2025
@andrey-churkin andrey-churkin added NNCF Common Pull request that updates NNCF Common NNCF ONNX Pull requests that updates NNCF ONNX labels Oct 15, 2025
@github-actions github-actions bot removed the NNCF ONNX Pull requests that updates NNCF ONNX label Oct 15, 2025
@daniil-lyakhov daniil-lyakhov self-requested a review October 15, 2025 09:28
Copy link
Collaborator

@nikita-savelyevv nikita-savelyevv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we perhaps add a test with an ONNX model for which ndim is not known beforehand to have an example of why keep_dims approach is introduced?

@andrey-churkin
Copy link
Contributor Author

andrey-churkin commented Oct 16, 2025

Should we perhaps add a test with an ONNX model for which ndim is not known beforehand to have an example of why keep_dims approach is introduced?

Thank you for the suggestion. I’ll consider how to implement it.

UPD: This problem is reproduced on timm/visformer_small model from the ptq scope.

UPD: tests/onnx/test_nncf_graph_builder.py::test_unknown_shape test was added

def __init__(self, reduction_axes: Optional[ReductionAxes] = None, inplace: bool = False):
def __init__(
self,
reduction_axes: Optional[ReductionAxes] = None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we forward this parameter in the children of the TensorReducerBase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

def __init__(
self,
reduction_axes: Optional[ReductionAxes] = None,
keep_axes: Optional[tuple[int, ...]] = None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
keep_axes: Optional[tuple[int, ...]] = None,
keep_axes: Optional[Axes] = None,

Perhaps we could rename ReductionAxes and reuse them there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


def __hash__(self) -> int:
return hash((self.__class__.__name__, self.inplace, self._reduction_axes))
return hash((self.__class__.__name__, self.inplace, self._reduction_axes, self._keep_axes))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should update __hash__ methods for some of the TensorReducerBase as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


def test_get_abs_max_channel_collector(self, inplace_statistics: bool):
backend = self.get_backend()
reduction_axes = (3, 2, 1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please test self._backend_entity.get_abs_max_reducer_cls() and _backend_entity.get_shape_reducer_cls

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 307 to 312
if model_backend == BackendType.ONNX:
keep_axes = (self._backend_entity.get_activation_channel_axis(node_to_smooth, input_act_port),)
reduction_axes = None
else:
keep_axes = None
reduction_axes = self._calculate_input_reduction_axes(graph, node_to_smooth, input_act_port)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we create a method in the backend to resolve such situation, why don't you introduce a method in the backend? The comment could be placed as a docstring for the method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It helps simplify the code and avoid duplication.

):
stats = tensor_collector.get_statistics()
shape = stats[SHAPE_BRANCH_KEY]
shape = tuple() if shape is None else tuple(shape.tolist())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When shape could be None?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def test_empty_statistics(self, mode, mocker):

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If shape can be None only during testing and not in any real life scenario then I would suggest to properly mock the returned shape in tests, rather that adopting algorithm logic to support None shapes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@github-actions github-actions bot added the NNCF OpenVINO Pull requests that updates NNCF OpenVINO label Oct 23, 2025
@github-actions github-actions bot added the NNCF ONNX Pull requests that updates NNCF ONNX label Oct 23, 2025
@andrey-churkin
Copy link
Contributor Author

@ljaljushkin @nikita-savelyevv @daniil-lyakhov Please review

@andrey-churkin andrey-churkin changed the title [ONNX][SmoothQuant] Introduce a new keep_axes parameter [ONNX][SmoothQuant] Introduce new axes and axes_mode parameters Oct 23, 2025
):
stats = tensor_collector.get_statistics()
shape = stats[SHAPE_BRANCH_KEY]
shape = tuple() if shape is None else tuple(shape.tolist())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If shape can be None only during testing and not in any real life scenario then I would suggest to properly mock the returned shape in tests, rather that adopting algorithm logic to support None shapes.

@andrey-churkin andrey-churkin merged commit 0379ea1 into openvinotoolkit:develop Oct 24, 2025
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Code Freeze NNCF Common Pull request that updates NNCF Common NNCF ONNX Pull requests that updates NNCF ONNX NNCF OpenVINO Pull requests that updates NNCF OpenVINO

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants