Files
2026-07-13 12:40:42 +08:00

472 lines
17 KiB
C++

// Copyright (c) 2025 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
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
#include "paddle/common/flags.h"
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/contiguous_kernel.h"
#include "paddle/phi/kernels/elementwise_add_kernel.h"
#include "paddle/phi/kernels/funcs/broadcast_function.h"
#include "paddle/phi/kernels/funcs/dense_tensor_iterator.h"
#include "paddle/phi/kernels/funcs/elementwise_base.h"
#include "paddle/phi/kernels/funcs/elementwise_functor.h"
#include "paddle/phi/kernels/funcs/index_elementwise.cu.h"
#include "paddle/phi/kernels/impl/elementwise_kernel_impl.h"
#if defined(__NVCC__) || defined(__HIPCC__) || defined(__xpu__)
#include "paddle/phi/kernels/funcs/dims_simplifier.h"
#endif
namespace phi {
// Not Support Vectorized Kernel For Now
#define STRIDE_VEC_SIZE 1
template <typename Functor,
typename OutT,
typename OffsetT,
int Arity,
int NumOuts,
int VecSize,
int vt>
__global__ void BinaryElementwiseKernel(
Array<const _ptr_ char *__restrict__, Arity> ins,
Array<_ptr_ OutT *, NumOuts> outs,
int64_t numel,
int read_lens,
Functor func,
funcs::OffsetCalculator<Arity + NumOuts, OffsetT, false> offset_calc) {
int64_t tid = THREAD_ID_X;
int64_t nv = BLOCK_NUM_X * vt;
int64_t idx = nv * BLOCK_ID_X + tid;
#pragma unroll
for (int i = 0; i < vt; i++) {
if (idx < numel) {
auto offsets = offset_calc.get(idx);
using Traits = funcs::FunctionTraits<Functor>;
using ArgsT = typename Traits::ArgsTuple;
__simd__ ArgsT args[VecSize];
__simd__ ConditionalT<OutT, NumOuts> result[VecSize];
std::get<0>(args[0]) =
*(reinterpret_cast<const _ptr_ std::tuple_element_t<0, ArgsT> *>(
reinterpret_cast<const _ptr_ char *>(ins[0]) + offsets[1]));
std::get<1>(args[0]) =
*(reinterpret_cast<const _ptr_ std::tuple_element_t<1, ArgsT> *>(
reinterpret_cast<const _ptr_ char *>(ins[1]) + offsets[2]));
funcs::SameDimsElementwisePrimitiveCaller<ConditionalT<OutT, NumOuts>,
VecSize,
Functor,
ArgsT,
Arity>()(
func, args, result, read_lens);
char *out_ptr = reinterpret_cast<char *>(outs[0]) + offsets[0];
*reinterpret_cast<OutT *>(out_ptr) =
*reinterpret_cast<const OutT *>(&(result[0]));
idx += BLOCK_NUM_X;
}
}
}
template <typename Functor,
typename OutT,
typename OffsetT,
int Arity,
int NumOuts,
int VecSize,
int vt>
__global__ void UnaryElementwiseKernel(
Array<const _ptr_ char *__restrict__, Arity> ins,
Array<_ptr_ OutT *, NumOuts> outs,
int64_t numel,
int read_lens,
Functor func,
funcs::OffsetCalculator<Arity + NumOuts, OffsetT, false> offset_calc) {
int64_t tid = THREAD_ID_X;
int64_t nv = BLOCK_NUM_X * vt;
int64_t idx = nv * BLOCK_ID_X + tid;
#pragma unroll
for (int i = 0; i < vt; i++) {
if (idx < numel) {
auto offsets = offset_calc.get(idx);
using Traits = funcs::FunctionTraits<Functor>;
using ArgsT = typename Traits::ArgsTuple;
__simd__ ArgsT args[VecSize];
__simd__ ConditionalT<OutT, NumOuts> result[VecSize];
std::get<0>(args[0]) =
*(reinterpret_cast<const _ptr_ std::tuple_element_t<0, ArgsT> *>(
reinterpret_cast<const _ptr_ char *>(ins[0]) + offsets[1]));
funcs::SameDimsElementwisePrimitiveCaller<ConditionalT<OutT, NumOuts>,
VecSize,
Functor,
ArgsT,
Arity>()(
func, args, result, read_lens);
char *out_ptr = reinterpret_cast<char *>(outs[0]) + offsets[0];
*reinterpret_cast<OutT *>(out_ptr) =
*reinterpret_cast<const OutT *>(&(result[0]));
idx += BLOCK_NUM_X;
}
}
}
template <typename OutT, typename Context, typename Functor, int NumOuts = 1>
void BinaryStrideBroadcastKernel(const Context &dev_ctx,
const std::vector<const DenseTensor *> &ins,
std::vector<DenseTensor *> *outs,
Functor func,
int axis = -1) {
using Traits = funcs::FunctionTraits<Functor>;
const int Arity = Traits::arity;
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 = phi::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) {
max_rank = std::max(max_rank, (*outs)[0]->dims().size());
}
axis = axis == -1 ? max_rank - min_rank : axis;
auto classifier =
funcs::BroadcastTypeClassifier<OutT, Functor, Arity, NumOuts>(
ins, outs, axis);
DenseTensorIteratorConfig config;
config.add_output(*((*outs)[0]));
config.add_const_input(*(ins[0]));
config.add_const_input(*(ins[1]));
DenseTensorIterator iter = config.build();
// TODO(large-tensor): downstream functors may still use int; guard until
// upgraded.
const int64_t &numel = iter.numel();
constexpr int unroll_factor = sizeof(OutT) >= 4 ? 2 : 4;
auto stream = dev_ctx.stream();
auto threads = 128;
auto blocks = (numel + 128 * unroll_factor - 1) / (128 * unroll_factor);
int vec_size = STRIDE_VEC_SIZE;
bool is_big_tensor = false;
int64_t max_stride = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < iter.ndim(); j++) {
max_stride += iter.operands_[i].stride_bytes.data()[j] * iter.shape()[j];
}
}
if (!funcs::IsInUint32Range(max_stride * sizeof(OutT))) {
is_big_tensor = true;
}
if (is_big_tensor) {
funcs::OffsetCalculator offset_calc =
funcs::make_offset_calculator<3, false, uint64_t>(iter);
BinaryElementwiseKernel<Functor,
OutT,
uint64_t,
Arity,
NumOuts,
STRIDE_VEC_SIZE,
unroll_factor>
<<<blocks, threads, 0, stream>>>(classifier.ins_data,
classifier.outs_data,
numel,
vec_size,
func,
offset_calc);
} else {
funcs::OffsetCalculator offset_calc =
funcs::make_offset_calculator<3, false, uint32_t>(iter);
BinaryElementwiseKernel<Functor,
OutT,
uint32_t,
Arity,
NumOuts,
STRIDE_VEC_SIZE,
unroll_factor>
<<<blocks, threads, 0, stream>>>(classifier.ins_data,
classifier.outs_data,
numel,
vec_size,
func,
offset_calc);
}
}
template <typename OutT, typename Context, typename Functor, int NumOuts = 1>
void BinaryStrideElementwiseKernel(const Context &dev_ctx,
const std::vector<const DenseTensor *> &ins,
std::vector<DenseTensor *> *outs,
Functor func) {
using Traits = funcs::FunctionTraits<Functor>;
const int Arity = Traits::arity;
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;
}
int max_rank = 0;
int min_rank = phi::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) {
max_rank = std::max(max_rank, (*outs)[0]->dims().size());
}
int axis = max_rank - min_rank;
auto classifier =
funcs::BroadcastTypeClassifier<OutT, Functor, Arity, NumOuts>(
ins, outs, axis);
DenseTensorIteratorConfig config;
config.add_output(*((*outs)[0]));
config.add_const_input(*(ins[0]));
config.add_const_input(*(ins[1]));
DenseTensorIterator iter = config.build();
// TODO(large-tensor): downstream functors may still use int; guard until
// upgraded.
const int64_t &numel = iter.numel();
constexpr int unroll_factor = sizeof(OutT) >= 4 ? 2 : 4;
auto stream = dev_ctx.stream();
auto threads = 128;
auto blocks = (numel + 128 * unroll_factor - 1) / (128 * unroll_factor);
int vec_size = STRIDE_VEC_SIZE;
bool is_big_tensor = false;
int64_t max_stride = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < iter.ndim(); j++) {
max_stride += iter.operands_[i].stride_bytes.data()[j] * iter.shape()[j];
}
}
if (!funcs::IsInUint32Range(max_stride) * sizeof(OutT)) {
is_big_tensor = true;
}
if (is_big_tensor) {
funcs::OffsetCalculator offset_calc =
funcs::make_offset_calculator<3, false, uint64_t>(iter);
BinaryElementwiseKernel<Functor,
OutT,
uint64_t,
Arity,
NumOuts,
STRIDE_VEC_SIZE,
unroll_factor>
<<<blocks, threads, 0, stream>>>(classifier.ins_data,
classifier.outs_data,
numel,
vec_size,
func,
offset_calc);
} else {
funcs::OffsetCalculator offset_calc =
funcs::make_offset_calculator<3, false, uint32_t>(iter);
BinaryElementwiseKernel<Functor,
OutT,
uint32_t,
Arity,
NumOuts,
STRIDE_VEC_SIZE,
unroll_factor>
<<<blocks, threads, 0, stream>>>(classifier.ins_data,
classifier.outs_data,
numel,
vec_size,
func,
offset_calc);
}
}
template <typename OutT, typename Context, typename Functor, int NumOuts = 1>
void UnaryStrideElementwiseKernel(const Context &dev_ctx,
const std::vector<const DenseTensor *> &ins,
std::vector<DenseTensor *> *outs,
Functor func) {
using Traits = funcs::FunctionTraits<Functor>;
const int Arity = Traits::arity;
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;
}
int max_rank = 0;
int min_rank = phi::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) {
max_rank = std::max(max_rank, (*outs)[0]->dims().size());
}
int axis = max_rank - min_rank;
auto classifier =
funcs::BroadcastTypeClassifier<OutT, Functor, Arity, NumOuts>(
ins, outs, axis);
DenseTensorIteratorConfig config;
config.add_output(*((*outs)[0]));
config.add_const_input(*(ins[0]));
DenseTensorIterator iter = config.build();
// TODO(large-tensor): downstream functors may still use int; guard until
// upgraded.
const int64_t &numel = iter.numel();
funcs::OffsetCalculator offset_calc = funcs::make_offset_calculator<2>(iter);
constexpr int unroll_factor = sizeof(OutT) >= 4 ? 2 : 4;
auto stream = dev_ctx.stream();
auto threads = 128;
auto blocks = (numel + 128 * unroll_factor - 1) / (128 * unroll_factor);
int vec_size = STRIDE_VEC_SIZE;
bool is_big_tensor = false;
int64_t max_stride = 0;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < iter.ndim(); j++) {
max_stride += iter.operands_[i].stride_bytes.data()[j] * iter.shape()[j];
}
}
if (!funcs::IsInUint32Range(max_stride * sizeof(OutT))) {
is_big_tensor = true;
}
if (is_big_tensor) {
funcs::OffsetCalculator offset_calc =
funcs::make_offset_calculator<2, false, uint64_t>(iter);
UnaryElementwiseKernel<Functor,
OutT,
uint64_t,
Arity,
NumOuts,
STRIDE_VEC_SIZE,
unroll_factor>
<<<blocks, threads, 0, stream>>>(classifier.ins_data,
classifier.outs_data,
numel,
vec_size,
func,
offset_calc);
} else {
funcs::OffsetCalculator offset_calc =
funcs::make_offset_calculator<2, false, uint32_t>(iter);
UnaryElementwiseKernel<Functor,
OutT,
uint32_t,
Arity,
NumOuts,
STRIDE_VEC_SIZE,
unroll_factor>
<<<blocks, threads, 0, stream>>>(classifier.ins_data,
classifier.outs_data,
numel,
vec_size,
func,
offset_calc);
}
}
template <typename T, typename Context, typename Functor>
void LaunchUnaryElementwiseStrideKernel(const Context &dev_ctx,
const DenseTensor &x,
Functor func,
DenseTensor *out) {
std::vector<const DenseTensor *> inputs = {&x};
std::vector<DenseTensor *> outputs = {out};
dev_ctx.template Alloc<T>(out);
UnaryStrideElementwiseKernel<T, Context>(dev_ctx, inputs, &outputs, func);
}
template <typename T, typename Context, typename Functor>
void LaunchBinaryElementwiseStrideKernel(const Context &dev_ctx,
const DenseTensor &x,
const DenseTensor &y,
Functor func,
int axis,
DenseTensor *out) {
std::vector<const DenseTensor *> inputs = {&x, &y};
std::vector<DenseTensor *> outputs = {out};
dev_ctx.template Alloc<T>(out);
BinaryStrideBroadcastKernel<T, Context>(
dev_ctx, inputs, &outputs, func, axis);
}
template <typename Context>
DenseTensor Tensor2Contiguous(const Context &dev_ctx,
const DenseTensor &tensor) {
DenseTensor dense_out;
MetaTensor meta_input(tensor);
MetaTensor meta_out(&dense_out);
UnchangedInferMeta(meta_input, &meta_out);
PD_VISIT_ALL_TYPES(tensor.dtype(), "Tensor2Contiguous", ([&] {
ContiguousKernel<data_t, Context>(
dev_ctx, tensor, &dense_out);
}));
return dense_out;
}
#undef STRIDE_VEC_SIZE
} // namespace phi
#endif