Files
paddlepaddle--paddle/paddle/fluid/framework/ir/delete_repeated_ops_pass.cc
T
2026-07-13 12:40:42 +08:00

310 lines
10 KiB
C++

// 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 <string>
#include "paddle/fluid/framework/ir/fuse_pass_base.h"
#include "paddle/fluid/framework/ir/graph_pattern_detector.h"
#include "paddle/fluid/framework/ir/pass.h"
#include "paddle/fluid/framework/op_version_registry.h"
#include "paddle/fluid/platform/enforce.h"
namespace phi {
class DenseTensor;
} // namespace phi
namespace paddle::framework {
class Scope;
} // namespace paddle::framework
namespace paddle::framework::ir {
bool HasOutVarName(Node* op_node, std::string name) {
auto* op_desc = op_node->Op();
auto outputs = op_desc->Outputs();
for (auto const& iter : outputs) {
auto out_names = iter.second;
if (std::count(out_names.begin(), out_names.end(), name) > 0) {
return true;
}
}
return false;
}
} // namespace paddle::framework::ir
namespace paddle::framework::ir::patterns {
struct VarWithRepeatedOpsPattern : public PatternBase {
VarWithRepeatedOpsPattern(PDPattern* pattern,
const std::string& name_scope,
const std::string& op_type);
// declare variable node's name
PATTERN_DECL_NODE(in_var);
std::string op_type_;
};
VarWithRepeatedOpsPattern::VarWithRepeatedOpsPattern(
PDPattern* pattern,
const std::string& name_scope,
const std::string& op_type)
: PatternBase(pattern, name_scope, name_scope), op_type_(op_type) {
pattern->NewNode(in_var_repr())
->assert_is_var()
->assert_more([&](Node* node) {
auto out_nodes = node->outputs;
if (out_nodes.size() <= 1) return false;
int op_counts = 0;
for (auto* next_op : out_nodes) {
if (next_op->Name() == op_type_) {
op_counts++;
}
}
return op_counts > 1;
});
}
} // namespace paddle::framework::ir::patterns
namespace paddle::framework::ir {
/*
Delete repeated ops, for example:
Origin subgraph:
(input_variable)
/ | \ ...
shape shape shape ...
| | | ...
op0 op1 op2 ...
Optimized subgraph:
(input_variable)
|
shape
/ | \ ...
op0 op1 op2 ...
*/
class DeleteRepeatedOpsPass : public FusePassBase {
protected:
void ApplyImpl(ir::Graph* graph) const override;
private:
void DeleteRepeatedOps(ir::Graph* graph,
const std::string& op_type,
std::function<std::string(Node*)> gen_op_key_fn) const;
const std::string name_scope_{"delete_repeated_ops_pass"};
mutable int delete_op_count{0};
};
void DeleteRepeatedOpsPass::DeleteRepeatedOps(
ir::Graph* graph,
const std::string& op_type,
std::function<std::string(Node*)> gen_op_key_fn) const {
GraphPatternDetector gpd;
patterns::VarWithRepeatedOpsPattern pattern(
gpd.mutable_pattern(), name_scope_, op_type);
int delete_counts = 0;
auto handler = [&](const GraphPatternDetector::subgraph_t& subgraph,
Graph* graph) {
VLOG(4) << "handle DeleteRepeatedOps";
GET_IR_NODE_FROM_SUBGRAPH(in_var, in_var, pattern);
// in_var node may be deleted by the previous detected subgraph
if (graph->Nodes().count(in_var) == 0) {
return;
}
std::vector<std::string> invalid_out_ops{
"while", "conditional_block", "fetch"};
std::map<std::string, std::vector<Node*>> ops_map;
for (auto* next_op : in_var->outputs) {
if (next_op->Name() != op_type) continue;
auto* op = next_op;
bool out_op_is_invalid = false;
for (auto* out_op : op->outputs[0]->outputs) {
if (std::count(invalid_out_ops.begin(),
invalid_out_ops.end(),
out_op->Name()) > 0 ||
HasOutVarName(out_op, op->outputs[0]->Name())) {
out_op_is_invalid = true;
break;
}
}
if (out_op_is_invalid) continue;
auto attr_key = gen_op_key_fn(op);
ops_map[attr_key].push_back(op);
}
for (auto iter = ops_map.begin(); iter != ops_map.end();) {
if (iter->second.size() <= 1) {
iter = ops_map.erase(iter);
} else {
iter++;
}
}
for (auto const& iter : ops_map) {
auto ops = iter.second;
auto* first_op_out = ops[0]->outputs[0];
auto first_op_out_name = first_op_out->Name();
std::unordered_set<const Node*> delete_nodes;
for (size_t i = 1; i < ops.size(); i++) {
auto* cur_op = ops[i];
auto* cur_op_out = cur_op->outputs[0];
auto cur_op_out_name = cur_op_out->Name();
for (auto* out_op : cur_op_out->outputs) {
out_op->Op()->RenameInput(cur_op_out_name, first_op_out_name);
IR_NODE_LINK_TO(first_op_out, out_op);
}
delete_nodes.insert(cur_op);
delete_nodes.insert(cur_op_out);
delete_counts++;
}
GraphSafeRemoveNodes(graph, delete_nodes);
}
};
gpd(graph, handler);
delete_op_count += delete_counts;
if (delete_counts > 0) {
LOG(INFO) << "--- delete " << delete_counts << " repeated " << op_type
<< " ops";
}
}
std::string GenShapeAttrKey(Node* shape_op_node) { return ""; }
std::string GenSliceAttrKey(Node* slice_op_node) {
std::string attr_key;
auto slice_op_desc = slice_op_node->Op();
auto starts = slice_op_desc->GetAttrIfExists<std::vector<int>>("starts");
auto ends = slice_op_desc->GetAttrIfExists<std::vector<int>>("ends");
auto axes = slice_op_desc->GetAttrIfExists<std::vector<int>>("axes");
auto decrease_axis =
slice_op_desc->GetAttrIfExists<std::vector<int>>("decrease_axis");
attr_key += "starts_";
for (auto start : starts) {
attr_key += std::to_string(start) + "_";
}
attr_key += "ends_";
for (auto end : ends) {
attr_key += std::to_string(end) + "_";
}
attr_key += "axes_";
for (auto axis : axes) {
attr_key += std::to_string(axis) + "_";
}
attr_key += "decrease_axis_";
for (auto axis : decrease_axis) {
attr_key += std::to_string(axis) + "_";
}
return attr_key;
}
std::string GenCastAttrKey(Node* cast_op_node) {
auto cast_op_desc = cast_op_node->Op();
auto in_dtype = cast_op_desc->GetAttrIfExists<int>("in_dtype");
auto out_dtype = cast_op_desc->GetAttrIfExists<int>("out_dtype");
return "in_dtype_" + std::to_string(in_dtype) + "_out_dtype_" +
std::to_string(out_dtype);
}
std::string GenAddAttrKey(Node* add_op_node) {
auto add_op_desc = add_op_node->Op();
std::string x_name = add_op_desc->Input("X")[0];
std::string y_name = add_op_desc->Input("Y")[0];
auto axis = add_op_desc->GetAttrIfExists<int>("axis");
return x_name + "_" + y_name + "_axis_" + std::to_string(axis);
}
std::string GenTranspose2AttrKey(Node* transpose_op_node) {
auto transpose_op_desc = transpose_op_node->Op();
auto axis = transpose_op_desc->GetAttrIfExists<std::vector<int>>("axis");
std::string attr_key;
attr_key += "axis_";
for (auto x : axis) {
attr_key += std::to_string(x) + "_";
}
return attr_key;
}
std::string GenScaleAttrKey(Node* scale_op_node) {
auto scale_op_desc = scale_op_node->Op();
auto scale = scale_op_desc->GetAttrIfExists<float>("scale");
auto bias = scale_op_desc->GetAttrIfExists<float>("bias");
auto bias_after_scale =
scale_op_desc->GetAttrIfExists<bool>("bias_after_scale");
return "scale_" + std::to_string(scale) + "_bias_" + std::to_string(bias) +
"_bias_after_scale_" + std::to_string(bias_after_scale);
}
std::string GenGatherAttrKey(Node* gather_op_node) {
std::string input_names{""};
for (auto input_var : gather_op_node->inputs) {
input_names += input_var->Var()->Name();
}
auto gather_op_desc = gather_op_node->Op();
auto axis = gather_op_desc->GetAttrIfExists<int>("axis");
return "axis_" + std::to_string(axis) + "_input_names_" + input_names;
}
std::string GenSqueeze2AttrKey(Node* squeeze2_op_node) {
auto squeeze2_op_desc = squeeze2_op_node->Op();
auto axes = squeeze2_op_desc->GetAttrIfExists<std::vector<int>>("axes");
std::string attr_key{""};
attr_key += "axes_";
for (auto axis : axes) {
attr_key += std::to_string(axis) + "_";
}
return attr_key;
}
void DeleteRepeatedOpsPass::ApplyImpl(ir::Graph* graph) const {
PADDLE_ENFORCE_NOT_NULL(
graph, common::errors::PreconditionNotMet("graph should not be null."));
Init(name_scope_, graph);
int repeat_time = 0;
int total_delete_op_count = 0;
// This pass needs to loop run until there are no nodes in the graph that need
// to be deleted.
while (true) {
delete_op_count = 0;
DeleteRepeatedOps(graph, "shape", GenShapeAttrKey);
DeleteRepeatedOps(graph, "slice", GenSliceAttrKey);
DeleteRepeatedOps(graph, "cast", GenCastAttrKey);
DeleteRepeatedOps(graph, "elementwise_add", GenAddAttrKey);
DeleteRepeatedOps(graph, "scale", GenScaleAttrKey);
DeleteRepeatedOps(graph, "gather", GenGatherAttrKey);
DeleteRepeatedOps(graph, "squeeze2", GenSqueeze2AttrKey);
DeleteRepeatedOps(graph, "unsqueeze2", GenSqueeze2AttrKey);
DeleteRepeatedOps(graph, "transpose2", GenTranspose2AttrKey);
LOG(INFO) << "Round " << repeat_time++
<< ": delete op counts: " << delete_op_count;
total_delete_op_count += delete_op_count;
if (delete_op_count == 0) {
break; // No node need to delete.
}
}
LOG(INFO) << "Total delete op counts: " << total_delete_op_count;
}
} // namespace paddle::framework::ir
REGISTER_PASS(delete_repeated_ops_pass,
paddle::framework::ir::DeleteRepeatedOpsPass);
REGISTER_PASS_CAPABILITY(delete_repeated_ops_pass)
.AddCombination(
paddle::framework::compatible::OpVersionComparatorCombination().EQ(
"shape", 0));