// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include "paddle/fluid/framework/block_desc.h" #include "paddle/fluid/framework/ir/layer_norm_fuse_pass.h" #include "paddle/fluid/framework/ir/pass_test_util.h" #include "paddle/fluid/framework/naive_executor.h" #include "paddle/fluid/framework/op_desc.h" #include "paddle/fluid/framework/op_version_registry.h" #include "paddle/fluid/framework/program_desc.h" #include "paddle/fluid/framework/scope.h" #include "paddle/fluid/platform/errors.h" #include "paddle/fluid/platform/place.h" namespace paddle { namespace framework { namespace ir { namespace { class LayerNormFuseTest { public: LayerNormFuseTest() : m_prog{test::BuildProgramDesc( {"x", "x_mean_out", "x_sub_mean_out", "x_sub_mean_sqr_out", "std_dev_out", "std_dev_eps_out", "std_dev_eps_sqrt_out", "division_out", "scale_out", "shift_out"}, {"sqr_pow", "eps", "gamma", "beta"})}, m_place{}, m_exe{m_place} { const BlockDesc& block_desc = m_prog.Block(0); auto* x_var_desc = block_desc.FindVar("x"); x_var_desc->SetDataType(proto::VarType::FP32); x_var_desc->SetShape({3, 32, 48}); auto* eps_var_desc = block_desc.FindVar("eps"); eps_var_desc->SetDataType(proto::VarType::FP32); eps_var_desc->SetShape({1}); auto* gamma_var_desc = block_desc.FindVar("gamma"); gamma_var_desc->SetDataType(proto::VarType::FP32); gamma_var_desc->SetShape({48}); auto* beta_var_desc = block_desc.FindVar("beta"); beta_var_desc->SetDataType(proto::VarType::FP32); beta_var_desc->SetShape({48}); auto* x_mean = test::CreateOp(&m_prog, "reduce_mean", {{"X", "x"}}, {{"Out", "x_mean_out"}}, false); x_mean->SetAttr("dim", std::vector{-1}); x_mean->SetAttr("keep_dim", true); x_mean->SetAttr("reduce_all", false); auto* x_sub = test::CreateOp(&m_prog, "elementwise_sub", {{"X", "x"}, {"Y", "x_mean_out"}}, {{"Out", "x_sub_mean_out"}}, false); x_sub->SetAttr("axis", 1); auto* x_pow = test::CreateOp(&m_prog, "elementwise_pow", {{"X", "x_sub_mean_out"}, {"Y", "sqr_pow"}}, {{"Out", "x_sub_mean_sqr_out"}}, false); x_pow->SetAttr("axis", 1); auto* std_dev = test::CreateOp(&m_prog, "reduce_mean", {{"X", "x_sub_mean_sqr_out"}}, {{"Out", "std_dev_out"}}, false); std_dev->SetAttr("dim", std::vector{-1}); std_dev->SetAttr("keep_dim", true); std_dev->SetAttr("reduce_all", false); auto* x_add = test::CreateOp(&m_prog, "elementwise_add", {{"X", "std_dev_out"}, {"Y", "eps"}}, {{"Out", "std_dev_eps_out"}}, false); x_add->SetAttr("axis", 1); test::CreateOp(&m_prog, "sqrt", {{"X", "std_dev_eps_out"}}, {{"Out", "std_dev_eps_sqrt_out"}}, false); auto* x_div = test::CreateOp(&m_prog, "elementwise_div", {{"X", "x_sub_mean_out"}, {"Y", "std_dev_eps_sqrt_out"}}, {{"Out", "division_out"}}, false); x_div->SetAttr("axis", 1); auto* x_mul = test::CreateOp(&m_prog, "elementwise_mul", {{"X", "division_out"}, {"Y", "gamma"}}, {{"Out", "scale_out"}}, false); x_mul->SetAttr("axis", 1); auto* x_add_v1 = test::CreateOp(&m_prog, "elementwise_add", {{"X", "scale_out"}, {"Y", "beta"}}, {{"Out", "shift_out"}}, false); x_add_v1->SetAttr("axis", 1); } template LayerNormFuseTest(const Func& func, int removed_nodes = 0, int added_nodes = 0) : LayerNormFuseTest() { m_removed_nodes = removed_nodes; m_added_nodes = added_nodes; func(m_prog.Block(0)); } void setupGraph() { auto initFun = [this](const Scope& scope, const paddle::platform::CPUPlace& place) { this->initEpsTensorValue(scope, place); }; setupGraphWithInitFunc(initFun); } template void setupGraphWithInitFunc(const Func& func) { m_graph.reset(new Graph(m_prog)); // Init scope, as it is used in pass m_exe.CreateVariables(m_prog, 0, true, &m_scope); func(m_scope, m_place); m_graph->SetNotOwned(kParamScopeAttr, &m_scope); } void run(bool fusion = false) const { EXPECT_TRUE(test::RunPassAndAssert(m_graph.get(), "layer_norm_fuse_pass", "x", "shift_out", m_removed_nodes, m_added_nodes)); EXPECT_TRUE(CheckSubgraphOpsCount(*m_graph, fusion)); } const ProgramDesc& getProgramDesc() const { return m_prog; } const Graph* getGraph() const { return m_graph.get(); } private: void initEpsTensorValue(const Scope& scope, const paddle::platform::CPUPlace& place) { float eps_value = 1e-5; test::InitLoDTensorHolder(scope, place, "eps", {1}, &eps_value); } bool CheckSubgraphOpsCount(const Graph& graph, bool fusion) const { if (fusion) return test::AssertOpsCount(graph, {{"reduce_mean", 0}, {"elementwise_sub", 0}, {"elementwise_pow", 0}, {"elementwise_add", 0}, {"sqrt", 0}, {"elementwise_div", 0}, {"elementwise_mul", 0}, {"layer_norm", 1}}); else return test::AssertOpsCount(graph, {{"reduce_mean", 2}, {"elementwise_sub", 1}, {"elementwise_pow", 1}, {"elementwise_add", 2}, {"sqrt", 1}, {"elementwise_div", 1}, {"elementwise_mul", 1}, {"layer_norm", 0}}); } int m_removed_nodes{19}; int m_added_nodes{3}; ProgramDesc m_prog; paddle::platform::CPUPlace m_place; NaiveExecutor m_exe; Scope m_scope; std::unique_ptr m_graph{nullptr}; }; } // namespace // ------------------------------ Test cases ----------------------------------- TEST(FuseLayerNormPass, TestFuse) { LayerNormFuseTest lnorm_test; lnorm_test.setupGraph(); lnorm_test.run(true); // additional attribute checks for (const auto* node : lnorm_test.getGraph()->Nodes()) { if (node->IsOp() && node->Op()->Type() == "layer_norm") { const auto* op = node->Op(); ASSERT_TRUE(op->HasAttr("is_test")); EXPECT_TRUE(BOOST_GET_CONST(bool, op->GetAttr("is_test"))); ASSERT_TRUE(op->HasAttr("begin_norm_axis")); ASSERT_TRUE(op->HasAttr("epsilon")); } } } TEST(FuseLayerNormPass, TestInvalidEpsNumel) { const auto editEpsFun = [](const BlockDesc& block_desc) { auto* eps_var_desc = block_desc.FindVar("eps"); eps_var_desc->SetDataType(proto::VarType::FP32); eps_var_desc->SetShape({2}); }; const auto initEpsTensor = [](const Scope& scope, const paddle::platform::CPUPlace& place) { auto eps_values = std::vector{1e-5f, 1e-5f}; test::InitLoDTensorHolder(scope, place, "eps", {2}, eps_values.data()); }; LayerNormFuseTest lnorm_test(editEpsFun); lnorm_test.setupGraphWithInitFunc(initEpsTensor); lnorm_test.run(false); } TEST(FuseLayerNormPass, TestInvalidEpsDataType) { const auto editEpsFun = [](const BlockDesc& block_desc) { auto* eps_var_desc = block_desc.FindVar("eps"); eps_var_desc->SetDataType(proto::VarType::FP64); eps_var_desc->SetShape({1}); }; const auto initEpsTensor = [](const Scope& scope, const paddle::platform::CPUPlace& place) { double eps_value = 1e-5; test::InitLoDTensorHolder(scope, place, "eps", {1}, &eps_value); }; LayerNormFuseTest lnorm_test(editEpsFun); lnorm_test.setupGraphWithInitFunc(initEpsTensor); lnorm_test.run(false); } TEST(FuseLayerNormPass, TestInvalidGammaRank) { const auto editGammaFun = [](const BlockDesc& block_desc) { auto* gamma_var_desc = block_desc.FindVar("gamma"); gamma_var_desc->SetDataType(proto::VarType::FP32); gamma_var_desc->SetShape({48, 32}); }; LayerNormFuseTest lnorm_test(editGammaFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, TestInvalidBetaRank) { const auto editBetaFun = [](const BlockDesc& block_desc) { auto* beta_var_desc = block_desc.FindVar("beta"); beta_var_desc->SetDataType(proto::VarType::FP32); beta_var_desc->SetShape({48, 32}); }; LayerNormFuseTest lnorm_test(editBetaFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, TestUnequalGammaBetaShapes) { const auto editGammaBetaFun = [](const BlockDesc& block_desc) { auto* beta_var_desc = block_desc.FindVar("beta"); beta_var_desc->SetDataType(proto::VarType::FP32); beta_var_desc->SetShape({32}); }; LayerNormFuseTest lnorm_test(editGammaBetaFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, TestGammaBetaUnequalInputChannelShape) { const auto editGammaBetaFun = [](const BlockDesc& block_desc) { auto* beta_var_desc = block_desc.FindVar("beta"); beta_var_desc->SetDataType(proto::VarType::FP32); beta_var_desc->SetShape({32}); auto* gamma_var_desc = block_desc.FindVar("gamma"); gamma_var_desc->SetDataType(proto::VarType::FP32); gamma_var_desc->SetShape({32}); }; LayerNormFuseTest lnorm_test(editGammaBetaFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, NoFusionBadInMeanDimAttrRank) { const auto editFun = [](const BlockDesc& block_desc) { auto* x_mean_desc = test::GetOp(block_desc, "reduce_mean", "Out", "x_mean_out"); ASSERT_NE(x_mean_desc, nullptr); x_mean_desc->SetAttr("dim", std::vector{1, 1}); }; LayerNormFuseTest lnorm_test(editFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, NoFusionBadInMeanDimAttr) { const auto editFun = [](const BlockDesc& block_desc) { auto* x_mean_desc = test::GetOp(block_desc, "reduce_mean", "Out", "x_mean_out"); ASSERT_NE(x_mean_desc, nullptr); x_mean_desc->SetAttr("dim", std::vector{1}); }; LayerNormFuseTest lnorm_test(editFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, NoFusionBadInMeanKeepDimAttr) { const auto editFun = [](const BlockDesc& block_desc) { auto* x_mean_desc = test::GetOp(block_desc, "reduce_mean", "Out", "x_mean_out"); ASSERT_NE(x_mean_desc, nullptr); x_mean_desc->SetAttr("keep_dim", false); }; LayerNormFuseTest lnorm_test(editFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, NoFusionBadInMeanReduceAllAttr) { const auto editFun = [](const BlockDesc& block_desc) { auto* x_mean_desc = test::GetOp(block_desc, "reduce_mean", "Out", "x_mean_out"); ASSERT_NE(x_mean_desc, nullptr); x_mean_desc->SetAttr("reduce_all", true); }; LayerNormFuseTest lnorm_test(editFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, NoFusionBadStdDevMeanDimAttrRank) { const auto editFun = [](const BlockDesc& block_desc) { auto* std_dev_desc = test::GetOp(block_desc, "reduce_mean", "Out", "std_dev_out"); ASSERT_NE(std_dev_desc, nullptr); std_dev_desc->SetAttr("dim", std::vector{1, 1}); }; LayerNormFuseTest lnorm_test(editFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, NoFusionBadStdDevMeanDimAttr) { const auto editFun = [](const BlockDesc& block_desc) { auto* std_dev_desc = test::GetOp(block_desc, "reduce_mean", "Out", "std_dev_out"); ASSERT_NE(std_dev_desc, nullptr); std_dev_desc->SetAttr("dim", std::vector{1}); }; LayerNormFuseTest lnorm_test(editFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, NoFusionBadStdDevMeanKeepDimAttr) { const auto editFun = [](const BlockDesc& block_desc) { auto* std_dev_desc = test::GetOp(block_desc, "reduce_mean", "Out", "std_dev_out"); ASSERT_NE(std_dev_desc, nullptr); std_dev_desc->SetAttr("keep_dim", false); }; LayerNormFuseTest lnorm_test(editFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, NoFusionBadStdDevMeanReduceAllAttr) { const auto editFun = [](const BlockDesc& block_desc) { auto* std_dev_desc = test::GetOp(block_desc, "reduce_mean", "Out", "std_dev_out"); ASSERT_NE(std_dev_desc, nullptr); std_dev_desc->SetAttr("reduce_all", true); }; LayerNormFuseTest lnorm_test(editFun); lnorm_test.setupGraph(); lnorm_test.run(false); } TEST(FuseLayerNormPass, pass_op_version_check) { ASSERT_TRUE( paddle::framework::compatible::PassVersionCheckerRegistrar::GetInstance() .IsPassCompatible("layer_norm_fuse_pass")); } } // namespace ir } // namespace framework } // namespace paddle USE_PASS(layer_norm_fuse_pass);