// 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 #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 static Tensor get_slice(const Tensor& x, int64_t idx) { return slice(x, {0}, {idx}, {idx + 1}, {1}, {}); } template static Tensor get_slice_vec(const Tensor& x, int64_t start_idx, int64_t end_idx) { return slice(x, {0}, {start_idx}, {end_idx}, {1}, {}); } template void set_output(const Tensor& x_tmp, Tensor* x); template 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 get_expand_dims(const Tensor& origin, const std::vector& axis) { std::vector 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 get_unsqueeze_dims( const Tensor& origin, const std::vector& 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 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 Tensor get_unsqueeze_dims(const Tensor& origin_shape, const std::vector& axis) { auto total_shape_size = origin_shape.numel() + axis.size(); const Tensor one = full({1}, 1, origin_shape.dtype()); std::vector result(total_shape_size, one); // to support axis not in increasing order. std::vector 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(origin_shape, int64_t(j)); is_set[i] = true; ++j; } return concat(result); } // This function compute unsqueeze dims for reshape to replace unsqueeze. static std::vector get_squeeze_dims(const Tensor& origin, const std::vector& axis) { auto origin_dims = origin.shape(); auto total_shape_size = origin_dims.size(); std::vector 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 process_dims(const Tensor& origin, const std::vector& axis) { auto origin_dims = origin.shape(); auto total_shape_size = origin_dims.size(); std::vector 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 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>& outputs, const std::vector>& stop_gradients); std::vector> ConstructVjpResultByStopGradients( const std::vector>& outputs, const std::vector>& stop_gradients); static bool find_value(const std::vector& 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& shape) { return std::find(shape.begin(), shape.end(), -1) != shape.end(); } static bool has_dynamic_shape(const std::vector& shape, const std::vector& 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 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(x, DataType::FLOAT32); } return x; } template Tensor ConvertToOrig(const Tensor& out, DataType input_dtype) { bool need_cast = out.dtype() != input_dtype; if (need_cast) { return cast(out, input_dtype); } return out; } class LayerNormDecompHelper { public: LayerNormDecompHelper(const Tensor& x, const paddle::optional& scale, const paddle::optional& 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 Tensor Process(const Tensor& s, const Tensor& x) { if (!scale_need_reshape_) { return s; } if (static_norm_shape_) { return reshape(s, normalized_shape_); } else { return backend::reshape( s, get_slice_vec(shape64(x), begin_norm_axis_, x_rank_)); } } template Tensor GetNormalizedNumel(const Tensor& x) { if (normalized_numel_ != -1) { return full_scalar(normalized_numel_, x.dtype()); } else { auto x_shape = shape64(x); auto numel = get_slice(x_shape, begin_norm_axis_); for (int64_t i = begin_norm_axis_ + 1; i < x_rank_; ++i) { numel = numel * get_slice(x_shape, i); } return cast(numel, x.type()); } } private: std::vector normalized_shape_; bool scale_need_reshape_; bool static_norm_shape_; int64_t x_rank_; int64_t normalized_numel_{1}; int begin_norm_axis_; }; template class BatchNormDecompHelper { public: BatchNormDecompHelper(const Tensor& x, const paddle::optional& scale, const paddle::optional& 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& GetReduceAxis() const { return reduce_axis_; } const std::vector& 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(nhw_numel, x.dtype()); } else { auto x_shape = shape64(x); auto nhw = get_slice(x_shape, 0); for (int64_t i = 1; i < x_rank_; ++i) { if (i == channel_axis_) { continue; } nhw = nhw * get_slice(x_shape, i); } return cast(nhw, x.dtype()); } } private: std::vector reduce_axis_; std::vector scale_bias_new_shape_; int64_t channel_axis_; int64_t x_rank_; }; template 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(x); auto hw = full_scalar(1.0, x.dtype()); for (int64_t i = 2; i < rank; ++i) { hw = hw * get_slice(x_shape, i); } return cast(hw, x.dtype()); } else { int64_t hw = 1; for (int64_t i = 2; i < rank; ++i) { hw *= dims[i]; } return full_scalar(hw, x.dtype()); } } const std::vector GetReduceAxis() const { return reduce_axis_; } const std::vector GetNPlusReduceAxis() const { return n_plus_reduce_axis_; } const std::vector& GetDims() const { return x_dims_; } private: std::vector reduce_axis_; std::vector n_plus_reduce_axis_; std::vector x_dims_; int64_t x_rank_; }; template class GroupNormDecompHelper { public: GroupNormDecompHelper(const Tensor& x, const paddle::optional& scale, const paddle::optional& 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 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 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(x); if (channel_axis_ > 0) { split_shape_tensor_.push_back( get_slice_vec(x_shape, 0, channel_axis_)); merge_shape_tensor_.push_back( get_slice_vec(x_shape, 0, channel_axis_)); } split_shape_tensor_.push_back( full({1}, split_dim[0], DataType::INT64)); split_shape_tensor_.push_back( full({1}, split_dim[1], DataType::INT64)); merge_shape_tensor_.push_back(full({1}, channel_dim, DataType::INT64)); if (channel_axis_ + 1 < x_rank_) { split_shape_tensor_.push_back( get_slice_vec(x_shape, channel_axis_ + 1, x_rank_)); merge_shape_tensor_.push_back( get_slice_vec(x_shape, channel_axis_ + 1, x_rank_)); } } } Tensor Split(const Tensor& s) { if (can_use_vector_int_as_output_shape_) { return reshape(s, split_out_shape_); } else { return backend::reshape(s, concat(split_shape_tensor_, 0)); } } Tensor Merge(const Tensor& x) { if (can_use_vector_int_as_output_shape_) { return reshape(x, merge_out_shape_); } else { return backend::reshape(x, concat(merge_shape_tensor_, 0)); } } const std::vector& GetReduceAxis() const { return reduce_axis_; } std::vector GetMeanVarSqueezeAxis() const { std::vector 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& 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(hwg_numel, x.dtype()); } else { auto x_shape = shape64(x); auto numel = get_slice(x_shape, reduce_axis_.front()); for (size_t i = 1; i < reduce_axis_.size(); ++i) { numel = numel * get_slice(x_shape, reduce_axis_[i]); } return cast(numel, x.dtype()); } } std::vector GetReduceAxisExceptChannel() const { std::vector 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 split_out_shape_; std::vector split_shape_tensor_; std::vector merge_out_shape_; std::vector merge_shape_tensor_; std::vector reduce_axis_; std::vector scale_bias_new_shape_; int64_t group_num_; int64_t channel_axis_; int64_t x_rank_; }; } // namespace primitive } // namespace paddle