Files
paddlepaddle--paddle/paddle/phi/kernels/funcs/broadcast_function.h
T
2026-07-13 12:40:42 +08:00

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