// Copyright (c) 2023 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 #include #include #include #include "paddle/common/enforce.h" #include "paddle/common/errors.h" #include "paddle/fluid/framework/scope.h" #include "paddle/fluid/pir/dialect/operator/ir/op_attribute.h" #include "paddle/fluid/pir/dialect/operator/ir/op_dialect.h" #include "paddle/fluid/pir/dialect/operator/ir/op_type.h" #include "paddle/fluid/pir/dialect/operator/ir/pd_op.h" #include "paddle/fluid/pir/transforms/general/constant_folding_pass.h" #include "paddle/fluid/pir/transforms/general/dead_code_elimination_pass.h" #include "paddle/fluid/pir/transforms/gpu/conv2d_add_act_fuse_pass.h" #include "paddle/fluid/pir/transforms/gpu/conv2d_add_fuse_pass.h" #include "paddle/fluid/pir/transforms/gpu/conv2d_bn_fuse_pass.h" #include "paddle/fluid/pir/utils/general_functions.h" #include "paddle/pir/include/core/builder.h" #include "paddle/pir/include/core/builtin_attribute.h" #include "paddle/pir/include/core/builtin_dialect.h" #include "paddle/pir/include/core/builtin_op.h" #include "paddle/pir/include/core/cast_utils.h" #include "paddle/pir/include/core/dialect.h" #include "paddle/pir/include/core/ir_context.h" #include "paddle/pir/include/core/op_info.h" #include "paddle/pir/include/core/parameter.h" #include "paddle/pir/include/core/program.h" #include "paddle/pir/include/core/value.h" #include "paddle/pir/include/pass/pass.h" #include "paddle/pir/include/pass/pass_manager.h" #include "paddle/pir/include/pattern_rewrite/frozen_rewrite_pattern_set.h" #include "paddle/pir/include/pattern_rewrite/pattern_applicator.h" #include "paddle/pir/include/pattern_rewrite/pattern_match.h" #include "paddle/pir/include/pattern_rewrite/pattern_rewrite_driver.h" #include "paddle/common/ddim.h" #include "paddle/phi/common/place.h" #include "paddle/phi/core/kernel_registry.h" #include "test/cpp/pir/tools/macros_utils.h" PD_DECLARE_KERNEL(full, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(add, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(sqrt, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(divide, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(multiply, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(subtract, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(full_int_array, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(reshape, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(fetch, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(conv2d, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(transpose, CPU, ALL_LAYOUT); PD_DECLARE_KERNEL(cummax, CPU, ALL_LAYOUT); // Define op1. class Operation1 : public pir::Op { public: using Op::Op; static const char *name() { return "test.Operation1"; } static constexpr uint32_t attributes_num = 2; static const char *attributes_name[attributes_num]; // NOLINT void VerifySig(); static void InferShape() { VLOG(2) << "This is op2's InferShape interface."; } }; void Operation1::VerifySig() { auto &attributes = this->attributes(); if (attributes.count("op2_attr1") == 0 || (!attributes.at("op2_attr1").isa())) { PADDLE_THROW(common::errors::InvalidArgument( "Type of attribute: parameter_name is not right.")); } if (attributes.count("op2_attr2") == 0 || (!attributes.at("op2_attr2").isa())) { PADDLE_THROW(common::errors::InvalidArgument( "Type of attribute: parameter_name is not right.")); } } const char *Operation1::attributes_name[attributes_num] = { // NOLINT "op2_attr1", "op2_attr2"}; IR_DECLARE_EXPLICIT_TEST_TYPE_ID(Operation1) IR_DEFINE_EXPLICIT_TYPE_ID(Operation1) // Define a dialect, op1 and op2 will be registered by this dialect. class TestDialect : public pir::Dialect { public: explicit TestDialect(pir::IrContext *context) : pir::Dialect(name(), context, pir::TypeId::get()) { initialize(); } static const char *name() { return "test"; } private: void initialize() { RegisterOps(); } }; IR_DECLARE_EXPLICIT_TEST_TYPE_ID(TestDialect) IR_DEFINE_EXPLICIT_TYPE_ID(TestDialect) // TODO(wilber): Add logical when ir support erase, replace or update. class TestPatternRewrite : public pir::OpRewritePattern { public: using pir::OpRewritePattern::OpRewritePattern; void Rewrite(Operation1 op, pir::PatternRewriter &rewriter) const override {} bool Match(Operation1 op) const override { return false; } }; class TestPatternRewrite2 : public pir::OpRewritePattern { public: using pir::OpRewritePattern::OpRewritePattern; bool MatchAndRewrite( Operation1 op, pir::PatternRewriter &rewriter) const override { // NOLINT return false; } }; TEST(PatternRewrite, PatternBenefit) { pir::PatternBenefit benefit1(1); EXPECT_EQ(benefit1.benefit(), 1U); pir::PatternBenefit benefit2(2); EXPECT_EQ(benefit2.benefit(), 2U); EXPECT_TRUE(benefit2 > benefit1); EXPECT_TRUE(benefit2 >= benefit1); EXPECT_TRUE(benefit1 < benefit2); EXPECT_TRUE(benefit1 <= benefit2); EXPECT_TRUE(benefit1 != benefit2); pir::PatternBenefit benefit3(2); EXPECT_TRUE(benefit2 == benefit3); } TEST(RewritePattern, RewritePatternSet) { pir::IrContext *ctx = pir::IrContext::Instance(); ctx->GetOrRegisterDialect(); auto *test_dialect = ctx->GetOrRegisterDialect(); test_dialect->RegisterOp(); pir::RewritePatternSet ps(ctx); ps.Add(ctx, 1); EXPECT_EQ(ps.native_patterns().size(), 1U); EXPECT_TRUE(ps.native_patterns().back()->debug_labels().empty()); EXPECT_EQ(ps.native_patterns().back()->benefit(), 1U); ps.AddWithLabel({"TestPatternRewrite2"}, ctx, 2); EXPECT_EQ(ps.native_patterns().size(), 2U); EXPECT_EQ(ps.native_patterns().back()->debug_labels()[0], "TestPatternRewrite2"); EXPECT_EQ(ps.native_patterns().back()->benefit(), 2U); ps.Clear(); ps.Add(ctx, 2); EXPECT_EQ(ps.native_patterns().size(), 2U); EXPECT_EQ(ps.native_patterns()[0]->benefit(), 2U); EXPECT_EQ(ps.native_patterns()[1]->benefit(), 2U); } // TODO(wilber): Add actual case. // TEST(PatternRewrite, PatternApplicator) { // pir::IrContext *ctx = pir::IrContext::Instance(); // ctx->GetOrRegisterDialect(); // auto *test_dialect = ctx->GetOrRegisterDialect(); // test_dialect->RegisterOp(); // pir::RewritePatternSet ps(ctx); // ps.Add(ctx, 2); // pir::FrozenRewritePatternSet frozen_set(std::move(ps)); // pir::PatternApplicator applicator(frozen_set); // applicator.ApplyDefaultCostModel(); // } // // TODO(wilber): Add actual case. TEST(PatternRewrite, FrozenRewritePatternSet) { pir::FrozenRewritePatternSet frozen_set; EXPECT_TRUE(frozen_set.match_any_op_native_patterns().empty()); EXPECT_TRUE(frozen_set.op_specific_native_patterns().empty()); pir::IrContext *ctx = pir::IrContext::Instance(); ctx->GetOrRegisterDialect(); auto *test_dialect = ctx->GetOrRegisterDialect(); test_dialect->RegisterOp(); pir::RewritePatternSet ps(ctx); ps.Add(ctx, 2); pir::FrozenRewritePatternSet frozen_set2(std::move(ps)); EXPECT_TRUE(frozen_set2.match_any_op_native_patterns().empty()); const auto &pattern_maps = frozen_set2.op_specific_native_patterns(); EXPECT_EQ(pattern_maps.size(), 1U); EXPECT_EQ(pattern_maps.at(ctx->GetRegisteredOpInfo("test.Operation1")).size(), 2U); } class RedundantTransposeFusePattern : public pir::OpRewritePattern { public: using pir::OpRewritePattern::OpRewritePattern; bool MatchAndRewrite(paddle::dialect::TransposeOp op, pir::PatternRewriter &rewriter) const override { auto prev_op = pir::GetDefiningOpForInput(op, 0); std::vector axis_last = GetAxis(op); auto prev_trans_op = prev_op->dyn_cast(); if (prev_trans_op) { std::vector axis_first = GetAxis(prev_trans_op); PADDLE_ENFORCE_EQ(axis_first.size(), axis_last.size(), common::errors::InvalidArgument( "transpose op's perm rank should be same.")); auto new_perm = GetPerm(axis_first, axis_last); rewriter.set_insertion_point(op); auto new_transpose_op = rewriter.Build( pir::GetDefiningOpForInput(prev_trans_op, 0)->result(0), new_perm); rewriter.ReplaceOp(op, {new_transpose_op.out()}); return true; } return false; } private: std::vector GetAxis(paddle::dialect::TransposeOp op) const { auto array_attr = op.attribute("perm").AsVector(); std::vector axis(array_attr.size()); for (size_t i = 0; i < array_attr.size(); ++i) { axis[i] = array_attr[i].dyn_cast().data(); } return axis; } std::vector GetPerm(const std::vector &perm1, const std::vector &perm2) const { int n = static_cast(perm1.size()); std::vector axis(n), axis1(n), axis2(n); std::iota(axis.begin(), axis.end(), 0); for (int i = 0; i < n; ++i) { axis1[i] = axis[perm1[i]]; } for (int i = 0; i < n; ++i) { axis2[i] = axis1[perm2[i]]; } return axis2; } }; class TestPass : public pir::PatternRewritePass { public: TestPass() : pir::PatternRewritePass("test_pass", 1) {} pir::RewritePatternSet InitializePatterns(pir::IrContext *context) override { pir::RewritePatternSet ps(context); ps.Add(context); return ps; } }; void BuildProgram(pir::Builder &builder) { // NOLINT paddle::dialect::FullOp full_input_op = builder.Build(std::vector{4, 3, 16, 16}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp full_filter_op = builder.Build(std::vector{64, 3, 3, 3}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp full_input_op_1 = builder.Build(std::vector{4, 3, 16, 16}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp full_filter_op_1 = builder.Build(std::vector{64, 3, 3, 3}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp add_op_y = builder.Build(std::vector{1, 64, 1, 1}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp full_input_op_2 = builder.Build(std::vector{4, 3, 16, 16}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp full_filter_op_2 = builder.Build(std::vector{64, 3, 3, 3}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp add_op_y_1 = builder.Build(std::vector{1, 64, 1, 1}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp add_op_2_x = builder.Build( std::vector{4, 64, 14, 14}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp full_mean_op = builder.Build( std::vector{64}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp full_variance_op = builder.Build(std::vector{64}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp full_scale_op = builder.Build(std::vector{64}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::FullOp full_bias_op = builder.Build( std::vector{64}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace()); paddle::dialect::Conv2dOp conv2d_op = builder.Build(full_input_op.out(), full_filter_op.out()); paddle::dialect::BatchNorm_Op batch_norm_op = builder.Build(conv2d_op.out(), full_mean_op.out(), full_variance_op.out(), full_scale_op.out(), full_bias_op.out(), true, 0.9, 1e-6, "NCHW", false, false); paddle::dialect::Conv2dOp conv2d_op_1 = builder.Build(full_input_op_1.out(), full_filter_op_1.out()); paddle::dialect::AddOp add_op = builder.Build(conv2d_op_1.out(), add_op_y.out()); paddle::dialect::ReluOp relu_op = builder.Build(add_op.out()); paddle::dialect::Conv2dOp conv2d_op_2 = builder.Build(full_input_op_2.out(), full_filter_op_2.out()); paddle::dialect::AddOp add_op_1 = builder.Build( conv2d_op_2.out(), add_op_y_1.out()); paddle::dialect::AddOp add_op_2 = builder.Build(add_op_2_x.out(), add_op_1.out()); paddle::dialect::ReluOp relu_op_1 = builder.Build(add_op_2.out()); auto transpose1_op = builder.Build( batch_norm_op.out(), std::vector{0, 2, 3, 1}); auto transpose2_op = builder.Build( transpose1_op.out(), std::vector{0, 3, 1, 2}); auto add_out = builder.Build(transpose2_op.out(), relu_op_1.out()); auto add_out_1 = builder.Build(add_out.out(), relu_op.out()); builder.Build(add_out_1.out(), "out", 0); } TEST(pattern_rewrite, Patterns) { pir::IrContext *ctx = pir::IrContext::Instance(); ctx->GetOrRegisterDialect(); ctx->GetOrRegisterDialect(); pir::Program program(ctx); pir::Builder builder = pir::Builder(ctx, program.block()); BuildProgram(builder); EXPECT_EQ(program.block()->size(), 27u); pir::PassManager pm(ctx); pm.AddPass(std::make_unique()); pm.AddPass(pir::CreateConv2dBnFusePass()); pm.AddPass(pir::CreateConv2dAddActFusePass()); pm.AddPass(pir::CreateConv2dAddFusePass()); std::unique_ptr constant_folding_pass = pir::CreateConstantFoldingPass(); phi::Place place = phi::CPUPlace(); constant_folding_pass->SetNotOwned(pir::Pass::kPlaceAttr, &place); constant_folding_pass->Set(pir::Pass::kParamScopeAttr, new paddle::framework::Scope()); pm.AddPass(std::move(constant_folding_pass)); pm.AddPass(pir::CreateDeadCodeEliminationPass()); // pm.EnablePassTiming(); pm.EnableIRPrinting(); // pm.EnableIRPrinting(std::make_unique( // [](pir::Pass *pass, pir::Operation *op) { // return pass->name() == "constant_folding_pass"; // }, // [](pir::Pass *pass, pir::Operation *op) { // return pass->name() == "constant_folding_pass"; // }, // true, // true)); PADDLE_ENFORCE_EQ(pm.Run(&program), true, common::errors::ExecutionTimeout( "Sorry, the for program test is timeout.")); EXPECT_EQ(program.block()->size(), 17u); } void BuildConstantFoldingProgram(pir::Program *program, pir::IrContext *ctx, paddle::framework::Scope *scope) { pir::Builder builder = pir::Builder(ctx, program->block()); pir::Type fp32_dtype = pir::Float32Type::get(ctx); phi::DDim dims = {2, 2}; phi::DataLayout data_layout = phi::DataLayout::NCHW; phi::LegacyLoD lod = {{0, 1, 2}}; size_t offset = 0; pir::Type dense_tensor_dtype = paddle::dialect::DenseTensorType::get( ctx, fp32_dtype, dims, data_layout, lod, offset); phi::DenseTensorMeta meta( phi::DataType::FLOAT32, dims, data_layout, lod, offset); phi::DeviceContext *dev_ctx = phi::DeviceContextPool::Instance().Get(phi::CPUPlace()); auto op1 = builder.Build("a", dense_tensor_dtype); auto op2 = builder.Build("b", dense_tensor_dtype); auto op3 = builder.Build(op1->result(0), op2->result(0)); auto op4 = builder.Build("c", dense_tensor_dtype); auto op5 = builder.Build(op3->result(0), op4->result(0)); builder.Build(op5.out(), "out", 0); auto *tensor_a = scope->Var("a")->GetMutable(); auto *tensor_b = scope->Var("b")->GetMutable(); auto *tensor_c = scope->Var("c")->GetMutable(); tensor_a->set_meta(meta); tensor_b->set_meta(meta); tensor_c->set_meta(meta); dev_ctx->Alloc(tensor_a, phi::DataType::FLOAT32); dev_ctx->Alloc(tensor_b, phi::DataType::FLOAT32); dev_ctx->Alloc(tensor_c, phi::DataType::FLOAT32); } TEST(constant_folding, ConstantFolding) { pir::IrContext *ctx = pir::IrContext::Instance(); ctx->GetOrRegisterDialect(); ctx->GetOrRegisterDialect(); pir::Program program(ctx); paddle::framework::Scope scope; BuildConstantFoldingProgram(&program, ctx, &scope); pir::PassManager pm(ctx); std::unique_ptr constant_folding_pass = pir::CreateConstantFoldingPass(); phi::Place place = phi::CPUPlace(); constant_folding_pass->SetNotOwned(pir::Pass::kPlaceAttr, &place); constant_folding_pass->SetNotOwned(pir::Pass::kParamScopeAttr, &scope); pm.AddPass(std::move(constant_folding_pass)); pm.AddPass(pir::CreateDeadCodeEliminationPass()); pm.EnableIRPrinting(); PADDLE_ENFORCE_EQ(pm.Run(&program), true, common::errors::ExecutionTimeout( "Sorry, the test for program is timeout.")); EXPECT_EQ(program.block()->size(), 2u); } TEST(constant_folding, ConstantFolding_Train) { pir::IrContext *ctx = pir::IrContext::Instance(); ctx->GetOrRegisterDialect(); ctx->GetOrRegisterDialect(); pir::Program program(ctx); paddle::framework::Scope scope; BuildConstantFoldingProgram(&program, ctx, &scope); pir::PassManager pm(ctx); std::unique_ptr constant_folding_pass = pir::CreateConstantFoldingPass(); phi::Place place = phi::CPUPlace(); constant_folding_pass->SetNotOwned(pir::Pass::kPlaceAttr, &place); constant_folding_pass->SetNotOwned(pir::Pass::kParamScopeAttr, &scope); constant_folding_pass->Set("train_mode", new bool(true)); pm.AddPass(std::move(constant_folding_pass)); pm.AddPass(pir::CreateDeadCodeEliminationPass()); pm.EnableIRPrinting(); PADDLE_ENFORCE_EQ(pm.Run(&program), true, common::errors::ExecutionTimeout( "Sorry, the test for program is timeout.")); EXPECT_EQ(program.block()->size(), 4u); } void BuildConcatProgram(pir::Program *program, pir::IrContext *ctx) { pir::Builder builder = pir::Builder(ctx, program->block()); auto x = builder .Build(std::vector({16, 16}), 2.0, phi::DataType::FLOAT32, phi::GPUPlace()) .result(0); auto y = builder .Build(std::vector({16, 16}), 2.0, phi::DataType::FLOAT32, phi::GPUPlace()) .result(0); auto t1 = builder.Build(std::vector({x, y})).result(0); auto out1 = builder.Build(t1, 1).result(0); auto z = builder .Build(std::vector({16, 16}), 2.0, phi::DataType::FLOAT32, phi::GPUPlace()) .result(0); auto w = builder .Build(std::vector({16, 16}), 2.0, phi::DataType::FLOAT32, phi::GPUPlace()) .result(0); auto t2 = builder.Build(std::vector({z, w})).result(0); auto out2 = builder.Build(t2, 1).result(0); auto out = builder.Build(out1, out2).result(0); builder.Build(out, "out", 0); } TEST(constant_folding, ConstantFolding_Combine) { pir::IrContext *ctx = pir::IrContext::Instance(); ctx->GetOrRegisterDialect(); ctx->GetOrRegisterDialect(); pir::Program program(ctx); BuildConcatProgram(&program, ctx); pir::PassManager pm(ctx); std::unique_ptr constant_folding_pass = pir::CreateConstantFoldingPass(); phi::Place place = phi::CPUPlace(); constant_folding_pass->SetNotOwned(pir::Pass::kPlaceAttr, &place); constant_folding_pass->Set(pir::Pass::kParamScopeAttr, new paddle::framework::Scope()); pm.AddPass(std::move(constant_folding_pass)); pm.AddPass(pir::CreateDeadCodeEliminationPass()); pm.EnableIRPrinting(); PADDLE_ENFORCE_EQ(pm.Run(&program), true, common::errors::ExecutionTimeout( "Sorry, the test for program is timeout.")); EXPECT_EQ(program.block()->size(), 2u); } void BuildMultiOutputProgram(pir::Program *program, pir::IrContext *ctx) { pir::Builder builder = pir::Builder(ctx, program->block()); auto x = builder .Build(std::vector({2, 2}), 2.0, phi::DataType::FLOAT32, phi::GPUPlace()) .result(0); auto cummax_op = builder.Build(x, 0); auto out = cummax_op.out(); auto indices = cummax_op.indices(); builder.Build(out, "out", 0); builder.Build(indices, "indices", 1); } TEST(constant_folding, ConstantFolding_MultiOutput) { pir::IrContext *ctx = pir::IrContext::Instance(); ctx->GetOrRegisterDialect(); ctx->GetOrRegisterDialect(); pir::Program program(ctx); BuildMultiOutputProgram(&program, ctx); pir::PassManager pm(ctx); std::unique_ptr constant_folding_pass = pir::CreateConstantFoldingPass(); phi::Place place = phi::CPUPlace(); constant_folding_pass->SetNotOwned(pir::Pass::kPlaceAttr, &place); constant_folding_pass->Set(pir::Pass::kParamScopeAttr, new paddle::framework::Scope()); pm.AddPass(std::move(constant_folding_pass)); pm.AddPass(pir::CreateDeadCodeEliminationPass()); pm.EnableIRPrinting(); PADDLE_ENFORCE_EQ(pm.Run(&program), true, common::errors::ExecutionTimeout( "Sorry, the test for program is timeout.")); EXPECT_EQ(program.block()->size(), 4u); }