// 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/replace_cross_thread_reduction.h" #include "paddle/cinn/hlir/pe/reduction.h" #include "paddle/cinn/ir/schedule/ir_schedule_util.h" #include "paddle/cinn/lang/compute.h" #include "paddle/cinn/pass/pass_manager.h" namespace cinn { namespace optim { namespace { struct BufferCmp { bool operator()(const ir::Buffer& a, const ir::Buffer& b) const { if (a->name == b->name) return false; return true; } }; thread_local std::set shm_buffer_; struct CrossThreadReductionReplacer { void operator()(ir::LoweredFunc fn) { Visit(fn.As()); } private: bool CanReplace(const ir::stmt::Schedule block) { if (block->name().substr(0, 4) == "root") { return false; } if (cur_loops_.empty()) { return false; } const auto& innermost_loop = cur_loops_.back(); if (!innermost_loop->is_gpu_thread_binded()) { return false; } const std::vector& iter_values = block->iter_values(); const std::vector& iter_vars = block->iter_vars(); std::unordered_set reduce_var_names; for (int i = 0; i < iter_values.size(); ++i) { if (!iter_vars[i]->is_reduce_axis) { continue; } ir::ir_utils::CollectIRNodesWithoutTensor( iter_values[i], [&](const ir::Expr* x) { if (x->as_var()) { reduce_var_names.insert(x->as_var()->name); } return false; }); } return reduce_var_names.count(innermost_loop->loop_var()->name) > 0; } int GetBlockSize() const { int block_size = 1; for (auto& loop : cur_loops_) { if (loop->is_gpu_thread_binded()) { block_size *= ir::GetLoopExtent(loop); } } return block_size; } ir::Expr GetRightOperand(ir::Expr expr) { switch (expr->node_type()) { #define CASE_NODE_TYPE(__Ty) \ case ir::IrNodeTy::__Ty: \ return expr.As()->b() CASE_NODE_TYPE(Add); CASE_NODE_TYPE(Mul); CASE_NODE_TYPE(Max); CASE_NODE_TYPE(Min); CASE_NODE_TYPE(And); CASE_NODE_TYPE(Or); #undef CASE_NODE_TYPE default: PADDLE_THROW(::common::errors::InvalidArgument( "Unsupported operation in cross-thread reduction: %s", expr)); } } void ReplaceByNoReduce(ir::stmt::Store store) { // NoReduce refers to fake cross-thread reduction such as: // for (threadIdx.y, 0, 1) { // var_1[0] = var_1[0] + var_0[0] // } // This should be replaced by: // var_1[0] = var_0[0] ir::Expr operand = GetRightOperand(store->value()); store->set_value(operand); } void ReplaceByContinuousReduceExternCall(ir::stmt::Store store, bool return_warp) { ir::Expr operand = GetRightOperand(store->value()); std::string reduce_func_name = hlir::pe::CrossThreadReduceExternalFuncName( store->value(), operand.template As()->tensor); auto tmp_dtype = operand.template As()->tensor.as_tensor()->type(); auto tmp_buffer = ir::_Buffer_::Make( "shm32_" + hlir::pe::Type2StrForReduce(tmp_dtype) + "_reduce", {ir::Expr(32)}); tmp_buffer->dtype = tmp_dtype; tmp_buffer->memory_type = ir::MemoryType::GPUShared; shm_buffer_.insert(tmp_buffer); store->set_value(lang::CallExtern( reduce_func_name, {operand, tmp_buffer, ir::Expr(return_warp)})); } void ReplaceByDiscreteReduceExternCall(ir::stmt::Store store) { ir::Expr operand = GetRightOperand(store->value()); std::string reduce_func_name = hlir::pe::DiscreteReduceExternalFuncName( store->value(), operand.template As()->tensor); auto tmp_dtype = operand.template As()->tensor.as_tensor()->type(); auto tmp_buffer = ir::_Buffer_::Make( "shm32_" + hlir::pe::Type2StrForReduce(tmp_dtype) + "_reduce", {ir::Expr(GetBlockSize())}); tmp_buffer->dtype = tmp_dtype; tmp_buffer->memory_type = ir::MemoryType::GPUShared; shm_buffer_.insert(tmp_buffer); store->set_value(lang::CallExtern(reduce_func_name, {operand, tmp_buffer})); } void ReplaceByReduceExternCall(ir::stmt::Store store, const ir::ReduceMethod& method) { std::visit( cinn::adt::match{ [&](const ir::NoneReduceMethod&) { ReplaceByNoReduce(store); }, [&](const ir::WarpReduceMethod&) { ReplaceByContinuousReduceExternCall(store, true); }, [&](const ir::BlockReduceMethod&) { ReplaceByContinuousReduceExternCall(store, false); }, [&](const ir::DiscreteReduceMethod&) { ReplaceByDiscreteReduceExternCall(store); }}, method); } void Visit(ir::_LoweredFunc_* fn) { ir::stmt::Mutate( fn->body_block, [&](ir::stmt::StmtRef stmt) { PreCall(stmt); }, [&](ir::stmt::StmtRef stmt) { PostCall(stmt); }); if (std::find_if(fn->temp_bufs.begin(), fn->temp_bufs.end(), [&](const ir::Buffer& buf) -> bool { for (auto& tmp_buf : shm_buffer_) { if (buf->name == tmp_buf->name) return true; } return false; }) == fn->temp_bufs.end()) fn->temp_bufs.insert( fn->temp_bufs.end(), shm_buffer_.begin(), shm_buffer_.end()); shm_buffer_.clear(); } void PreCall(ir::stmt::StmtRef stmt) { switch (stmt->stmt_type()) { case ir::StmtNodeTy::Schedule: VisitStmt(stmt.as()); break; case ir::StmtNodeTy::For: cur_loops_.push_back(stmt.as()); break; default: break; } } void PostCall(ir::stmt::StmtRef stmt) { switch (stmt->stmt_type()) { case ir::StmtNodeTy::For: cur_loops_.pop_back(); break; default: break; } } void VisitStmt(ir::stmt::Schedule stmt) { if (!CanReplace(stmt)) { return; } ir::stmt::BlockRef original_update_body = stmt->body(); PADDLE_ENFORCE_EQ(original_update_body->stmts().size(), 1, ::common::errors::InvalidArgument( "The size of statements is incorrect." "Expected size is 1, but receive %d.", original_update_body->stmts().size())); PADDLE_ENFORCE_EQ(original_update_body->stmts()[0].isa(), true, ::common::errors::InvalidArgument( "The stmt in schedule's body should be store " "statement, but get %s.", original_update_body->stmts()[0]->stmt_type())); ir::stmt::Store original_update_stmt = original_update_body->stmts()[0].as(); ReplaceByReduceExternCall(original_update_stmt, stmt->reduce_method()); } private: std::vector cur_loops_; }; } // namespace void ReplaceCrossThreadReduction(ir::LoweredFunc fn) { FuncPassManager manager; manager.AddPass(std::make_unique()); manager.Run(fn); } LogicalResult ReplaceCrossThreadReductionPass::Run(ir::LoweredFunc func) { CrossThreadReductionReplacer replacer; replacer(func); return LogicalResult::success(); } } // namespace optim } // namespace cinn