Files
paddlepaddle--paddle/paddle/fluid/primitive/decomp_utils/decomp_utils.h
T
2026-07-13 12:40:42 +08:00

717 lines
21 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.
#pragma once
#include <vector>
#include "paddle/common/ddim.h"
#include "paddle/fluid/framework/details/op_registry.h"
#include "paddle/fluid/framework/operator.h"
#include "paddle/fluid/primitive/base/lazy_tensor.h"
#include "paddle/fluid/primitive/primitive/primitive.h"
#include "paddle/phi/api/include/tensor.h"
#include "paddle/phi/kernels/funcs/common_infer_shape_functions.h"
namespace paddle {
class Tensor;
namespace primitive {
template <typename T>
static Tensor get_slice(const Tensor& x, int64_t idx) {
return slice<T>(x, {0}, {idx}, {idx + 1}, {1}, {});
}
template <typename T>
static Tensor get_slice_vec(const Tensor& x,
int64_t start_idx,
int64_t end_idx) {
return slice<T>(x, {0}, {start_idx}, {end_idx}, {1}, {});
}
template <typename T>
void set_output(const Tensor& x_tmp, Tensor* x);
template <typename T>
void by_pass(const Tensor& x_tmp, Tensor* x);
// This function determine whether dtype is in [float16, bfloat16, uint16]
static bool is_half_dtype(const DataType& dtype) {
if (dtype == DataType::FLOAT16 || dtype == DataType::BFLOAT16 ||
dtype == DataType::UINT16) {
return true;
} else {
return false;
}
}
// This function expands the dimension of origin Tensor based on the value of
// axis
static std::vector<int64_t> get_expand_dims(const Tensor& origin,
const std::vector<int64_t>& axis) {
std::vector<int64_t> result(origin.shape());
for (size_t i = 0; i < axis.size(); ++i) {
int64_t offset = axis[i];
if (offset < 0) {
offset += result.size() + 1;
}
PADDLE_ENFORCE_LE(
offset,
result.size(),
common::errors::OutOfRange("Your index [%lu] exceeds the number of "
"elements in origin_dims[%lu].",
offset,
result.size()));
result.insert(result.begin() + offset, 1);
}
return result;
}
// This function compute unsqueeze dims for reshape to replace unsqueeze.
static std::vector<int64_t> get_unsqueeze_dims(
const Tensor& origin, const std::vector<int64_t>& axis) {
auto sort_axis = axis;
std::sort(sort_axis.begin(), sort_axis.end());
auto origin_dims = origin.shape();
auto total_shape_size = origin_dims.size() + sort_axis.size();
std::vector<int64_t> result;
size_t j = 0, k = 0;
for (size_t i = 0; i < total_shape_size; ++i) {
if (j < sort_axis.size() && sort_axis[j] == int64_t(i)) {
result.push_back(1);
j++;
} else {
PADDLE_ENFORCE_LT(
k,
origin_dims.size(),
common::errors::OutOfRange("Your index [%lu] exceeds the number of "
"elements in origin_dims[%lu].",
k,
origin_dims.size()));
result.push_back(origin_dims[k]);
k++;
}
}
return result;
}
// This function compute `dynamic` unsqueeze dims for reshape to replace
// unsqueeze. And should used only on `dynamic`.
template <typename T>
Tensor get_unsqueeze_dims(const Tensor& origin_shape,
const std::vector<int64_t>& axis) {
auto total_shape_size = origin_shape.numel() + axis.size();
const Tensor one = full<T>({1}, 1, origin_shape.dtype());
std::vector<Tensor> result(total_shape_size, one);
// to support axis not in increasing order.
std::vector<bool> is_set(total_shape_size, false);
for (size_t i = 0; i < axis.size(); ++i) {
PADDLE_ENFORCE_LT(
axis[i],
total_shape_size,
common::errors::OutOfRange("Your index [%lu] exceeds the number of "
"elements in origin_dims[%lu].",
axis[i],
total_shape_size));
is_set[axis[i]] = true;
}
size_t j = 0;
for (size_t i = 0; i < total_shape_size; ++i) {
if (is_set[i]) {
continue;
}
result[i] = get_slice<T>(origin_shape, int64_t(j));
is_set[i] = true;
++j;
}
return concat<T>(result);
}
// This function compute unsqueeze dims for reshape to replace unsqueeze.
static std::vector<int64_t> get_squeeze_dims(const Tensor& origin,
const std::vector<int64_t>& axis) {
auto origin_dims = origin.shape();
auto total_shape_size = origin_dims.size();
std::vector<int64_t> result;
for (size_t i = 0; i < total_shape_size; ++i) {
if (origin_dims[i] != 1) {
result.push_back(origin_dims[i]);
} else if (origin_dims[i] == 1 &&
std::find(axis.begin(), axis.end(), int64_t(i)) == axis.end()) {
result.push_back(1);
} else {
continue;
}
}
return result;
}
static std::vector<int64_t> process_dims(const Tensor& origin,
const std::vector<int64_t>& axis) {
auto origin_dims = origin.shape();
auto total_shape_size = origin_dims.size();
std::vector<int64_t> result;
auto axis_size = axis.size();
if (axis_size == 0) {
for (size_t i = 0; i < total_shape_size; ++i) {
result.push_back(i);
}
} else {
for (size_t i = 0; i < axis_size; ++i) {
if (axis[i] < 0) {
result.push_back(axis[i] + total_shape_size);
} else {
result.push_back(axis[i]);
}
}
}
return result;
}
// These method don't need to be specified
// These method only handle the static shape case
static phi::DDim get_reduce_dims_from_out(const phi::DDim& dout_dims,
const phi::DDim& in_dims) {
bool has_dynamic_shape = false;
for (int i = 0; i < dout_dims.size(); i++) {
if (dout_dims[i] == -1) {
has_dynamic_shape = true;
break;
}
}
PADDLE_ENFORCE_EQ(
has_dynamic_shape,
false,
common::errors::InvalidArgument(
"Function get_reduce_dims_from_out() only use in static shape case, "
"but the input [dout_dims] have the dynamic shape."));
for (int i = 0; i < in_dims.size(); i++) {
if (in_dims[i] == -1) {
has_dynamic_shape = true;
break;
}
}
PADDLE_ENFORCE_EQ(
has_dynamic_shape,
false,
common::errors::InvalidArgument(
"Function get_reduce_dims_from_out() only use in static shape case, "
"but the input [in_dims] have the dynamic shape."));
int bat = dout_dims.size() - in_dims.size();
std::vector<int64_t> result;
for (int i = 0; i < bat; ++i) {
result.push_back(i);
}
for (int i = 0; i < in_dims.size(); ++i) {
if (in_dims[i] == 1 && dout_dims[i + bat] != 1) {
result.push_back(i + bat);
} else {
PADDLE_ENFORCE_EQ(
in_dims[i],
dout_dims[i + bat],
common::errors::InvalidArgument(
"ReduceDims dimension mismatch. Operands could "
"not be broadcast together with the shape of dout = [%s] and "
"the shape of in_dims = [%s]. Received [%d] in X is not equal to "
"[%d] in Y at i:%d.",
dout_dims,
in_dims,
dout_dims[i + bat],
in_dims[i],
i));
}
}
return common::make_ddim(result);
}
static phi::DDim get_reduce_dims(const phi::DDim& x_dims,
const phi::DDim& y_dims) {
auto out_dims = phi::funcs::BroadcastTwoDims(x_dims, y_dims);
return get_reduce_dims_from_out(out_dims, x_dims);
}
void SetEmptyGrad(const std::vector<std::vector<Tensor>>& outputs,
const std::vector<std::vector<bool>>& stop_gradients);
std::vector<std::vector<Tensor>> ConstructVjpResultByStopGradients(
const std::vector<std::vector<Tensor>>& outputs,
const std::vector<std::vector<bool>>& stop_gradients);
static bool find_value(const std::vector<int64_t>& vec, int64_t value) {
if (std::find(vec.begin(), vec.end(), value) != vec.end()) {
return true;
} else {
return false;
}
}
static bool has_dynamic_shape(const std::vector<int64_t>& shape) {
return std::find(shape.begin(), shape.end(), -1) != shape.end();
}
static bool has_dynamic_shape(const std::vector<int64_t>& shape,
const std::vector<int64_t>& axis) {
bool flag = false;
const int64_t rank = shape.size();
for (int64_t idx : axis) {
if (idx < 0) idx += rank;
PADDLE_ENFORCE_LT(
idx,
rank,
::common::errors::PreconditionNotMet(
"Required idx < shape.size(), but received %d.", idx));
if (shape[idx] == -1) {
flag = true;
break;
}
}
return flag;
}
template <typename T>
Tensor ConvertToMT(const Tensor& x) {
bool need_cast = x.dtype() == DataType::FLOAT16 ||
x.dtype() == DataType::BFLOAT16 ||
x.dtype() == DataType::UINT16;
if (need_cast) {
return cast<T>(x, DataType::FLOAT32);
}
return x;
}
template <typename T>
Tensor ConvertToOrig(const Tensor& out, DataType input_dtype) {
bool need_cast = out.dtype() != input_dtype;
if (need_cast) {
return cast<T>(out, input_dtype);
}
return out;
}
class LayerNormDecompHelper {
public:
LayerNormDecompHelper(const Tensor& x,
const paddle::optional<Tensor>& scale,
const paddle::optional<Tensor>& bias,
int begin_norm_axis) {
auto x_dims = x.dims();
x_rank_ = x_dims.size();
begin_norm_axis_ = begin_norm_axis;
if (begin_norm_axis_ < 0) {
begin_norm_axis_ += x_rank_;
}
scale_need_reshape_ = (begin_norm_axis + 1 != x_rank_);
static_norm_shape_ = true;
for (int i = begin_norm_axis; i < x_rank_; ++i) {
if (x_dims[i] < 0) {
static_norm_shape_ = false;
normalized_numel_ = -1;
break;
}
normalized_shape_.push_back(x_dims[i]);
normalized_numel_ *= x_dims[i];
}
if (!static_norm_shape_) {
// try get static norm numel from scale for bias
normalized_numel_ = -1;
if (scale.get_ptr()) {
normalized_numel_ = scale->dims()[0];
} else if (bias.get_ptr()) {
normalized_numel_ = bias->dims()[0];
}
}
}
template <typename T>
Tensor Process(const Tensor& s, const Tensor& x) {
if (!scale_need_reshape_) {
return s;
}
if (static_norm_shape_) {
return reshape<T>(s, normalized_shape_);
} else {
return backend::reshape<T>(
s, get_slice_vec<T>(shape64<T>(x), begin_norm_axis_, x_rank_));
}
}
template <typename T>
Tensor GetNormalizedNumel(const Tensor& x) {
if (normalized_numel_ != -1) {
return full_scalar<T>(normalized_numel_, x.dtype());
} else {
auto x_shape = shape64<T>(x);
auto numel = get_slice<T>(x_shape, begin_norm_axis_);
for (int64_t i = begin_norm_axis_ + 1; i < x_rank_; ++i) {
numel = numel * get_slice<T>(x_shape, i);
}
return cast<T>(numel, x.type());
}
}
private:
std::vector<int64_t> normalized_shape_;
bool scale_need_reshape_;
bool static_norm_shape_;
int64_t x_rank_;
int64_t normalized_numel_{1};
int begin_norm_axis_;
};
template <typename T>
class BatchNormDecompHelper {
public:
BatchNormDecompHelper(const Tensor& x,
const paddle::optional<Tensor>& scale,
const paddle::optional<Tensor>& bias,
const std::string& data_format) {
auto x_dims = phi::vectorize(x.dims());
x_rank_ = x_dims.size();
if (data_format == "NCHW") {
channel_axis_ = 1;
reduce_axis_.push_back(0);
for (int64_t i = channel_axis_ + 1; i < x_rank_; ++i) {
reduce_axis_.push_back(i);
}
} else if (data_format == "NHWC") {
channel_axis_ = x_rank_ - 1;
for (int64_t i = 0; i < channel_axis_; ++i) {
reduce_axis_.push_back(i);
}
} else {
PADDLE_THROW(
common::errors::Unimplemented("Only support NCHW and NHWC format."));
}
scale_bias_new_shape_.push_back(0);
for (int64_t i = channel_axis_ + 1; i < x_rank_; ++i) {
scale_bias_new_shape_.push_back(1);
}
// int64_t channel_dim = x_dims[channel_axis_];
// if ((channel_dim < 0) && scale) {
// channel_dim = scale->dims()[0];
// }
// if ((channel_dim < 0) && bias) {
// channel_dim = bias->dims()[0];
// }
}
const std::vector<int64_t>& GetReduceAxis() const { return reduce_axis_; }
const std::vector<int64_t>& GetScaleBiasNewShape() const {
return scale_bias_new_shape_;
}
Tensor GetNHW(const Tensor& x) {
auto x_dims = x.dims();
bool static_nhw = true;
int64_t nhw_numel = 1;
for (int64_t i = 0; i < x_rank_; ++i) {
if (i == channel_axis_) {
continue;
}
if (x_dims[i] < 0) {
static_nhw = false;
break;
}
nhw_numel *= x_dims[i];
}
if (static_nhw) {
return full_scalar<T>(nhw_numel, x.dtype());
} else {
auto x_shape = shape64<T>(x);
auto nhw = get_slice<T>(x_shape, 0);
for (int64_t i = 1; i < x_rank_; ++i) {
if (i == channel_axis_) {
continue;
}
nhw = nhw * get_slice<T>(x_shape, i);
}
return cast<T>(nhw, x.dtype());
}
}
private:
std::vector<int64_t> reduce_axis_;
std::vector<int64_t> scale_bias_new_shape_;
int64_t channel_axis_;
int64_t x_rank_;
};
template <typename T>
class InstanceNormDecompHelper {
public:
explicit InstanceNormDecompHelper(const Tensor& x) {
x_dims_ = phi::vectorize(x.dims());
x_rank_ = x_dims_.size();
for (int64_t i = 2; i < x_rank_; ++i) {
reduce_axis_.push_back(i);
n_plus_reduce_axis_.push_back(i);
}
n_plus_reduce_axis_.push_back(0);
}
Tensor GetHW(const Tensor& x) {
auto dims = phi::vectorize(x.dims());
int64_t rank = dims.size();
if (has_dynamic_shape(x.shape())) {
Tensor x_shape = shape64<T>(x);
auto hw = full_scalar<T>(1.0, x.dtype());
for (int64_t i = 2; i < rank; ++i) {
hw = hw * get_slice<T>(x_shape, i);
}
return cast<T>(hw, x.dtype());
} else {
int64_t hw = 1;
for (int64_t i = 2; i < rank; ++i) {
hw *= dims[i];
}
return full_scalar<T>(hw, x.dtype());
}
}
const std::vector<int64_t> GetReduceAxis() const { return reduce_axis_; }
const std::vector<int64_t> GetNPlusReduceAxis() const {
return n_plus_reduce_axis_;
}
const std::vector<int64_t>& GetDims() const { return x_dims_; }
private:
std::vector<int64_t> reduce_axis_;
std::vector<int64_t> n_plus_reduce_axis_;
std::vector<int64_t> x_dims_;
int64_t x_rank_;
};
template <typename T>
class GroupNormDecompHelper {
public:
GroupNormDecompHelper(const Tensor& x,
const paddle::optional<Tensor>& scale,
const paddle::optional<Tensor>& bias,
int64_t group_num,
const std::string& data_format) {
auto x_dims = phi::vectorize(x.dims());
x_rank_ = x_dims.size();
if (data_format == "NCHW") {
channel_axis_ = 1;
for (int64_t i = channel_axis_ + 1; i < x_rank_ + 1; ++i) {
reduce_axis_.push_back(i);
}
} else if (data_format == "NHWC") {
channel_axis_ = x_rank_ - 1;
for (int64_t i = 1; i < channel_axis_; ++i) {
reduce_axis_.push_back(i);
}
reduce_axis_.push_back(x_rank_);
} else {
PADDLE_THROW(
common::errors::Unimplemented("Only support NCHW and NHWC format."));
}
scale_bias_new_shape_.push_back(group_num);
scale_bias_new_shape_.push_back(-1);
for (int64_t i = channel_axis_ + 1; i < x_rank_; ++i) {
scale_bias_new_shape_.push_back(1);
}
int64_t channel_dim = x_dims[channel_axis_];
if ((channel_dim < 0) && scale) {
channel_dim = scale->dims()[0];
}
if ((channel_dim < 0) && bias) {
channel_dim = bias->dims()[0];
}
int unk_count = 0;
for (int64_t i = 0; i < x_rank_; ++i) {
if ((i != channel_axis_) && (x_dims[i] < 0)) {
unk_count++;
}
}
if (channel_dim > 0) {
// Can use vector<int64> as output shape
// case 1: axis is the last one
// case 2: from axis + 1 to end all positive
can_use_vector_int_as_output_shape_ =
(channel_axis_ + 1 == x_rank_) ||
std::find(x_dims.begin() + channel_axis_ + 1, x_dims.end(), -1) ==
x_dims.end();
// case 3: one ONE unk dim(-1) except axis
can_use_vector_int_as_output_shape_ =
can_use_vector_int_as_output_shape_ || (unk_count <= 1);
} else {
can_use_vector_int_as_output_shape_ = (unk_count == 0);
}
std::vector<int64_t> split_dim;
split_dim.push_back(group_num);
split_dim.push_back(channel_dim < 0 ? -1 : channel_dim / group_num);
if (can_use_vector_int_as_output_shape_) {
split_out_shape_.reserve(x_rank_ + 1);
for (int64_t i = 0; i < channel_axis_; ++i) {
split_out_shape_.push_back(0);
merge_out_shape_.push_back(0);
}
split_out_shape_.insert(
split_out_shape_.end(), split_dim.begin(), split_dim.end());
merge_out_shape_.push_back(channel_dim);
for (int64_t i = channel_axis_ + 1; i < x_rank_; ++i) {
split_out_shape_.push_back(x_dims[i]);
merge_out_shape_.push_back(x_dims[i]);
}
} else {
auto x_shape = shape64<T>(x);
if (channel_axis_ > 0) {
split_shape_tensor_.push_back(
get_slice_vec<T>(x_shape, 0, channel_axis_));
merge_shape_tensor_.push_back(
get_slice_vec<T>(x_shape, 0, channel_axis_));
}
split_shape_tensor_.push_back(
full<T>({1}, split_dim[0], DataType::INT64));
split_shape_tensor_.push_back(
full<T>({1}, split_dim[1], DataType::INT64));
merge_shape_tensor_.push_back(full<T>({1}, channel_dim, DataType::INT64));
if (channel_axis_ + 1 < x_rank_) {
split_shape_tensor_.push_back(
get_slice_vec<T>(x_shape, channel_axis_ + 1, x_rank_));
merge_shape_tensor_.push_back(
get_slice_vec<T>(x_shape, channel_axis_ + 1, x_rank_));
}
}
}
Tensor Split(const Tensor& s) {
if (can_use_vector_int_as_output_shape_) {
return reshape<T>(s, split_out_shape_);
} else {
return backend::reshape<T>(s, concat<T>(split_shape_tensor_, 0));
}
}
Tensor Merge(const Tensor& x) {
if (can_use_vector_int_as_output_shape_) {
return reshape<T>(x, merge_out_shape_);
} else {
return backend::reshape<T>(x, concat<T>(merge_shape_tensor_, 0));
}
}
const std::vector<int64_t>& GetReduceAxis() const { return reduce_axis_; }
std::vector<int64_t> GetMeanVarSqueezeAxis() const {
std::vector<int64_t> output;
for (int64_t i = 1; i < channel_axis_; ++i) {
output.push_back(1);
}
for (int64_t i = channel_axis_ + 1; i <= x_rank_; ++i) {
output.push_back(-1);
}
return output;
}
const std::vector<int64_t>& GetScaleBiasNewShape() const {
return scale_bias_new_shape_;
}
Tensor GetHW(const Tensor& x) {
auto x_dims = x.dims();
// process reduce axis
bool static_hw = true;
int64_t hwg_numel = 1;
for (size_t i = 0; i < reduce_axis_.size(); ++i) {
if (x_dims[reduce_axis_[i]] < 0) {
static_hw = false;
break;
}
hwg_numel *= x_dims[reduce_axis_[i]];
}
if (static_hw) {
return full_scalar<T>(hwg_numel, x.dtype());
} else {
auto x_shape = shape64<T>(x);
auto numel = get_slice<T>(x_shape, reduce_axis_.front());
for (size_t i = 1; i < reduce_axis_.size(); ++i) {
numel = numel * get_slice<T>(x_shape, reduce_axis_[i]);
}
return cast<T>(numel, x.dtype());
}
}
std::vector<int64_t> GetReduceAxisExceptChannel() const {
std::vector<int64_t> reduce_axis;
reduce_axis.reserve(x_rank_ - 1);
for (int64_t i = 0; i < x_rank_ + 1; ++i) {
if (i != channel_axis_ && i != channel_axis_ + 1) {
reduce_axis.push_back(i);
}
}
return reduce_axis;
}
private:
bool can_use_vector_int_as_output_shape_{false};
std::vector<int64_t> split_out_shape_;
std::vector<Tensor> split_shape_tensor_;
std::vector<int64_t> merge_out_shape_;
std::vector<Tensor> merge_shape_tensor_;
std::vector<int64_t> reduce_axis_;
std::vector<int64_t> scale_bias_new_shape_;
int64_t group_num_;
int64_t channel_axis_;
int64_t x_rank_;
};
} // namespace primitive
} // namespace paddle