Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 58 additions & 25 deletions onnxruntime/core/optimizer/fast_gelu_fusion.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "core/graph/graph_utils.h"
#include "float.h"
#include <deque>
#include <optional>

Check warning on line 10 in onnxruntime/core/optimizer/fast_gelu_fusion.cc

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Found C++ system header after other header. Should be: fast_gelu_fusion.h, c system, c++ system, other. [build/include_order] [4] Raw Output: onnxruntime/core/optimizer/fast_gelu_fusion.cc:10: Found C++ system header after other header. Should be: fast_gelu_fusion.h, c system, c++ system, other. [build/include_order] [4]

using namespace ONNX_NAMESPACE;
using namespace onnxruntime::common;
Expand All @@ -32,10 +33,24 @@
!graph.NodeProducesGraphOutput(node);
}

static std::optional<size_t> OtherInputIndex(const Node& node, const NodeArg& input) {
if (node.InputDefs().size() != 2) {
return std::nullopt;
}

const int input_index = optimizer_utils::IndexOfNodeInput(node, input);
if (input_index < 0 || input_index >= 2) {
return std::nullopt;
}

return 1u - onnxruntime::narrow<size_t>(input_index);
}

MatchResult FastGeluFusion::CheckFirstFormula(Graph& graph, Node& mul1_node,
InlinedVector<std::reference_wrapper<Node>>& nodes_to_fuse) const {
MatchResult matchResult{false, nullptr, nullptr};
if (!graph_utils::IsSupportedOptypeVersionAndDomain(mul1_node, "Mul", {7, 13, 14}) ||
mul1_node.InputDefs().size() != 2 ||
!graph_utils::IsSupportedProvider(mul1_node, GetCompatibleExecutionProviders()) ||
mul1_node.GetOutputEdgesCount() != 1 ||
!IsSupportedDataType(mul1_node)) {
Comment thread
apsonawane marked this conversation as resolved.
Resolved
Expand All @@ -53,24 +68,26 @@

if (input_index == -1) return matchResult;

NodeArg* gelu_without_bias_input_arg = mul1_node.MutableInputDefs()[(input_index + 1) % 2];
NodeArg* gelu_without_bias_input_arg = mul1_node.MutableInputDefs()[1u - onnxruntime::narrow<size_t>(input_index)];
nodes_to_fuse.push_back(mul1_node);

Node& mul2_node = *graph.GetNode(mul1_node.OutputNodesBegin()->Index());
input_index = optimizer_utils::IndexOfNodeInput(mul2_node, *mul1_node.MutableOutputDefs()[0]);
auto other_input_index = OtherInputIndex(mul2_node, *mul1_node.MutableOutputDefs()[0]);
if (!(graph_utils::IsSupportedOptypeVersionAndDomain(mul2_node, "Mul", {7, 13, 14}) &&
CheckNode(graph, mul2_node, mul1_node.GetExecutionProviderType(), true)) ||
mul2_node.MutableInputDefs()[(input_index + 1) % 2]->Name() != gelu_without_bias_input_arg->Name()) {
!other_input_index ||
mul2_node.InputDefs()[*other_input_index]->Name() != gelu_without_bias_input_arg->Name()) {
return matchResult;
}
nodes_to_fuse.push_back(mul2_node);

Node& add1_node = *graph.GetNode(mul2_node.OutputNodesBegin()->Index());
input_index = optimizer_utils::IndexOfNodeInput(add1_node, *mul2_node.MutableOutputDefs()[0]);
other_input_index = OtherInputIndex(add1_node, *mul2_node.MutableOutputDefs()[0]);

if (!(graph_utils::IsSupportedOptypeVersionAndDomain(add1_node, "Add", {7, 13, 14}) &&
CheckNode(graph, add1_node, mul1_node.GetExecutionProviderType(), true)) ||
!optimizer_utils::IsInitializerWithExpectedValue(graph, *(add1_node.InputDefs()[(input_index + 1) % 2]), 1.0f, true)) {
!other_input_index ||
!optimizer_utils::IsInitializerWithExpectedValue(graph, *add1_node.InputDefs()[*other_input_index], 1.0f, true)) {
return matchResult;
}
nodes_to_fuse.push_back(add1_node);
Expand All @@ -82,11 +99,14 @@
}
nodes_to_fuse.push_back(mul3_node);

input_index = optimizer_utils::IndexOfNodeInput(mul3_node, *add1_node.MutableOutputDefs()[0]);
const Node* p_mul3_input_node = graph_utils::GetInputNode(mul3_node, (input_index + 1) % 2);
other_input_index = OtherInputIndex(mul3_node, *add1_node.MutableOutputDefs()[0]);
if (!other_input_index) return matchResult;
const Node* p_mul3_input_node =
graph_utils::GetInputNode(mul3_node, onnxruntime::narrow<int>(*other_input_index));
if (p_mul3_input_node == nullptr) return matchResult;
Node& mul4_node = const_cast<Node&>(*p_mul3_input_node);
if (!(graph_utils::IsSupportedOptypeVersionAndDomain(mul3_node, "Mul", {7, 13, 14}) &&
if (!(graph_utils::IsSupportedOptypeVersionAndDomain(mul4_node, "Mul", {7, 13, 14}) &&
mul4_node.InputDefs().size() == 2 &&
CheckNode(graph, mul4_node, mul1_node.GetExecutionProviderType(), true))) {
return matchResult;
}
Expand All @@ -100,7 +120,8 @@
}
}

if (input_index == -1 || mul4_node.InputDefs()[(input_index + 1) % 2]->Name() != gelu_without_bias_input_arg->Name())
if (input_index == -1 ||
mul4_node.InputDefs()[1u - onnxruntime::narrow<size_t>(input_index)]->Name() != gelu_without_bias_input_arg->Name())
return matchResult;
nodes_to_fuse.push_back(mul4_node);

Expand All @@ -114,6 +135,7 @@
InlinedVector<std::reference_wrapper<Node>>& nodes_to_fuse) const {
MatchResult matchResult{false, nullptr, nullptr};
if (!graph_utils::IsSupportedOptypeVersionAndDomain(pow1_node, "Pow", {7, 12, 13, 15}) ||
pow1_node.InputDefs().size() != 2 ||
!graph_utils::IsSupportedProvider(pow1_node, GetCompatibleExecutionProviders()) ||
pow1_node.GetOutputEdgesCount() != 1 ||
!IsSupportedDataType(pow1_node)) {
Expand All @@ -128,20 +150,22 @@
nodes_to_fuse.push_back(pow1_node);

Node& mul1_node = *graph.GetNode(pow1_node.OutputNodesBegin()->Index());
auto input_index = optimizer_utils::IndexOfNodeInput(mul1_node, *pow1_node.MutableOutputDefs()[0]);
auto other_input_index = OtherInputIndex(mul1_node, *pow1_node.MutableOutputDefs()[0]);
if (!(graph_utils::IsSupportedOptypeVersionAndDomain(mul1_node, "Mul", {7, 13, 14}) &&
CheckNode(graph, mul1_node, pow1_node.GetExecutionProviderType(), true)) ||
!optimizer_utils::IsInitializerWithExpectedValue(graph, *(mul1_node.InputDefs()[(input_index + 1) % 2]),
!other_input_index ||
!optimizer_utils::IsInitializerWithExpectedValue(graph, *mul1_node.InputDefs()[*other_input_index],
0.044714998453855515f, true)) {
return matchResult;
}
nodes_to_fuse.push_back(mul1_node);

Node& add1_node = *graph.GetNode(mul1_node.OutputNodesBegin()->Index());
input_index = optimizer_utils::IndexOfNodeInput(add1_node, *mul1_node.MutableOutputDefs()[0]);
other_input_index = OtherInputIndex(add1_node, *mul1_node.MutableOutputDefs()[0]);
if (!(graph_utils::IsSupportedOptypeVersionAndDomain(add1_node, "Add", {7, 13, 14}) &&
CheckNode(graph, add1_node, pow1_node.GetExecutionProviderType(), true)) ||
add1_node.MutableInputDefs()[(input_index + 1) % 2]->Name() != pow_input_arg->Name()) {
!other_input_index ||
add1_node.InputDefs()[*other_input_index]->Name() != pow_input_arg->Name()) {
return matchResult;
}
nodes_to_fuse.push_back(add1_node);
Expand All @@ -152,6 +176,7 @@
Node& cast1_node = *graph.GetNode(p_cast1_node->Index());
// this is fused Cast node, so expect 2 output edges
if (!(graph_utils::IsSupportedOptypeVersionAndDomain(cast1_node, "Cast", {9, 13, 19, 21, 23, 24, 25}) &&
cast1_node.InputDefs().size() == 1 &&
CheckNode(graph, cast1_node, pow1_node.GetExecutionProviderType(), false)) ||
cast1_node.GetOutputEdgesCount() != 2) {
return matchResult;
Expand All @@ -167,10 +192,11 @@
}

Node& mul2_node = *graph.GetNode(add1_node.OutputNodesBegin()->Index());
input_index = optimizer_utils::IndexOfNodeInput(mul2_node, *add1_node.MutableOutputDefs()[0]);
other_input_index = OtherInputIndex(mul2_node, *add1_node.MutableOutputDefs()[0]);
if (!(graph_utils::IsSupportedOptypeVersionAndDomain(mul2_node, "Mul", {7, 13, 14}) &&
CheckNode(graph, mul2_node, pow1_node.GetExecutionProviderType(), true)) ||
!optimizer_utils::IsInitializerWithExpectedValue(graph, *(mul2_node.InputDefs()[(input_index + 1) % 2]),
!other_input_index ||
!optimizer_utils::IsInitializerWithExpectedValue(graph, *mul2_node.InputDefs()[*other_input_index],
0.7978845834732056f, true)) {
return matchResult;
}
Expand Down Expand Up @@ -233,8 +259,9 @@
continue;
}

auto input_index = optimizer_utils::IndexOfNodeInput(add2_node, *tanh_node.MutableOutputDefs()[0]);
if (!optimizer_utils::IsInitializerWithExpectedValue(graph, *(add2_node.InputDefs()[(input_index + 1) % 2]), 1.0f, true)) {
auto other_input_index = OtherInputIndex(add2_node, *tanh_node.MutableOutputDefs()[0]);
if (!other_input_index ||
!optimizer_utils::IsInitializerWithExpectedValue(graph, *add2_node.InputDefs()[*other_input_index], 1.0f, true)) {
continue;
}

Expand All @@ -245,8 +272,10 @@
continue;
}

input_index = optimizer_utils::IndexOfNodeInput(mul5_node, *add2_node.MutableOutputDefs()[0]);
const Node* p_mul5_input_node = graph_utils::GetInputNode(mul5_node, (input_index + 1) % 2);
other_input_index = OtherInputIndex(mul5_node, *add2_node.MutableOutputDefs()[0]);
if (!other_input_index) continue;
const Node* p_mul5_input_node =
graph_utils::GetInputNode(mul5_node, onnxruntime::narrow<int>(*other_input_index));
if (p_mul5_input_node == nullptr) continue;

// if this is second formula and if pow node has Cast parent, expect mul5_node has Cast parent as well
Expand All @@ -263,37 +292,41 @@

Node& cast3_node = *graph.GetNode(p_cast3_node->Index());
if (!(graph_utils::IsSupportedOptypeVersionAndDomain(cast3_node, "Cast", {9, 13, 19, 21, 23, 24, 25}) &&
cast3_node.InputDefs().size() == 1 &&
CheckNode(graph, cast3_node, node.GetExecutionProviderType(), true))) {
continue;
}
// overwrite and continue as usual
p_mul5_input_node = graph_utils::FirstParentByType(cast3_node, "Mul");
if (p_mul5_input_node == nullptr) continue;
nodes_to_fuse.push_back(cast3_node);
// keep cast1_node for reuse, its output edges will be adjusted in FinalizeNodeFusion()
}
}

Node& mul6_node = const_cast<Node&>(*p_mul5_input_node);
if (!(graph_utils::IsSupportedOptypeVersionAndDomain(mul6_node, "Mul", {7, 13, 14}) &&
mul6_node.InputDefs().size() == 2 &&
CheckNode(graph, mul6_node, node.GetExecutionProviderType(), false))) {
continue;
}

input_index = -1;
for (auto i = 0; i < 2; i++) {
std::optional<size_t> scale_input_index;
for (size_t i = 0; i < 2; ++i) {
if (optimizer_utils::IsInitializerWithExpectedValue(graph, *(mul6_node.InputDefs()[i]), 0.5f, true)) {
input_index = i;
scale_input_index = i;
break;
}
}

if (input_index == -1) continue;
if (!scale_input_index) continue;
const size_t mul6_other_input_index = 1u - *scale_input_index;
// check same parent for both mul6 and pow, with or without cast
if (cast_input_arg != nullptr) {
if (mul6_node.InputDefs()[(input_index + 1) % 2]->Name() != cast_input_arg->Name())
if (mul6_node.InputDefs()[mul6_other_input_index]->Name() != cast_input_arg->Name())
continue;
} else {
if (mul6_node.InputDefs()[(input_index + 1) % 2]->Name() != matchRet.gelu_without_bias_input_arg->Name())
if (mul6_node.InputDefs()[mul6_other_input_index]->Name() != matchRet.gelu_without_bias_input_arg->Name())
continue;
}

Expand Down
89 changes: 89 additions & 0 deletions onnxruntime/test/optimizer/graph_transform_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7905,6 +7905,95 @@ TEST_F(GraphTransformationTests, FastGeluFusionTest) {
ASSERT_TRUE(op_to_count["com.microsoft.FastGelu"] == 1);
}

TEST_F(GraphTransformationTests, FastGeluFusionSkipsMalformedScaleMul) {
constexpr const ORTCHAR_T* model_uri = MODEL_FOLDER "fusion/fast_gelu.onnx";
std::shared_ptr<Model> model;
ASSERT_STATUS_OK(Model::Load(model_uri, model, nullptr, *logger_));
Graph& graph = model->MainGraph();

Node* scale_mul = nullptr;
for (auto& node : graph.Nodes()) {
if (node.OpType() != "Mul" || node.InputDefs().size() != 2) {
continue;
}

for (const NodeArg* input : node.InputDefs()) {
if (optimizer_utils::IsInitializerWithExpectedValue(graph, *input, 0.7978845834732056f, true)) {
scale_mul = &node;
break;
}
}

if (scale_mul != nullptr) {
break;
}
}

ASSERT_NE(scale_mul, nullptr);
scale_mul->MutableInputDefs().pop_back();

GraphTransformerManager graph_transformation_mgr{5};
ASSERT_STATUS_OK(graph_transformation_mgr.Register(std::make_unique<FastGeluFusion>(), TransformerLevel::Level2));
ASSERT_STATUS_OK(graph_transformation_mgr.ApplyTransformers(graph, TransformerLevel::Level2, *logger_));

std::map<std::string, int> op_to_count = CountOpsInGraph(graph);
EXPECT_EQ(op_to_count["com.microsoft.FastGelu"], 0);
}

TEST_F(GraphTransformationTests, FastGeluFusionSkipsMalformedFirstFormulaIntermediateMul) {
constexpr const ORTCHAR_T* model_uri = MODEL_FOLDER "fusion/fast_gelu.onnx";
std::shared_ptr<Model> model;
ASSERT_STATUS_OK(Model::Load(model_uri, model, nullptr, *logger_));
Graph& graph = model->MainGraph();

Node* coefficient_mul = nullptr;
for (auto& node : graph.Nodes()) {
if (node.OpType() == "Mul" && node.InputDefs().size() == 2 &&
(optimizer_utils::IsInitializerWithExpectedValue(graph, *node.InputDefs()[0], 0.044715f, true) ||
optimizer_utils::IsInitializerWithExpectedValue(graph, *node.InputDefs()[1], 0.044715f, true))) {
coefficient_mul = &node;
break;
}
}

ASSERT_NE(coefficient_mul, nullptr);
ASSERT_EQ(coefficient_mul->GetOutputEdgesCount(), 1);
Node& intermediate_mul = *graph.GetNode(coefficient_mul->OutputNodesBegin()->Index());
intermediate_mul.MutableInputDefs().pop_back();

GraphTransformerManager graph_transformation_mgr{5};
ASSERT_STATUS_OK(graph_transformation_mgr.Register(std::make_unique<FastGeluFusion>(), TransformerLevel::Level2));
ASSERT_STATUS_OK(graph_transformation_mgr.ApplyTransformers(graph, TransformerLevel::Level2, *logger_));

EXPECT_EQ(CountOpsInGraph(graph)["com.microsoft.FastGelu"], 0);
}

TEST_F(GraphTransformationTests, FastGeluFusionSkipsMalformedSecondFormulaIntermediateMul) {
constexpr const ORTCHAR_T* model_uri = MODEL_FOLDER "fusion/fast_gelu2.onnx";
std::shared_ptr<Model> model;
ASSERT_STATUS_OK(Model::Load(model_uri, model, nullptr, *logger_));
Graph& graph = model->MainGraph();

Node* pow_node = nullptr;
for (auto& node : graph.Nodes()) {
if (node.OpType() == "Pow") {
pow_node = &node;
break;
}
}

ASSERT_NE(pow_node, nullptr);
ASSERT_EQ(pow_node->GetOutputEdgesCount(), 1);
Node& intermediate_mul = *graph.GetNode(pow_node->OutputNodesBegin()->Index());
intermediate_mul.MutableInputDefs().pop_back();

GraphTransformerManager graph_transformation_mgr{5};
ASSERT_STATUS_OK(graph_transformation_mgr.Register(std::make_unique<FastGeluFusion>(), TransformerLevel::Level2));
ASSERT_STATUS_OK(graph_transformation_mgr.ApplyTransformers(graph, TransformerLevel::Level2, *logger_));

EXPECT_EQ(CountOpsInGraph(graph)["com.microsoft.FastGelu"], 0);
}

TEST_F(GraphTransformationTests, FastGeluUseGraphInputFusionTest) {
constexpr const ORTCHAR_T* model_uri = MODEL_FOLDER "fusion/fast_gelu_use_graph_input.onnx";
std::shared_ptr<Model> p_model;
Expand Down
Loading