920 lines
30 KiB
C++
920 lines
30 KiB
C++
/* Copyright (c) 2021 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 "paddle/phi/backends/all_context.h"
|
|
#include "paddle/phi/common/transform.h"
|
|
#include "paddle/phi/core/dense_tensor.h"
|
|
#include "paddle/phi/kernels/empty_kernel.h"
|
|
#include "paddle/phi/kernels/funcs/common_shape.h"
|
|
#include "paddle/phi/kernels/funcs/elementwise_utils.h"
|
|
#include "paddle/phi/kernels/funcs/math_function.h"
|
|
|
|
#if defined(__NVCC__) || defined(__HIPCC__) || defined(__xpu__)
|
|
#include "paddle/phi/backends/gpu/gpu_launch_config.h"
|
|
#include "paddle/phi/kernels/funcs/aligned_vector.h"
|
|
#include "paddle/phi/kernels/funcs/function_traits.h"
|
|
#include "paddle/phi/kernels/primitive/kernel_primitives.h"
|
|
|
|
#define HOSTDEVICE __host__ __device__
|
|
|
|
#endif
|
|
|
|
namespace phi {
|
|
|
|
/* Packing scalar type T(float, int etc.) into Array<T, NumOuts> type
|
|
for supporting multiple-output feature in elementwise system.*/
|
|
template <class T, int Num>
|
|
using ConditionalT = typename std::conditional_t<Num == 1, T, Array<T, Num>>;
|
|
|
|
namespace funcs {
|
|
using DDim = DDim;
|
|
|
|
template <typename T, typename DeviceContext>
|
|
class RowwiseTransformIterator;
|
|
|
|
template <typename T, typename DeviceContext>
|
|
class MidWiseTransformIterator;
|
|
|
|
// NOTE(dzhwinter): ptrdiff_t in iterator is deprecated in c++17
|
|
template <typename T>
|
|
class RowwiseTransformIterator<T, CPUContext> {
|
|
public:
|
|
using iterator_category = std::random_access_iterator_tag;
|
|
using value_type = T;
|
|
using difference_type = std::ptrdiff_t;
|
|
using pointer = T *;
|
|
using reference = T &;
|
|
|
|
RowwiseTransformIterator(const T *ptr, int n) : ptr_(ptr), i_(0), n_(n) {}
|
|
|
|
RowwiseTransformIterator<T, CPUContext> &operator++() {
|
|
++i_;
|
|
if (UNLIKELY(i_ == n_)) {
|
|
i_ = 0;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
RowwiseTransformIterator<T, CPUContext> &operator+(int n) {
|
|
while (n-- > 0) {
|
|
++i_;
|
|
if (UNLIKELY(i_ == n_)) {
|
|
i_ = 0;
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool operator==(const RowwiseTransformIterator<T, CPUContext> &rhs) const {
|
|
return (ptr_ + i_) == &(*rhs);
|
|
}
|
|
|
|
bool operator!=(const RowwiseTransformIterator<T, CPUContext> &rhs) const {
|
|
return (ptr_ + i_) != &(*rhs);
|
|
}
|
|
|
|
const T &operator*() { return ptr_[i_]; }
|
|
|
|
private:
|
|
const T *ptr_;
|
|
int i_;
|
|
int64_t n_;
|
|
};
|
|
|
|
template <typename T>
|
|
class MidWiseTransformIterator<T, CPUContext> {
|
|
public:
|
|
using iterator_category = std::random_access_iterator_tag;
|
|
using value_type = T;
|
|
using difference_type = std::ptrdiff_t;
|
|
using pointer = T *;
|
|
using reference = T &;
|
|
|
|
MidWiseTransformIterator(const T *ptr, int n, int post)
|
|
: ptr_(ptr), i_(0), j_(0), n_(n), post_(post) {}
|
|
|
|
MidWiseTransformIterator<T, CPUContext> &operator++() {
|
|
++j_;
|
|
if (UNLIKELY(j_ == post_)) {
|
|
++i_;
|
|
j_ = 0;
|
|
if (UNLIKELY(i_ == n_)) {
|
|
i_ = 0;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
MidWiseTransformIterator<T, CPUContext> &operator+(int n) {
|
|
while (n-- > 0) {
|
|
++j_;
|
|
if (UNLIKELY(j_ == post_)) {
|
|
++i_;
|
|
j_ = 0;
|
|
if (UNLIKELY(i_ == n_)) {
|
|
i_ = 0;
|
|
}
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool operator==(const MidWiseTransformIterator<T, CPUContext> &rhs) const {
|
|
return (ptr_ + i_) == &(*rhs);
|
|
}
|
|
|
|
bool operator!=(const MidWiseTransformIterator<T, CPUContext> &rhs) const {
|
|
return (ptr_ + i_) != &(*rhs);
|
|
}
|
|
|
|
const T &operator*() { return ptr_[i_]; }
|
|
|
|
private:
|
|
const T *ptr_;
|
|
int64_t i_;
|
|
int64_t j_;
|
|
int64_t n_;
|
|
int64_t post_;
|
|
};
|
|
|
|
#if defined(__NVCC__) || defined(__HIPCC__)
|
|
template <typename T>
|
|
class RowwiseTransformIterator<T, GPUContext>
|
|
: public thrust::iterator_adaptor<RowwiseTransformIterator<T, GPUContext>,
|
|
const T *> {
|
|
public:
|
|
typedef thrust::iterator_adaptor<RowwiseTransformIterator<T, GPUContext>,
|
|
const T *>
|
|
super_t;
|
|
HOSTDEVICE RowwiseTransformIterator(const T *x, int n)
|
|
: super_t(x), begin_(x), n_(n) {}
|
|
friend class thrust::iterator_core_access;
|
|
|
|
private:
|
|
unsigned int n_;
|
|
const T *begin_;
|
|
HOSTDEVICE typename super_t::reference dereference() const {
|
|
return *(begin_ + (this->base() - begin_) % n_);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
class MidWiseTransformIterator<T, GPUContext>
|
|
: public thrust::iterator_adaptor<MidWiseTransformIterator<T, GPUContext>,
|
|
const T *> {
|
|
public:
|
|
typedef thrust::iterator_adaptor<MidWiseTransformIterator<T, GPUContext>,
|
|
const T *>
|
|
super_t;
|
|
HOSTDEVICE MidWiseTransformIterator(const T *x, int n, int post)
|
|
: super_t(x), begin_(x), n_(n), post_(post) {}
|
|
friend class thrust::iterator_core_access;
|
|
|
|
private:
|
|
unsigned int post_;
|
|
unsigned int n_;
|
|
const T *begin_;
|
|
HOSTDEVICE typename super_t::reference dereference() const {
|
|
return *(begin_ + (((this->base() - begin_) / post_) % n_));
|
|
}
|
|
};
|
|
#endif
|
|
|
|
template <typename Functor,
|
|
typename T,
|
|
typename DeviceContext,
|
|
typename OutType = T>
|
|
class TransformFunctor {
|
|
public:
|
|
TransformFunctor(const DenseTensor &x,
|
|
const DenseTensor &y,
|
|
DenseTensor *z,
|
|
const DeviceContext &dev_ctx,
|
|
Functor func,
|
|
const bool is_xsize_larger = true)
|
|
: x_(x.data<T>()),
|
|
y_(y.data<T>()),
|
|
z_(dev_ctx.template Alloc<OutType>(z)),
|
|
nx_(x.numel()),
|
|
dev_ctx_(dev_ctx),
|
|
func_(func),
|
|
is_xsize_larger_(is_xsize_larger) {
|
|
if (is_xsize_larger_ == false) {
|
|
nx_ = y.numel();
|
|
}
|
|
}
|
|
|
|
inline void Run() const {
|
|
phi::Transform<DeviceContext> trans;
|
|
trans(dev_ctx_, x_, x_ + nx_, y_, z_, func_);
|
|
}
|
|
|
|
inline void RunRowWise(int n) const {
|
|
phi::Transform<DeviceContext> trans;
|
|
if (is_xsize_larger_) {
|
|
trans(dev_ctx_,
|
|
x_,
|
|
x_ + nx_,
|
|
RowwiseTransformIterator<T, DeviceContext>(y_, n),
|
|
z_,
|
|
func_);
|
|
} else {
|
|
trans(dev_ctx_,
|
|
y_,
|
|
y_ + nx_,
|
|
RowwiseTransformIterator<T, DeviceContext>(x_, n),
|
|
z_,
|
|
func_);
|
|
}
|
|
}
|
|
|
|
inline void RunMidWise(int n, int post) const {
|
|
phi::Transform<DeviceContext> trans;
|
|
if (is_xsize_larger_) {
|
|
trans(dev_ctx_,
|
|
x_,
|
|
x_ + nx_,
|
|
MidWiseTransformIterator<T, DeviceContext>(y_, n, post),
|
|
z_,
|
|
func_);
|
|
} else {
|
|
trans(dev_ctx_,
|
|
y_,
|
|
y_ + nx_,
|
|
MidWiseTransformIterator<T, DeviceContext>(x_, n, post),
|
|
z_,
|
|
func_);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const T *x_;
|
|
const T *y_;
|
|
OutType *z_;
|
|
int64_t nx_;
|
|
const DeviceContext &dev_ctx_;
|
|
Functor func_;
|
|
bool is_xsize_larger_;
|
|
};
|
|
|
|
template <typename Functor, typename T, typename OutType = T>
|
|
void CommonForwardBroadcastCPU(const DenseTensor &x,
|
|
const DenseTensor &y,
|
|
DenseTensor *z,
|
|
int64_t *x_dims_array,
|
|
int64_t *y_dims_array,
|
|
int64_t *out_dims_array,
|
|
int max_dim,
|
|
const CPUContext &dev_ctx,
|
|
Functor func,
|
|
const bool is_xsize_larger = true) {
|
|
std::vector<int64_t> index_array(max_dim, 0);
|
|
const T *x_data = x.data<T>();
|
|
const T *y_data = y.data<T>();
|
|
if (z && z->numel() == 0) {
|
|
dev_ctx.Alloc<OutType>(z);
|
|
return;
|
|
}
|
|
OutType *out_data = dev_ctx.Alloc<OutType>(z);
|
|
|
|
const int64_t out_size = std::accumulate(out_dims_array,
|
|
out_dims_array + max_dim,
|
|
1ll,
|
|
std::multiplies<int64_t>());
|
|
int64_t x_index, y_index;
|
|
for (int64_t out_index = 0; out_index < out_size; ++out_index) {
|
|
x_index =
|
|
GetElementwiseIndex<int64_t>(x_dims_array, max_dim, index_array.data());
|
|
y_index =
|
|
GetElementwiseIndex<int64_t>(y_dims_array, max_dim, index_array.data());
|
|
if (is_xsize_larger) {
|
|
out_data[out_index] = func(x_data[x_index], y_data[y_index]);
|
|
} else {
|
|
out_data[out_index] = func(y_data[y_index], x_data[x_index]);
|
|
}
|
|
|
|
UpdateElementwiseIndexArray<int64_t>(
|
|
out_dims_array, max_dim, index_array.data());
|
|
}
|
|
}
|
|
|
|
template <typename Functor, typename T, typename OutType = T>
|
|
void CommonElementwiseBroadcastForward(const CPUContext &dev_ctx,
|
|
const DenseTensor &x,
|
|
const DenseTensor &y,
|
|
DenseTensor *z,
|
|
const DDim &x_dims,
|
|
const DDim &y_dims,
|
|
Functor func,
|
|
int axis,
|
|
const bool is_xsize_larger = true) {
|
|
int max_dim = (std::max)(x_dims.size(), y_dims.size());
|
|
axis = (axis == -1 ? std::abs(x_dims.size() - y_dims.size()) : axis);
|
|
PADDLE_ENFORCE_GE(
|
|
axis,
|
|
0,
|
|
common::errors::InvalidArgument(
|
|
"Axis should be great than or equal to 0, but received axis is %d.",
|
|
axis));
|
|
PADDLE_ENFORCE_LE(
|
|
axis,
|
|
max_dim,
|
|
common::errors::InvalidArgument(
|
|
"Axis should be less than or equal to %d, but received axis is %d.",
|
|
max_dim,
|
|
axis));
|
|
std::vector<int64_t> x_dims_array(max_dim);
|
|
std::vector<int64_t> y_dims_array(max_dim);
|
|
std::vector<int64_t> out_dims_array(max_dim);
|
|
GetBroadcastDimsArrays(x_dims,
|
|
y_dims,
|
|
x_dims_array.data(),
|
|
y_dims_array.data(),
|
|
out_dims_array.data(),
|
|
max_dim,
|
|
axis);
|
|
|
|
CommonForwardBroadcastCPU<Functor, T, OutType>(x,
|
|
y,
|
|
z,
|
|
x_dims_array.data(),
|
|
y_dims_array.data(),
|
|
out_dims_array.data(),
|
|
max_dim,
|
|
dev_ctx,
|
|
func,
|
|
is_xsize_larger);
|
|
}
|
|
|
|
// It is a common CPU implementation to compute binary calculation with the
|
|
// support of broadcast. Note:
|
|
// 1. CPU implementation cannot support the case when x needs broadcast, thus
|
|
// this function need to be called with XxxFunctor and XxxInverseFunctor,
|
|
// like AddFunctor and InverseAddFunctor.
|
|
// 2. The corresponding GPU implementation supports all the broadcast cases,
|
|
// thus there is no need to define and call with XxxInverseFunctor.
|
|
// TODO(liuyiqun): optimize the CPU implementation to support all broadcast
|
|
// cases and avoid the need of XxxInverseFunctor.
|
|
template <typename Functor, typename T, typename OutType = T>
|
|
void ElementwiseCompute(const CPUContext &dev_ctx,
|
|
const DenseTensor &x,
|
|
const DenseTensor &y,
|
|
Functor func,
|
|
DenseTensor *z,
|
|
int axis = -1) {
|
|
dev_ctx.Alloc<OutType>(z);
|
|
if (z && z->numel() == 0) {
|
|
return;
|
|
}
|
|
auto x_dims = x.dims();
|
|
auto y_dims = y.dims();
|
|
bool is_xsize_larger = true;
|
|
int max_dim = x_dims.size();
|
|
if (x_dims.size() < y_dims.size()) {
|
|
is_xsize_larger = false;
|
|
max_dim = y_dims.size();
|
|
}
|
|
TransformFunctor<Functor, T, CPUContext, OutType> functor(
|
|
x, y, z, dev_ctx, func, is_xsize_larger);
|
|
if (x_dims == y_dims) {
|
|
functor.Run();
|
|
return;
|
|
}
|
|
|
|
axis = (axis == -1 ? std::abs(x_dims.size() - y_dims.size()) : axis);
|
|
PADDLE_ENFORCE_GE(
|
|
axis,
|
|
0,
|
|
errors::InvalidArgument(
|
|
"Axis should be great than or equal to 0, but received axis is %d.",
|
|
axis));
|
|
PADDLE_ENFORCE_LE(
|
|
axis,
|
|
max_dim,
|
|
errors::InvalidArgument(
|
|
"Axis should be less than or equal to %d, but received axis is %d.",
|
|
max_dim,
|
|
axis));
|
|
|
|
size_t pre, n, post;
|
|
int is_run_common_broadcast, axis_trim = 0;
|
|
if (is_xsize_larger) {
|
|
auto y_dims_trimmed = TrimTrailingSingularDims(y_dims);
|
|
axis_trim = (y_dims_trimmed.size() == 0) ? x_dims.size() : axis;
|
|
GetMidDims(x_dims,
|
|
y_dims_trimmed,
|
|
axis_trim,
|
|
&pre,
|
|
&n,
|
|
&post,
|
|
&is_run_common_broadcast);
|
|
} else {
|
|
auto x_dims_trimmed = TrimTrailingSingularDims(x_dims);
|
|
axis_trim = (x_dims_trimmed.size() == 0) ? y_dims.size() : axis;
|
|
GetMidDims(y_dims,
|
|
x_dims_trimmed,
|
|
axis_trim,
|
|
&pre,
|
|
&n,
|
|
&post,
|
|
&is_run_common_broadcast);
|
|
}
|
|
// special case for common implementation.
|
|
// case 1: x=[2,3,1,5], y=[2,1,4,1]
|
|
// case 2: x=[2,3,4], y=[1,1,4]
|
|
if (is_run_common_broadcast == 1) {
|
|
CommonElementwiseBroadcastForward<Functor, T, OutType>(
|
|
dev_ctx, x, y, z, x_dims, y_dims, func, axis, is_xsize_larger);
|
|
return;
|
|
}
|
|
|
|
if (post == 1) {
|
|
functor.RunRowWise(n);
|
|
return;
|
|
} else {
|
|
functor.RunMidWise(n, post);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// for broadcast backwards
|
|
static inline std::vector<int> GetReduceDim(const DDim &in,
|
|
const DDim &out,
|
|
int axis) {
|
|
axis =
|
|
(axis == -1 ? std::abs(static_cast<int>(out.size() - in.size())) : axis);
|
|
std::vector<int> dims;
|
|
for (int i = 0; i < axis; ++i) {
|
|
dims.push_back(i);
|
|
}
|
|
for (int i = 0; i < in.size(); ++i) {
|
|
if (out[i + axis] != in[i]) {
|
|
dims.push_back(i + axis);
|
|
}
|
|
}
|
|
for (int i = axis + in.size(); i < out.size(); ++i) {
|
|
dims.push_back(i);
|
|
}
|
|
return dims;
|
|
}
|
|
|
|
template <typename DeviceContext, typename T>
|
|
static inline void GetDoubleGradSafeTensor(const DeviceContext &dev_ctx,
|
|
const DenseTensor &x,
|
|
const DenseTensor *ddx,
|
|
DenseTensor *ddx_safe) {
|
|
if (ddx) {
|
|
*ddx_safe = *ddx;
|
|
} else {
|
|
auto meta = DenseTensorMeta(x.dtype(), x.dims(), x.layout());
|
|
*ddx_safe = Empty(dev_ctx, std::move(meta));
|
|
dev_ctx.template Alloc<T>(ddx_safe);
|
|
SetConstant<DeviceContext, T> set_zero;
|
|
set_zero(dev_ctx, ddx_safe, static_cast<T>(0));
|
|
}
|
|
}
|
|
|
|
inline void ElementwiseGradPreProcess(const DenseTensor &dout,
|
|
DenseTensor *dx) {
|
|
if (dx != nullptr) {
|
|
dx->set_lod(dout.lod());
|
|
}
|
|
}
|
|
|
|
#if defined(__NVCC__) || defined(__HIPCC__) || defined(__xpu__)
|
|
|
|
// static unroller
|
|
template <template <int Index, int VecSize> typename Func,
|
|
int VecSize,
|
|
int End,
|
|
int Begin = 0>
|
|
struct Unroller {
|
|
template <typename... Args>
|
|
static HOSTDEVICE inline void step(Args &&...args) {
|
|
Func<Begin, VecSize>::Apply(std::forward<Args>(args)...);
|
|
Unroller<Func, VecSize, End, Begin + 1>::step(args...);
|
|
}
|
|
};
|
|
|
|
template <template <int Index, int VecSize> typename Func, int VecSize, int End>
|
|
struct Unroller<Func, VecSize, End, End> {
|
|
template <typename... Args>
|
|
static HOSTDEVICE inline void step(Args &&...args) {}
|
|
};
|
|
|
|
// static unroller without VecSize for broadcast
|
|
template <template <int Index> typename Func, int End, int Begin = 0>
|
|
struct UnrollerWithoutVecSize {
|
|
template <typename... Args>
|
|
static HOSTDEVICE inline void step(Args &&...args) {
|
|
Func<Begin>::Apply(std::forward<Args>(args)...);
|
|
UnrollerWithoutVecSize<Func, End, Begin + 1>::step(args...);
|
|
}
|
|
};
|
|
|
|
template <template <int Index> typename Func, int End>
|
|
struct UnrollerWithoutVecSize<Func, End, End> {
|
|
template <typename... Args>
|
|
static HOSTDEVICE inline void step(Args &&...args) {}
|
|
};
|
|
|
|
template <int Index, int VecSize>
|
|
struct Loader {
|
|
template <typename Array, typename ArgsT>
|
|
static __device__ __forceinline__ void Apply(const Array &in,
|
|
ArgsT *args,
|
|
kps::IndexType offset,
|
|
int num,
|
|
int read_lens,
|
|
bool is_boundary) {
|
|
using Type = std::tuple_element_t<Index, ArgsT>;
|
|
kps::Init<Type, ArgsT, Index, VecSize>(
|
|
args, static_cast<Type>(1.0f), read_lens);
|
|
if (is_boundary) {
|
|
kps::ReadData<Type, VecSize, 1, ArgsT, Index, true>(
|
|
args,
|
|
reinterpret_cast<const _ptr_ Type *>(in[Index]) + offset,
|
|
num,
|
|
read_lens);
|
|
} else {
|
|
kps::ReadData<Type, VecSize, 1, ArgsT, Index, false>(
|
|
args,
|
|
reinterpret_cast<const _ptr_ Type *>(in[Index]) + offset,
|
|
num,
|
|
read_lens);
|
|
}
|
|
}
|
|
};
|
|
|
|
template <int Index>
|
|
struct InputSetter {
|
|
template <typename Array, typename ArgsT>
|
|
static void Apply(const std::vector<const DenseTensor *> &ins_tensor,
|
|
const ArgsT &args,
|
|
Array *ins_data) {
|
|
using Type = std::tuple_element_t<Index, ArgsT>;
|
|
(*ins_data)[Index] = (const _ptr_ char *)(ins_tensor[Index]->data<Type>());
|
|
}
|
|
};
|
|
|
|
static inline int GetVectorizedSizeWithDtype(const DenseTensor *tensor) {
|
|
int element_size = phi::SizeOf(tensor->dtype());
|
|
if (element_size > sizeof(float)) {
|
|
return 1;
|
|
}
|
|
constexpr int max_load_bits = 128;
|
|
int vec_size = max_load_bits / CHAR_BIT / element_size;
|
|
return vec_size;
|
|
}
|
|
static inline int GetVectorizedSizeWithAddress(const DenseTensor *tensor) {
|
|
int element_size = phi::SizeOf(tensor->dtype());
|
|
if (element_size > sizeof(float)) {
|
|
return 1;
|
|
}
|
|
uint64_t address = reinterpret_cast<uint64_t>(tensor->data());
|
|
|
|
// Currently, decide to deal with no more than 4 data once while adopting
|
|
// vectorization load/store, if performance test shows that dealing with
|
|
// 8 data once in vectorization load/store does get optimized, code below
|
|
// can begin with :
|
|
if (address % (element_size * 8) == 0) {
|
|
return 8;
|
|
} else if (address % (element_size * 4) == 0) {
|
|
return 4;
|
|
} else if (address % (element_size * 2) == 0) {
|
|
return 2;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int GetVectorizedSizeForTensors(
|
|
const std::vector<const DenseTensor *> &ins,
|
|
const std::vector<DenseTensor *> &outs,
|
|
bool only_consider_outs_dtype = false) {
|
|
#ifdef PADDLE_WITH_XPU_KP
|
|
int vec_size = 256;
|
|
#else
|
|
constexpr int max_vec_size = 8;
|
|
int vec_size = 1;
|
|
if (!only_consider_outs_dtype) {
|
|
for (size_t i = 0; i < ins.size(); ++i) {
|
|
vec_size = std::max(vec_size, GetVectorizedSizeWithDtype(ins[i]));
|
|
}
|
|
}
|
|
for (size_t i = 0; i < outs.size(); ++i) {
|
|
vec_size = std::max(vec_size, GetVectorizedSizeWithDtype(outs[i]));
|
|
}
|
|
|
|
for (size_t i = 0; i < ins.size(); ++i) {
|
|
vec_size = std::min(vec_size, GetVectorizedSizeWithAddress(ins[i]));
|
|
}
|
|
for (size_t i = 0; i < outs.size(); ++i) {
|
|
vec_size = std::min(vec_size, GetVectorizedSizeWithAddress(outs[i]));
|
|
}
|
|
vec_size = std::min(vec_size, max_vec_size);
|
|
#endif
|
|
return vec_size;
|
|
}
|
|
|
|
namespace detail {
|
|
template <class F, class Tuple, std::size_t... Index>
|
|
// GCC/Clang need the decltype() return type
|
|
HOSTDEVICE constexpr decltype(auto) ApplyImpl(F &&f,
|
|
Tuple &&t,
|
|
std::index_sequence<Index...>) {
|
|
return std::forward<F>(f)(std::get<Index>(std::forward<Tuple>(t))...);
|
|
}
|
|
} // namespace detail
|
|
|
|
template <class F, class Tuple>
|
|
HOSTDEVICE constexpr decltype(auto) Apply(F &&f, Tuple &&t) {
|
|
return detail::ApplyImpl(
|
|
std::forward<F>(f),
|
|
std::forward<Tuple>(t),
|
|
std::make_index_sequence<
|
|
std::tuple_size<std::remove_reference_t<Tuple>>::value>{});
|
|
}
|
|
|
|
template <typename OutT,
|
|
int VecSize,
|
|
typename Functor,
|
|
typename ArgsT,
|
|
int Arity>
|
|
struct SameDimsElementwisePrimitiveCaller {
|
|
__device__ inline void operator()(Functor func,
|
|
ArgsT *args,
|
|
OutT *result,
|
|
int read_lens) {
|
|
#ifdef PADDLE_WITH_XPU_KP
|
|
for (int idx = 0; idx < read_lens; ++idx) {
|
|
result[idx] = static_cast<OutT>(Apply(func, args[idx]));
|
|
}
|
|
#else
|
|
#pragma unroll
|
|
for (int idx = 0; idx < VecSize; ++idx) {
|
|
result[idx] = static_cast<OutT>(Apply(func, args[idx]));
|
|
}
|
|
#endif
|
|
}
|
|
};
|
|
|
|
template <typename OutT, int VecSize, bool IsBoundary, int NumOuts>
|
|
struct ElementwiseWriteDataCallerBc {
|
|
__device__ __forceinline__ void operator()(
|
|
Array<_ptr_ OutT *, NumOuts> outs,
|
|
ConditionalT<OutT, NumOuts> src[VecSize],
|
|
kps::IndexType block_offset,
|
|
int num,
|
|
int read_lens) {
|
|
OutT dst[NumOuts][VecSize];
|
|
#pragma unroll
|
|
for (int i = 0; i < read_lens; ++i) {
|
|
#pragma unroll
|
|
for (int j = 0; j < NumOuts; ++j) {
|
|
dst[j][i] = (src[i])[j];
|
|
}
|
|
}
|
|
#pragma unroll
|
|
for (int i = 0; i < NumOuts; ++i) {
|
|
kps::WriteData<OutT, VecSize, 1, IsBoundary>(
|
|
outs[i] + block_offset, dst[i], num, read_lens);
|
|
}
|
|
}
|
|
};
|
|
|
|
template <typename OutT, int VecSize, bool IsBoundary>
|
|
struct ElementwiseWriteDataCallerBc<OutT, VecSize, IsBoundary, 1> {
|
|
__device__ __forceinline__ void operator()(Array<_ptr_ OutT *, 1> outs,
|
|
OutT src[VecSize],
|
|
kps::IndexType block_offset,
|
|
int num,
|
|
int read_lens) {
|
|
kps::WriteData<OutT, VecSize, 1, IsBoundary>(
|
|
outs[0] + block_offset, src, num, read_lens);
|
|
}
|
|
};
|
|
|
|
template <typename OutT,
|
|
typename Functor,
|
|
int Arity,
|
|
int NumOuts,
|
|
int VecSize,
|
|
bool IsBoundary>
|
|
__device__ void VectorizedElementwiseKernelImpl(
|
|
const Array<const _ptr_ char *__restrict__, Arity> &in,
|
|
Array<_ptr_ OutT *, NumOuts> outs,
|
|
kps::IndexType offset,
|
|
int num,
|
|
int read_lens,
|
|
Functor func) {
|
|
using Traits = funcs::FunctionTraits<Functor>;
|
|
using ArgsT = typename Traits::ArgsTuple;
|
|
ArgsT args[VecSize];
|
|
ConditionalT<OutT, NumOuts> result[VecSize];
|
|
|
|
Unroller<Loader, VecSize, Arity>::step(
|
|
in, args, offset, num, read_lens, IsBoundary);
|
|
|
|
SameDimsElementwisePrimitiveCaller<ConditionalT<OutT, NumOuts>,
|
|
VecSize,
|
|
Functor,
|
|
ArgsT,
|
|
Arity>()(func, args, result, read_lens);
|
|
|
|
ElementwiseWriteDataCallerBc<OutT, VecSize, IsBoundary, NumOuts>()(
|
|
outs, result, offset, num, read_lens);
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int Arity, int NumOuts, int VecSize>
|
|
__global__ void VectorizedElementwiseKernel(
|
|
Array<const _ptr_ char *__restrict__, Arity> ins,
|
|
Array<_ptr_ OutT *, NumOuts> outs,
|
|
kps::IndexType numel,
|
|
kps::IndexType main_offset,
|
|
int read_lens,
|
|
Functor func) {
|
|
kps::IndexType data_offset =
|
|
static_cast<kps::IndexType>(BLOCK_ID_X) * BLOCK_NUM_X * read_lens;
|
|
kps::IndexType stride =
|
|
static_cast<kps::IndexType>(BLOCK_NUM_X) * GRID_NUM_X * read_lens;
|
|
for (; data_offset < main_offset; data_offset += stride) {
|
|
VectorizedElementwiseKernelImpl<OutT,
|
|
Functor,
|
|
Arity,
|
|
NumOuts,
|
|
VecSize,
|
|
false>(
|
|
ins, outs, data_offset, read_lens * BLOCK_NUM_X, read_lens, func);
|
|
}
|
|
|
|
kps::IndexType remain = numel - data_offset;
|
|
if (remain > 0) {
|
|
VectorizedElementwiseKernelImpl<OutT,
|
|
Functor,
|
|
Arity,
|
|
NumOuts,
|
|
VecSize,
|
|
true>(
|
|
ins, outs, data_offset, static_cast<int>(remain), read_lens, func);
|
|
}
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int Arity, int NumOuts, int VecSize>
|
|
void LaunchElementwiseKernel(const KPDevice &dev_ctx,
|
|
const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
Functor func) {
|
|
// There are at least 1 output, but maybe 0 input (ins.size() == 0).
|
|
// For large tensor numel * sizeof(T) > 2^31, we must use int64_t as index
|
|
// type.
|
|
int64_t numel = (*outs)[0]->numel();
|
|
Array<const _ptr_ char *__restrict__, Arity> ins_data;
|
|
Array<_ptr_ OutT *, NumOuts> outs_data;
|
|
|
|
using Traits = funcs::FunctionTraits<Functor>;
|
|
using ArgsT = typename Traits::ArgsTuple;
|
|
ArgsT arg;
|
|
UnrollerWithoutVecSize<InputSetter, Arity>::step(ins, arg, &ins_data);
|
|
for (int i = 0; i < outs->size(); ++i) {
|
|
outs_data[i] = (*outs)[i]->data<OutT>();
|
|
}
|
|
|
|
#ifdef PADDLE_WITH_XPU_KP
|
|
int block_size = 64;
|
|
int grid_size = 8;
|
|
int read_lens = kps::details::GetXpuReadLens(numel, block_size, grid_size);
|
|
auto stream = dev_ctx.x_context()->xpu_stream;
|
|
int64_t main_offset =
|
|
(numel / (read_lens * block_size)) * read_lens * block_size;
|
|
VectorizedElementwiseKernel<OutT, Functor, Arity, NumOuts, VecSize>
|
|
<<<grid_size, block_size, 0, stream>>>(
|
|
ins_data, outs_data, numel, main_offset, read_lens, func);
|
|
#else
|
|
auto gpu_config =
|
|
phi::backends::gpu::GetGpuLaunchConfig1D(dev_ctx, numel, VecSize);
|
|
int64_t main_offset = (numel / (VecSize * gpu_config.GetBlockSize())) *
|
|
VecSize * gpu_config.GetBlockSize();
|
|
auto stream = dev_ctx.stream();
|
|
VectorizedElementwiseKernel<OutT, Functor, Arity, NumOuts, VecSize>
|
|
<<<gpu_config.block_per_grid, gpu_config.thread_per_block, 0, stream>>>(
|
|
ins_data, outs_data, numel, main_offset, VecSize, func);
|
|
#endif
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int Arity, int NumOuts = 1>
|
|
typename std::enable_if<!NeedVectorized<OutT>::value, void>::type
|
|
ElementwiseKernelForDifferentVecSize(
|
|
const KPDevice &dev_ctx,
|
|
const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
Functor func) {
|
|
LaunchElementwiseKernel<OutT, Functor, Arity, NumOuts, VecSizeS>(
|
|
dev_ctx, ins, outs, func);
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int Arity, int NumOuts = 1>
|
|
typename std::enable_if<NeedVectorized<OutT>::value, void>::type
|
|
ElementwiseKernelForDifferentVecSize(
|
|
const KPDevice &dev_ctx,
|
|
const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
Functor func) {
|
|
static int capability = dev_ctx.GetComputeCapability();
|
|
// For Hopper and Blackwell, max vectorized size is 8.
|
|
static int max_vec_size = capability >= 90 ? VecSizeVL : VecSizeL;
|
|
// calculate the max vec_size for all ins and outs
|
|
int vec_size = GetVectorizedSizeForTensors(ins, *outs);
|
|
vec_size = std::min(vec_size, max_vec_size);
|
|
|
|
switch (vec_size) {
|
|
case VecSizeVL:
|
|
LaunchElementwiseKernel<OutT, Functor, Arity, NumOuts, VecSizeVL>(
|
|
dev_ctx, ins, outs, func);
|
|
break;
|
|
case VecSizeL:
|
|
LaunchElementwiseKernel<OutT, Functor, Arity, NumOuts, VecSizeL>(
|
|
dev_ctx, ins, outs, func);
|
|
break;
|
|
case VecSizeM:
|
|
LaunchElementwiseKernel<OutT, Functor, Arity, NumOuts, VecSizeM>(
|
|
dev_ctx, ins, outs, func);
|
|
break;
|
|
case VecSizeS:
|
|
LaunchElementwiseKernel<OutT, Functor, Arity, NumOuts, VecSizeS>(
|
|
dev_ctx, ins, outs, func);
|
|
break;
|
|
default: {
|
|
PADDLE_THROW(common::errors::Unimplemented(
|
|
"Unsupported vectorized size: %d !", vec_size));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int NumOuts = 1>
|
|
void ElementwiseKernel(const KPDevice &dev_ctx,
|
|
const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
Functor func) {
|
|
using Traits = funcs::FunctionTraits<Functor>;
|
|
const int kArity = Traits::arity;
|
|
PADDLE_ENFORCE_EQ(ins.size(),
|
|
kArity,
|
|
common::errors::InvalidArgument(
|
|
"The number of inputs is expected to be equal to the "
|
|
"arity of functor. But received: the number of inputs "
|
|
"is %d, the arity of functor is %d.",
|
|
ins.size(),
|
|
kArity));
|
|
PADDLE_ENFORCE_EQ(outs->size(),
|
|
NumOuts,
|
|
common::errors::InvalidArgument(
|
|
"Number of outputs shall equal to number of functions, "
|
|
"but number of outputs is %d, of functions is %d.",
|
|
outs->size(),
|
|
NumOuts));
|
|
|
|
bool have_0_size = false;
|
|
for (int i = 0; i < outs->size(); ++i) {
|
|
if (outs->at(i)->numel() == 0) {
|
|
have_0_size = true;
|
|
}
|
|
if (i > 0) {
|
|
PADDLE_ENFORCE_EQ(
|
|
(*outs)[i]->dims(),
|
|
(*outs)[0]->dims(),
|
|
common::errors::InvalidArgument(
|
|
"The shape of each output tensor shall be identical yet, "
|
|
"but %dth output tensor`s shape is not.",
|
|
i));
|
|
}
|
|
dev_ctx.template Alloc<OutT>((*outs)[i]);
|
|
}
|
|
if (have_0_size) {
|
|
return;
|
|
}
|
|
|
|
ElementwiseKernelForDifferentVecSize<OutT, Functor, kArity, NumOuts>(
|
|
dev_ctx, ins, outs, func);
|
|
}
|
|
|
|
#endif
|
|
|
|
} // namespace funcs
|
|
} // namespace phi
|