Files
paddlepaddle--paddle/paddle/cinn/optim/eliminate_invariant_loop.cc
T
2026-07-13 12:40:42 +08:00

135 lines
4.5 KiB
C++

// Copyright (c) 2024 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/eliminate_invariant_loop.h"
#include "paddle/cinn/common/ir_util.h"
#include "paddle/cinn/ir/ir_analyzer/ir_analyzer.h"
#include "paddle/cinn/ir/ir_mutator.h"
#include "paddle/cinn/ir/utils/ir_replace.h"
#include "paddle/cinn/optim/replace_var_with_expr.h"
namespace cinn {
namespace optim {
namespace {
// Check whether var is used in the block's store indices or value.
bool HasVarInIndicesOrValue(const ir::Expr& block, const ir::Var& var) {
auto* block_realize = block.As<ir::ScheduleBlockRealize>();
auto* schedule_block = block_realize->schedule_block.As<ir::ScheduleBlock>();
// collect iter vars that refer to `var`
std::set<std::string> ref_iter_vars;
for (int i = 0; i < block_realize->iter_values.size(); ++i) {
auto var_use = ir::ir_utils::CollectIRNodes(
block_realize->iter_values[i],
[&](const ir::Expr* x) {
return x->is_var() && x->as_var_ref() == var;
},
/* uniq_target = */ true);
if (var_use.size() > 0) {
ref_iter_vars.insert(schedule_block->iter_vars[i]->name);
}
}
ir::Expr store = ir::analyzer::GetStoreOfSBlock(block);
auto var_use = ir::ir_utils::CollectIRNodes(
store,
[&](const ir::Expr* x) {
if (x->is_var()) {
if (x->as_var_ref() == var) return true;
if (ref_iter_vars.count(x->as_var()->name) > 0) return true;
}
return false;
},
/* uniq_target = */ true);
return var_use.size() > 0;
}
// Check whether the block is writing to a buffer whose scope is smaller than
// the For node's scope.
bool HasWriteToSmallerScope(const ir::Expr& block, const ir::For* for_node) {
ir::Expr store_tensor = ir::analyzer::GetStoreTensorOfSBlock(block);
ir::MemoryType memory_type = store_tensor.as_tensor()->buffer->memory_type;
if (for_node->is_gpu_thread_binded()) {
if (memory_type == ir::MemoryType::GPULocal) {
return true;
}
} else if (for_node->is_gpu_block_binded()) {
if (memory_type == ir::MemoryType::GPULocal ||
memory_type == ir::MemoryType::GPUShared) {
return true;
}
}
return false;
}
struct InvariantLoopEliminator : public ir::IRMutator<> {
using ir::IRMutator<>::Visit;
void operator()(ir::Expr* expr) { ir::IRMutator<>::Visit(expr, expr); }
void Visit(const ir::For* op, ir::Expr* expr) override {
auto* node = expr->As<ir::For>();
ir::IRMutator<>::Visit(&node->body, &node->body);
// do nothing if we are outside the root schedule block
if (!root_) return;
// check that all child schedule blocks satisfy the four rules
std::vector<ir::Expr> child_blocks = ir::analyzer::GetChildBlocks(*expr);
ir::Var loop_var = node->loop_var;
for (auto& block : child_blocks) {
if (HasVarInIndicesOrValue(block, loop_var)) return;
if (ir::analyzer::IsReductionSBlock(block)) return;
if (node->is_binded()) {
if (HasWriteToSmallerScope(block, node)) return;
if (!ir::analyzer::GetConsumerSBlocks(block, *root_).empty()) return;
}
}
// now we can eliminate this For node
ir::Expr body = node->body;
ir::ir_utils::IrReplaceVarBroadcast(&body, ir::Expr(loop_var), ir::Expr(0));
if (node->is_binded()) {
ir::Expr cond = ir::EQ::Make(loop_var, ir::Expr(0));
ir::Expr wrapped_body = ir::IfThenElse::Make(cond, body);
node->body = ir::Block::Make({wrapped_body});
} else {
*expr = body;
}
}
void Visit(const ir::ScheduleBlockRealize* op, ir::Expr* expr) override {
auto* schedule_block = op->schedule_block.As<ir::ScheduleBlock>();
// find and set the root schedule block
if (schedule_block->name.substr(0, 4) == "root") {
root_ = expr;
ir::IRMutator<>::Visit(op, expr);
root_ = nullptr;
}
}
private:
const ir::Expr* root_{nullptr};
};
} // namespace
void EliminateInvariantLoop(ir::Expr* expr) { InvariantLoopEliminator()(expr); }
} // namespace optim
} // namespace cinn