diff --git a/src/frontends/onnx/frontend/src/op/com.microsoft/qlinear_concat.cpp b/src/frontends/onnx/frontend/src/op/com.microsoft/qlinear_concat.cpp new file mode 100644 index 00000000000000..a94adf00d2db1e --- /dev/null +++ b/src/frontends/onnx/frontend/src/op/com.microsoft/qlinear_concat.cpp @@ -0,0 +1,61 @@ +// Copyright (C) 2018-2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "core/operator_set.hpp" +#include "exceptions.hpp" +#include "openvino/frontend/exception.hpp" +#include "openvino/op/add.hpp" +#include "openvino/op/concat.hpp" +#include "openvino/op/convert.hpp" +#include "openvino/op/divide.hpp" +#include "openvino/op/multiply.hpp" +#include "openvino/op/subtract.hpp" +#include "utils/common.hpp" + +using namespace ov::op; + +namespace ov { +namespace frontend { +namespace onnx { +namespace com_microsoft { +namespace opset_1 { + +ov::OutputVector qlinear_concat(const ov::frontend::onnx::Node& node) { + common::default_op_checks(node, 3); + + auto inputs = node.get_ov_inputs(); + auto Y_scale = inputs[0]; + auto Y_zero_point = inputs[1]; + + std::vector> dequantized_inputs; + for (size_t i = 2; i < inputs.size(); i += 3) { + auto X = inputs[i]; + auto X_scale = inputs[i + 1]; + auto X_zero_point = inputs[i + 2]; + + auto X_minus_zero_point = std::make_shared(X, X_zero_point); + auto X_minus_zero_point_float = std::make_shared(X_minus_zero_point, X_scale.get_element_type()); + auto dequantized_X = std::make_shared(X_scale, X_minus_zero_point_float); + + dequantized_inputs.push_back(dequantized_X); + } + + auto axis = node.get_attribute_value("axis"); + auto concatenated = + std::make_shared(ov::OutputVector(dequantized_inputs.begin(), dequantized_inputs.end()), axis); + + auto requantized = std::make_shared(concatenated, Y_scale); + auto Y_zero_point_float = std::make_shared(Y_zero_point, Y_scale.get_element_type()); + auto Y_float = std::make_shared(requantized, Y_zero_point_float); + auto Y = std::make_shared(Y_float, inputs[2].get_element_type()); + + return {Y}; +} + +ONNX_OP("QLinearConcat", OPSET_SINCE(1), com_microsoft::opset_1::qlinear_concat, MICROSOFT_DOMAIN); + +} // namespace opset_1 +} // namespace com_microsoft +} // namespace onnx +} // namespace frontend +} // namespace ov diff --git a/src/frontends/onnx/tests/models/com.microsoft/qlinear_concat_i8.prototxt b/src/frontends/onnx/tests/models/com.microsoft/qlinear_concat_i8.prototxt new file mode 100644 index 00000000000000..3205827d396821 --- /dev/null +++ b/src/frontends/onnx/tests/models/com.microsoft/qlinear_concat_i8.prototxt @@ -0,0 +1,141 @@ +ir_version: 3 +producer_name: "OpenVINO ONNX Frontend" +producer_version: "" +model_version: 0 +graph { + name: "test_qlinear_concat_i8" + + node { + input: "Y_scale" + input: "Y_zero_point" + input: "X1" + input: "X1_scale" + input: "X1_zero_point" + input: "X2" + input: "X2_scale" + input: "X2_zero_point" + output: "Y" + op_type: "QLinearConcat" + attribute { + name: "axis" + i: 1 + type: INT + } + domain: "com.microsoft" + } + + input { + name: "Y_scale" + type { + tensor_type { + elem_type: 1 + shape { + dim { dim_value: 1 } + } + } + } + } + + input { + name: "Y_zero_point" + type { + tensor_type { + elem_type: 3 + shape { + dim { dim_value: 1 } + } + } + } + } + + input { + name: "X1" + type { + tensor_type { + elem_type: 3 + shape { + dim { dim_value: 2 } + dim { dim_value: 2 } + } + } + } + } + + input { + name: "X1_scale" + type { + tensor_type { + elem_type: 1 + shape { + dim { dim_value: 1 } + } + } + } + } + + input { + name: "X1_zero_point" + type { + tensor_type { + elem_type: 3 + shape { + dim { dim_value: 1 } + } + } + } + } + + input { + name: "X2" + type { + tensor_type { + elem_type: 3 + shape { + dim { dim_value: 2 } + dim { dim_value: 2 } + } + } + } + } + + input { + name: "X2_scale" + type { + tensor_type { + elem_type: 1 + shape { + dim { dim_value: 1 } + } + } + } + } + + input { + name: "X2_zero_point" + type { + tensor_type { + elem_type: 3 + shape { + dim { dim_value: 1 } + } + } + } + } + + output { + name: "Y" + type { + tensor_type { + elem_type: 3 + shape { + dim { dim_value: 2 } + dim { dim_value: 4 } + } + } + } + } +} + +opset_import { + version: 1 +} diff --git a/src/frontends/onnx/tests/models/com.microsoft/qlinear_concat_u8.prototxt b/src/frontends/onnx/tests/models/com.microsoft/qlinear_concat_u8.prototxt new file mode 100644 index 00000000000000..66ebc50093aaa8 --- /dev/null +++ b/src/frontends/onnx/tests/models/com.microsoft/qlinear_concat_u8.prototxt @@ -0,0 +1,141 @@ +ir_version: 3 +producer_name: "OpenVINO ONNX Frontend" +producer_version: "" +model_version: 0 +graph { + name: "test_qlinear_concat_u8" + + node { + input: "Y_scale" + input: "Y_zero_point" + input: "X1" + input: "X1_scale" + input: "X1_zero_point" + input: "X2" + input: "X2_scale" + input: "X2_zero_point" + output: "Y" + op_type: "QLinearConcat" + attribute { + name: "axis" + i: 1 + type: INT + } + domain: "com.microsoft" + } + + input { + name: "Y_scale" + type { + tensor_type { + elem_type: 1 + shape { + dim { dim_value: 1 } + } + } + } + } + + input { + name: "Y_zero_point" + type { + tensor_type { + elem_type: 2 + shape { + dim { dim_value: 1 } + } + } + } + } + + input { + name: "X1" + type { + tensor_type { + elem_type: 2 + shape { + dim { dim_value: 4 } + dim { dim_value: 6 } + } + } + } + } + + input { + name: "X1_scale" + type { + tensor_type { + elem_type: 1 + shape { + dim { dim_value: 1 } + } + } + } + } + + input { + name: "X1_zero_point" + type { + tensor_type { + elem_type: 2 + shape { + dim { dim_value: 1 } + } + } + } + } + + input { + name: "X2" + type { + tensor_type { + elem_type: 2 + shape { + dim { dim_value: 4 } + dim { dim_value: 6 } + } + } + } + } + + input { + name: "X2_scale" + type { + tensor_type { + elem_type: 1 + shape { + dim { dim_value: 1 } + } + } + } + } + + input { + name: "X2_zero_point" + type { + tensor_type { + elem_type: 2 + shape { + dim { dim_value: 1 } + } + } + } + } + + output { + name: "Y" + type { + tensor_type { + elem_type: 2 + shape { + dim { dim_value: 4 } + dim { dim_value: 12 } + } + } + } + } +} + +opset_import { + version: 1 +} diff --git a/src/frontends/onnx/tests/onnx_import_com_microsoft.in.cpp b/src/frontends/onnx/tests/onnx_import_com_microsoft.in.cpp index 170476aae05dd3..c4bd4f95e72047 100644 --- a/src/frontends/onnx/tests/onnx_import_com_microsoft.in.cpp +++ b/src/frontends/onnx/tests/onnx_import_com_microsoft.in.cpp @@ -1740,3 +1740,71 @@ OPENVINO_TEST(${BACKEND_NAME}, onnx_com_microsoft_bias_add) { test_case.run(); } + +OPENVINO_TEST(${BACKEND_NAME}, onnx_com_microsoft_qlinear_concat_i8) { + const auto model = convert_model("com.microsoft/qlinear_concat_i8.onnx"); + auto test_case = ov::test::TestCase(model, s_device); + + const std::vector y_scale{0.1f}; + const std::vector y_zero_point{5}; + + const std::vector X1{1, 2, 3, 4}; + const std::vector X1_scale{0.1f}; + const std::vector X1_zero_point{0}; + + const std::vector X2{5, 6, 7, 8}; + const std::vector X2_scale{0.1f}; + const std::vector X2_zero_point{0}; + + const std::vector expected_output{6, 7, 10, 11, 8, 9, 12, 13}; + + test_case.add_input(Shape{1}, y_scale); + test_case.add_input(Shape{1}, y_zero_point); + + test_case.add_input(Shape{2, 2}, X1); + test_case.add_input(Shape{1}, X1_scale); + test_case.add_input(Shape{1}, X1_zero_point); + + test_case.add_input(Shape{2, 2}, X2); + test_case.add_input(Shape{1}, X2_scale); + test_case.add_input(Shape{1}, X2_zero_point); + + test_case.add_expected_output(Shape{2, 4}, expected_output); + test_case.run(); +} + +OPENVINO_TEST(${BACKEND_NAME}, onnx_com_microsoft_qlinear_concat_u8) { + const auto model = convert_model("com.microsoft/qlinear_concat_u8.onnx"); + auto test_case = ov::test::TestCase(model, s_device); + + const std::vector y_scale{0.1f}; + const std::vector y_zero_point{5}; + + const std::vector X1{10, 20, 30, 40, 50, 60, 15, 25, 35, 45, 55, 65, + 12, 22, 32, 42, 52, 62, 18, 28, 38, 48, 58, 68}; + const std::vector X1_scale{0.1f}; + const std::vector X1_zero_point{0}; + + const std::vector X2{70, 80, 90, 100, 110, 120, 75, 85, 95, 105, 115, 125, + 72, 82, 92, 102, 112, 122, 78, 88, 98, 108, 118, 128}; + const std::vector X2_scale{0.1f}; + const std::vector X2_zero_point{0}; + + const std::vector expected_output{ + 15, 25, 35, 45, 55, 65, 75, 85, 95, 105, 115, 125, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, + 17, 27, 37, 47, 57, 67, 77, 87, 97, 107, 117, 127, 23, 33, 43, 53, 63, 73, 83, 93, 103, 113, 123, 133}; + + test_case.add_input(Shape{1}, y_scale); + test_case.add_input(Shape{1}, y_zero_point); + + test_case.add_input(Shape{4, 6}, X1); + test_case.add_input(Shape{1}, X1_scale); + test_case.add_input(Shape{1}, X1_zero_point); + + test_case.add_input(Shape{4, 6}, X2); + test_case.add_input(Shape{1}, X2_scale); + test_case.add_input(Shape{1}, X2_zero_point); + + test_case.add_expected_output(Shape{4, 12}, expected_output); + test_case.run(); +}