Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
136173d
init
mryzhov May 26, 2026
55e072f
merge FQs
mryzhov May 27, 2026
b65caba
fixed tests
mryzhov May 27, 2026
f904754
Merge branch 'master' into fqs_remove
mryzhov Jun 11, 2026
dc88a91
replace transformation into separate file
mryzhov Jun 12, 2026
989bed7
move tests
mryzhov Jun 12, 2026
47ba7bd
clang fixes
mryzhov Jun 12, 2026
1ee9f2c
fixed old test
mryzhov Jun 12, 2026
36fc099
Merge branch 'master' into fqs_remove
mryzhov Jun 12, 2026
f94a673
Merge branch 'master' into fqs_remove
mryzhov Jun 15, 2026
c9f8d7e
Exclude fq_fuse_dynamic_shapes changes
mryzhov Jun 15, 2026
c233abd
Exclude nop_elimination changes
mryzhov Jun 15, 2026
7372c89
Remove eliminate_sequential_fake_quantize pass
mryzhov Jun 15, 2026
b03e5ef
aligned the naming
mryzhov Jun 15, 2026
feed7e0
Rename FQEliminateSequential to FakeQuantizeEliminateSequential and a…
mryzhov Jun 15, 2026
ea02c5b
optimization
mryzhov Jun 22, 2026
7aeba44
refactoring
mryzhov Jun 22, 2026
1b49164
Reshaping support
mryzhov Jun 23, 2026
66fcadf
reduce supported cases
mryzhov Jun 23, 2026
92051f8
clang fixes
mryzhov Jun 23, 2026
41b4b7e
Merge branch 'master' into fqs_remove
mryzhov Jun 23, 2026
d7ababd
Merge branch 'master' into fqs_remove
mryzhov Jun 23, 2026
48f6e67
fixed tests
mryzhov Jun 24, 2026
43bca8a
per_channel_fake_quantize test
mryzhov Jun 24, 2026
739cc33
switch to modelpass
mryzhov Jun 24, 2026
e849ba2
Merge branch 'master' into fqs_remove
mryzhov Jun 24, 2026
e95f5d6
share have_same_fake_quantize_params()
mryzhov Jun 24, 2026
9e3431c
excluded cases with different FQ shape constants
mryzhov Jun 24, 2026
2136bc6
Merge branch 'master' into fqs_remove
mryzhov Jun 24, 2026
f105696
Potential fix for pull request finding
mryzhov Jun 25, 2026
5ab9dec
Update src/common/transformations/include/transformations/common_opti…
mryzhov Jun 25, 2026
6d43d85
use is_type_any_of
mryzhov Jun 26, 2026
d592883
Merge branch 'master' into fqs_remove
mryzhov Jun 26, 2026
f686324
Potential fix for pull request finding
mryzhov Jun 26, 2026
ef3f380
Merge branch 'master' into fqs_remove
mryzhov Jun 26, 2026
8c18ea8
Merge branch 'master' into fqs_remove
mryzhov Jun 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (C) 2018-2026 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include "openvino/pass/pass.hpp"
#include "transformations_visibility.hpp"

namespace ov {
namespace pass {

class TRANSFORMATIONS_API FakeQuantizeEliminateSequential;

} // namespace pass
} // namespace ov

/**
* @ingroup ov_transformation_common_api
* @brief FakeQuantizeEliminateSequential removes a redundant second FakeQuantize from a sequential
* pair (FQ1 -> FQ2). The notation below is FQ(in_low, in_high, out_low, out_high, levels).
*
* FQ1 and FQ2 may be separated by one or several value-preserving ops.
* A matched FakeQuantize is applied element-wise (whether per-tensor or per-channel), so it
* commutes with such ops and the folding stays valid through the chain.
*
* FQ2 is removed (FQ1 kept) when it is identical to FQ1, i.e. it has the same levels, auto broadcast,
* and range constants (input_low/input_high/output_low/output_high). In that case FQ2 only re-applies
* the quantization already produced by FQ1 and is redundant.
*
* Before:
* ┌─────────┐
* │ Data │
* └────┬────┘
* │
* ┌────┴────┐
* │ FQ1 │
* └────┬────┘
* │
* ┌──────┴───────┐
* │ Reshape / │ (optional, zero or more value-preserving ops)
* │ Transpose / │
* │ Squeeze / │
* │ Unsqueeze │
* └──────┬───────┘
* │
* ┌────┴────┐
* │ FQ2 │ (identical params to FQ1)
* └────┬────┘
* │
* ┌────┴────┐
* │Consumer │
* └─────────┘
*
* After:
* ┌─────────┐
* │ Data │
* └────┬────┘
* │
* ┌────┴────┐
* │ FQ1 │
* └────┬────┘
* │
* ┌──────┴───────┐
* │ Reshape / │ (optional, preserved)
* │ Transpose / │
* │ Squeeze / │
* │ Unsqueeze │
* └──────┬───────┘
* │
* ┌────┴────┐
* │Consumer │
* └─────────┘
*
* For example: FQ1(-1, 1, -1, 1, 256) -> FQ2(-1, 1, -1, 1, 256) => FQ1(-1, 1, -1, 1, 256)
*/
class ov::pass::FakeQuantizeEliminateSequential : public ov::pass::ModelPass {
public:
OPENVINO_MODEL_PASS_RTTI("FakeQuantizeEliminateSequential");
bool run_on_model(const std::shared_ptr<ov::Model>& model) override;
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@

namespace ov {
namespace op {
namespace v0 {
class FakeQuantize;
} // namespace v0
namespace util {

template <class T>
Expand Down Expand Up @@ -223,6 +226,18 @@ TRANSFORMATIONS_API void visit_path_forward(ov::Node* start_node,
std::function<void(ov::Node*)> func,
std::function<bool(ov::Node*)> skip_node_predicate);

/**
* \brief Checks whether two FakeQuantize ops share identical quantization parameters: the same number
* of levels, the same auto-broadcast spec, and equal input_low/input_high/output_low/output_high
* constants.
*
* \param lhs The first FakeQuantize.
* \param rhs The second FakeQuantize.
* \return true if both ops are valid and have identical parameters.
*/
TRANSFORMATIONS_API bool have_same_fake_quantize_params(const std::shared_ptr<ov::op::v0::FakeQuantize>& lhs,
const std::shared_ptr<ov::op::v0::FakeQuantize>& rhs);

/**
* \brief Traverses a shapeOf subgraph starting from the node and not including the ShapeOf nodes,
* and calls "func" for each ov::Node.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "transformations/common_optimizations/dropout_with_random_uniform_replacer.hpp"
#include "transformations/common_optimizations/eliminate_unsqueeze_gather.hpp"
#include "transformations/common_optimizations/fq_concat_fusion.hpp"
#include "transformations/common_optimizations/fq_eliminate_sequential.hpp"
#include "transformations/common_optimizations/fq_mul_fusion.hpp"
#include "transformations/common_optimizations/fq_reshape_fusion.hpp"
#include "transformations/common_optimizations/fuse_clamp_and_fake_quantize.hpp"
Expand Down Expand Up @@ -257,6 +258,8 @@ bool ov::pass::CommonOptimizations::run_on_model(const std::shared_ptr<ov::Model
ADD_MATCHER(fq_fusions, MulFakeQuantizeFusion)
fq_fusions->set_name("ov::pass::FakeQuantizeFusions");

REGISTER_PASS(manager, FakeQuantizeEliminateSequential)
Comment thread
mryzhov marked this conversation as resolved.

// Temporary transformation to allow for PyTorch frontend to
// partially support bitwise operators with boolean inputs for plugins
// that didn't enabled BitwiseOps from opset13 and to allow for constant
Expand Down
Comment thread
mryzhov marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,18 @@

#include "compare.hpp"
#include "itt.hpp"
#include "openvino/core/graph_util.hpp"
#include "openvino/core/rt_info.hpp"
#include "openvino/op/concat.hpp"
#include "openvino/op/constant.hpp"
#include "openvino/op/fake_quantize.hpp"
#include "openvino/pass/pattern/op/wrap_type.hpp"
#include "transformations/utils/utils.hpp"

namespace v0 = ov::op::v0;
namespace op_util = ov::op::util;

namespace ov::pass {

namespace {

bool have_same_fake_quantize_params(const std::shared_ptr<v0::FakeQuantize>& lhs,
const std::shared_ptr<v0::FakeQuantize>& rhs) {
if (!lhs || !rhs || lhs->get_levels() != rhs->get_levels() ||
lhs->get_auto_broadcast() != rhs->get_auto_broadcast()) {
return false;
}

for (size_t index = 1; index < lhs->get_input_size(); ++index) {
if (!ov::compare_constants(lhs->input_value(index).get_node_shared_ptr(),
rhs->input_value(index).get_node_shared_ptr())) {
return false;
}
}

return true;
}

} // namespace

FakeQuantizeConcatFusion::FakeQuantizeConcatFusion() {
MATCHER_SCOPE(FakeQuantizeConcatFusion);
auto concat_pattern = pattern::wrap_type<v0::Concat>({}, pattern::consumers_count(1));
Expand All @@ -63,7 +43,7 @@ FakeQuantizeConcatFusion::FakeQuantizeConcatFusion() {

for (const auto& concat_input : concat->input_values()) {
const auto input_fq = ov::as_type_ptr<v0::FakeQuantize>(concat_input.get_node_shared_ptr());
if (!have_same_fake_quantize_params(input_fq, output_fq)) {
if (!op_util::have_same_fake_quantize_params(input_fq, output_fq)) {
return false;
}

Comment thread
mryzhov marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (C) 2018-2026 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "transformations/common_optimizations/fq_eliminate_sequential.hpp"

#include <memory>
#include <unordered_set>
#include <vector>

#include "itt.hpp"
#include "openvino/core/graph_util.hpp"
#include "openvino/core/model.hpp"
#include "openvino/op/fake_quantize.hpp"
#include "openvino/op/reshape.hpp"
#include "openvino/op/transpose.hpp"
#include "openvino/op/unsqueeze.hpp"
#include "openvino/op/util/op_types.hpp"
#include "openvino/op/util/squeeze_base.hpp"
#include "transformations/utils/utils.hpp"

namespace v0 = ov::op::v0;
namespace v1 = ov::op::v1;
namespace op_util = ov::op::util;

namespace ov::pass {

namespace {

bool is_value_preserving(const std::shared_ptr<ov::Node>& node) {
return ov::is_type_any_of<v1::Reshape, v1::Transpose, op_util::SqueezeBase, v0::Unsqueeze>(node);
}

} // namespace

bool FakeQuantizeEliminateSequential::run_on_model(const std::shared_ptr<ov::Model>& model) {
RUN_ON_MODEL_SCOPE(FakeQuantizeEliminateSequential);

bool eliminated = false;
for (const auto& op : model->get_ordered_ops()) {
auto fq1 = ov::as_type_ptr<v0::FakeQuantize>(op);
if (!fq1) {
continue;
}

// Walk forward from FQ1 following its consumers and collect the redundant FakeQuantizes.
// Traversal continues only through FQ1, value-preserving ops, and redundant FakeQuantizes, so
// branches into several consumers are handled as well. visit_path_forward queries a node's
// consumers right after the visitor runs, so the redundant FakeQuantizes are only collected
// here and detached afterwards to avoid dereferencing a freed node.
std::vector<std::shared_ptr<v0::FakeQuantize>> redundant_fqs;
std::unordered_set<ov::Node*> visited;
auto skip_node = [&](ov::Node* node) {
auto shared_node = node->shared_from_this();
if (shared_node == fq1 || is_value_preserving(shared_node)) {
return false;
}
return !op_util::have_same_fake_quantize_params(fq1, ov::as_type_ptr<v0::FakeQuantize>(shared_node));
};
auto collect_redundant_fq = [&](ov::Node* node) {
if (auto fq2 = ov::as_type_ptr<v0::FakeQuantize>(node->shared_from_this()); fq2 && fq2 != fq1) {
redundant_fqs.push_back(fq2);
}
};
op_util::visit_path_forward(fq1.get(), visited, collect_redundant_fq, skip_node);

for (const auto& fq2 : redundant_fqs) {
eliminated = replace_output_update_name(fq2->output(0), fq2->input_value(0)) || eliminated;
}
Comment thread
mryzhov marked this conversation as resolved.
}
Comment thread
mryzhov marked this conversation as resolved.

return eliminated;
}

} // namespace ov::pass
21 changes: 21 additions & 0 deletions src/common/transformations/src/transformations/utils/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <tuple>
#include <vector>

#include "openvino/core/graph_util.hpp"
#include "openvino/core/validation_util.hpp"
#include "openvino/op/abs.hpp"
#include "openvino/op/add.hpp"
Expand Down Expand Up @@ -107,6 +108,26 @@ void visit_path_forward(ov::Node* start_node,
}
}

bool have_same_fake_quantize_params(const std::shared_ptr<v0::FakeQuantize>& lhs,
const std::shared_ptr<v0::FakeQuantize>& rhs) {
Comment thread
mryzhov marked this conversation as resolved.
if (!lhs || !rhs || lhs->get_levels() != rhs->get_levels() ||
lhs->get_auto_broadcast() != rhs->get_auto_broadcast()) {
return false;
}

for (size_t index = 1; index < lhs->get_input_size(); ++index) {
// compare_constants matches flattened values only, so the shapes are compared separately to
// avoid treating constants that broadcast differently (e.g. {1, 3, 1, 1} vs {3, 1}) as equal.
if (lhs->get_input_partial_shape(index) != rhs->get_input_partial_shape(index) ||
!ov::compare_constants(lhs->input_value(index).get_node_shared_ptr(),
rhs->input_value(index).get_node_shared_ptr())) {
return false;
}
}

return true;
}

bool get_single_value(const std::shared_ptr<op::v0::Constant>& const_node, float& value, bool check_value_range) {
switch (const_node->get_element_type()) {
case element::Type_t::f16:
Expand Down
Loading
Loading