Add GELU fusion (#5067)

* GeLU fusion

* Fix matcher and sqrt(2) value

* Replace with math constant

* Correct comments in header file

* Add tests for transformations

* Add define of math constants in tests

* refactor code and add checks for nullptr

* Extend tests with failing examples

* change tests
This commit is contained in:
Jan Iwaszkiewicz authored and GitHub committed 2021-04-20 15:34:55 +03:00
1 parent f3d1aa6490
commit 7a634490dc
4 files changed
+511

No files matched your search

@@ -0,0 +1,68 @@
// Copyright (C) 2018-2021 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 GeluFusion;
class TRANSFORMATIONS_API GeluFusionWithErfOne;
class TRANSFORMATIONS_API GeluFusionWithErfTwo;
class TRANSFORMATIONS_API GeluFusionWithErfThree;
} // namespace pass
} // namespace ngraph
/**
* @ingroup ie_transformation_common_api
* @brief GeluFusion transformation replaces a sub-graph
* (0.5 * x) * (1 + erf(x / sqrt(2))) with a Gelu op.
*/
class ngraph::pass::GeluFusionWithErfOne : public ngraph::pass::MatcherPass {
public:
NGRAPH_RTTI_DECLARATION;
GeluFusionWithErfOne();
};
/**
* @ingroup ie_transformation_common_api
* @brief GeluFusion transformation replaces a sub-graph
* 0.5 * (x * (1 + erf(x / sqrt(2))) with a Gelu op.
*/
class ngraph::pass::GeluFusionWithErfTwo : public ngraph::pass::MatcherPass {
public:
NGRAPH_RTTI_DECLARATION;
GeluFusionWithErfTwo();
};
/**
* @ingroup ie_transformation_common_api
* @brief GeluFusion transformation replaces a sub-graph
* x * (0.5 * (1 + erf(x / sqrt(2))) with a Gelu op.
*/
class ngraph::pass::GeluFusionWithErfThree : public ngraph::pass::MatcherPass {
public:
NGRAPH_RTTI_DECLARATION;
GeluFusionWithErfThree();
};
/**
* @ingroup ie_transformation_common_api
* @brief GeluFusion transformation replaces various sub-graphs with a Gelu op.
*/
class ngraph::pass::GeluFusion : public ngraph::pass::GraphRewrite {
public:
NGRAPH_RTTI_DECLARATION;
GeluFusion() {
add_matcher<ngraph::pass::GeluFusionWithErfOne>();
add_matcher<ngraph::pass::GeluFusionWithErfTwo>();
add_matcher<ngraph::pass::GeluFusionWithErfThree>();
}
};
@@ -13,6 +13,7 @@
#include "transformations/common_optimizations/conv_mul_fusion.hpp"
#include "transformations/common_optimizations/fq_mul_fusion.hpp"
#include "transformations/common_optimizations/fq_reshape_fusion.hpp"
#include "transformations/common_optimizations/gelu_fusion.hpp"
#include "transformations/common_optimizations/depth_to_space_fusion.hpp"
#include "transformations/common_optimizations/optimize_strided_slice.hpp"
#include "transformations/common_optimizations/softplus_fusion.hpp"
@@ -113,6 +114,7 @@ bool ngraph::pass::CommonOptimizations::run_on_function(std::shared_ptr<ngraph::
common_fusions->add_matcher<ngraph::pass::SpaceToBatchFusion>();
common_fusions->add_matcher<ngraph::pass::BatchToSpaceFusion>();
common_fusions->add_matcher<ngraph::pass::DilatedConvolutionConverter>();
common_fusions->add_matcher<ngraph::pass::GeluFusion>();
common_fusions->set_name("ngraph::pass::CommonFusions");
manager.register_pass<ngraph::pass::ConvertPadToGroupConvolution, false>();
@@ -0,0 +1,229 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#define _USE_MATH_DEFINES
#include "transformations/common_optimizations/gelu_fusion.hpp"
#include <math.h>
#include <memory>
#include <ngraph/opsets/opset7.hpp>
#include <ngraph/pattern/op/wrap_type.hpp>
#include <ngraph/rt_info.hpp>
#include "itt.hpp"
#include "transformations/utils/utils.hpp"
NGRAPH_RTTI_DEFINITION(ngraph::pass::GeluFusion, "GeluFusion", 0);
NGRAPH_RTTI_DEFINITION(ngraph::pass::GeluFusionWithErfOne,
"GeluFusionWithErfOne", 0);
ngraph::pass::GeluFusionWithErfOne::GeluFusionWithErfOne() {
MATCHER_SCOPE(GeluFusionWithErfOne);
// Replaces a sub-graph with a Gelu op
// Shared by every pattern: (1 + erf(x / sqrt(2)))
auto input = ngraph::pattern::any_input();
auto div_constant = ngraph::pattern::wrap_type<ngraph::opset7::Constant>();
auto div = ngraph::pattern::wrap_type<ngraph::opset7::Divide>(
{input, div_constant});
auto erf = ngraph::pattern::wrap_type<ngraph::opset7::Erf>({div});
auto add_constant = ngraph::pattern::wrap_type<ngraph::opset7::Constant>();
auto add =
ngraph::pattern::wrap_type<ngraph::opset7::Add>({add_constant, erf});
auto mul_constant = ngraph::pattern::wrap_type<ngraph::opset7::Constant>();
// (0.5 * x) * (1 + erf(x / sqrt(2))
auto mul_first = ngraph::pattern::wrap_type<ngraph::opset7::Multiply>(
{input, mul_constant});
auto mul =
ngraph::pattern::wrap_type<ngraph::opset7::Multiply>({mul_first, add});
ngraph::matcher_pass_callback callback = [=](ngraph::pattern::Matcher &m) {
auto &pattern_to_output = m.get_pattern_value_map();
auto x_output = pattern_to_output.at(input);
auto div_const_value =
std::dynamic_pointer_cast<ngraph::opset7::Constant>(
pattern_to_output.at(div_constant).get_node_shared_ptr());
auto add_const_value =
std::dynamic_pointer_cast<ngraph::opset7::Constant>(
pattern_to_output.at(add_constant).get_node_shared_ptr());
auto mul_const_value =
std::dynamic_pointer_cast<ngraph::opset7::Constant>(
pattern_to_output.at(mul_constant).get_node_shared_ptr());
if (!div_const_value || !add_const_value || !mul_const_value) {
return false;
}
bool valid_constant_values =
op::util::has_constant_value<float>(div_const_value, M_SQRT2) &&
op::util::has_constant_value<float>(add_const_value, 1.0f) &&
op::util::has_constant_value<float>(mul_const_value, 0.5f);
if (!valid_constant_values) {
return false;
}
auto gelu = std::make_shared<ngraph::opset7::Gelu>(x_output);
gelu->set_friendly_name(m.get_match_root()->get_friendly_name());
ngraph::copy_runtime_info(
{
pattern_to_output.at(div).get_node_shared_ptr(),
pattern_to_output.at(erf).get_node_shared_ptr(),
pattern_to_output.at(add).get_node_shared_ptr(),
pattern_to_output.at(mul_first).get_node_shared_ptr(),
pattern_to_output.at(mul).get_node_shared_ptr(),
},
gelu);
ngraph::replace_node(m.get_match_root(), gelu);
return true;
};
auto m = std::make_shared<ngraph::pattern::Matcher>(mul, matcher_name);
register_matcher(m, callback);
}
NGRAPH_RTTI_DEFINITION(ngraph::pass::GeluFusionWithErfTwo,
"GeluFusionWithErfTwo", 0);
ngraph::pass::GeluFusionWithErfTwo::GeluFusionWithErfTwo() {
MATCHER_SCOPE(GeluFusionWithErfTwo);
// Replaces a sub-graph with a Gelu op
// Shared by every pattern: (1 + erf(x / sqrt(2)))
auto input = ngraph::pattern::any_input();
auto div_constant = ngraph::pattern::wrap_type<ngraph::opset7::Constant>();
auto div = ngraph::pattern::wrap_type<ngraph::opset7::Divide>(
{input, div_constant});
auto erf = ngraph::pattern::wrap_type<ngraph::opset7::Erf>({div});
auto add_constant = ngraph::pattern::wrap_type<ngraph::opset7::Constant>();
auto add =
ngraph::pattern::wrap_type<ngraph::opset7::Add>({add_constant, erf});
auto mul_constant = ngraph::pattern::wrap_type<ngraph::opset7::Constant>();
// 0.5 * (x * (1 + erf(x / sqrt(2)))
auto mul_first =
ngraph::pattern::wrap_type<ngraph::opset7::Multiply>({input, add});
auto mul = ngraph::pattern::wrap_type<ngraph::opset7::Multiply>(
{mul_constant, mul_first});
ngraph::matcher_pass_callback callback = [=](ngraph::pattern::Matcher &m) {
auto &pattern_to_output = m.get_pattern_value_map();
auto x_output = pattern_to_output.at(input);
auto div_const_value =
std::dynamic_pointer_cast<ngraph::opset7::Constant>(
pattern_to_output.at(div_constant).get_node_shared_ptr());
auto add_const_value =
std::dynamic_pointer_cast<ngraph::opset7::Constant>(
pattern_to_output.at(add_constant).get_node_shared_ptr());
auto mul_const_value =
std::dynamic_pointer_cast<ngraph::opset7::Constant>(
pattern_to_output.at(mul_constant).get_node_shared_ptr());
if (!div_const_value || !add_const_value || !mul_const_value) {
return false;
}
bool valid_constant_values =
op::util::has_constant_value<float>(div_const_value, M_SQRT2) &&
op::util::has_constant_value<float>(add_const_value, 1.0f) &&
op::util::has_constant_value<float>(mul_const_value, 0.5f);
if (!valid_constant_values) {
return false;
}
auto gelu = std::make_shared<ngraph::opset7::Gelu>(x_output);
gelu->set_friendly_name(m.get_match_root()->get_friendly_name());
ngraph::copy_runtime_info(
{
pattern_to_output.at(div).get_node_shared_ptr(),
pattern_to_output.at(erf).get_node_shared_ptr(),
pattern_to_output.at(add).get_node_shared_ptr(),
pattern_to_output.at(mul_first).get_node_shared_ptr(),
pattern_to_output.at(mul).get_node_shared_ptr(),
},
gelu);
ngraph::replace_node(m.get_match_root(), gelu);
return true;
};
auto m = std::make_shared<ngraph::pattern::Matcher>(mul, matcher_name);
register_matcher(m, callback);
}
NGRAPH_RTTI_DEFINITION(ngraph::pass::GeluFusionWithErfThree,
"GeluFusionWithErfThree", 0);
ngraph::pass::GeluFusionWithErfThree::GeluFusionWithErfThree() {
MATCHER_SCOPE(GeluFusionWithErfThree);
// Replaces a sub-graph with a Gelu op
// Shared by every pattern: (1 + erf(x / sqrt(2)))
auto input = ngraph::pattern::any_input();
auto div_constant = ngraph::pattern::wrap_type<ngraph::opset7::Constant>();
auto div = ngraph::pattern::wrap_type<ngraph::opset7::Divide>(
{input, div_constant});
auto erf = ngraph::pattern::wrap_type<ngraph::opset7::Erf>({div});
auto add_constant = ngraph::pattern::wrap_type<ngraph::opset7::Constant>();
auto add =
ngraph::pattern::wrap_type<ngraph::opset7::Add>({add_constant, erf});
auto mul_constant = ngraph::pattern::wrap_type<ngraph::opset7::Constant>();
// x * (0.5 * (1 + erf(x / sqrt(2)))
auto mul_first = ngraph::pattern::wrap_type<ngraph::opset7::Multiply>(
{add, mul_constant});
auto mul = ngraph::pattern::wrap_type<ngraph::opset7::Multiply>(
{input, mul_first});
ngraph::matcher_pass_callback callback = [=](ngraph::pattern::Matcher &m) {
auto &pattern_to_output = m.get_pattern_value_map();
auto x_output = pattern_to_output.at(input);
auto div_const_value =
std::dynamic_pointer_cast<ngraph::opset7::Constant>(
pattern_to_output.at(div_constant).get_node_shared_ptr());
auto add_const_value =
std::dynamic_pointer_cast<ngraph::opset7::Constant>(
pattern_to_output.at(add_constant).get_node_shared_ptr());
auto mul_const_value =
std::dynamic_pointer_cast<ngraph::opset7::Constant>(
pattern_to_output.at(mul_constant).get_node_shared_ptr());
if (!div_const_value || !add_const_value || !mul_const_value) {
return false;
}
bool valid_constant_values =
op::util::has_constant_value<float>(div_const_value, M_SQRT2) &&
op::util::has_constant_value<float>(add_const_value, 1.0f) &&
op::util::has_constant_value<float>(mul_const_value, 0.5f);
if (!valid_constant_values) {
return false;
}
auto gelu = std::make_shared<ngraph::opset7::Gelu>(x_output);
gelu->set_friendly_name(m.get_match_root()->get_friendly_name());
ngraph::copy_runtime_info(
{
pattern_to_output.at(div).get_node_shared_ptr(),
pattern_to_output.at(erf).get_node_shared_ptr(),
pattern_to_output.at(add).get_node_shared_ptr(),
pattern_to_output.at(mul_first).get_node_shared_ptr(),
pattern_to_output.at(mul).get_node_shared_ptr(),
},
gelu);
ngraph::replace_node(m.get_match_root(), gelu);
return true;
};
auto m = std::make_shared<ngraph::pattern::Matcher>(mul, matcher_name);
register_matcher(m, callback);
}
@@ -0,0 +1,212 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#define _USE_MATH_DEFINES
#include <gtest/gtest.h>
#include <math.h>
#include <memory>
#include <ngraph/function.hpp>
#include <ngraph/opsets/opset7.hpp>
#include <ngraph/pass/constant_folding.hpp>
#include <ngraph/pass/manager.hpp>
#include <queue>
#include <string>
#include <transformations/common_optimizations/gelu_fusion.hpp>
#include <transformations/init_node_info.hpp>
#include <transformations/utils/utils.hpp>
#include "common_test_utils/ngraph_test_utils.hpp"
using namespace testing;
using namespace ngraph;
TEST(TransformationTests, GeluFusionPatternOne) {
std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
{
auto data =
std::make_shared<opset7::Parameter>(element::f32, Shape{2, 2});
auto div_const =
opset7::Constant::create(element::f32, Shape{1}, {M_SQRT2});
auto add_const =
opset7::Constant::create(element::f32, Shape{1}, {1.0});
auto mul_const =
opset7::Constant::create(element::f32, Shape{1}, {0.5});
auto div = std::make_shared<opset7::Divide>(data, div_const);
auto erf = std::make_shared<opset7::Erf>(div);
auto add = std::make_shared<opset7::Add>(erf, add_const);
auto mul_first = std::make_shared<opset7::Multiply>(data, mul_const);
auto mul = std::make_shared<opset7::Multiply>(mul_first, add);
f = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data});
pass::Manager m;
m.register_pass<pass::InitNodeInfo>();
m.register_pass<pass::GeluFusionWithErfOne>();
m.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
{
auto data =
std::make_shared<opset1::Parameter>(element::f32, Shape{2, 2});
auto gelu = std::make_shared<opset7::Gelu>(data);
f_ref =
std::make_shared<Function>(NodeVector{gelu}, ParameterVector{data});
}
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
}
TEST(TransformationTests, GeluFusionPatternTwo) {
std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
{
auto data =
std::make_shared<opset7::Parameter>(element::f32, Shape{2, 2});
auto div_const =
opset7::Constant::create(element::f32, Shape{1}, {M_SQRT2});
auto add_const =
opset7::Constant::create(element::f32, Shape{1}, {1.0});
auto mul_const =
opset7::Constant::create(element::f32, Shape{1}, {0.5});
auto div = std::make_shared<opset7::Divide>(data, div_const);
auto erf = std::make_shared<opset7::Erf>(div);
auto add = std::make_shared<opset7::Add>(erf, add_const);
auto mul_first = std::make_shared<opset7::Multiply>(data, add);
auto mul = std::make_shared<opset7::Multiply>(mul_first, mul_const);
f = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data});
pass::Manager m;
m.register_pass<pass::InitNodeInfo>();
m.register_pass<pass::GeluFusionWithErfTwo>();
m.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
{
auto data =
std::make_shared<opset1::Parameter>(element::f32, Shape{2, 2});
auto gelu = std::make_shared<opset7::Gelu>(data);
f_ref =
std::make_shared<Function>(NodeVector{gelu}, ParameterVector{data});
}
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
}
TEST(TransformationTests, GeluFusionPatternThree) {
std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
{
auto data =
std::make_shared<opset7::Parameter>(element::f32, Shape{2, 2});
auto div_const =
opset7::Constant::create(element::f32, Shape{1}, {M_SQRT2});
auto add_const =
opset7::Constant::create(element::f32, Shape{1}, {1.0});
auto mul_const =
opset7::Constant::create(element::f32, Shape{1}, {0.5});
auto div = std::make_shared<opset7::Divide>(data, div_const);
auto erf = std::make_shared<opset7::Erf>(div);
auto add = std::make_shared<opset7::Add>(erf, add_const);
auto mul_first = std::make_shared<opset7::Multiply>(add, mul_const);
auto mul = std::make_shared<opset7::Multiply>(data, mul_first);
f = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data});
pass::Manager m;
m.register_pass<pass::InitNodeInfo>();
m.register_pass<pass::GeluFusionWithErfThree>();
m.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
{
auto data =
std::make_shared<opset1::Parameter>(element::f32, Shape{2, 2});
auto gelu = std::make_shared<opset7::Gelu>(data);
f_ref =
std::make_shared<Function>(NodeVector{gelu}, ParameterVector{data});
}
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
}
TEST(TransformationTests, GeluFusionPatternIncorrectDivConstValue) {
std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
{
auto data =
std::make_shared<opset7::Parameter>(element::f32, Shape{2, 2});
auto div_const =
opset7::Constant::create(element::f32, Shape{1}, {1.4149});
auto add_const =
opset7::Constant::create(element::f32, Shape{1}, {1.0});
auto mul_const =
opset7::Constant::create(element::f32, Shape{1}, {0.5});
auto div = std::make_shared<opset7::Divide>(data, div_const);
auto erf = std::make_shared<opset7::Erf>(div);
auto add = std::make_shared<opset7::Add>(erf, add_const);
auto mul_first = std::make_shared<opset7::Multiply>(data, add);
auto mul = std::make_shared<opset7::Multiply>(mul_first, mul_const);
f = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data});
f_ref =
std::make_shared<Function>(NodeVector{mul}, ParameterVector{data});
pass::Manager m;
m.register_pass<pass::InitNodeInfo>();
m.register_pass<pass::GeluFusionWithErfTwo>();
m.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
}
TEST(TransformationTests, GeluFusionPatternTooShortDivConstValue) {
std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
{
auto data =
std::make_shared<opset7::Parameter>(element::f32, Shape{2, 2});
auto div_const =
opset7::Constant::create(element::f32, Shape{1}, {1.4142});
auto add_const =
opset7::Constant::create(element::f32, Shape{1}, {1.0});
auto mul_const =
opset7::Constant::create(element::f32, Shape{1}, {0.5});
auto div = std::make_shared<opset7::Divide>(data, div_const);
auto erf = std::make_shared<opset7::Erf>(div);
auto add = std::make_shared<opset7::Add>(erf, add_const);
auto mul_first = std::make_shared<opset7::Multiply>(data, add);
auto mul = std::make_shared<opset7::Multiply>(mul_first, mul_const);
f = std::make_shared<Function>(NodeVector{mul}, ParameterVector{data});
f_ref =
std::make_shared<Function>(NodeVector{mul}, ParameterVector{data});
pass::Manager m;
m.register_pass<pass::InitNodeInfo>();
m.register_pass<pass::GeluFusionWithErfTwo>();
m.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
}