diff --git a/docs/IE_PLUGIN_DG/layout.xml b/docs/IE_PLUGIN_DG/layout.xml
index dbd424edc2c..5b84575851e 100644
--- a/docs/IE_PLUGIN_DG/layout.xml
+++ b/docs/IE_PLUGIN_DG/layout.xml
@@ -25,6 +25,7 @@
+
diff --git a/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/lpt.md b/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/lpt.md
index c695f20ce15..0b7c24ab8e1 100644
--- a/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/lpt.md
+++ b/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/lpt.md
@@ -128,6 +128,7 @@ The model on this step is changed. There are more details in developer guide [Pr
### Step 2. Markup
This step creates runtime attributes for operations. These attributes will be used in next step. Transformations:
+* [MarkupBias](@ref openvino_docs_OV_UG_lpt_MarkupBias)
* [MarkupCanBeQuantized](@ref openvino_docs_OV_UG_lpt_MarkupCanBeQuantized)
* [MarkupPrecisions](@ref openvino_docs_OV_UG_lpt_MarkupPrecisions)
* [MarkupPerTensorQuantization](@ref openvino_docs_OV_UG_lpt_MarkupPerTensorQuantization)
diff --git a/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/pipeline/step2_markup.md b/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/pipeline/step2_markup.md
index f1590c229c8..df93ec64454 100644
--- a/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/pipeline/step2_markup.md
+++ b/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/pipeline/step2_markup.md
@@ -2,18 +2,20 @@
This step defines the optimal `FakeQuantize` decomposition precisions for the best inference performance via operations markup with runtime attribute instances. Attributes are created for input and output ports and operations. Transformations do not change the operation output port precisions. A model markup low precision logic is decomposed and implemented into the following common markup transformations. The order of transformations is important:
-1. [MarkupCanBeQuantized](@ref openvino_docs_OV_UG_lpt_MarkupCanBeQuantized)
-2. [MarkupPrecisions](@ref openvino_docs_OV_UG_lpt_MarkupPrecisions)
-3. [MarkupPerTensorQuantization](@ref openvino_docs_OV_UG_lpt_MarkupPerTensorQuantization)
-4. [MarkupAvgPoolPrecisionPreserved](@ref openvino_docs_OV_UG_lpt_MarkupAvgPoolPrecisionPreserved)
-5. [PropagatePrecisions](@ref openvino_docs_OV_UG_lpt_PropagatePrecisions)
-6. [AlignQuantizationIntervals](@ref openvino_docs_OV_UG_lpt_AlignQuantizationIntervals)
-7. [AlignQuantizationParameters](@ref openvino_docs_OV_UG_lpt_AlignQuantizationParameters)
+1. [MarkupBias](@ref openvino_docs_OV_UG_lpt_MarkupBias)
+2. [MarkupCanBeQuantized](@ref openvino_docs_OV_UG_lpt_MarkupCanBeQuantized)
+3. [MarkupPrecisions](@ref openvino_docs_OV_UG_lpt_MarkupPrecisions)
+4. [MarkupPerTensorQuantization](@ref openvino_docs_OV_UG_lpt_MarkupPerTensorQuantization)
+5. [MarkupAvgPoolPrecisionPreserved](@ref openvino_docs_OV_UG_lpt_MarkupAvgPoolPrecisionPreserved)
+6. [PropagatePrecisions](@ref openvino_docs_OV_UG_lpt_PropagatePrecisions)
+7. [AlignQuantizationIntervals](@ref openvino_docs_OV_UG_lpt_AlignQuantizationIntervals)
+8. [AlignQuantizationParameters](@ref openvino_docs_OV_UG_lpt_AlignQuantizationParameters)
The table of transformations and used attributes:
| Transformation name | Create attributes | Use attributes |
|---------------------------------|-------------------------------|-------------------------------------------|
+| MarkupBias | Bias | |
| MarkupCanBeQuantized | Precisions | |
| MarkupPrecisions | Precisions,PrecisionPreserved | |
| MarkupPerTensorQuantization | PerTensorQuantization | |
diff --git a/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/transformations/step2_markup/markup_bias.md b/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/transformations/step2_markup/markup_bias.md
new file mode 100644
index 00000000000..632d50cee6c
--- /dev/null
+++ b/docs/IE_PLUGIN_DG/plugin_transformation_pipeline/low_precision_transformations/transformations/step2_markup/markup_bias.md
@@ -0,0 +1,3 @@
+# MarkupBias transformation {#openvino_docs_OV_UG_lpt_MarkupBias}
+
+ngraph::pass::low_precision::MarkupBias class represents the `MarkupBias` transformation.
diff --git a/docs/doxygen-xfail.txt b/docs/doxygen-xfail.txt
index f3f1abd334e..bfb5159f631 100644
--- a/docs/doxygen-xfail.txt
+++ b/docs/doxygen-xfail.txt
@@ -27,6 +27,7 @@ openvino_docs_OV_UG_lpt_gathertransformation.rst
openvino_docs_OV_UG_lpt_linopsequencefusion.rst
openvino_docs_OV_UG_lpt_mvntransformation.rst
openvino_docs_OV_UG_lpt_markupavgpoolprecisionpreserved.rst
+openvino_docs_OV_UG_lpt_markupbias.rst
openvino_docs_OV_UG_lpt_markupcanbequantized.rst
openvino_docs_OV_UG_lpt_markuppertensorquantization.rst
openvino_docs_OV_UG_lpt_markupprecisions.rst
diff --git a/src/common/low_precision_transformations/include/low_precision/markup_bias.hpp b/src/common/low_precision_transformations/include/low_precision/markup_bias.hpp
new file mode 100644
index 00000000000..ce4b262075b
--- /dev/null
+++ b/src/common/low_precision_transformations/include/low_precision/markup_bias.hpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2018-2023 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace ngraph {
+namespace pass {
+namespace low_precision {
+
+/**
+ * @ingroup ie_transformation_common_api
+ * @brief MarkupBias transformation marks biases after target layers.
+ *
+ * For more details about the transformation, refer to
+ * [MarkupBias](@ref openvino_docs_OV_UG_lpt_MarkupBias) page
+ * in the Inference Engine Developer Guide.
+ */
+class LP_TRANSFORMATIONS_API MarkupBias : public ov::pass::MatcherPass {
+public:
+ OPENVINO_RTTI("MarkupBias", "0");
+ MarkupBias();
+};
+
+} // namespace low_precision
+} // namespace pass
+} // namespace ngraph
\ No newline at end of file
diff --git a/src/common/low_precision_transformations/include/low_precision/rt_info/bias_attribute.hpp b/src/common/low_precision_transformations/include/low_precision/rt_info/bias_attribute.hpp
new file mode 100644
index 00000000000..fdefec850ca
--- /dev/null
+++ b/src/common/low_precision_transformations/include/low_precision/rt_info/bias_attribute.hpp
@@ -0,0 +1,21 @@
+// Copyright (C) 2018-2023 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace ov {
+LP_TRANSFORMATIONS_API void mark_as_bias(const std::shared_ptr& node);
+
+LP_TRANSFORMATIONS_API bool marked_as_bias(const std::shared_ptr& node);
+
+class LP_TRANSFORMATIONS_API BiasAttribute : public ov::RuntimeAttribute {
+public:
+ OPENVINO_RTTI("LowPrecision::Bias", "", ov::RuntimeAttribute);
+ bool is_copyable(const std::shared_ptr& to) const override;
+};
+} // namespace ov
diff --git a/src/common/low_precision_transformations/src/add.cpp b/src/common/low_precision_transformations/src/add.cpp
index f20fad5061d..32dcac99b80 100644
--- a/src/common/low_precision_transformations/src/add.cpp
+++ b/src/common/low_precision_transformations/src/add.cpp
@@ -15,6 +15,7 @@
#include "low_precision/common/ie_lpt_exception.hpp"
#include "low_precision/network_helper.hpp"
+#include "low_precision/rt_info/bias_attribute.hpp"
#include "itt.hpp"
namespace ngraph {
@@ -29,7 +30,7 @@ std::shared_ptr replaceToSubtract(const std::shared_ptr&
// - single responsibility
// - keep AddTransformation and AddToSubtractTransformation transformations independent and optional
const auto add = ov::as_type_ptr(op);
- if (add == nullptr) {
+ if (add == nullptr || ov::marked_as_bias(add)) {
return nullptr;
}
@@ -40,17 +41,8 @@ std::shared_ptr replaceToSubtract(const std::shared_ptr&
if (constBranchIndex == -1) {
return nullptr;
}
+
const size_t dataBranchIndex = constBranchIndex == 0 ? 1ul : 0;
-
- const auto parent = add->get_input_node_shared_ptr(dataBranchIndex);
- if (ov::is_type(parent) ||
- ov::is_type(parent) ||
- ov::is_type(parent) ||
- (ov::is_type(parent) &&
- (ov::is_type(parent->get_input_node_ptr(0)) || ov::is_type(parent->get_input_node_ptr(1))))) {
- return nullptr;
- }
-
auto constant = fold(add->input_value(constBranchIndex));
auto constOutput = constant->output(0);
diff --git a/src/common/low_precision_transformations/src/eltwise_base_transformation.cpp b/src/common/low_precision_transformations/src/eltwise_base_transformation.cpp
index 106eef1d7fc..293d28dcebd 100644
--- a/src/common/low_precision_transformations/src/eltwise_base_transformation.cpp
+++ b/src/common/low_precision_transformations/src/eltwise_base_transformation.cpp
@@ -10,6 +10,7 @@
#include
#include "low_precision/network_helper.hpp"
+#include "low_precision/rt_info/bias_attribute.hpp"
using namespace ngraph;
using namespace ngraph::pass;
@@ -70,8 +71,18 @@ static std::shared_ptr getDataParent(const std::shared_ptr branchDat
parent = parent->get_input_node_shared_ptr(0);
}
- if (ov::is_type(parent) && isTargetType(parent->get_input_node_shared_ptr(0))) {
- return parent->get_input_node_shared_ptr(0);
+ if (ov::marked_as_bias(parent)) {
+ const auto bias_parent = parent->get_input_node_shared_ptr(0);
+ // target node just before bias
+ if (isTargetType(bias_parent)) {
+ return bias_parent;
+ }
+ // between target node and bias are placed some DQ operations
+ const auto dq = NetworkHelper::getDequantization(parent->get_input_node_shared_ptr(0));
+ const auto data_node = dq.data.get_node_shared_ptr();
+ if (isTargetType(data_node)) {
+ return data_node;
+ }
}
return parent;
}
diff --git a/src/common/low_precision_transformations/src/fake_quantize.cpp b/src/common/low_precision_transformations/src/fake_quantize.cpp
index 7ab60dca339..3dc308962b2 100644
--- a/src/common/low_precision_transformations/src/fake_quantize.cpp
+++ b/src/common/low_precision_transformations/src/fake_quantize.cpp
@@ -10,6 +10,7 @@
#include
#include "low_precision/network_helper.hpp"
+#include "low_precision/rt_info/bias_attribute.hpp"
#include "itt.hpp"
namespace ngraph {
@@ -191,17 +192,8 @@ std::shared_ptr FakeQuantizeTransformation::fuseElementwis
inputLowConst_f32 = fq::updateShape(fold(inputLowConst_f32, value), fakeQuantize->get_output_partial_shape(0));
inputHighConst_f32 = fq::updateShape(fold(inputHighConst_f32, value), fakeQuantize->get_output_partial_shape(0));
- } else if (ov::is_type(eltwise) && checkElementwise(eltwise)) {
- if (ov::is_type(fq::getDataNode(eltwise)) ||
- ov::is_type(fq::getDataNode(eltwise)) ||
- ov::is_type(fq::getDataNode(eltwise)) ||
- ov::is_type(fq::getDataNode(eltwise)) ||
- ov::is_type(fq::getDataNode(eltwise))) {
- return nullptr;
- }
-
+ } else if (ov::is_type(eltwise) && checkElementwise(eltwise) && !ov::marked_as_bias(eltwise)) {
const auto value = foldConvert(constant, element::f32);
-
inputLowConst_f32 = fq::updateShape(fold(inputLowConst_f32, value), fakeQuantize->get_output_partial_shape(0));
inputHighConst_f32 = fq::updateShape(fold(inputHighConst_f32, value), fakeQuantize->get_output_partial_shape(0));
} else if (ov::is_type(eltwise)) {
diff --git a/src/common/low_precision_transformations/src/low_precision.cpp b/src/common/low_precision_transformations/src/low_precision.cpp
index 0a476b6f436..1ed6ba96412 100644
--- a/src/common/low_precision_transformations/src/low_precision.cpp
+++ b/src/common/low_precision_transformations/src/low_precision.cpp
@@ -19,6 +19,7 @@
#include "low_precision/align_quantization_intervals.hpp"
#include "low_precision/fake_quantize_decomposition.hpp"
+#include "low_precision/markup_bias.hpp"
#include "low_precision/markup_precisions.hpp"
#include "low_precision/markup_can_be_quantized.hpp"
#include "low_precision/markup_avg_pool_precision_preserved.hpp"
@@ -201,6 +202,7 @@ bool ngraph::pass::low_precision::MarkupOptimizations::run_on_model(const std::s
markup.register_pass(params.defaultPrecisions);
markup.register_pass(params.defaultPrecisions);
}
+ markup.register_pass();
markup.run_passes(f);
return false;
}
diff --git a/src/common/low_precision_transformations/src/markup_bias.cpp b/src/common/low_precision_transformations/src/markup_bias.cpp
new file mode 100644
index 00000000000..50ff66c0366
--- /dev/null
+++ b/src/common/low_precision_transformations/src/markup_bias.cpp
@@ -0,0 +1,41 @@
+// Copyright (C) 2018-2023 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include "low_precision/markup_bias.hpp"
+
+#include
+#include
+#include
+
+#include "itt.hpp"
+#include "low_precision/rt_info/bias_attribute.hpp"
+
+using namespace ngraph::pass::low_precision;
+
+MarkupBias::MarkupBias() {
+ MATCHER_SCOPE(MarkupBias);
+ auto layer_m = ov::pass::pattern::wrap_type(ov::pass::pattern::has_static_rank());
+ auto bias_const_m = ov::pass::pattern::wrap_type();
+ auto bias_m = ov::pass::pattern::wrap_type({layer_m, bias_const_m});
+
+ ov::matcher_pass_callback callback = [=](ov::pass::pattern::Matcher& m) {
+ const auto& pattern_map = m.get_pattern_value_map();
+ const auto& const_shape = pattern_map.at(bias_const_m).get_shape();
+
+ const bool per_channel = std::count_if(const_shape.begin(), const_shape.end(), [](size_t x) { return x > 1; }) == 1;
+ if (ov::shape_size(const_shape) == 1 || per_channel) {
+ const auto bias = pattern_map.at(bias_m).get_node_shared_ptr();
+ ov::mark_as_bias(bias);
+ }
+
+ return false;
+ };
+
+ auto m = std::make_shared(bias_m, matcher_name);
+ register_matcher(m, callback);
+}
diff --git a/src/common/low_precision_transformations/src/network_helper.cpp b/src/common/low_precision_transformations/src/network_helper.cpp
index c7db834e0b2..b5ea92e61ff 100644
--- a/src/common/low_precision_transformations/src/network_helper.cpp
+++ b/src/common/low_precision_transformations/src/network_helper.cpp
@@ -1286,7 +1286,14 @@ FakeQuantizeDequantization NetworkHelper::getDequantization(const std::shared_pt
return 1ul;
};
- Output dataNode = inPlace ? std::const_pointer_cast(node)->output(0) : node->input_value(parentIndex);
+ Output dataNode;
+ if (inPlace) {
+ dataNode = std::const_pointer_cast(node);
+ } else {
+ if (parentIndex >= node->get_input_size())
+ return FakeQuantizeDequantization();
+ dataNode = node->input_value(parentIndex);
+ }
const std::shared_ptr multiply = ov::as_type_ptr(dataNode.get_node_shared_ptr());
std::shared_ptr multiplyConstant;
diff --git a/src/common/low_precision_transformations/src/rt_info/bias_attribute.cpp b/src/common/low_precision_transformations/src/rt_info/bias_attribute.cpp
new file mode 100644
index 00000000000..40d75e06f87
--- /dev/null
+++ b/src/common/low_precision_transformations/src/rt_info/bias_attribute.cpp
@@ -0,0 +1,27 @@
+// Copyright (C) 2018-2023 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include "low_precision/rt_info/bias_attribute.hpp"
+#include "low_precision/network_helper.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+void ov::mark_as_bias(const std::shared_ptr& node) {
+ auto& rt = node->get_rt_info();
+ rt[ov::BiasAttribute::get_type_info_static()] = ov::BiasAttribute();
+}
+
+bool ov::marked_as_bias(const std::shared_ptr& node) {
+ const auto& rt_info = node->get_rt_info();
+ return rt_info.find(ov::BiasAttribute::get_type_info_static()) != rt_info.end();
+}
+
+bool ov::BiasAttribute::is_copyable(const std::shared_ptr& to) const {
+ return ov::is_type(to) && ngraph::pass::low_precision::NetworkHelper::getConstantInput(to) != nullptr;
+}
diff --git a/src/common/low_precision_transformations/tests/markup_bias_transformation.cpp b/src/common/low_precision_transformations/tests/markup_bias_transformation.cpp
new file mode 100644
index 00000000000..3fa19665969
--- /dev/null
+++ b/src/common/low_precision_transformations/tests/markup_bias_transformation.cpp
@@ -0,0 +1,107 @@
+// Copyright (C) 2018-2023 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include "common_test_utils/ngraph_test_utils.hpp"
+#include "layer_transformation.hpp"
+#include "lpt_ngraph_functions/markup_bias_function.hpp"
+#include "simple_low_precision_transformer.hpp"
+
+using namespace testing;
+
+class MarkupBiasTestParams {
+public:
+ ov::PartialShape input_shape;
+ ov::PartialShape bias_shape;
+ bool is_bias;
+};
+
+using MarkupBiasTestValues = std::tuple;
+
+class MarkupBiasTests : public testing::WithParamInterface, public LayerTransformation {
+public:
+ static std::string getTestCaseName(const testing::TestParamInfo& obj) {
+ ov::element::Type precision;
+ MarkupBiasTestParams test_values;
+ std::string layer_type;
+ std::tie(precision, test_values, layer_type) = obj.param;
+
+ std::ostringstream result;
+ result << precision << "IS=" << test_values.input_shape << "_bias_shape=" << test_values.bias_shape << "_"
+ << layer_type << "_is_bias=" << test_values.is_bias;
+ return result.str();
+ }
+
+protected:
+ void SetUp() override {
+ ov::element::Type precision;
+ MarkupBiasTestParams test_values;
+ std::string layer_type;
+ std::tie(precision, test_values, layer_type) = GetParam();
+
+ actualFunction = ngraph::builder::subgraph::MarkupBiasFunction::get(precision,
+ test_values.input_shape,
+ test_values.bias_shape,
+ layer_type);
+ SimpleLowPrecisionTransformer transformer;
+ transformer.transform(actualFunction);
+ }
+};
+
+TEST_P(MarkupBiasTests, CompareFunctions) {
+ actualFunction->validate_nodes_and_infer_types();
+
+ const auto addOps = LayerTransformation::get(actualFunction);
+ EXPECT_EQ(1ul, addOps.size()) << "unexpected addOps size";
+
+ const bool is_bias = std::get<1>(GetParam()).is_bias;
+ auto biasAttr = ngraph::pass::low_precision::getAttribute(addOps[0]);
+ EXPECT_EQ(!biasAttr.empty(), is_bias) << "Bias markup failed";
+}
+
+namespace MarkupBiasTestsInstantiation {
+std::vector precisions = {
+ ov::element::f32,
+};
+
+std::vector test_params_4d = {
+ {{1, 10, 16, 16}, {1, 10, 1, 1}, true},
+ {{1, 10, 16, 16}, {1, 1, 1, 1}, true},
+ {{1, 10, 16, 16}, {1, 10, 16, 16}, false},
+ {{1, 10, 16, 16}, ov::PartialShape::dynamic(), false},
+};
+
+std::vector layer_types_4d = {
+ "Convolution",
+ "GroupConvolution",
+ "ConvolutionBackpropData",
+};
+
+INSTANTIATE_TEST_SUITE_P(smoke_LPT_4D_Positive,
+ MarkupBiasTests,
+ ::testing::Combine(::testing::ValuesIn(precisions),
+ ::testing::ValuesIn(test_params_4d),
+ ::testing::ValuesIn(layer_types_4d)),
+ MarkupBiasTests::getTestCaseName);
+
+std::vector test_params_2d = {
+ {{1, 10}, {1, 10}, true},
+ {{1, 10}, {1, 1}, true},
+ {{1, 10}, ov::PartialShape::dynamic(), false},
+};
+
+INSTANTIATE_TEST_SUITE_P(smoke_LPT_2D_Positive,
+ MarkupBiasTests,
+ ::testing::Combine(::testing::ValuesIn(precisions),
+ ::testing::ValuesIn(test_params_2d),
+ ::testing::Values("MatMulWithConstant")),
+ MarkupBiasTests::getTestCaseName);
+
+} // namespace MarkupBiasTestsInstantiation
diff --git a/src/common/low_precision_transformations/tests/simple_low_precision_transformer.cpp b/src/common/low_precision_transformations/tests/simple_low_precision_transformer.cpp
index 686390d53fc..a83c6f8b5d8 100644
--- a/src/common/low_precision_transformations/tests/simple_low_precision_transformer.cpp
+++ b/src/common/low_precision_transformations/tests/simple_low_precision_transformer.cpp
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -26,6 +27,7 @@ SimpleLowPrecisionTransformer::SimpleLowPrecisionTransformer(
// TODO: use one pass manager
markup = std::make_shared(passConfig);
+ markup->register_pass();
markup->register_pass(params.defaultPrecisions);
markup->register_pass(precisionRestrictions,
params.defaultPrecisions);
diff --git a/src/plugins/intel_cpu/src/nodes/eltwise.cpp b/src/plugins/intel_cpu/src/nodes/eltwise.cpp
index 245fe2c92d0..a6a9ff7b995 100644
--- a/src/plugins/intel_cpu/src/nodes/eltwise.cpp
+++ b/src/plugins/intel_cpu/src/nodes/eltwise.cpp
@@ -2365,10 +2365,11 @@ bool Eltwise::canBeInPlace() const {
void Eltwise::fuseInto(NodePtr& parentNode) {
// Handling Convolution custom Add node fusing case which is processed via dnnl append_sum() API.
- specialConvolutionAddFusing = (parentNode->getType() == Type::Convolution
- || parentNode->getType() == Type::BinaryConvolution)
- && getAlgorithm() == Algorithm::EltwiseAdd &&
- dimsEqualWeak(getInputShapeAtPort(0).getDims(), getInputShapeAtPort(1).getDims());
+ specialConvolutionAddFusing =
+ (parentNode->getType() == Type::Convolution || parentNode->getType() == Type::BinaryConvolution) &&
+ getAlgorithm() == Algorithm::EltwiseAdd &&
+ dimsEqualWeak(getInputShapeAtPort(0).getDims(), getInputShapeAtPort(1).getDims()) &&
+ !getParentEdgeAt(0)->getParent()->isConstant() && !getParentEdgeAt(1)->getParent()->isConstant();
if ((scales.empty() && shifts.empty()) &&
!specialConvolutionAddFusing &&
canBePerformedAsScaleShift(parentNode.get())) {
diff --git a/src/plugins/intel_cpu/tests/functional/subgraph_tests/src/fq_layer_dq_bias.cpp b/src/plugins/intel_cpu/tests/functional/subgraph_tests/src/fq_layer_dq_bias.cpp
new file mode 100644
index 00000000000..68e5fbdbf81
--- /dev/null
+++ b/src/plugins/intel_cpu/tests/functional/subgraph_tests/src/fq_layer_dq_bias.cpp
@@ -0,0 +1,118 @@
+// Copyright (C) 2023 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include "lpt_ngraph_functions/markup_bias_function.hpp"
+#include "ngraph_functions/builders.hpp"
+#include "ngraph_functions/utils/ngraph_helpers.hpp"
+#include "shared_test_classes/base/layer_test_utils.hpp"
+#include "shared_test_classes/base/ov_subgraph.hpp"
+#include "test_utils/cpu_test_utils.hpp"
+#include "test_utils/fusing_test_utils.hpp"
+
+using namespace ngraph;
+using namespace ov::test;
+using namespace CPUTestUtils;
+using namespace InferenceEngine;
+
+namespace SubgraphTestsDefinitions {
+using FQLayerDQBiasParams = std::tuple;
+
+class FQLayerDQBias : virtual public SubgraphBaseTest,
+ public CpuTestWithFusing,
+ public testing::WithParamInterface {
+public:
+ static std::string getTestCaseName(testing::TestParamInfo obj) {
+ InputShape input_shape;
+ std::string layer_type;
+ std::tie(input_shape, layer_type) = obj.param;
+
+ std::ostringstream result;
+ result << "IS=(" << CommonTestUtils::partialShape2str({input_shape.first}) << ")_TS=(";
+ for (const auto& item : input_shape.second) {
+ result << CommonTestUtils::vec2str(item) << "_";
+ }
+ result << ")_layer_type=" << layer_type;
+ return result.str();
+ }
+
+protected:
+ void SetUp() override {
+ InputShape input_shape;
+ std::string layer_type;
+ std::tie(input_shape, layer_type) = GetParam();
+
+ targetDevice = CommonTestUtils::DEVICE_CPU;
+ fusedOps = std::vector{"Add"};
+ std::tie(inFmts, outFmts, priority, selectedType) = CPUSpecificParams{{}, {}, {}, CPUTestsBase::any_type};
+ std::unordered_map ngraph_type_to_plugin_type{
+ {"Convolution", "Convolution"},
+ {"GroupConvolution", "Convolution"},
+ {"ConvolutionBackpropData", "Deconvolution"},
+ {"MatMul", "MatMul"},
+ {"MatMulWithConstant", "FullyConnected"},
+ };
+ node_type = ngraph_type_to_plugin_type[layer_type];
+
+ const auto shapes = layer_type == "MatMul" ? std::vector{input_shape, input_shape}
+ : std::vector{input_shape};
+ init_input_shapes(shapes);
+ function = ngraph::builder::subgraph::MarkupBiasFunction::get(ov::element::f32, inputDynamicShapes[0], {}, layer_type);
+ }
+
+ std::string node_type;
+};
+
+TEST_P(FQLayerDQBias, smoke_CompareWithRefs) {
+ run();
+ CheckPluginRelatedResults(compiledModel, node_type);
+}
+
+namespace {
+const std::vector input_shapes_4D_static = {
+ {{}, {{1, 3, 1, 1}}},
+ {{}, {{1, 3, 64, 64}}}
+};
+
+const std::vector layer_types_4D_static = {
+ "Convolution",
+ "GroupConvolution",
+ "ConvolutionBackpropData",
+ "MatMul",
+};
+
+INSTANTIATE_TEST_SUITE_P(smoke_FQLayerDQBias_4D_static, FQLayerDQBias,
+ ::testing::Combine(::testing::ValuesIn(input_shapes_4D_static),
+ ::testing::ValuesIn(layer_types_4D_static)),
+ FQLayerDQBias::getTestCaseName);
+
+const std::vector input_shapes_4D_dynamic = {
+ {{-1, 3, -1, -1}, {{1, 3, 64, 64}}}
+};
+
+const std::vector layer_types_4D_dynamic = {
+ "Convolution",
+ "GroupConvolution",
+ "MatMul",
+};
+
+INSTANTIATE_TEST_SUITE_P(smoke_FQLayerDQBias_4D_dynamic, FQLayerDQBias,
+ ::testing::Combine(::testing::ValuesIn(input_shapes_4D_dynamic),
+ ::testing::ValuesIn(layer_types_4D_dynamic)),
+ FQLayerDQBias::getTestCaseName);
+
+const std::vector input_shapes_2D = {
+ {{-1, 768}, {{1, 768}}}
+};
+
+const std::vector layer_types_2D = {
+ "MatMulWithConstant",
+};
+
+INSTANTIATE_TEST_SUITE_P(smoke_FQLayerDQBias_2D, FQLayerDQBias,
+ ::testing::Combine(::testing::ValuesIn(input_shapes_2D),
+ ::testing::ValuesIn(layer_types_2D)),
+ FQLayerDQBias::getTestCaseName);
+
+} // namespace
+} // namespace SubgraphTestsDefinitions
diff --git a/src/tests/ngraph_helpers/lpt_ngraph_functions/include/lpt_ngraph_functions/markup_bias_function.hpp b/src/tests/ngraph_helpers/lpt_ngraph_functions/include/lpt_ngraph_functions/markup_bias_function.hpp
new file mode 100644
index 00000000000..4b53c19b57b
--- /dev/null
+++ b/src/tests/ngraph_helpers/lpt_ngraph_functions/include/lpt_ngraph_functions/markup_bias_function.hpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2018-2023 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#pragma once
+
+#include
+#include
+
+#include "common/builders.hpp"
+
+namespace ngraph {
+namespace builder {
+namespace subgraph {
+
+class MarkupBiasFunction {
+public:
+ static std::shared_ptr get(const ov::element::Type& precision,
+ const ov::PartialShape& input_shape,
+ const ov::PartialShape& add_shape,
+ const std::string& operation_type);
+};
+} // namespace subgraph
+} // namespace builder
+} // namespace ngraph
diff --git a/src/tests/ngraph_helpers/lpt_ngraph_functions/src/markup_bias_function.cpp b/src/tests/ngraph_helpers/lpt_ngraph_functions/src/markup_bias_function.cpp
new file mode 100644
index 00000000000..821ad174c23
--- /dev/null
+++ b/src/tests/ngraph_helpers/lpt_ngraph_functions/src/markup_bias_function.cpp
@@ -0,0 +1,112 @@
+// Copyright (C) 2018-2023 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+#include "lpt_ngraph_functions/markup_bias_function.hpp"
+#include "ngraph_functions/utils/ngraph_helpers.hpp"
+#include "ngraph_functions/builders.hpp"
+
+namespace ngraph {
+namespace builder {
+namespace subgraph {
+std::shared_ptr MarkupBiasFunction::get(const ov::element::Type& precision,
+ const ov::PartialShape& input_shape,
+ const ov::PartialShape& add_shape,
+ const std::string& layer_type) {
+ auto input_params = builder::makeDynamicParams(precision, {input_shape});
+ auto il = opset1::Constant::create(precision, {}, {0.f});
+ auto ih = opset1::Constant::create(precision, {}, {12.5f});
+ auto ol = opset1::Constant::create(precision, {}, {0.f});
+ auto oh = opset1::Constant::create(precision, {}, {12.5f});
+ auto fq = std::make_shared(input_params[0], il, ih, ol, oh, 256);
+
+ std::shared_ptr layer;
+ const size_t out_channels = 10;
+ if (layer_type == "Convolution") {
+ const size_t in_channels = input_params[0]->get_partial_shape()[1].get_length();
+ auto weights = builder::makeConstant(ov::element::i8, Shape{out_channels, in_channels, 1, 1}, {}, true);
+ auto convert = std::make_shared(weights, precision);
+ auto mul_const = builder::makeConstant(precision, Shape{1, 1, 1, 1}, {}, true);
+ auto mul = std::make_shared(convert, mul_const);
+
+ const ov::Strides strides = {1, 1};
+ const ov::CoordinateDiff pads_begin = {0, 0};
+ const ov::CoordinateDiff pads_end = {0, 0};
+ const ov::Strides dilations = {1, 1};
+ layer = std::make_shared(fq, mul, strides, pads_begin, pads_end, dilations);
+ } else if (layer_type == "GroupConvolution") {
+ const size_t in_channels = input_params[0]->get_partial_shape()[1].get_length();
+ auto weights = builder::makeConstant(ov::element::i8, Shape{in_channels, 1, 1, 1}, {}, true);
+ auto convert = std::make_shared(weights, precision);
+ auto mul_const = builder::makeConstant(precision, Shape{1, 1, 1, 1}, {}, true);
+ auto mul = std::make_shared(convert, mul_const);
+
+ std::vector target_shape{static_cast(in_channels), 1, 1, 1, 1};
+ auto reshape_const = ov::opset1::Constant::create(ov::element::i32, {5}, target_shape);
+ auto reshape = std::make_shared(mul, reshape_const, true);
+
+ const ov::Strides strides = {1, 1};
+ const ov::CoordinateDiff pads_begin = {0, 0};
+ const ov::CoordinateDiff pads_end = {0, 0};
+ const ov::Strides dilations = {1, 1};
+ layer = std::make_shared(fq, reshape, strides, pads_begin, pads_end, dilations);
+ } else if (layer_type == "ConvolutionBackpropData") {
+ const size_t in_channels = input_params[0]->get_partial_shape()[1].get_length();
+ auto weights = builder::makeConstant(ov::element::i8, Shape{in_channels, out_channels, 1, 1}, {}, true);
+ auto convert = std::make_shared(weights, precision);
+ auto mul_const = builder::makeConstant(precision, Shape{1, 1, 1, 1}, {}, true);
+ auto mul = std::make_shared(convert, mul_const);
+
+ const ov::Strides strides = {1, 1};
+ const ov::CoordinateDiff pads_begin = {0, 0};
+ const ov::CoordinateDiff pads_end = {0, 0};
+ const ov::Strides dilations = {1, 1};
+ layer = std::make_shared(fq, mul, strides, pads_begin, pads_end, dilations);
+ } else if (layer_type == "MatMul") {
+ auto new_param = std::make_shared(precision, input_shape);
+ input_params.push_back(new_param);
+ auto il_2 = opset1::Constant::create(precision, {}, {-128.f});
+ auto ih_2 = opset1::Constant::create(precision, {}, {127.f});
+ auto ol_2 = opset1::Constant::create(precision, {}, {-128.f});
+ auto oh_2 = opset1::Constant::create(precision, {}, {127.f});
+ auto fq_2 = std::make_shared(new_param, il_2, ih_2, ol_2, oh_2, 256);
+ layer = std::make_shared(fq, fq_2, false, true);
+ } else if (layer_type == "MatMulWithConstant") {
+ const size_t in_channels = input_params[0]->get_partial_shape()[1].get_length();
+ auto weights = builder::makeConstant(ov::element::i8, Shape{out_channels, in_channels}, {}, true);
+ auto convert = std::make_shared(weights, precision);
+ auto mul_const = builder::makeConstant(precision, Shape{out_channels, 1}, {}, true);
+ auto mul = std::make_shared(convert, mul_const);
+ layer = std::make_shared(fq, mul, false, true);
+ } else {
+ throw std::runtime_error("Unsupported layer type");
+ }
+
+ layer->set_friendly_name(layer_type);
+
+ std::shared_ptr add_input;
+ // empty add_shape means that add_input must be generated automatically
+ if (add_shape.is_static() && add_shape.size() == 0) {
+ const auto& out_shape = layer->get_output_partial_shape(0);
+ Shape bias_shape(out_shape.size(), 1);
+ if (layer_type != "MatMul") {
+ bias_shape[1] = out_shape[1].get_length();
+ }
+ add_input = builder::makeConstant(precision, bias_shape, {}, true);
+ } else {
+ if (add_shape.is_static()) {
+ add_input = builder::makeConstant(precision, add_shape.to_shape(), {}, true);
+ } else {
+ auto new_param = std::make_shared(precision, input_shape);
+ input_params.push_back(new_param);
+ add_input = new_param;
+ }
+ }
+
+ auto add = std::make_shared(layer, add_input);
+ return std::make_shared(add, input_params);
+}
+} // namespace subgraph
+} // namespace builder
+} // namespace ngraph