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

331 lines
12 KiB
C++

// Copyright (c) 2024 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 "paddle/cinn/operator_fusion/utils.h"
#include "paddle/cinn/hlir/dialect/operator/ir/cinn_op.h"
#include "paddle/cinn/hlir/dialect/operator/ir/manual_op.h"
namespace cinn::fusion {
std::vector<int64_t> GetInt64ArrayAttributeData(
const ::pir::Attribute& attr_val) {
PADDLE_ENFORCE_EQ(attr_val.isa<::pir::ArrayAttribute>(),
true,
::common::errors::InvalidArgument(
"The input attribute should be an array."));
const auto& array_attr = attr_val.dyn_cast<::pir::ArrayAttribute>();
std::vector<int64_t> data;
for (int i = 0; i < array_attr.size(); ++i) {
const auto& int64_attr = array_attr.at(i).dyn_cast<::pir::Int64Attribute>();
PADDLE_ENFORCE_EQ(static_cast<bool>(int64_attr),
true,
::common::errors::InvalidArgument(
"The array element should be int64 type."));
data.push_back(int64_attr.data());
}
return data;
}
std::vector<int32_t> GetInt32ArrayAttributeData(
const ::pir::Attribute& attr_val) {
PADDLE_ENFORCE_EQ(attr_val.isa<::pir::ArrayAttribute>(),
true,
::common::errors::InvalidArgument(
"The input attribute should be an array."));
const auto& array_attr = attr_val.dyn_cast<::pir::ArrayAttribute>();
std::vector<int32_t> data;
for (int i = 0; i < array_attr.size(); ++i) {
const auto& int32_attr = array_attr.at(i).dyn_cast<::pir::Int32Attribute>();
PADDLE_ENFORCE_EQ(static_cast<bool>(int32_attr),
true,
::common::errors::InvalidArgument(
"The array element should be int32 type."));
data.push_back(int32_attr.data());
}
return data;
}
std::unordered_set<pir::Operation*> GetGroupOutputOps(
const std::vector<pir::Operation*>& ops) {
const auto is_global_inplace_op =
[](pir::Operation* op,
const std::unordered_set<pir::Operation*>& ops_set) -> bool {
if (op->num_results() != 1 ||
!op->HasInterface<paddle::dialect::OpYamlInfoInterface>()) {
return false;
}
auto op_info =
op->dyn_cast<paddle::dialect::OpYamlInfoInterface>().GetOpInfo();
auto input_info_list = std::get<0>(op_info);
auto output_info_list = std::get<2>(op_info);
auto inplace_info_map = std::get<3>(op_info).inplace;
// 1. Find which input is inplace with the output
std::string output_name = output_info_list.front().name;
std::string inplace_input_name;
for (const auto& [out, in] : inplace_info_map) {
if (out == output_name) inplace_input_name = in;
}
if (inplace_input_name.empty()) return false;
int inplace_input_idx = -1;
for (int i = 0; i < input_info_list.size(); ++i) {
if (input_info_list[i].name == inplace_input_name) {
inplace_input_idx = i;
break;
}
}
if (inplace_input_idx == -1) return false;
// 2. Check whether the inplace input is not the output of op in the ops_set
pir::Value inplace_input_value = op->operand_source(inplace_input_idx);
return ops_set.find(inplace_input_value.defining_op()) == ops_set.end();
};
auto ops_set = ToUnorderedSet(ops);
std::unordered_set<pir::Operation*> output_ops;
for (auto* op : ops) {
if (op->HasTrait<paddle::dialect::InplaceTrait>()) {
if (is_global_inplace_op(op, ops_set)) output_ops.insert(op);
continue;
}
for (size_t i = 0; i < op->num_results(); ++i) {
auto result = op->result(i);
if (!result) continue;
for (auto use_iter = result.use_begin(); use_iter != result.use_end();
++use_iter) {
auto* use_op = use_iter->owner();
if (ops_set.find(use_op) == ops_set.end()) {
output_ops.insert(op);
break;
}
}
if (output_ops.count(op)) break;
}
}
return output_ops;
}
std::vector<int64_t> GetReduceAxisIdx(pir::Operation* reduce_op) {
const size_t input_rank = GetCompatibleRank(reduce_op->operand_source(0));
const auto& attr_val = reduce_op->attributes().at("axis");
PADDLE_ENFORCE_EQ(attr_val.isa<::pir::ArrayAttribute>(),
true,
::common::errors::InvalidArgument(
"The axis attribute should be an array."));
const auto& axis_attr = attr_val.dyn_cast<::pir::ArrayAttribute>();
if (axis_attr.empty()) {
// dim: [] means reduce_all.
std::vector<int64_t> all_axis;
for (int i = 0; i < input_rank; ++i) {
all_axis.push_back(i);
}
return all_axis;
}
std::vector<int64_t> reduce_axis_idx;
for (int i = 0; i < axis_attr.size(); ++i) {
int64_t axis = axis_attr.at(i).dyn_cast<::pir::Int64Attribute>().data();
if (axis < 0) {
axis += input_rank;
}
PADDLE_ENFORCE_GE(
axis,
0,
::common::errors::InvalidArgument(
"The 'axis' must be greater than or equal to 0, but received %d.",
axis));
PADDLE_ENFORCE_LT(axis,
input_rank,
::common::errors::InvalidArgument(
"The 'axis' must be less than 'input_rank', but "
"received axis = %d and input_rank = %d.",
axis,
input_rank));
reduce_axis_idx.push_back(axis);
}
VLOG(4) << "GetReduceAxisIdx: " << utils::Join(reduce_axis_idx, ",");
return reduce_axis_idx;
}
bool GetReduceOpKeepDims(pir::Operation* reduce_op) {
const auto& attr_val = reduce_op->attributes().at("keepdim");
PADDLE_ENFORCE_EQ(attr_val.isa<::pir::BoolAttribute>(),
true,
::common::errors::InvalidArgument(
"The keepdim attribute should be a bool."));
return attr_val.dyn_cast<::pir::BoolAttribute>().data();
}
std::pair<std::vector<int64_t>, bool> GetSliceAxis(pir::Operation* slice_op) {
std::vector<int64_t> slice_axis =
GetInt64ArrayAttributeData(slice_op->attributes().at("axes"));
std::vector<int64_t> decrease_axis =
GetInt64ArrayAttributeData(slice_op->attributes().at("decrease_axis"));
PADDLE_ENFORCE_EQ(slice_axis.empty(),
false,
::common::errors::InvalidArgument(
"The axis attribute should not be empty."));
bool keepdim = true;
if (!decrease_axis.empty()) {
PADDLE_ENFORCE_EQ(
decrease_axis,
slice_axis,
::common::errors::InvalidArgument(
"The size of decrease axis should be equal to the size of axis."));
keepdim = false;
}
return std::make_pair(slice_axis, keepdim);
}
std::optional<std::pair<pir::Value, pir::Value>> GetBroadcastOpInputOutputValue(
pir::Operation* op) {
auto* mut_op = const_cast<pir::Operation*>(op);
if (op->isa<paddle::dialect::ExpandOp>()) {
auto expand_op = mut_op->dyn_cast<paddle::dialect::ExpandOp>();
return std::make_pair(expand_op.x(), expand_op.out());
} else if (op->isa<cinn::dialect::BroadcastOp>()) {
auto broadcast_op = mut_op->dyn_cast<cinn::dialect::BroadcastOp>();
return std::make_pair(broadcast_op.x(), broadcast_op.out());
} else {
PADDLE_THROW(::common::errors::Unimplemented("Unsupported broadcast op, %s",
op->name()));
}
return std::nullopt;
}
std::vector<std::pair<size_t, size_t>> GetNonBroadCastDims(pir::Operation* op) {
std::vector<std::pair<size_t, size_t>> res;
auto* shape_analysis =
&pir::ShapeAnalysisManager::Instance().Get(op->GetParentProgram());
const auto& broad_cast_value = GetBroadcastOpInputOutputValue(op);
CHECK(broad_cast_value.has_value());
const auto& [input_value, output_value] = broad_cast_value.value();
const int input_rank = GetRank(input_value);
const int output_rank = GetRank(output_value);
PADDLE_ENFORCE_GE(output_rank,
input_rank,
::common::errors::PreconditionNotMet(
"[Error info] The output_rank should "
"be greater or equal to input_rank."));
// Compare axis one by one, from back to front.
// The rule of broadcasting:
// https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/beginner/tensor_cn.html#id7
for (int i = 1; i <= input_rank; ++i) {
int input_axis = input_rank - i;
int output_axis = output_rank - i;
if (input_axis < 0 || output_axis < 0) break;
if (shape_analysis->IsProductEqual(
input_value, {input_axis}, output_value, {output_axis})) {
res.emplace_back(input_axis, output_axis);
}
}
return res;
}
std::shared_ptr<pir::ShapeConstraintIRAnalysis> GetShapeAnalysisFromValue(
const pir::Value& value) {
pir::Operation* related_op = value.defining_op();
if (value.defining_op() == nullptr) {
// For inputs of the program, the defining_op is nullptr,
// we use it's user as the related op.
PADDLE_ENFORCE_EQ(value.use_empty(),
false,
::common::errors::PreconditionNotMet(
"Value is an input value, it should have a use."));
related_op = value.first_use().owner();
}
return pir::ShapeAnalysisManager::Instance()
.Get(related_op->GetParentProgram())
.shared_from_this();
}
std::vector<symbol::DimExpr> GetValueAllDims(const pir::Value& value) {
return GetValueDims(value, ArangeVector<int>(0, GetRank(value)));
}
std::vector<symbol::DimExpr> GetCompatibleValueAllDims(
const pir::Value& value) {
return GetRank(value) == 0 ? std::vector<symbol::DimExpr>{symbol::DimExpr(1)}
: GetValueAllDims(value);
}
symbol::DimExpr GetShapeProduct(const std::vector<symbol::DimExpr>& shape,
int start,
int end) {
symbol::DimExpr product(1);
for (int i = start; i < end; ++i) {
product = product * shape[i];
}
return symbol::SimplifyDimExpr(product);
}
bool ShapeProductEqual(const std::vector<symbol::DimExpr>& in_shape,
const std::vector<symbol::DimExpr>& out_shape,
int in_start,
int in_end,
int out_start,
int out_end) {
return GetShapeProduct(in_shape, in_start, in_end) ==
GetShapeProduct(out_shape, out_start, out_end);
}
bool ShapeProductEqual(const std::vector<symbol::DimExpr>& in_shape,
const std::vector<symbol::DimExpr>& out_shape) {
return ShapeProductEqual(
in_shape, out_shape, 0, in_shape.size(), 0, out_shape.size());
}
std::vector<std::pair<int, int>> PartitionReshapeAxes(
const std::vector<symbol::DimExpr>& in_shape,
const std::vector<symbol::DimExpr>& out_shape) {
PADDLE_ENFORCE(ShapeProductEqual(in_shape, out_shape),
::common::errors::InvalidArgument(
"Shape product should be equal for reshape operation."));
int input_rank = in_shape.size();
int output_rank = out_shape.size();
std::vector<std::pair<int, int>> partition = {{0, 0}};
for (int i = 1, j = 1; i <= in_shape.size() && j <= out_shape.size();) {
bool shape_product_equal = ShapeProductEqual(in_shape,
out_shape,
partition.back().first,
i,
partition.back().second,
j);
if (shape_product_equal) {
partition.emplace_back(i++, j++);
if (i > input_rank || j > output_rank) {
// In case of the last few dims are 1
partition.back().first = input_rank;
partition.back().second = output_rank;
}
} else if (j < output_rank) {
j++;
} else if (i < input_rank) {
i++;
j = partition.back().second + 1;
} else {
PADDLE_THROW(::common::errors::InvalidArgument(
"Shape product should be equal for reshape operation."));
}
}
return partition;
}
} // namespace cinn::fusion