Add ReduceMerge transformation (#11746)

Tickets: 60918
This commit is contained in:
Artur Kulikowski
2022-06-04 13:44:50 +02:00
committed by GitHub
parent 8029fd9675
commit 32580ca65b
4 changed files with 495 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include <memory>
#include <ngraph/pass/graph_rewrite.hpp>
#include <transformations_visibility.hpp>
#include <utility>
namespace ngraph {
namespace pass {
class TRANSFORMATIONS_API ReduceMerge;
} // namespace pass
} // namespace ngraph
/**
* @ingroup ie_transformation_common_api
* @brief ReduceMerge transformation matches following graph:
*
* +----------+ +----------+
* | A | | B |
* +----------+ +----------+
* | |
* --------- ---------
* | |
* v v
* +--------+ +--------+
* | Reduce | | C |
* +--------+ +--------+
* | |
* | -------
* | |
* v v
* +----------+
* | Reduce |
* +----------+
*
*
* and replaces with:
*
* +----------+ +----------+
* | B | | C |
* +----------+ +----------+
* | |
* ------- -------
* | |
* v v
* +----------+ +-------------------+
* | A | | Concat/Constant |
* +----------+ +-------------------+
* | |
* | --------
* | |
* v v
* +----------+
* | Reduce |
* +----------+
*
*/
class ngraph::pass::ReduceMerge : public ngraph::pass::MatcherPass {
public:
OPENVINO_RTTI("ReduceMerge", "0");
ReduceMerge();
};

View File

@@ -51,6 +51,7 @@
#include "transformations/common_optimizations/pad_fusion.hpp"
#include "transformations/common_optimizations/pull_transpose_through_fq.hpp"
#include "transformations/common_optimizations/random_uniform_fusion.hpp"
#include "transformations/common_optimizations/reduce_merge.hpp"
#include "transformations/common_optimizations/relu_fake_quantize_fusion.hpp"
#include "transformations/common_optimizations/remove_filtering_boxes_by_size.hpp"
#include "transformations/common_optimizations/skip_gather_before_transpose_and_reshape.hpp"
@@ -128,6 +129,7 @@ bool ngraph::pass::CommonOptimizations::run_on_model(const std::shared_ptr<ngrap
common_fusions->add_matcher<ngraph::pass::BatchToSpaceFusion>();
common_fusions->add_matcher<ngraph::pass::InterpolateSequenceFusion>();
common_fusions->add_matcher<ngraph::pass::SkipGatherBeforeTransposeAndReshape>();
common_fusions->add_matcher<ngraph::pass::ReduceMerge>();
common_fusions->set_name("ngraph::pass::CommonFusions");
manager.register_pass<ngraph::pass::ConcatReduceFusion>();

View File

@@ -0,0 +1,107 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "transformations/common_optimizations/reduce_merge.hpp"
#include <memory>
#include <ngraph/opsets/opset9.hpp>
#include <ngraph/pattern/op/or.hpp>
#include <ngraph/pattern/op/wrap_type.hpp>
#include <ngraph/rt_info.hpp>
#include <ngraph/validation_util.hpp>
#include "itt.hpp"
using namespace ngraph;
template <typename T>
std::shared_ptr<Node> create_pattern() {
auto input = pattern::any_input();
auto first_axis = pattern::any_input();
auto reduce = pattern::wrap_type<T>({input, first_axis});
auto second_axis = pattern::any_input();
return pattern::wrap_type<T>({reduce, second_axis});
}
template <typename T>
bool fuse_reduce_operations(const std::shared_ptr<Node>& node) {
const auto bottom_reduce = as_type_ptr<T>(node);
if (!bottom_reduce) {
return false;
}
const auto top_reduce = as_type_ptr<T>(bottom_reduce->get_input_node_shared_ptr(0));
if (!top_reduce) {
return false;
}
if (top_reduce->get_keep_dims() != bottom_reduce->get_keep_dims()) {
return false;
}
if (!top_reduce->get_keep_dims()) {
const auto first_axes = top_reduce->get_reduction_axes();
const auto second_axes = bottom_reduce->get_reduction_axes();
// check if each axis in the second set is smaller than every axis in the first set
if (!std::all_of(first_axes.begin(), first_axes.end(), [&second_axes](const size_t first_axis) {
return std::all_of(second_axes.begin(), second_axes.end(), [first_axis](const size_t second_axis) {
return first_axis > second_axis;
});
})) {
return false;
}
}
std::shared_ptr<Node> axes =
std::make_shared<opset9::Concat>(OutputVector{top_reduce->input_value(1), bottom_reduce->input_value(1)},
int64_t(0));
if (auto constant = ov::get_constant_from_source(axes)) {
axes = constant;
}
axes->set_friendly_name(bottom_reduce->get_friendly_name() + "/Axes");
auto new_reduce = bottom_reduce->copy_with_new_inputs({top_reduce->input_value(0), axes->get_default_output()});
new_reduce->set_friendly_name(bottom_reduce->get_friendly_name());
copy_runtime_info({top_reduce, bottom_reduce}, {axes, new_reduce});
ngraph::replace_node(bottom_reduce, new_reduce);
return true;
}
pass::ReduceMerge::ReduceMerge() {
MATCHER_SCOPE(ReduceMerge);
auto reducel1_pattern = create_pattern<opset9::ReduceL1>();
auto reducel2_pattern = create_pattern<opset9::ReduceL2>();
auto reduce_log_and_pattern = create_pattern<opset9::ReduceLogicalAnd>();
auto reduce_log_or_pattern = create_pattern<opset9::ReduceLogicalOr>();
auto reduce_max_pattern = create_pattern<opset9::ReduceMax>();
auto reduce_mean_pattern = create_pattern<opset9::ReduceMean>();
auto reduce_min_pattern = create_pattern<opset9::ReduceMin>();
auto reduce_prod_pattern = create_pattern<opset9::ReduceProd>();
auto reduce_sum_pattern = create_pattern<opset9::ReduceSum>();
auto pattern = std::make_shared<pattern::op::Or>(OutputVector{reducel1_pattern,
reducel2_pattern,
reduce_log_and_pattern,
reduce_log_or_pattern,
reduce_max_pattern,
reduce_mean_pattern,
reduce_min_pattern,
reduce_prod_pattern,
reduce_sum_pattern});
ngraph::matcher_pass_callback callback = [=](ngraph::pattern::Matcher& m) {
const auto node = m.get_match_root();
if (ov::is_type<op::util::ArithmeticReductionKeepDims>(node)) {
return fuse_reduce_operations<op::util::ArithmeticReductionKeepDims>(node);
} else if (ov::is_type<op::util::LogicalReductionKeepDims>(node)) {
return fuse_reduce_operations<op::util::LogicalReductionKeepDims>(node);
} else {
return false;
}
};
auto m = std::make_shared<pattern::Matcher>(pattern, matcher_name);
register_matcher(m, callback);
}

View File

@@ -0,0 +1,318 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <gtest/gtest.h>
#include <ngraph/function.hpp>
#include <ngraph/opsets/opset9.hpp>
#include <ngraph/pass/manager.hpp>
#include <transformations/common_optimizations/reduce_merge.hpp>
#include "common_test_utils/ngraph_test_utils.hpp"
using namespace ngraph;
TEST_F(TransformationTestsF, ReduceMergeReduceL1) {
{
auto data = std::make_shared<op::Parameter>(element::f32, Shape{3, 2});
auto reduce1_axes = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceL1>(data, reduce1_axes, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceL1>(reduce1, reduce2_axis, true)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::f32, Shape{3, 2});
auto axes = op::Constant::create(element::i64, Shape{2}, {0, 0});
auto reduce = std::make_shared<opset9::ReduceL1>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeReduceL2) {
{
auto data = std::make_shared<op::Parameter>(element::f32, Shape{3, 2});
auto reduce1_axes = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceL2>(data, reduce1_axes, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceL2>(reduce1, reduce2_axis, true)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::f32, Shape{3, 2});
auto axes = op::Constant::create(element::i64, Shape{2}, {0, 0});
auto reduce = std::make_shared<opset9::ReduceL2>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeReduceLogicalAnd) {
{
auto data = std::make_shared<op::Parameter>(element::boolean, Shape{3, 2});
auto reduce1_axis = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceLogicalAnd>(data, reduce1_axis, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function = std::make_shared<Function>(
OutputVector{std::make_shared<opset9::ReduceLogicalAnd>(reduce1, reduce2_axis, true)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::boolean, Shape{3, 2});
auto axes = op::Constant::create(element::i64, Shape{2}, {0, 0});
auto reduce = std::make_shared<opset9::ReduceLogicalAnd>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeReduceLogicalOr) {
{
auto data = std::make_shared<op::Parameter>(element::boolean, Shape{1});
auto reduce1_axes = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceLogicalOr>(data, reduce1_axes, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function = std::make_shared<Function>(
OutputVector{std::make_shared<opset9::ReduceLogicalOr>(reduce1, reduce2_axis, true)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::boolean, Shape{1});
auto axes = op::Constant::create(element::i64, Shape{2}, {0, 0});
auto reduce = std::make_shared<opset9::ReduceLogicalOr>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeReduceMax) {
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto reduce1_axes = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceMax>(data, reduce1_axes, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceMax>(reduce1, reduce2_axis, true)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto axes = op::Constant::create(element::i64, Shape{2}, {0, 0});
auto reduce = std::make_shared<opset9::ReduceMax>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeReduceMean) {
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto reduce1_axes = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceMean>(data, reduce1_axes, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceMean>(reduce1, reduce2_axis, true)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto axes = op::Constant::create(element::i64, Shape{2}, {0, 0});
auto reduce = std::make_shared<opset9::ReduceMean>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeReduceMin) {
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto reduce1_axes = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceMin>(data, reduce1_axes, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceMin>(reduce1, reduce2_axis, true)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto axes = op::Constant::create(element::i64, Shape{2}, {0, 0});
auto reduce = std::make_shared<opset9::ReduceMin>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeReduceProd) {
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto reduce1_axes = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceProd>(data, reduce1_axes, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceProd>(reduce1, reduce2_axis, true)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto axes = op::Constant::create(element::i64, Shape{2}, {0, 0});
auto reduce = std::make_shared<opset9::ReduceProd>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeReduceSum) {
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto reduce1_axes = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceSum>(data, reduce1_axes, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceSum>(reduce1, reduce2_axis, true)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto axes = op::Constant::create(element::i64, Shape{2}, {0, 0});
auto reduce = std::make_shared<opset9::ReduceSum>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeNoReduceDiffKeepDims) {
{
auto A = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto reduce1_axes = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceL1>(A, reduce1_axes, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceL1>(reduce1, reduce2_axis, false)},
ParameterVector{A});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2});
auto reduce1_axes = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceL1>(data, reduce1_axes, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function_ref =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceL1>(reduce1, reduce2_axis, false)},
ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeNotPassInvalidAxes) {
{
auto data = std::make_shared<op::Parameter>(element::f32, Shape{3, 2});
auto reduce1_axis = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceL2>(data, reduce1_axis, false);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceL2>(reduce1, reduce2_axis, false)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::f32, Shape{3, 2});
auto reduce1_axis = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceL2>(data, reduce1_axis, false);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function_ref =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceL2>(reduce1, reduce2_axis, false)},
ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeDynamicShapes) {
{
auto data =
std::make_shared<op::Parameter>(element::i64, PartialShape{Dimension::dynamic(), Dimension::dynamic()});
auto reduce1_axis = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceL2>(data, reduce1_axis, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {0});
function =
std::make_shared<Function>(OutputVector{std::make_shared<opset9::ReduceL2>(reduce1, reduce2_axis, true)},
ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data =
std::make_shared<op::Parameter>(element::i64, PartialShape{Dimension::dynamic(), Dimension::dynamic()});
auto axes = op::Constant::create(element::i64, Shape{2}, {0, 0});
auto reduce = std::make_shared<opset9::ReduceL2>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMerge3ReducesL1) {
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2, 4});
auto reduce1_axis = op::Constant::create(element::i64, Shape{1}, {0});
auto reduce1 = std::make_shared<opset9::ReduceL1>(data, reduce1_axis, true);
auto reduce2_axis = op::Constant::create(element::i64, Shape{1}, {2});
auto reduce2 = std::make_shared<opset9::ReduceL1>(reduce1, reduce2_axis, true);
auto reduce3_axis = op::Constant::create(element::i64, Shape{1}, {1});
auto reduce3 = std::make_shared<opset9::ReduceL1>(reduce2, reduce3_axis, true);
function = std::make_shared<Function>(OutputVector{reduce3}, ParameterVector{data});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2, 4});
auto axes = op::Constant::create(element::i64, Shape{3}, {0, 2, 1});
auto reduce = std::make_shared<opset9::ReduceL1>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, ReduceMergeConcatAxes) {
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2, 4});
auto axis1 = std::make_shared<op::Parameter>(element::i64, Shape{1});
auto reduce1 = std::make_shared<opset9::ReduceL1>(data, axis1, true);
auto axis2 = std::make_shared<op::Parameter>(element::i64, Shape{1});
auto reduce2 = std::make_shared<opset9::ReduceL1>(reduce1, axis2, true);
function = std::make_shared<Function>(OutputVector{reduce2}, ParameterVector{data, axis1, axis2});
manager.register_pass<pass::ReduceMerge>();
}
{
auto data = std::make_shared<op::Parameter>(element::i64, Shape{3, 2, 4});
auto axis1 = std::make_shared<op::Parameter>(element::i64, Shape{1});
auto axis2 = std::make_shared<op::Parameter>(element::i64, Shape{1});
auto axes = std::make_shared<opset9::Concat>(OutputVector{axis1, axis2}, 0);
auto reduce = std::make_shared<opset9::ReduceL1>(data, axes, true);
function_ref = std::make_shared<Function>(OutputVector{reduce}, ParameterVector{data, axis1, axis2});
}
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}