Files
paddlepaddle--paddle/paddle/phi/kernels/gpu/add_n_kernel.cu
T
2026-07-13 12:40:42 +08:00

357 lines
13 KiB
Plaintext

// 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.
#include "paddle/phi/kernels/add_n_kernel.h"
#include "paddle/phi/backends/gpu/cuda/cuda_graph_with_memory_pool.h"
#include "paddle/phi/common/amp_type_traits.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/kernels/impl/add_n_kernel_impl.h"
namespace phi {
#define CEIL_DIV(x, y) (((x) + (y)-1) / (y))
template <class T>
__global__ void SumArrayCUDAKernel(
T **in, T *out, int64_t N, size_t in_size, bool read_dst) {
using MT = typename MPTypeTrait<T>::Type;
CUDA_KERNEL_LOOP_TYPE(idx, N, int64_t) {
MT total(read_dst ? static_cast<MT>(out[idx]) : static_cast<MT>(0));
for (int i = 0; i < in_size; ++i) {
const T *tmp = in[i];
if (tmp) {
total += static_cast<MT>(tmp[idx]);
}
}
out[idx] = static_cast<T>(total);
}
}
template <class T, class HALF>
__global__ void SumArrayMixedTypeCUDAKernel(const T *in_0,
void **in_others,
T *out,
int64_t N,
size_t in_others_size,
bool read_dst) {
using MT = typename MPTypeTrait<T>::Type;
CUDA_KERNEL_LOOP_TYPE(idx, N, int64_t) {
MT total(read_dst ? static_cast<MT>(out[idx]) : static_cast<MT>(0));
total += static_cast<MT>(in_0[idx]);
for (int i = 0; i < in_others_size; ++i) {
const HALF *tmp = static_cast<HALF *>(in_others[i]);
if (tmp) {
total += static_cast<MT>(tmp[idx]);
}
}
out[idx] = static_cast<T>(total);
}
}
template <class T>
__global__ void SumSelectedRowsCUDAKernel(T **sr_in_out,
int64_t N,
size_t rows) {
CUDA_KERNEL_LOOP_TYPE(idx, N, int64_t) {
for (int i = 0; i < 2 * rows; i += 2) {
const T *tmp = sr_in_out[i];
T *tmp_out = sr_in_out[i + 1];
if (tmp && tmp_out) {
tmp_out[idx] += tmp[idx];
}
}
}
}
template <typename T, typename Context>
void AddNKernel(const Context &dev_ctx,
const std::vector<const TensorBase *> &x,
DenseTensor *out) {
if (out && out->numel() == 0) {
dev_ctx.template Alloc<T>(out);
return;
}
const size_t in_num = x.size();
for (int i = 0; i < in_num; ++i) {
PADDLE_ENFORCE_EQ(
x[i]->valid() && x[i]->has_allocation(),
true,
common::errors::InvalidArgument(
"This argument is invalid, %d-th tensor is uninitialized.", i));
}
constexpr size_t theory_sm_threads = 1024;
auto stream = dev_ctx.stream();
auto max_threads = dev_ctx.GetMaxPhysicalThreadCount();
auto sm_count = max_threads / theory_sm_threads;
size_t tile_size = 0;
dim3 grids;
dim3 blocks;
auto ComputeKernelParameter = [&](size_t length) {
if (length >= max_threads)
tile_size = 1024;
else if (length < max_threads && length > sm_count * 128)
tile_size = 512;
else if (length <= sm_count * 128)
tile_size = 256;
grids = dim3(CEIL_DIV(length, tile_size), 1, 1);
blocks = dim3(tile_size, 1, 1);
};
auto *out_ptr = dev_ctx.template Alloc<T>(out);
bool in_place = false;
if (x.size() > 0 && x[0]->initialized() && DenseTensor::classof(x[0])) {
if ((static_cast<const DenseTensor *>(x[0]))->data() == out->data()) {
in_place = true;
}
}
// Sum of two tensors
if (in_num == 2 && DenseTensor::classof(x[0]) && DenseTensor::classof(x[1])) {
auto &in_0 = *(static_cast<const DenseTensor *>(x[0]));
auto &in_1 = *(static_cast<const DenseTensor *>(x[1]));
int64_t length_0 = in_0.numel();
int64_t length_1 = in_1.numel();
if (length_0 && length_1 && in_0.IsInitialized() && in_1.IsInitialized()) {
using MT = typename MPTypeTrait<T>::Type;
auto result = EigenVector<T>::Flatten(*out);
auto &place = *dev_ctx.eigen_device();
auto in_0_e = EigenVector<T>::Flatten(in_0).template cast<MT>();
auto in_1_e = EigenVector<T>::Flatten(in_1).template cast<MT>();
result.device(place) = (in_0_e + in_1_e).template cast<T>();
} else if (length_0 && in_0.IsInitialized()) {
auto result = EigenVector<T>::Flatten(*out);
auto &place = *dev_ctx.eigen_device();
result.device(place) = EigenVector<T>::Flatten(in_0);
} else if (length_1 && in_1.IsInitialized()) {
auto result = EigenVector<T>::Flatten(*out);
auto &place = *dev_ctx.eigen_device();
result.device(place) = EigenVector<T>::Flatten(in_1);
}
return;
}
int start = in_place ? 1 : 0;
if (!in_place) {
funcs::SetConstant<GPUContext, T> constant_functor;
constant_functor(dev_ctx, out, static_cast<T>(0));
}
// Support mixed inputs for master grad accumulation
// conditions:
// 1. all inputs are DensorTensor and number >= 2
// 2. the first tensor is fp32 type and the others are fp16/bf16 type
if (in_num >= 2 && DenseTensor::classof(x[0]) &&
x[0]->dtype() == DataType::FLOAT32 &&
x[1]->dtype() != DataType::FLOAT32) {
auto in_other_dtype = x[1]->dtype();
int64_t numel = static_cast<const DenseTensor *>(x[0])->numel();
bool all_dense_tensor = true;
std::vector<const void *> in_data;
const T *in_0 = static_cast<const DenseTensor *>(x[0])->data<T>();
for (int i = 1; i < in_num; ++i) {
PADDLE_ENFORCE_EQ(
in_other_dtype,
x[i]->dtype(),
errors::InvalidArgument("The dtype of inputs should be the same, "
"but received the dtype of input 1 is %s, "
"input %d is %s",
in_other_dtype,
i,
x[i]->dtype()));
if (DenseTensor::classof(x[i])) {
auto &in_i = *(static_cast<const DenseTensor *>(x[i]));
if (in_i.IsInitialized()) {
in_data.emplace_back(in_i.data());
}
} else {
all_dense_tensor = false;
break;
}
}
if (all_dense_tensor && (in_other_dtype == DataType::BFLOAT16 ||
in_other_dtype == DataType::FLOAT16)) {
auto tmp_in_array = phi::memory_utils::Alloc(
dev_ctx.GetPlace(), in_data.size() * sizeof(void *));
size_t nbytes_in = in_data.size() * sizeof(void *);
const void *stable_in = backends::gpu::RestoreHostMemIfCapturingCUDAGraph(
reinterpret_cast<uint8_t *>(const_cast<void **>(in_data.data())),
nbytes_in);
memory_utils::Copy(dev_ctx.GetPlace(),
tmp_in_array->ptr(),
CPUPlace(),
stable_in,
nbytes_in,
dev_ctx.stream());
void **in_array_data = reinterpret_cast<void **>(tmp_in_array->ptr());
ComputeKernelParameter(numel);
VLOG(4) << "Call SumArrayMixedTypeCUDAKernel";
if (in_other_dtype == DataType::FLOAT16) {
SumArrayMixedTypeCUDAKernel<T, phi::float16>
<<<grids, blocks, 0, stream>>>(in_0,
in_array_data,
out->data<T>(),
numel,
in_data.size(),
in_place);
} else if (in_other_dtype == DataType::BFLOAT16) {
SumArrayMixedTypeCUDAKernel<T, phi::bfloat16>
<<<grids, blocks, 0, stream>>>(in_0,
in_array_data,
out->data<T>(),
numel,
in_data.size(),
in_place);
}
return;
}
}
std::vector<const T *> in_data;
std::vector<int> selectrow_index;
int64_t lod_length = 0;
bool dst_write = false;
for (int i = start; i < in_num; ++i) {
if (DenseTensor::classof(x[i])) {
auto &in_i = *(static_cast<const DenseTensor *>(x[i]));
lod_length = in_i.numel();
if (lod_length && in_i.IsInitialized()) {
in_data.emplace_back(in_i.data<T>());
}
} else if (SelectedRows::classof(x[i])) {
selectrow_index.push_back(i);
}
}
// compute select rows separately.
if (!selectrow_index.empty()) {
std::vector<const T *> sr_in_out_data;
size_t rows = 0;
int64_t length = 0;
for (auto index : selectrow_index) {
auto &sr = *(static_cast<const SelectedRows *>(x[index]));
auto &sr_value = sr.value();
auto &sr_rows = sr.rows();
auto row_numel = sr_value.numel() / sr_rows.size();
auto out_dims = out->dims();
PADDLE_ENFORCE_EQ(sr.height(),
out_dims[0],
errors::InvalidArgument(
"The table height of input must be same as output, "
"but received input height is %d"
", output height is %d",
sr.height(),
out_dims[0]));
PADDLE_ENFORCE_EQ(row_numel,
out->numel() / sr.height(),
errors::InvalidArgument(
"The table width of input must be same as output, "
"but received input width is %d"
", output width is %d",
row_numel,
out->numel() / sr.height()));
auto *sr_data = sr_value.data<T>();
auto *sr_out_data = out->data<T>();
rows += sr_rows.size();
length = row_numel;
for (size_t i = 0; i < sr_rows.size(); ++i) {
sr_in_out_data.emplace_back(&sr_data[i * row_numel]);
sr_in_out_data.emplace_back(&sr_out_data[sr_rows[i] * row_numel]);
}
}
if (!sr_in_out_data.empty()) {
auto tmp_sr_in_out_array = memory_utils::Alloc(
dev_ctx.GetPlace(), sr_in_out_data.size() * sizeof(T *));
size_t nbytes_sr = sr_in_out_data.size() * sizeof(T *);
const void *stable_sr = backends::gpu::RestoreHostMemIfCapturingCUDAGraph(
reinterpret_cast<uint8_t *>(sr_in_out_data.data()), nbytes_sr);
memory_utils::Copy(dev_ctx.GetPlace(),
tmp_sr_in_out_array->ptr(),
CPUPlace(),
stable_sr,
nbytes_sr,
dev_ctx.stream());
T **sr_in_out_array_data =
reinterpret_cast<T **>(tmp_sr_in_out_array->ptr());
ComputeKernelParameter(length);
SumSelectedRowsCUDAKernel<T>
<<<grids, blocks, 0, stream>>>(sr_in_out_array_data, length, rows);
dst_write = true;
}
}
// if indata not null, merge into one kernel call.
if (!in_data.empty()) {
auto tmp_in_array =
memory_utils::Alloc(dev_ctx.GetPlace(), in_data.size() * sizeof(T *));
size_t nbytes_in2 = in_data.size() * sizeof(T *);
const void *stable_in2 = backends::gpu::RestoreHostMemIfCapturingCUDAGraph(
reinterpret_cast<uint8_t *>(const_cast<T **>(in_data.data())),
nbytes_in2);
memory_utils::Copy(dev_ctx.GetPlace(),
tmp_in_array->ptr(),
CPUPlace(),
stable_in2,
nbytes_in2,
dev_ctx.stream());
T **in_array_data = reinterpret_cast<T **>(tmp_in_array->ptr());
ComputeKernelParameter(lod_length);
SumArrayCUDAKernel<T><<<grids, blocks, 0, stream>>>(in_array_data,
out->data<T>(),
lod_length,
in_data.size(),
dst_write | in_place);
}
}
} // namespace phi
PD_REGISTER_KERNEL(add_n,
GPU,
ALL_LAYOUT,
phi::AddNKernel,
float,
double,
int,
phi::bfloat16,
phi::float16,
int64_t,
phi::complex64,
phi::complex128) {}
PD_REGISTER_KERNEL(add_n_array,
GPU,
ALL_LAYOUT,
phi::AddNArrayKernel,
float,
double,
int,
phi::bfloat16,
phi::float16,
int64_t,
phi::complex64,
phi::complex128) {}