From 2ef42d4b93c7a42cfac698608f16e131fa59f5aa Mon Sep 17 00:00:00 2001 From: Chen Xu Date: Sun, 29 Dec 2024 20:09:55 +0800 Subject: [PATCH] [CPU] [TRANSFORMATIONS] FakeConvert decomposition transformation (#28118) ### Details: - *Implement FakeConvert decomposition transformation.* ### Tickets: - *[CVS-156963](https://jira.devtools.intel.com/browse/CVS-156963)* ### Prerequisites: - *https://github.com/openvinotoolkit/openvino/pull/27949* --- .../fake_convert_decomposition.hpp | 32 ++++ .../fake_convert_decomposition.cpp | 76 +++++++++ .../fake_convert_decomposition_test.cpp | 149 ++++++++++++++++++ .../transformation_pipeline.cpp | 2 + .../single_layer_tests/fake_convert.cpp | 59 +++++++ .../include/single_op_tests/fake_convert.hpp | 16 ++ .../single_op/fake_convert.hpp | 28 ++++ .../src/single_op/fake_convert.cpp | 64 ++++++++ 8 files changed, 426 insertions(+) create mode 100644 src/common/transformations/include/transformations/op_conversions/fake_convert_decomposition.hpp create mode 100644 src/common/transformations/src/transformations/op_conversions/fake_convert_decomposition.cpp create mode 100644 src/common/transformations/tests/op_conversions/fake_convert_decomposition_test.cpp create mode 100644 src/plugins/intel_cpu/tests/functional/shared_tests_instances/single_layer_tests/fake_convert.cpp create mode 100644 src/tests/functional/plugin/shared/include/single_op_tests/fake_convert.hpp create mode 100644 src/tests/functional/shared_test_classes/include/shared_test_classes/single_op/fake_convert.hpp create mode 100644 src/tests/functional/shared_test_classes/src/single_op/fake_convert.cpp diff --git a/src/common/transformations/include/transformations/op_conversions/fake_convert_decomposition.hpp b/src/common/transformations/include/transformations/op_conversions/fake_convert_decomposition.hpp new file mode 100644 index 00000000000000..e149152b2bcf6d --- /dev/null +++ b/src/common/transformations/include/transformations/op_conversions/fake_convert_decomposition.hpp @@ -0,0 +1,32 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "openvino/pass/matcher_pass.hpp" +#include "transformations_visibility.hpp" + +namespace ov { +namespace pass { + +class TRANSFORMATIONS_API FakeConvertDecomposition; + +} // namespace pass +} // namespace ov + +/** + * @ingroup ov_transformation_common_api + * @brief FakeConvertDecomposition transformation decomposes FakeConvert layer. + * f8: f8e4m3, f8e5m2 + * downconvert: f32->f8, f16->f8, bf16->f8 + * upconvert: f8->f32, f8->f16, f8->bf16 + * output = (upconvert(downconvert(input * scale - shift)) + shift) / scale + * + */ + +class ov::pass::FakeConvertDecomposition : public ov::pass::MatcherPass { +public: + OPENVINO_MATCHER_PASS_RTTI("FakeConvertDecomposition"); + FakeConvertDecomposition(); +}; diff --git a/src/common/transformations/src/transformations/op_conversions/fake_convert_decomposition.cpp b/src/common/transformations/src/transformations/op_conversions/fake_convert_decomposition.cpp new file mode 100644 index 00000000000000..7f0a44df6a151d --- /dev/null +++ b/src/common/transformations/src/transformations/op_conversions/fake_convert_decomposition.cpp @@ -0,0 +1,76 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "transformations/op_conversions/fake_convert_decomposition.hpp" + +#include "itt.hpp" +#include "openvino/core/rt_info.hpp" +#include "openvino/op/add.hpp" +#include "openvino/op/constant.hpp" +#include "openvino/op/convert.hpp" +#include "openvino/op/divide.hpp" +#include "openvino/op/fake_convert.hpp" +#include "openvino/op/multiply.hpp" +#include "openvino/op/subtract.hpp" +#include "openvino/pass/pattern/op/wrap_type.hpp" + +ov::pass::FakeConvertDecomposition::FakeConvertDecomposition() { + MATCHER_SCOPE(FakeConvertDecomposition); + auto data = pattern::any_input(); + + auto fake_convert = ov::pass::pattern::wrap_type(); + + matcher_pass_callback callback = [OV_CAPTURE_CPY_AND_THIS](ov::pass::pattern::Matcher& m) { + auto& pattern_to_output = m.get_pattern_value_map(); + const auto fake_convert_node = + ov::as_type_ptr(pattern_to_output.at(fake_convert).get_node_shared_ptr()); + + if (fake_convert_node == nullptr || transformation_callback(fake_convert_node)) { + return false; + } + + Output data{fake_convert_node->input_value(0)}; + const Output input_scale{fake_convert_node->input_value(1)}; + auto input_type = data.get_element_type(); + + ov::pass::NodeRegistry decomp_ops; + if (input_type != input_scale.get_element_type()) { + input_type = input_scale.get_element_type(); + data = std::make_shared(data, input_type); + data = decomp_ops.add(data.get_node_shared_ptr()); + } + + std::shared_ptr result; + const auto scale = decomp_ops.make(data, input_scale); + if (fake_convert_node->get_input_size() == 2) { + const auto downconvert = + decomp_ops.make(scale, fake_convert_node->get_destination_element_type()); + const auto upconvert = decomp_ops.make(downconvert, input_type); + + result = decomp_ops.make(upconvert, input_scale); + } else { + const Output input_shift{fake_convert_node->input_value(2)}; + const auto shift = decomp_ops.make(scale, input_shift); + + const auto downconvert = + decomp_ops.make(shift, fake_convert_node->get_destination_element_type()); + const auto upconvert = decomp_ops.make(downconvert, input_type); + + const auto deshift = decomp_ops.make(upconvert, input_shift); + result = decomp_ops.make(deshift, input_scale); + } + + if (result->get_output_element_type(0) != fake_convert_node->get_output_element_type(0)) { + result = decomp_ops.make(result, fake_convert_node->get_output_element_type(0)); + } + + result->set_friendly_name(m.get_match_root()->get_friendly_name()); + ov::copy_runtime_info(fake_convert_node, decomp_ops.get()); + ov::replace_node(m.get_match_root(), result); + return true; + }; + + auto m = std::make_shared(fake_convert, matcher_name); + register_matcher(m, callback); +} diff --git a/src/common/transformations/tests/op_conversions/fake_convert_decomposition_test.cpp b/src/common/transformations/tests/op_conversions/fake_convert_decomposition_test.cpp new file mode 100644 index 00000000000000..33b167ace11e24 --- /dev/null +++ b/src/common/transformations/tests/op_conversions/fake_convert_decomposition_test.cpp @@ -0,0 +1,149 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "transformations/op_conversions/fake_convert_decomposition.hpp" + +#include + +#include "common_test_utils/common_utils.hpp" +#include "common_test_utils/ov_test_utils.hpp" +#include "openvino/opsets/opset1.hpp" +#include "openvino/opsets/opset13.hpp" + +using namespace ov; + +using FakeConvertDecompositionParams = std::tuple; // default shift + +class FakeConvertDecompositionTest : public ov::test::TestsCommon, + public ::testing::WithParamInterface { +public: + static std::string getTestCaseName(::testing::TestParamInfo obj) { + FakeConvertDecompositionParams params = obj.param; + + Shape data_shape, scale_shape, shift_shape; + element::Type_t data_prec, dst_prec; + bool default_shift; + std::tie(data_shape, scale_shape, shift_shape, data_prec, dst_prec, default_shift) = params; + + std::ostringstream result; + result << "dataShape=" << ov::test::utils::vec2str(data_shape) << "_"; + result << "scaleShape=" << ov::test::utils::vec2str(scale_shape) << "_"; + result << "shiftShape=" << ov::test::utils::vec2str(shift_shape) << "_"; + result << "dataPrecision=" << element::Type(data_prec) << "_"; + result << "destinationPrecision=" << element::Type(dst_prec) << "_"; + if (default_shift) + result << "defaultShift=true"; + else + result << "defaultShift=false"; + return result.str(); + } +}; + +TEST_P(FakeConvertDecompositionTest, CompareFunctions) { + FakeConvertDecompositionParams params = this->GetParam(); + + Shape data_shape, scale_shape, shift_shape; + element::Type_t data_prec, dst_prec; + bool default_shift; + std::tie(data_shape, scale_shape, shift_shape, data_prec, dst_prec, default_shift) = params; + + std::shared_ptr model(nullptr); + { + const auto data = std::make_shared(data_prec, PartialShape(data_shape)); + const auto scale = std::make_shared(data_prec, scale_shape); + const auto shift = std::make_shared(data_prec, shift_shape); + + const auto fake_convert = default_shift ? std::make_shared(data, scale, dst_prec) + : std::make_shared(data, scale, shift, dst_prec); + model = std::make_shared(NodeVector{fake_convert}, ParameterVector{data}); + + pass::Manager manager; + manager.register_pass(); + manager.register_pass(); + manager.run_passes(model); + + OV_ASSERT_NO_THROW(check_rt_info(model)); + } + + std::shared_ptr model_ref(nullptr); + { + const auto input_data = std::make_shared(data_prec, PartialShape(data_shape)); + const auto input_scale = std::make_shared(data_prec, scale_shape); + const auto input_shift = std::make_shared(data_prec, shift_shape); + ParameterVector params; + params.push_back(input_data); + std::shared_ptr data = input_data; + + std::shared_ptr result; + const auto scale = std::make_shared(data, input_scale); + if (default_shift) { + const auto downconvert = std::make_shared(scale, dst_prec); + const auto upconvert = std::make_shared(downconvert, data_prec); + + result = std::make_shared(upconvert, input_scale); + } else { + const auto shift = std::make_shared(scale, input_shift); + + const auto downconvert = std::make_shared(shift, dst_prec); + const auto upconvert = std::make_shared(downconvert, data_prec); + + const auto deshift = std::make_shared(upconvert, input_shift); + result = std::make_shared(deshift, input_scale); + } + + model_ref = std::make_shared(NodeVector{result}, params); + } + + const auto res = compare_functions(model, model_ref); + ASSERT_TRUE(res.first) << res.second; +} + +const std::vector data_precisions = {element::Type_t::f32, + element::Type_t::f16, + element::Type_t::bf16}; + +const std::vector destination_precisions = {element::Type_t::f8e4m3, element::Type_t::f8e5m2}; + +const std::vector default_shift = {true, false}; + +const auto simple_fake_convert_params = ::testing::Combine(::testing::Values(Shape{2, 3, 4, 5}), + ::testing::Values(Shape{1}), + ::testing::Values(Shape{1}), + ::testing::ValuesIn(data_precisions), + ::testing::ValuesIn(destination_precisions), + ::testing::ValuesIn(default_shift)); + +const auto broadcast_fake_convert_params = ::testing::Combine(::testing::Values(Shape{2, 3, 4, 5}), + ::testing::Values(Shape{2, 3, 1, 1}), + ::testing::Values(Shape{2, 3, 1, 1}), + ::testing::ValuesIn(data_precisions), + ::testing::ValuesIn(destination_precisions), + ::testing::ValuesIn(default_shift)); + +const auto elementwise_fake_convert_params = ::testing::Combine(::testing::Values(Shape{2, 3, 4, 5}), + ::testing::Values(Shape{2, 3, 4, 5}), + ::testing::Values(Shape{2, 3, 4, 5}), + ::testing::ValuesIn(data_precisions), + ::testing::ValuesIn(destination_precisions), + ::testing::ValuesIn(default_shift)); + +INSTANTIATE_TEST_SUITE_P(SimpleFakeConvert_Decomposition, + FakeConvertDecompositionTest, + simple_fake_convert_params, + FakeConvertDecompositionTest::getTestCaseName); + +INSTANTIATE_TEST_SUITE_P(BroadcastFakeConvert_Decomposition, + FakeConvertDecompositionTest, + broadcast_fake_convert_params, + FakeConvertDecompositionTest::getTestCaseName); + +INSTANTIATE_TEST_SUITE_P(ElementwiseFakeConvert_Decomposition, + FakeConvertDecompositionTest, + elementwise_fake_convert_params, + FakeConvertDecompositionTest::getTestCaseName); diff --git a/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp b/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp index fb9e0925bc89e2..4d7df9a335e98a 100644 --- a/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp +++ b/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp @@ -80,6 +80,7 @@ #include "transformations/op_conversions/detection_output_downgrade.hpp" #include "transformations/op_conversions/detection_output_upgrade.hpp" #include "transformations/op_conversions/eye_decomposition.hpp" +#include "transformations/op_conversions/fake_convert_decomposition.hpp" #include "transformations/op_conversions/fq_decomposition.hpp" #include "transformations/op_conversions/gelu7_downgrade.hpp" #include "transformations/op_conversions/group_normalization_decomposition.hpp" @@ -1293,6 +1294,7 @@ void Transformations::PostSnippets(void) { return node::FakeQuantize::isSupportedOperation(node, errMsg); }, ov::pass::FakeQuantizeDecomposition); + CPU_REGISTER_PASS_COMMON(postSnippetsManager, ov::pass::FakeConvertDecomposition); CPU_REGISTER_PASS_COMMON(postSnippetsManager, ov::pass::ConstantFolding); postSnippetsManager.run_passes(model); } diff --git a/src/plugins/intel_cpu/tests/functional/shared_tests_instances/single_layer_tests/fake_convert.cpp b/src/plugins/intel_cpu/tests/functional/shared_tests_instances/single_layer_tests/fake_convert.cpp new file mode 100644 index 00000000000000..a2f17ea72cbb3e --- /dev/null +++ b/src/plugins/intel_cpu/tests/functional/shared_tests_instances/single_layer_tests/fake_convert.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "single_op_tests/fake_convert.hpp" + +namespace { +using ov::test::FakeConvertLayerTest; + +const std::vector> shapes = {{{2, 3, 4, 5}}}; + +const std::vector data_precisions = {ov::element::f32, ov::element::f16, ov::element::bf16}; + +const std::vector destination_precisions = {ov::element::f8e4m3, ov::element::f8e5m2}; + +const std::vector default_shift = {true, false}; + +const auto simple_fake_convert_params = + ::testing::Combine(::testing::ValuesIn(ov::test::static_shapes_to_test_representation(shapes)), + ::testing::Values(ov::Shape{1}), + ::testing::Values(ov::Shape{1}), + ::testing::ValuesIn(data_precisions), + ::testing::ValuesIn(destination_precisions), + ::testing::ValuesIn(default_shift), + ::testing::Values(ov::test::utils::DEVICE_CPU)); + +const auto broadcast_fake_convert_params = + ::testing::Combine(::testing::ValuesIn(ov::test::static_shapes_to_test_representation(shapes)), + ::testing::Values(ov::Shape{2, 3, 1, 1}), + ::testing::Values(ov::Shape{2, 3, 1, 1}), + ::testing::ValuesIn(data_precisions), + ::testing::ValuesIn(destination_precisions), + ::testing::ValuesIn(default_shift), + ::testing::Values(ov::test::utils::DEVICE_CPU)); + +const auto elementwise_fake_convert_params = + ::testing::Combine(::testing::ValuesIn(ov::test::static_shapes_to_test_representation(shapes)), + ::testing::Values(ov::Shape{2, 3, 4, 5}), + ::testing::Values(ov::Shape{2, 3, 4, 5}), + ::testing::ValuesIn(data_precisions), + ::testing::ValuesIn(destination_precisions), + ::testing::ValuesIn(default_shift), + ::testing::Values(ov::test::utils::DEVICE_CPU)); + +INSTANTIATE_TEST_SUITE_P(smoke_FakeConvert_simple, + FakeConvertLayerTest, + simple_fake_convert_params, + FakeConvertLayerTest::getTestCaseName); + +INSTANTIATE_TEST_SUITE_P(smoke_FakeConvert_broadcast, + FakeConvertLayerTest, + broadcast_fake_convert_params, + FakeConvertLayerTest::getTestCaseName); + +INSTANTIATE_TEST_SUITE_P(smoke_FakeConvert_elementwise, + FakeConvertLayerTest, + elementwise_fake_convert_params, + FakeConvertLayerTest::getTestCaseName); +} // namespace diff --git a/src/tests/functional/plugin/shared/include/single_op_tests/fake_convert.hpp b/src/tests/functional/plugin/shared/include/single_op_tests/fake_convert.hpp new file mode 100644 index 00000000000000..d22809e332b0a3 --- /dev/null +++ b/src/tests/functional/plugin/shared/include/single_op_tests/fake_convert.hpp @@ -0,0 +1,16 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "shared_test_classes/single_op/fake_convert.hpp" + +namespace ov { +namespace test { + +TEST_P(FakeConvertLayerTest, Inference) { + run(); +} +} // namespace test +} // namespace ov diff --git a/src/tests/functional/shared_test_classes/include/shared_test_classes/single_op/fake_convert.hpp b/src/tests/functional/shared_test_classes/include/shared_test_classes/single_op/fake_convert.hpp new file mode 100644 index 00000000000000..ce6ad97aba1b5d --- /dev/null +++ b/src/tests/functional/shared_test_classes/include/shared_test_classes/single_op/fake_convert.hpp @@ -0,0 +1,28 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "shared_test_classes/base/ov_subgraph.hpp" + +namespace ov { +namespace test { +using FakeConvertParams = std::tuple, // Data shape + Shape, // Scale shape + Shape, // Shift shape + ov::element::Type, // Input precision + ov::element::Type, // Ddestination precision + bool, // Default shift + std::string>; // Device name + +class FakeConvertLayerTest : public testing::WithParamInterface, + virtual public ov::test::SubgraphBaseTest { +public: + static std::string getTestCaseName(const testing::TestParamInfo& obj); + +protected: + void SetUp() override; +}; +} // namespace test +} // namespace ov diff --git a/src/tests/functional/shared_test_classes/src/single_op/fake_convert.cpp b/src/tests/functional/shared_test_classes/src/single_op/fake_convert.cpp new file mode 100644 index 00000000000000..d207a8dabfb883 --- /dev/null +++ b/src/tests/functional/shared_test_classes/src/single_op/fake_convert.cpp @@ -0,0 +1,64 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "shared_test_classes/single_op/fake_convert.hpp" + +#include "openvino/opsets/opset1.hpp" +#include "openvino/opsets/opset13.hpp" + +namespace ov { +namespace test { +std::string FakeConvertLayerTest::getTestCaseName(const testing::TestParamInfo& obj) { + FakeConvertParams params = obj.param; + + std::vector data_shapes; + Shape scale_shape, shift_shape; + element::Type_t data_prec, dst_prec; + bool default_shift; + std::string target_device; + std::tie(data_shapes, scale_shape, shift_shape, data_prec, dst_prec, default_shift, target_device) = params; + + std::ostringstream result; + result << "IS=("; + for (const auto& shape : data_shapes) { + result << ov::test::utils::partialShape2str({shape.first}) << "_"; + } + result << ")_TS=("; + for (const auto& shape : data_shapes) { + for (const auto& item : shape.second) { + result << ov::test::utils::vec2str(item) << "_"; + } + } + result << ")_scaleShape=" << ov::test::utils::vec2str(scale_shape) << "_"; + result << "shiftShape=" << ov::test::utils::vec2str(shift_shape) << "_"; + result << "dataPrecision=" << element::Type(data_prec) << "_"; + result << "destinationPrecision=" << element::Type(dst_prec) << "_"; + if (default_shift) + result << "defaultShift=true"; + else + result << "defaultShift=false"; + return result.str(); +} + +void FakeConvertLayerTest::SetUp() { + FakeConvertParams params = this->GetParam(); + + std::vector data_shapes; + Shape scale_shape, shift_shape; + element::Type_t data_prec, dst_prec; + bool default_shift; + std::tie(data_shapes, scale_shape, shift_shape, data_prec, dst_prec, default_shift, targetDevice) = params; + + init_input_shapes(data_shapes); + + const auto data = std::make_shared(data_prec, inputDynamicShapes.front()); + const auto scale = std::make_shared(data_prec, scale_shape); + const auto shift = std::make_shared(data_prec, shift_shape); + + const auto fake_convert = default_shift ? std::make_shared(data, scale, dst_prec) + : std::make_shared(data, scale, shift, dst_prec); + function = std::make_shared(NodeVector{fake_convert}, ParameterVector{data}); +} +} // namespace test +} // namespace ov