// Copyright (c) 2021 CINN 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 "paddle/cinn/optim/transform_gpu_forloop.h" #include #include #include #include #include #include "paddle/cinn/backends/cuda_util.h" #include "paddle/cinn/common/integer_set.h" #include "paddle/cinn/common/ir_util.h" #include "paddle/cinn/ir/ir.h" #include "paddle/cinn/ir/ir_mutator.h" #include "paddle/cinn/ir/ir_printer.h" #include "paddle/cinn/ir/stmt_visitors.h" #include "paddle/cinn/ir/utils/ir_copy.h" #include "paddle/cinn/ir/utils/stmt_converter.h" #include "paddle/cinn/optim/eliminate_common_factor_of_local_index.h" #include "paddle/cinn/optim/ir_simplify.h" #include "paddle/cinn/optim/replace_var_with_expr.h" #include "paddle/cinn/optim/resize_buffer.h" #include "paddle/cinn/optim/update_buffer_axis_pass.h" #include "paddle/cinn/pass/pass_manager.h" #include "paddle/cinn/runtime/intrinsic.h" #include "paddle/cinn/utils/string.h" #include "paddle/common/enforce.h" namespace cinn { namespace optim { class GPUForLoopsMutator { public: void operator()(ir::stmt::BlockRef block) { VisitBlock(block); } explicit GPUForLoopsMutator(const ir::CudaAxisInfo &cuda_axis_info) : cuda_axis_info_(cuda_axis_info) {} private: void VisitBlock(ir::stmt::BlockRef block) { std::vector stmts = block->stmts(); std::vector new_stmts; for (ir::stmt::StmtRef &stmt : stmts) { switch (stmt->stmt_type()) { case ir::StmtNodeTy::For: { ir::stmt::For for_stmt = stmt.as(); switch (VisitStmt(for_stmt)) { case 0: { ReplaceForloopWithIfThenElse(stmt); ir::stmt::IfThenElse if_stmt = stmt.as(); // Visit true case only VisitBlock(if_stmt->true_case()); new_stmts.push_back(if_stmt); break; } case 1: { VisitBlock(for_stmt->body()); for (const auto &stmt : for_stmt->body()->stmts()) { new_stmts.push_back(stmt); } break; } case 2: { VisitBlock(for_stmt->body()); new_stmts.push_back(for_stmt); break; } default: break; } break; } case ir::StmtNodeTy::Schedule: { ir::stmt::Schedule schedule = stmt.as(); VisitBlock(schedule->body()); new_stmts.push_back(stmt); break; } case ir::StmtNodeTy::IfThenElse: { ir::stmt::IfThenElse if_then_else = stmt.as(); VisitBlock(if_then_else->true_case()); if (if_then_else->false_case().defined()) { VisitBlock(if_then_else->true_case()); } new_stmts.push_back(stmt); break; } default: new_stmts.push_back(stmt); break; } } block->set_stmts(new_stmts); } // NOLINTNEXTLINE(runtime/references) int VisitStmt(const ir::stmt::For &stmt) { if (stmt->for_type() == ir::ForType::GPUBlock || stmt->for_type() == ir::ForType::GPUThread) { if (NeedToReplaceForloopWithIfThenElse(stmt)) { // Replace the GPU For loop with an IfThenElse. return 0; } else { // Replace the GPU For loop with its body. return 1; } } // Keep this For loop, traverse the body of it. return 2; } bool NeedToReplaceForloopWithIfThenElse(const ir::stmt::For &stmt) const { // If the loop doesn't start from 0. if (stmt->min() != cinn::common::make_const(0)) { return true; } // Get dim_size from the functions's cuda_axis_info as pre-condition. ir::Expr dim_size; switch (stmt->bind_info().for_type) { case ir::ForType::GPUThread: dim_size = cuda_axis_info_.block_dim(stmt->bind_info().offset); break; case ir::ForType::GPUBlock: dim_size = cuda_axis_info_.grid_dim(stmt->bind_info().offset); break; } if (!dim_size.defined()) { return true; } // If we can prove the loop's extent >= dim_size, then it's safe not // to add the IfThenElse guard. common::cas_intervals_t var_intervals = common::CollectVarIntervalsOfExprs({stmt->extent(), dim_size}); common::SymbolicExprAnalyzer analyzer{var_intervals}; std::optional proved_ge = analyzer.ProveGE(stmt->extent(), dim_size); if (proved_ge.value_or(false)) { return false; } return true; } // NOLINTNEXTLINE(runtime/references) void ReplaceForloopWithIfThenElse(ir::stmt::StmtRef &stmt) { ir::stmt::For for_n = stmt.as(); Expr condition; const auto AppendCondition = [&](Expr new_cond) { if (condition.defined()) { condition = ir::And::Make(condition, new_cond); } else { condition = new_cond; } }; // for(i, 2, 100); // ^ if (for_n->min() != cinn::common::make_const(0)) { AppendCondition(ir::GE::Make(for_n->loop_var(), for_n->min())); } // for(i, 2, min(M/2, 20) // ^ AppendCondition(ir::LT::Make(for_n->loop_var(), for_n->extent())); PADDLE_ENFORCE_EQ(condition.defined(), true, ::common::errors::InvalidArgument( "Condition is not defined, please check.")); stmt = ir::stmt::IfThenElse(condition, for_n->body()); } ir::CudaAxisInfo cuda_axis_info_; }; LogicalResult RemoveGpuForLoopsPass::Run(ir::LoweredFunc fn) { GPUForLoopsMutator mutator(fn->cuda_axis_info); mutator(fn->body_block); return LogicalResult::success(); } std::unique_ptr CreateRemoveGpuForLoopsPass() { return std::make_unique(); } /** * The generated __syncthreads call will be wrapped with a `if (xxxx == 0) { }`, * this is the problem of isl AST output, drop it to make it run in all the * threads. */ class DropIfThenElseMutator { public: void operator()(ir::stmt::BlockRef block) { VisitBlock(block); } private: bool isDropCandidate(const ir::stmt::IfThenElse &stmt) { if (!stmt->condition().defined()) return false; const ir::Expr &cond = stmt->condition(); if (auto *eq_n = cond.As()) { if (eq_n->b() == cinn::common::make_const(0)) { ir::stmt::BlockRef true_case = stmt->true_case(); if (true_case.defined() && true_case->stmts().size() == 1) { auto eval_stmt = true_case->stmts()[0]; if (eval_stmt->stmt_type() == ir::StmtNodeTy::Evaluate) { auto eval_expr = eval_stmt.as()->value(); if (auto *call = eval_expr.As()) { if (call->name == runtime::intrinsic::cuda_sync_threads) { return true; } } } } } } return false; } void VisitBlock(ir::stmt::BlockRef block) { std::vector stmts = block->stmts(); std::vector new_stmts; for (ir::stmt::StmtRef &stmt : stmts) { switch (stmt->stmt_type()) { case ir::StmtNodeTy::IfThenElse: { const ir::stmt::IfThenElse &if_node = stmt.as(); if (isDropCandidate(if_node)) { const ir::stmt::BlockRef true_case = if_node->true_case(); for (const auto &true_stmt : true_case->stmts()) { new_stmts.push_back(true_stmt); } } else { new_stmts.push_back(stmt); } } break; case ir::StmtNodeTy::For: { ir::stmt::For for_stmt = stmt.as(); VisitBlock(for_stmt->body()); new_stmts.push_back(stmt); } break; case ir::StmtNodeTy::Schedule: { ir::stmt::Schedule schedule = stmt.as(); VisitBlock(schedule->body()); new_stmts.push_back(stmt); } break; default: new_stmts.push_back(stmt); break; } } block->set_stmts(new_stmts); } }; LogicalResult CudaSyncThreadsDropIfThenElsePass::Run(ir::stmt::BlockRef block) { DropIfThenElseMutator mutator; mutator(block); return LogicalResult::success(); } std::unique_ptr CreateCudaSyncThreadsDropIfThenElsePass() { return std::make_unique(); } class RestructureVarNodes : public ir::IRMutator<>, public ir::stmt::StmtMutator<> { public: void operator()(ir::stmt::BlockRef block) { VisitBlock(block); } private: void Visit(const ir::Load *load, Expr *op) override { std::vector indices_copied; for (const ir::Expr &indice : load->indices) { indices_copied.push_back(ir::ir_utils::IRCopy(indice)); } op->As()->indices = indices_copied; IRMutator::Visit(load, op); } void VisitStmt(ir::stmt::Store stmt) override { std::vector indices_copied; for (const ir::Expr &indice : stmt->indices()) { indices_copied.push_back(ir::ir_utils::IRCopy(indice)); } stmt->set_indices(indices_copied); ir::Expr value = stmt->value(); IRMutator::Visit(&value, &value); stmt->set_value(value); } void VisitStmt(ir::stmt::For stmt) override { operator()(stmt->body()); } void VisitStmt(ir::stmt::IfThenElse stmt) override { operator()(stmt->true_case()); if (stmt->false_case().defined()) { operator()(stmt->false_case()); } } void VisitStmt(ir::stmt::Schedule stmt) override { operator()(stmt->body()); } void VisitStmt(ir::stmt::Let stmt) override { ir::Expr body = stmt->body(); IRMutator::Visit(&body, &body); stmt->set_body(body); } void VisitStmt(ir::stmt::Alloc) override {} void VisitStmt(ir::stmt::Evaluate) override {} void VisitStmt(ir::stmt::Free) override {} }; class ReplaceIndexToBindExpr { public: void operator()(ir::stmt::BlockRef block) { for (ir::stmt::StmtRef stmt : block->stmts()) { switch (stmt->stmt_type()) { case ir::StmtNodeTy::For: { operator()(stmt.as()->body()); break; } case ir::StmtNodeTy::Schedule: { VisitStmt(stmt.as()); break; } case ir::StmtNodeTy::IfThenElse: { ir::stmt::IfThenElse if_node = stmt.as(); operator()(if_node->true_case()); if (if_node->false_case().defined()) { operator()(if_node->false_case()); } break; } default: break; } } } private: void VisitStmt(ir::stmt::Schedule stmt) { std::vector iter_values = stmt->iter_values(); std::vector iter_vars = stmt->iter_vars(); ir::stmt::BlockRef body = stmt->body(); PADDLE_ENFORCE_EQ(iter_values.size(), iter_vars.size(), ::common::errors::InvalidArgument( "The size of iter values and iter vars is not equal," "where iter values:%d but iter vars:%d.", iter_values.size(), iter_vars.size())); for (int idx = 0; idx < iter_values.size(); ++idx) { ReplaceVarWithExpr( body, iter_vars[idx], iter_values[idx]); } stmt->set_body(body); operator()(stmt->body()); } }; class ReplaceLoopVarToGpu { public: void operator()(ir::stmt::BlockRef block) { std::vector stmts = block->stmts(); for (ir::stmt::StmtRef stmt : stmts) { switch (stmt->stmt_type()) { case ir::StmtNodeTy::For: { VisitStmt(stmt.as()); break; } case ir::StmtNodeTy::Schedule: { operator()(stmt.as()->body()); break; } case ir::StmtNodeTy::IfThenElse: { ir::stmt::IfThenElse if_node = stmt.as(); operator()(if_node->true_case()); if (if_node->false_case().defined()) { operator()(if_node->false_case()); } break; } default: break; } } block->set_stmts(stmts); } private: void VisitStmt(ir::stmt::For stmt) { auto bind_info = stmt->bind_info(); std::string var_name = ""; if (bind_info.offset <= 0) var_name = "x"; else if (bind_info.offset == 1) var_name = "y"; else if (bind_info.offset == 2) var_name = "z"; if (stmt->is_gpu_block_binded()) { var_name = "blockIdx." + var_name; optim::ReplaceVarWithExpr( stmt, stmt->loop_var(), ir::Expr(ir::Var(var_name))); } else if (stmt->is_gpu_thread_binded()) { var_name = "threadIdx." + var_name; optim::ReplaceVarWithExpr( stmt, stmt->loop_var(), ir::Expr(ir::Var(var_name))); } operator()(stmt->body()); } }; class SharedAxisVisitor : public ir::IRMutator<>, public ir::stmt::StmtMutator<> { public: void operator()(ir::Expr *expr) { ir::IRMutator<>::Visit(expr, expr); } void operator()(ir::stmt::BlockRef block) { ir::stmt::StmtMutator<>::VisitBlock(block); } private: void VisitStmt(ir::stmt::Store stmt) override { if (!stmt->tensor().as_tensor_ref()->buffer.defined()) { return; } if (stmt->tensor().as_tensor_ref()->buffer->memory_type == ir::MemoryType::GPUShared) { std::vector indices = stmt->indices(); for (ir::Expr &index : indices) { for (const std::string &axis : gpu_axis) { optim::ReplaceVarWithExpr( &index, ir::Var(axis), ir::Expr(0)); } index = cinn::optim::ArithSimplify(index); } stmt->set_indices(indices); } ir::Expr value = stmt->value(); ir::IRMutator<>::Visit(&value, &value); stmt->set_value(value); } void Visit(const ir::Load *op, Expr *expr) override { auto load = expr->As(); if (load->is_addr_scalar()) { return; } if (!load->tensor.as_tensor_ref()->buffer.defined()) { return; } if (load->tensor.as_tensor_ref()->buffer->memory_type == ir::MemoryType::GPUShared) { for (auto &index : load->indices) { for (const std::string &axis : gpu_axis) { optim::ReplaceVarWithExpr( &index, ir::Var(axis), ir::Expr(0)); } index = cinn::optim::ArithSimplify(index); } } ir::IRMutator<>::Visit(op, expr); } void VisitStmt(ir::stmt::For stmt) override { ir::Expr min = stmt->min(); ir::Expr extent = stmt->extent(); operator()(&min); operator()(&extent); stmt->set_min(min); stmt->set_extent(extent); operator()(stmt->body()); } void VisitStmt(ir::stmt::IfThenElse stmt) override { ir::Expr condition = stmt->condition(); operator()(&condition); stmt->set_condition(condition); operator()(stmt->true_case()); if (stmt->false_case().defined()) { operator()(stmt->false_case()); } } void VisitStmt(ir::stmt::Schedule stmt) override { std::vector iter_values = stmt->iter_values(); for (ir::Expr &iter_value : iter_values) { operator()(&iter_value); } stmt->set_iter_values(iter_values); operator()(stmt->body()); } void VisitStmt(ir::stmt::Let stmt) override { ir::Expr body = stmt->body(); ir::IRMutator<>::Visit(&body, &body); stmt->set_body(body); } void VisitStmt(ir::stmt::Alloc) override {} void VisitStmt(ir::stmt::Evaluate) override {} void VisitStmt(ir::stmt::Free) override {} const std::vector gpu_axis = { "blockIdx.x", "blockIdx.y", "blockIdx.z"}; }; class LocalAxisVisitor : public ir::IRMutator<>, public ir::stmt::StmtMutator<> { public: void operator()(ir::Expr *expr) { ir::IRMutator<>::Visit(expr, expr); } void operator()(ir::stmt::BlockRef block) { ir::stmt::StmtMutator<>::VisitBlock(block); } private: void VisitStmt(ir::stmt::Store stmt) override { ir::Expr value = stmt->value(); operator()(&value); stmt->set_value(value); if (!stmt->tensor().as_tensor_ref()->buffer.defined()) { return; } if (stmt->tensor().as_tensor_ref()->buffer->memory_type == ir::MemoryType::GPULocal) { std::vector indices = stmt->indices(); for (ir::Expr &index : indices) { for (const std::string &axis : gpu_axis) { optim::ReplaceVarWithExpr( &index, ir::Var(axis), ir::Expr(0)); } index = cinn::optim::ArithSimplify(index); } stmt->set_indices(indices); } } void Visit(const ir::Load *op, Expr *expr) override { auto load = expr->As(); if (load->is_addr_scalar()) { return; } if (!load->tensor.as_tensor_ref()->buffer.defined()) { return; } if (load->tensor.as_tensor_ref()->buffer->memory_type == ir::MemoryType::GPULocal) { for (ir::Expr &index : load->indices) { for (const std::string &axis : gpu_axis) { optim::ReplaceVarWithExpr(&index, ir::Var(axis), ir::Expr(0)); } index = cinn::optim::ArithSimplify(index); } } ir::IRMutator<>::Visit(op, expr); } void VisitStmt(ir::stmt::For stmt) override { operator()(stmt->body()); } void VisitStmt(ir::stmt::IfThenElse stmt) override { operator()(stmt->true_case()); if (stmt->false_case().defined()) { operator()(stmt->false_case()); } } void VisitStmt(ir::stmt::Schedule stmt) override { std::vector iter_values = stmt->iter_values(); for (ir::Expr &iter_value : iter_values) { operator()(&iter_value); } stmt->set_iter_values(iter_values); operator()(stmt->body()); } void VisitStmt(ir::stmt::Let stmt) override { ir::Expr body = stmt->body(); ir::IRMutator<>::Visit(&body, &body); stmt->set_body(body); } void VisitStmt(ir::stmt::Alloc) override {} void VisitStmt(ir::stmt::Evaluate) override {} void VisitStmt(ir::stmt::Free) override {} const std::vector gpu_axis = {"blockIdx.x", "blockIdx.y", "blockIdx.z", "threadIdx.x", "threadIdx.y", "threadIdx.z"}; }; class ReplaceUnitVarToZero : public ir::IRMutator<>, public ir::stmt::StmtMutator<> { public: void operator()(ir::Expr *expr) { ir::IRMutator<>::Visit(expr, expr); } void operator()(ir::stmt::BlockRef block) { ir::stmt::StmtMutator<>::VisitBlock(block); } private: void VisitStmt(ir::stmt::Store stmt) override { if (!stmt->tensor().as_tensor_ref()->buffer.defined()) { return; } std::vector indices = stmt->indices(); for (ir::Expr &index : indices) { for (const std::string &var_ : loop_var_) { optim::ReplaceVarWithExpr( &index, ir::Var(var_), ir::Expr(0)); } index = cinn::optim::ArithSimplify(index); } stmt->set_indices(indices); ir::Expr value = stmt->value(); operator()(&value); stmt->set_value(value); } void Visit(const ir::Load *op, Expr *expr) override { auto load = expr->As(); if (!load->tensor.as_tensor_ref()->buffer.defined()) { return; } auto &indices = load->indices; for (auto &index : indices) { for (const std::string &var_ : loop_var_) { optim::ReplaceVarWithExpr( &index, ir::Var(var_), ir::Expr(0)); } index = cinn::optim::ArithSimplify(index); } ir::IRMutator<>::Visit(op, expr); } void VisitStmt(ir::stmt::For stmt) override { auto var_name = stmt->loop_var()->name; auto extent_i = stmt->extent(); if (extent_i.is_constant() && extent_i.as_int64() == 1) loop_var_.insert(var_name); operator()(stmt->body()); loop_var_.erase(var_name); } void VisitStmt(ir::stmt::IfThenElse stmt) override { operator()(stmt->true_case()); if (stmt->false_case().defined()) { operator()(stmt->false_case()); } } void VisitStmt(ir::stmt::Schedule stmt) override { std::vector iter_values = stmt->iter_values(); for (ir::Expr &iter_value : iter_values) { operator()(&iter_value); } stmt->set_iter_values(iter_values); operator()(stmt->body()); } void VisitStmt(ir::stmt::Let stmt) override { ir::Expr body = stmt->body(); ir::IRMutator<>::Visit(&body, &body); stmt->set_body(body); } void VisitStmt(ir::stmt::Alloc) override {} void VisitStmt(ir::stmt::Evaluate) override {} void VisitStmt(ir::stmt::Free) override {} std::unordered_set loop_var_; }; // void OptimizeExprGPU(Expr *expr) { void OptimizeExprGPU(ir::stmt::BlockRef block) { VLOG(4) << "Before Optimize Expr:\n" << block; // ir::stmt::BlockRef block = ir::ConvertExprBlockToStmtBlock(*expr); // Make independent copies for each load/store's indices to prevent cross // modification in later passes. RestructureVarNodes restructure_var_nodes; restructure_var_nodes(block); // Replace iter_vars used in ScheduleBlocks to their corresponding // iter_values in ScheduleBlockRealizes. ReplaceIndexToBindExpr replace_index_to_bind_expr; replace_index_to_bind_expr(block); // resize buffer axis BlockPassManager pass_manager; pass_manager.AddPass(optim::CreateUpdateBufferAxisPass()); pass_manager.Run(block); ir::Expr new_expr = ir::ConvertStmtBlockToExprBlock(block); // Replace variables bound on block/thread to the actual // blockIdx/threadIdx. VLOG(4) << "Before ReplaceLoopVarToGpu: \n" << block; ReplaceLoopVarToGpu replace_loop_var_to_gpu; replace_loop_var_to_gpu(block); VLOG(4) << "After ReplaceLoopVarToGpu: \n" << block; // Replace blockIdx in shared memory's indices to zero, because shared // memory cannot be accessed from another block. SharedAxisVisitor shared_axis_visitor; shared_axis_visitor(block); // Replace blockIdx/threadIdx in local buffer's indices to zero, because // local buffers cannot be accessed from another block/thread. LocalAxisVisitor local_axis_visitor; local_axis_visitor(block); // Replace variables that are in range [0, 1) to zero. ReplaceUnitVarToZero replace_unit_var_to_zero; replace_unit_var_to_zero(block); EliminateCommonFactorOfLocalIndex(block); VLOG(10) << "After EliminateCommonFactorOfLocalIndex: \n" << block; ir::Expr expr = ir::ConvertStmtBlockToExprBlock(block); ResizeBufferToMaxVarRange(&expr); VLOG(4) << "After Optimize Expr: \n" << expr; } } // namespace optim } // namespace cinn