912 lines
34 KiB
C++
912 lines
34 KiB
C++
/* Copyright (c) 2022 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 <sstream>
|
|
#include "paddle/common/enforce.h"
|
|
#include "paddle/phi/kernels/funcs/elementwise_base.h"
|
|
|
|
#if defined(__NVCC__) || defined(__HIPCC__) || defined(__xpu__)
|
|
#include "paddle/phi/kernels/funcs/dims_simplifier.h"
|
|
|
|
#endif
|
|
|
|
namespace phi {
|
|
namespace funcs {
|
|
|
|
#if defined(__NVCC__) || defined(__HIPCC__) || defined(__xpu__)
|
|
|
|
enum BroadcastType { kMixed = 1, kBroadcast = 2, kElementwise = 3 };
|
|
|
|
template <typename OutT, typename Functor, int Arity, int NumOuts>
|
|
struct BroadcastTypeClassifier {
|
|
int64_t numel{0};
|
|
int broadcast_num{0}; // Not used for XPU
|
|
bool all_elementwise{true}; // Not used for XPU
|
|
Array<bool, Arity> use_broadcast; // Not used for XPU
|
|
Array<kps::details::BroadcastConfig, Arity> configs;
|
|
Array<const _ptr_ char *__restrict__, Arity> ins_data;
|
|
Array<_ptr_ OutT *, NumOuts> outs_data;
|
|
|
|
BroadcastTypeClassifier() {}
|
|
BroadcastTypeClassifier(const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
int axis) {
|
|
numel = (*outs)[0]->numel();
|
|
|
|
#ifndef PADDLE_WITH_XPU_KP
|
|
for (size_t i = 0; i < ins.size(); ++i) {
|
|
bool is_same_dim = ins[i]->numel() == numel;
|
|
if (is_same_dim) {
|
|
use_broadcast[i] = false;
|
|
} else {
|
|
use_broadcast[i] = true;
|
|
broadcast_num++;
|
|
}
|
|
all_elementwise &= is_same_dim;
|
|
}
|
|
#endif
|
|
|
|
InitBroadcastConfigs(ins, outs, axis);
|
|
|
|
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 < NumOuts; ++i) {
|
|
outs_data[i] = (*outs)[i]->data<OutT>();
|
|
}
|
|
}
|
|
|
|
void InitBroadcastConfigs(const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
int axis) {
|
|
#ifdef PADDLE_WITH_XPU_KP
|
|
const auto dims_simplifier =
|
|
BroadcastDimsSimplifier(ins, (*outs)[0]->dims(), axis);
|
|
if (VLOG_IS_ON(6)) {
|
|
DimsSimplifiedLogger<int64_t>::Log(
|
|
ins, outs, dims_simplifier, "BroadcastKernel");
|
|
}
|
|
configs[0] = kps::details::BroadcastConfig(dims_simplifier.out_dims,
|
|
dims_simplifier.in_dims[0],
|
|
dims_simplifier.in_dims[1],
|
|
dims_simplifier.rank);
|
|
configs[1] = kps::details::BroadcastConfig(dims_simplifier.out_dims,
|
|
dims_simplifier.in_dims[1],
|
|
dims_simplifier.in_dims[0],
|
|
dims_simplifier.rank);
|
|
#else
|
|
if (!all_elementwise) {
|
|
const auto dims_simplifier =
|
|
BroadcastDimsSimplifier(ins, (*outs)[0]->dims(), axis);
|
|
if (VLOG_IS_ON(6)) {
|
|
DimsSimplifiedLogger<int64_t>::Log(
|
|
ins, outs, dims_simplifier, "BroadcastKernel");
|
|
}
|
|
for (int i = 0; i < Arity; ++i) {
|
|
// if data shape is[m, n], then you should set data_dim = {n, m}
|
|
// eg: out's shape [3, 45, 1]. then out_dims = {1, 45, 3}
|
|
// if (ins[i]->numel() != (*outs)[0]->numel()) {
|
|
if (ins[i]->numel()) {
|
|
configs[i] = kps::details::BroadcastConfig(dims_simplifier.out_dims,
|
|
dims_simplifier.in_dims[i],
|
|
dims_simplifier.rank);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
};
|
|
|
|
// Common broadcast/elementwise Loader.
|
|
template <int Index, int VecSize, bool IsBoundary, int LoadType>
|
|
struct BroadcastDataLoader {
|
|
template <typename Array1, typename Array2, typename Array3, typename ArgsT>
|
|
static __device__ __forceinline__ void Apply(const Array1 &ins,
|
|
ArgsT *args,
|
|
const Array2 &configs,
|
|
const Array3 &use_broadcast,
|
|
const int block_offset,
|
|
const int num,
|
|
const uint32_t numel,
|
|
int read_lens) {
|
|
using Type = std::tuple_element_t<Index, ArgsT>;
|
|
#ifdef PADDLE_WITH_XPU_KP
|
|
kps::Init<Type, ArgsT, Index, VecSize>(
|
|
args, static_cast<Type>(1.0f), read_lens);
|
|
if (use_broadcast[Index]) {
|
|
kps::ReadDataBc<Type, VecSize, 1, ArgsT, Index, IsBoundary>(
|
|
args,
|
|
reinterpret_cast<const _ptr_ Type *>(ins[Index]),
|
|
block_offset,
|
|
configs[Index],
|
|
numel,
|
|
read_lens);
|
|
} else {
|
|
kps::ReadData<Type, VecSize, 1, ArgsT, Index, IsBoundary>(
|
|
args,
|
|
reinterpret_cast<const _ptr_ Type *>(ins[Index]) + block_offset,
|
|
num,
|
|
read_lens);
|
|
}
|
|
#else
|
|
kps::Init<Type, ArgsT, Index, VecSize>(args, static_cast<Type>(1.0f));
|
|
if (use_broadcast[Index]) {
|
|
kps::ReadDataBc<Type, VecSize, 1, ArgsT, Index, IsBoundary>(
|
|
args,
|
|
reinterpret_cast<const _ptr_ Type *>(ins[Index]),
|
|
block_offset,
|
|
configs[Index],
|
|
numel,
|
|
VecSize);
|
|
}
|
|
// NOTE: If use if...else... with condition `use_broadcast[Index]` here,
|
|
// there will be some errs with clang12 while compiling in ROCm.
|
|
// When the compiler is upgraded, if...else... may be used.
|
|
if (!use_broadcast[Index]) {
|
|
kps::ReadData<Type, VecSize, 1, ArgsT, Index, IsBoundary>(
|
|
args,
|
|
reinterpret_cast<const _ptr_ Type *>(ins[Index]) + block_offset,
|
|
num,
|
|
VecSize);
|
|
}
|
|
#endif
|
|
}
|
|
};
|
|
|
|
/* BroadcastDataLoaders Partial specialization */
|
|
#ifndef PADDLE_WITH_XPU_KP
|
|
// Scalar elementwise Loader with consideration of IsBoundary.
|
|
template <int Index, int VecSize>
|
|
struct BroadcastDataLoader<Index, VecSize, true, kElementwise> {
|
|
template <typename Array1, typename Array2, typename Array3, typename ArgsT>
|
|
static __device__ __forceinline__ void Apply(const Array1 &ins,
|
|
ArgsT *args,
|
|
const Array2 &configs,
|
|
const Array3 &use_broadcast,
|
|
const int block_offset,
|
|
const int num,
|
|
const uint32_t numel,
|
|
int read_lens) {
|
|
using Type = std::tuple_element_t<Index, ArgsT>;
|
|
int thread_offset = threadIdx.x * VecSize + block_offset;
|
|
#pragma unroll
|
|
for (int idx = 0; idx < VecSize; ++idx) {
|
|
std::get<Index>(args[idx]) = static_cast<Type>(1);
|
|
int index = thread_offset + idx;
|
|
if (index < numel) {
|
|
std::get<Index>(args[idx]) =
|
|
reinterpret_cast<const _ptr_ Type *>(ins[Index])[index];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Vectorized elementwise Loader without consideration of IsBoundary.
|
|
template <int Index, int VecSize>
|
|
struct BroadcastDataLoader<Index, VecSize, false, kElementwise> {
|
|
template <typename Array1, typename Array2, typename Array3, typename ArgsT>
|
|
static __device__ __forceinline__ void Apply(const Array1 &ins,
|
|
ArgsT *args,
|
|
const Array2 &configs,
|
|
const Array3 &use_broadcast,
|
|
const int block_offset,
|
|
const int num,
|
|
const uint32_t numel,
|
|
int read_lens) {
|
|
using Type = std::tuple_element_t<Index, ArgsT>;
|
|
using VecType = phi::kps::details::VectorType<Type, VecSize>;
|
|
VecType vec_temp;
|
|
|
|
int64_t thread_offset =
|
|
static_cast<int64_t>(threadIdx.x) +
|
|
static_cast<int64_t>(blockIdx.x) * static_cast<int64_t>(blockDim.x);
|
|
const VecType *__restrict__ vec_input =
|
|
reinterpret_cast<const VecType *__restrict__>(ins[Index]);
|
|
vec_temp = vec_input[thread_offset];
|
|
#pragma unroll
|
|
for (int idx = 0; idx < VecSize; ++idx) {
|
|
std::get<Index>(args[idx]) = vec_temp.val[idx];
|
|
}
|
|
}
|
|
};
|
|
|
|
template <int Index, int VecSize>
|
|
struct BroadcastDataInit {
|
|
template <typename ArgsT>
|
|
static __device__ __forceinline__ void Apply(ArgsT *args) {
|
|
using Type = std::tuple_element_t<Index, ArgsT>;
|
|
#pragma unroll
|
|
for (int k = 0; k < VecSize; ++k) {
|
|
std::get<Index>(args[k]) = static_cast<Type>(1);
|
|
}
|
|
}
|
|
};
|
|
|
|
template <int Index, int VecSize>
|
|
struct BroadcastDataSetter {
|
|
template <typename Array, typename ArgsT>
|
|
static __device__ __forceinline__ void Apply(const Array &ins,
|
|
ArgsT *args,
|
|
uint32_t index_bc[][VecSize]) {
|
|
using Type = std::tuple_element_t<Index, ArgsT>;
|
|
#pragma unroll
|
|
for (int k = 0; k < VecSize; ++k) {
|
|
std::get<Index>(args[k]) =
|
|
reinterpret_cast<const _ptr_ Type *>(ins[Index])[index_bc[Index][k]];
|
|
}
|
|
}
|
|
};
|
|
|
|
#endif
|
|
|
|
// static broadcast unroller
|
|
template <template <int Index, int VecSize, bool IsBoundary, int LoadType>
|
|
typename Func,
|
|
bool IsBoundary,
|
|
int LoadType,
|
|
int VecSize,
|
|
int End,
|
|
int Begin = 0>
|
|
struct BcUnroller {
|
|
template <typename... Args>
|
|
static HOSTDEVICE inline void step(Args &&...args) {
|
|
Func<Begin, VecSize, IsBoundary, LoadType>::Apply(
|
|
std::forward<Args>(args)...);
|
|
BcUnroller<Func, IsBoundary, LoadType, VecSize, End, Begin + 1>::step(
|
|
args...);
|
|
}
|
|
};
|
|
|
|
template <template <int Index, int VecSize, bool IsBoundary, int LoadType>
|
|
typename Func,
|
|
bool IsBoundary,
|
|
int LoadType,
|
|
int VecSize,
|
|
int End>
|
|
struct BcUnroller<Func, IsBoundary, LoadType, VecSize, End, End> {
|
|
template <typename... Args>
|
|
static HOSTDEVICE inline void step(Args &&...args) {}
|
|
};
|
|
|
|
template <typename OutT,
|
|
typename Functor,
|
|
int Arity,
|
|
int NumOuts,
|
|
int VecSize,
|
|
bool IsBoundary,
|
|
int LoadType>
|
|
__device__ void VectorizedBroadcastKernelImpl(
|
|
const Array<const _ptr_ char *__restrict__, Arity> &ins,
|
|
Array<_ptr_ OutT *, NumOuts> outs,
|
|
const Array<bool, Arity> &use_broadcast,
|
|
const uint32_t numel,
|
|
const Array<kps::details::BroadcastConfig, Arity> &configs,
|
|
uint32_t num,
|
|
uint32_t block_offset,
|
|
int read_lens,
|
|
Functor func) {
|
|
using Traits = funcs::FunctionTraits<Functor>;
|
|
using ArgsT = typename Traits::ArgsTuple;
|
|
__simd__ ArgsT args[VecSize];
|
|
__simd__ ConditionalT<OutT, NumOuts> result[VecSize];
|
|
|
|
#ifdef PADDLE_WITH_XPU_KP
|
|
BcUnroller<BroadcastDataLoader, IsBoundary, LoadType, VecSize, Arity>::step(
|
|
ins, args, configs, use_broadcast, block_offset, num, numel, read_lens);
|
|
#else
|
|
if (LoadType == kBroadcast) {
|
|
uint32_t index_bc[Arity][VecSize] = {0};
|
|
Unroller<BroadcastDataInit, VecSize, Arity>::step(args);
|
|
uint32_t thread_offset = block_offset + threadIdx.x * VecSize;
|
|
#pragma unroll
|
|
for (int k = 0; k < VecSize; ++k) {
|
|
uint32_t idx = thread_offset + k;
|
|
if (IsBoundary && idx == numel) break;
|
|
#pragma unroll
|
|
for (int i = 0; i < DDim::kMaxRank; ++i) {
|
|
if (i == configs[0].rank) break;
|
|
auto fast_divmoder = configs[0].divmoders[i].Divmod(idx);
|
|
idx = fast_divmoder.val[0];
|
|
#pragma unroll
|
|
for (int j = 0; j < Arity; ++j) {
|
|
index_bc[j][k] += fast_divmoder.val[1] * configs[j].strides[i];
|
|
}
|
|
}
|
|
}
|
|
Unroller<BroadcastDataSetter, VecSize, Arity>::step(ins, args, index_bc);
|
|
} else {
|
|
BcUnroller<BroadcastDataLoader, IsBoundary, LoadType, VecSize, Arity>::step(
|
|
ins, args, configs, use_broadcast, block_offset, num, numel, read_lens);
|
|
}
|
|
#endif
|
|
SameDimsElementwisePrimitiveCaller<ConditionalT<OutT, NumOuts>,
|
|
VecSize,
|
|
Functor,
|
|
ArgsT,
|
|
Arity>()(func, args, result, read_lens);
|
|
funcs::ElementwiseWriteDataCallerBc<OutT, VecSize, IsBoundary, NumOuts>()(
|
|
outs, result, block_offset, num, read_lens);
|
|
}
|
|
|
|
template <typename Functor,
|
|
typename OutT,
|
|
int Arity,
|
|
int NumOuts,
|
|
int VecSize,
|
|
int LoadType>
|
|
__global__ void VectorizedBroadcastKernel(
|
|
Array<const _ptr_ char *__restrict__, Arity> ins,
|
|
Array<_ptr_ OutT *, NumOuts> outs,
|
|
Array<bool, Arity> use_broadcast,
|
|
uint32_t numel,
|
|
Array<kps::details::BroadcastConfig, Arity> configs,
|
|
uint32_t main_offset,
|
|
uint32_t tail_tid,
|
|
int read_lens,
|
|
Functor func) {
|
|
#ifdef PADDLE_WITH_XPU_KP
|
|
int64_t block_offset =
|
|
static_cast<int64_t>(BLOCK_ID_X) * BLOCK_NUM_X * read_lens;
|
|
int64_t stride = static_cast<int64_t>(BLOCK_NUM_X) * GRID_NUM_X * read_lens;
|
|
for (; block_offset < main_offset; block_offset += stride) {
|
|
VectorizedBroadcastKernelImpl<OutT,
|
|
Functor,
|
|
Arity,
|
|
NumOuts,
|
|
VecSize,
|
|
false,
|
|
LoadType>(ins,
|
|
outs,
|
|
use_broadcast,
|
|
numel,
|
|
configs,
|
|
BLOCK_NUM_X * read_lens,
|
|
block_offset,
|
|
read_lens,
|
|
func);
|
|
}
|
|
int64_t num = numel - block_offset;
|
|
if (num > 0) {
|
|
VectorizedBroadcastKernelImpl<OutT,
|
|
Functor,
|
|
Arity,
|
|
NumOuts,
|
|
VecSize,
|
|
true,
|
|
LoadType>(ins,
|
|
outs,
|
|
use_broadcast,
|
|
numel,
|
|
configs,
|
|
num,
|
|
block_offset,
|
|
read_lens,
|
|
func);
|
|
}
|
|
#else
|
|
int64_t block_offset =
|
|
static_cast<int64_t>(BLOCK_ID_X) * BLOCK_NUM_X * VecSize;
|
|
if (block_offset < main_offset) {
|
|
VectorizedBroadcastKernelImpl<OutT,
|
|
Functor,
|
|
Arity,
|
|
NumOuts,
|
|
VecSize,
|
|
false,
|
|
LoadType>(ins,
|
|
outs,
|
|
use_broadcast,
|
|
numel,
|
|
configs,
|
|
BLOCK_NUM_X * VecSize,
|
|
block_offset,
|
|
read_lens,
|
|
func);
|
|
} else {
|
|
VectorizedBroadcastKernelImpl<OutT,
|
|
Functor,
|
|
Arity,
|
|
NumOuts,
|
|
VecSize,
|
|
true,
|
|
LoadType>(ins,
|
|
outs,
|
|
use_broadcast,
|
|
numel,
|
|
configs,
|
|
tail_tid,
|
|
block_offset,
|
|
read_lens,
|
|
func);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int Arity, int NumOuts, int VecSize>
|
|
void LaunchBroadcastKernel(
|
|
const KPDevice &dev_ctx,
|
|
const BroadcastTypeClassifier<OutT, Functor, Arity, NumOuts> &classifier,
|
|
Functor func) {
|
|
#ifdef PADDLE_WITH_XPU_KP
|
|
const int64_t numel_64 = classifier.numel;
|
|
PADDLE_ENFORCE_LE_UINT32_MAX(numel_64, "XPU broadcast kernel numel");
|
|
const uint32_t numel = static_cast<uint32_t>(numel_64);
|
|
const int threads = 64;
|
|
const int blocks = 8;
|
|
int read_lens = configs[0].buf_len;
|
|
auto stream = dev_ctx.x_context()->xpu_stream;
|
|
const int64_t block_len = static_cast<int64_t>(read_lens) * threads;
|
|
const int64_t main_offset_64 = (numel_64 / block_len) * block_len;
|
|
const int64_t tail_tid_64 = numel_64 % block_len;
|
|
const uint32_t main_offset = static_cast<uint32_t>(main_offset_64);
|
|
const uint32_t tail_tid = static_cast<uint32_t>(tail_tid_64);
|
|
|
|
VectorizedBroadcastKernel<Functor, OutT, Arity, NumOuts, VecSize, false>
|
|
<<<blocks, threads, 0, stream>>>(classifier.ins_data,
|
|
classifier.outs_data,
|
|
classifier.use_broadcast,
|
|
numel,
|
|
classifier.configs,
|
|
main_offset,
|
|
tail_tid,
|
|
read_lens,
|
|
func);
|
|
#else
|
|
const int64_t numel_64 = classifier.numel;
|
|
auto gpu_config =
|
|
phi::backends::gpu::GetGpuLaunchConfig1D(dev_ctx, numel_64, VecSize);
|
|
auto stream = dev_ctx.stream();
|
|
uint32_t threads = static_cast<uint32_t>(gpu_config.GetBlockSize());
|
|
auto blocks = gpu_config.block_per_grid;
|
|
PADDLE_ENFORCE_LE_UINT32_MAX(numel_64, "numel");
|
|
const uint32_t numel = static_cast<uint32_t>(numel_64);
|
|
const int64_t block_len = static_cast<int64_t>(VecSize) * threads;
|
|
const int64_t main_offset_64 = (numel_64 / block_len) * block_len;
|
|
const int64_t tail_tid_64 = numel_64 % block_len;
|
|
uint32_t main_offset = static_cast<uint32_t>(main_offset_64);
|
|
uint32_t tail_tid = static_cast<uint32_t>(tail_tid_64);
|
|
|
|
if (classifier.all_elementwise) {
|
|
VectorizedBroadcastKernel<Functor,
|
|
OutT,
|
|
Arity,
|
|
NumOuts,
|
|
VecSize,
|
|
kElementwise>
|
|
<<<blocks, threads, 0, stream>>>(classifier.ins_data,
|
|
classifier.outs_data,
|
|
classifier.use_broadcast,
|
|
numel,
|
|
classifier.configs,
|
|
main_offset,
|
|
tail_tid,
|
|
VecSize,
|
|
func);
|
|
} else if (classifier.broadcast_num > (Arity >> 1)) {
|
|
constexpr BroadcastType type_ = (Arity > 1) ? kBroadcast : kMixed;
|
|
VectorizedBroadcastKernel<Functor, OutT, Arity, NumOuts, VecSize, type_>
|
|
<<<blocks, threads, 0, stream>>>(classifier.ins_data,
|
|
classifier.outs_data,
|
|
classifier.use_broadcast,
|
|
numel,
|
|
classifier.configs,
|
|
main_offset,
|
|
tail_tid,
|
|
VecSize,
|
|
func);
|
|
} else {
|
|
VectorizedBroadcastKernel<Functor, OutT, Arity, NumOuts, VecSize, kMixed>
|
|
<<<blocks, threads, 0, stream>>>(classifier.ins_data,
|
|
classifier.outs_data,
|
|
classifier.use_broadcast,
|
|
numel,
|
|
classifier.configs,
|
|
main_offset,
|
|
tail_tid,
|
|
VecSize,
|
|
func);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int Arity, int NumOuts = 1>
|
|
typename std::enable_if<!NeedVectorized<OutT>::value, void>::type
|
|
BroadcastKernelForDifferentVecSize(const KPDevice &dev_ctx,
|
|
const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
int axis,
|
|
Functor func) {
|
|
auto classifier =
|
|
BroadcastTypeClassifier<OutT, Functor, Arity, NumOuts>(ins, outs, axis);
|
|
LaunchBroadcastKernel<OutT, Functor, Arity, NumOuts, VecSizeS>(
|
|
dev_ctx, classifier, func);
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int Arity, int NumOuts = 1>
|
|
typename std::enable_if<NeedVectorized<OutT>::value, void>::type
|
|
BroadcastKernelForDifferentVecSize(const KPDevice &dev_ctx,
|
|
const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
int axis,
|
|
Functor func) {
|
|
auto classifier =
|
|
BroadcastTypeClassifier<OutT, Functor, Arity, NumOuts>(ins, outs, axis);
|
|
#ifdef PADDLE_WITH_XPU_KP
|
|
auto type = kps::details::OptType::CanNotOptimize;
|
|
bool is_optimize = classifier.configs[0].cmp_type != type;
|
|
int vec_size = is_optimize ? VecSizeL : VecSizeM;
|
|
#else
|
|
static int capability = dev_ctx.GetComputeCapability();
|
|
// For Hopper and Blackwell, max vectorized size is VecSizeL(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, true);
|
|
vec_size = std::min(vec_size, max_vec_size);
|
|
int64_t numel = classifier.numel;
|
|
// For small tensor, using VecSizeL can improve performance more than
|
|
// VecSizeVL
|
|
constexpr int64_t large_vect_threshold = 1024 * 1024 * 4;
|
|
if (numel < large_vect_threshold) {
|
|
vec_size = std::min(vec_size, VecSizeL);
|
|
}
|
|
#endif
|
|
|
|
switch (vec_size) {
|
|
case VecSizeVL: {
|
|
LaunchBroadcastKernel<OutT, Functor, Arity, NumOuts, VecSizeVL>(
|
|
dev_ctx, classifier, func);
|
|
break;
|
|
}
|
|
case VecSizeL: {
|
|
LaunchBroadcastKernel<OutT, Functor, Arity, NumOuts, VecSizeL>(
|
|
dev_ctx, classifier, func);
|
|
break;
|
|
}
|
|
case VecSizeM: {
|
|
LaunchBroadcastKernel<OutT, Functor, Arity, NumOuts, VecSizeM>(
|
|
dev_ctx, classifier, func);
|
|
break;
|
|
}
|
|
case VecSizeS: {
|
|
LaunchBroadcastKernel<OutT, Functor, Arity, NumOuts, VecSizeS>(
|
|
dev_ctx, classifier, func);
|
|
break;
|
|
}
|
|
default: {
|
|
PADDLE_THROW(common::errors::Unimplemented(
|
|
"Unsupported vectorized size: %d!", vec_size));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void updateStridesDims(std::vector<int64_t> *strides,
|
|
std::vector<int64_t> *dims) {
|
|
for (int i = 1; i < strides->size(); i++) {
|
|
(*strides)[i] = (*strides)[i - 1] * (*dims)[i - 1];
|
|
}
|
|
// reverse origin_in_dim and origin_in_stride if in's dim_size > 0
|
|
std::reverse(strides->begin(), strides->end());
|
|
std::reverse(dims->begin(), dims->end());
|
|
}
|
|
|
|
static void SliceTensor(DenseTensor *x,
|
|
const DenseTensor *share,
|
|
const std::vector<int64_t> &out_compute_dims,
|
|
int64_t offset) {
|
|
auto new_dim = make_ddim(out_compute_dims);
|
|
DenseTensorMeta meta(share->dtype(),
|
|
new_dim,
|
|
share->layout(),
|
|
offset * SizeOf(share->dtype()) + share->offset());
|
|
x->set_meta(meta);
|
|
x->ShareBufferWith(*(share), true);
|
|
x->Resize(new_dim);
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int kArity, int NumOuts = 1>
|
|
void BroadcastKernelSplit(const KPDevice &dev_ctx,
|
|
const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
int axis,
|
|
Functor func,
|
|
const int64_t compute_size) {
|
|
const auto dims_simplifier =
|
|
BroadcastDimsSimplifier(ins, (*outs)[0]->dims(), axis);
|
|
if (VLOG_IS_ON(6)) {
|
|
DimsSimplifiedLogger<int64_t>::Log(
|
|
ins, outs, dims_simplifier, "GPU Broadcast");
|
|
}
|
|
|
|
int all_rank = dims_simplifier.rank;
|
|
std::vector<int64_t> origin_out_strides(all_rank, 1);
|
|
auto origin_in_dims = dims_simplifier.in_dims;
|
|
auto origin_out_dims = dims_simplifier.out_dims;
|
|
auto origin_in_strides = dims_simplifier.in_dims;
|
|
|
|
// for split
|
|
std::vector<int64_t> loop_num_out(all_rank, 1);
|
|
std::vector<int64_t> loop_num_out_stride(all_rank, 1);
|
|
|
|
// for input's offset
|
|
std::vector<int64_t> ins_offset(kArity, 0);
|
|
std::vector<int64_t> ins_scale_for_dim(kArity, 0);
|
|
|
|
// init offset and check in's dim
|
|
for (int k = 0; k < kArity; k++) {
|
|
ins_scale_for_dim[k] = ins[k]->dims().size() == 0 ? 0 : 1;
|
|
if (ins_scale_for_dim[k]) {
|
|
origin_in_strides[k][0] = 1;
|
|
}
|
|
}
|
|
|
|
updateStridesDims(&origin_out_strides, &origin_out_dims);
|
|
for (int k = 0; k < kArity; k++) {
|
|
if (ins_scale_for_dim[k]) {
|
|
updateStridesDims(&origin_in_strides[k], &origin_in_dims[k]);
|
|
}
|
|
}
|
|
|
|
// init out_split_dim and in_split_dims
|
|
auto out_split_dim = origin_out_dims;
|
|
auto in_split_dims = origin_in_dims;
|
|
|
|
// init
|
|
int64_t loop_num = 1;
|
|
int64_t split_idx = 0;
|
|
|
|
for (int r = 0; r < all_rank; r++) {
|
|
// if the compute_size was too small the split_size must be 0, but the
|
|
// dim_num must ge 1
|
|
int64_t split_size = compute_size / origin_out_strides[r];
|
|
out_split_dim[r] = std::max(split_size, static_cast<int64_t>(1));
|
|
loop_num_out[r] =
|
|
(origin_out_dims[r] + out_split_dim[r] - 1) / out_split_dim[r];
|
|
loop_num *= loop_num_out[r];
|
|
|
|
for (int k = 0; k < kArity; k++) {
|
|
if (ins_scale_for_dim[k]) {
|
|
in_split_dims[k][r] = std::min(origin_in_dims[k][r], out_split_dim[r]);
|
|
}
|
|
}
|
|
|
|
// split_idx is the index for lash split dim
|
|
if (split_size != 0) {
|
|
split_idx = r;
|
|
break;
|
|
}
|
|
}
|
|
|
|
loop_num_out_stride[all_rank - 1] = 1;
|
|
for (int r = all_rank - 2; r >= 0; r--) {
|
|
loop_num_out_stride[r] = loop_num_out_stride[r + 1] * loop_num_out[r + 1];
|
|
}
|
|
|
|
// compute
|
|
|
|
for (int iter = 0; iter < loop_num; iter++) {
|
|
std::vector<const DenseTensor *> new_ins = {};
|
|
std::vector<DenseTensor *> new_outs = {};
|
|
DenseTensor tmp_in[kArity];
|
|
DenseTensor tmp_out[NumOuts];
|
|
|
|
int64_t tmp_size = iter;
|
|
int64_t out_offset = 0;
|
|
// compute the offset before last split dim
|
|
for (int i = 0; i < split_idx; i++) {
|
|
auto repeat_times = tmp_size / loop_num_out_stride[i];
|
|
out_offset += repeat_times * origin_out_strides[i];
|
|
for (int k = 0; k < kArity; k++) {
|
|
if (ins_scale_for_dim[k]) {
|
|
ins_offset[k] +=
|
|
(repeat_times % origin_in_dims[k][i]) * origin_in_strides[k][i];
|
|
}
|
|
}
|
|
tmp_size = tmp_size % loop_num_out_stride[i];
|
|
}
|
|
// tmp_size is the last split_dims's repeat idx
|
|
auto pre_deal_size = tmp_size * out_split_dim[split_idx];
|
|
out_offset += pre_deal_size * origin_out_strides[split_idx];
|
|
// compute_size
|
|
auto remainder_size = origin_out_dims[split_idx] - pre_deal_size;
|
|
|
|
// get current compute size
|
|
auto out_compute_dims = out_split_dim;
|
|
out_compute_dims[split_idx] =
|
|
std::min(out_split_dim[split_idx], remainder_size);
|
|
|
|
// in + compute_size
|
|
auto in_compute_dims = in_split_dims;
|
|
for (int k = 0; k < kArity; k++) {
|
|
if (ins_scale_for_dim[k]) {
|
|
auto split_repeat =
|
|
origin_in_dims[k][split_idx] == origin_out_dims[split_idx]
|
|
? tmp_size
|
|
: 0;
|
|
ins_offset[k] += split_repeat * in_split_dims[k][split_idx] *
|
|
origin_in_strides[k][split_idx];
|
|
in_compute_dims[k][split_idx] =
|
|
std::min(in_split_dims[k][split_idx], out_compute_dims[split_idx]);
|
|
}
|
|
SliceTensor(&tmp_in[k],
|
|
ins[k],
|
|
in_compute_dims[k],
|
|
ins_scale_for_dim[k] * ins_offset[k]);
|
|
new_ins.emplace_back(&tmp_in[k]);
|
|
ins_offset[k] = 0;
|
|
}
|
|
|
|
for (int n = 0; n < NumOuts; n++) {
|
|
SliceTensor(&tmp_out[n], (*outs)[n], out_compute_dims, out_offset);
|
|
new_outs.emplace_back(&tmp_out[n]);
|
|
}
|
|
|
|
BroadcastKernelForDifferentVecSize<OutT, Functor, kArity, NumOuts>(
|
|
dev_ctx, new_ins, &new_outs, axis, func);
|
|
}
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int kArity, int NumOuts = 1>
|
|
void BroadcastKernelApply(const KPDevice &dev_ctx,
|
|
const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
int axis,
|
|
Functor func) {
|
|
#ifndef PADDLE_WITH_XPU_KP
|
|
auto compute_size =
|
|
static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 1;
|
|
bool use_int64_index_kernel = false;
|
|
for (auto *out : *outs) {
|
|
if (out->numel() >= compute_size) {
|
|
use_int64_index_kernel = true;
|
|
}
|
|
}
|
|
if (use_int64_index_kernel) { // use_int64_index_kernel
|
|
BroadcastKernelSplit<OutT, Functor, kArity, NumOuts>(
|
|
dev_ctx, ins, outs, axis, func, compute_size);
|
|
return;
|
|
}
|
|
#endif
|
|
BroadcastKernelForDifferentVecSize<OutT, Functor, kArity, NumOuts>(
|
|
dev_ctx, ins, outs, axis, func);
|
|
}
|
|
|
|
template <typename OutT, typename Functor, int NumOuts = 1>
|
|
void BroadcastKernel(const KPDevice &dev_ctx,
|
|
const std::vector<const DenseTensor *> &ins,
|
|
std::vector<DenseTensor *> *outs,
|
|
Functor func,
|
|
int axis = -1) {
|
|
// When there are multiple inputs, the outputs's rank should be equal the
|
|
// maximum rank of all inputs.
|
|
using Traits = funcs::FunctionTraits<Functor>;
|
|
const int kArity = Traits::arity;
|
|
|
|
#ifdef PADDLE_WITH_XPU_KP
|
|
PADDLE_ENFORCE_EQ(
|
|
ins.size(),
|
|
2,
|
|
common::errors::InvalidArgument(
|
|
"XPU only support inputs is 2, but received %d", ins.size()));
|
|
#endif
|
|
|
|
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));
|
|
|
|
for (auto i = 0; i < outs->size(); ++i) {
|
|
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 "
|
|
"%d-th output tensor`s shape is not.",
|
|
i));
|
|
}
|
|
dev_ctx.template Alloc<OutT>((*outs)[i]);
|
|
}
|
|
if ((*outs)[0]->numel() == 0) {
|
|
return;
|
|
}
|
|
int max_rank = 0;
|
|
int min_rank = DDim::kMaxRank;
|
|
for (auto *in : ins) {
|
|
max_rank = std::max(max_rank, in->dims().size());
|
|
min_rank = std::min(min_rank, in->dims().size());
|
|
}
|
|
if (ins.size() == 1) {
|
|
// When there is only 1 input, the input's rank may be less than outputs'
|
|
// rank.
|
|
max_rank = std::max(max_rank, (*outs)[0]->dims().size());
|
|
}
|
|
axis = axis == -1 ? max_rank - min_rank : axis;
|
|
BroadcastKernelApply<OutT, Functor, kArity, NumOuts>(
|
|
dev_ctx, ins, outs, axis, func);
|
|
}
|
|
|
|
template <typename Functor, typename T, typename OutType = T>
|
|
void ElementwiseCompute(const GPUContext &dev_ctx,
|
|
const DenseTensor &x,
|
|
const DenseTensor &y,
|
|
Functor func,
|
|
DenseTensor *z,
|
|
int axis = -1) {
|
|
std::vector<const DenseTensor *> ins = {&x, &y};
|
|
std::vector<DenseTensor *> outs = {z};
|
|
dev_ctx.template Alloc<OutType>(z);
|
|
|
|
BroadcastKernel<OutType, Functor, 1>(dev_ctx, ins, &outs, func, axis);
|
|
}
|
|
|
|
template <typename DeviceContext,
|
|
typename T,
|
|
typename Functor,
|
|
typename InverseFunctor>
|
|
void DefaultElementwiseOperator(const DeviceContext &dev_ctx,
|
|
const DenseTensor &x,
|
|
const DenseTensor &y,
|
|
DenseTensor *z,
|
|
int axis = -1) {
|
|
auto x_dims = x.dims();
|
|
auto y_dims = y.dims();
|
|
dev_ctx.template Alloc<T>(z);
|
|
funcs::ElementwiseCompute<Functor, T>(dev_ctx, x, y, Functor(), z, axis);
|
|
}
|
|
|
|
#else
|
|
|
|
template <typename DeviceContext,
|
|
typename T,
|
|
typename Functor,
|
|
typename InverseFunctor>
|
|
void DefaultElementwiseOperator(const DeviceContext &dev_ctx,
|
|
const DenseTensor &x,
|
|
const DenseTensor &y,
|
|
DenseTensor *z,
|
|
int axis = -1) {
|
|
auto x_dims = x.dims();
|
|
auto y_dims = y.dims();
|
|
dev_ctx.template Alloc<T>(z);
|
|
if (x_dims.size() >= y_dims.size()) {
|
|
funcs::ElementwiseCompute<Functor, T>(dev_ctx, x, y, Functor(), z, axis);
|
|
} else {
|
|
funcs::ElementwiseCompute<InverseFunctor, T>(
|
|
dev_ctx, x, y, InverseFunctor(), z, axis);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
} // namespace funcs
|
|
} // namespace phi
|