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

560 lines
17 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/cum_kernel.h"
#include <thrust/device_ptr.h>
#include <thrust/device_vector.h>
#include <thrust/reverse.h>
#include <thrust/scan.h>
#include "paddle/common/hostdevice.h"
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/common/amp_type_traits.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/funcs/cub.h"
#include "paddle/phi/kernels/funcs/cumprod.h"
#include "paddle/phi/kernels/funcs/elementwise_functor.h"
#include "paddle/phi/kernels/funcs/inclusive_scan.h"
#include "paddle/common/flags.h"
COMMON_DECLARE_bool(use_accuracy_compatible_kernel);
namespace phi {
template <typename T>
__global__ void MatrixRowReverse(const T* matrix_data,
T* reverse_data,
int64_t grid_size,
int64_t reverse_size) {
int item_per_block = 1024;
for (int64_t bx = blockIdx.x; bx < grid_size; bx += gridDim.x) {
for (int64_t block_offset = 0; block_offset < reverse_size;
block_offset += item_per_block) {
int64_t reverse_offset = block_offset + static_cast<int64_t>(threadIdx.x);
int64_t src_offset = bx * reverse_size + reverse_offset;
int64_t dst_offset =
bx * reverse_size + (reverse_size - reverse_offset - 1);
if (reverse_offset < reverse_size) {
reverse_data[dst_offset] = matrix_data[src_offset];
}
}
}
}
// No bank-conflict transpose
template <typename T, int TILE_DIM, int BLOCK_ROWS>
__global__ void MatrixTranspose(T* odata,
const T* idata,
size_t height,
size_t width) {
__shared__ T tile[TILE_DIM][TILE_DIM + 1];
int64_t wblocks = (width + TILE_DIM - 1) / TILE_DIM;
int64_t hblocks = (height + TILE_DIM - 1) / TILE_DIM;
int64_t block_i = blockIdx.x;
for (; block_i < wblocks * hblocks; block_i += gridDim.x) {
int64_t block_y = block_i / wblocks;
int64_t block_x = block_i % wblocks;
int64_t x = block_x * TILE_DIM + static_cast<int64_t>(threadIdx.x);
int64_t y = block_y * TILE_DIM + static_cast<int64_t>(threadIdx.y);
for (int j = 0; j < TILE_DIM; j += BLOCK_ROWS) {
if (x < width && (y + j) < height) {
tile[threadIdx.y + j][threadIdx.x] = idata[(y + j) * width + x];
}
}
__syncthreads();
x = block_y * TILE_DIM + threadIdx.x; // transpose block offset
y = block_x * TILE_DIM + threadIdx.y;
for (int j = 0; j < TILE_DIM; j += BLOCK_ROWS) {
if (x < height && (y + j) < width) {
odata[(y + j) * height + x] = tile[threadIdx.x][threadIdx.y + j];
}
}
}
}
struct LogAddExp {
template <typename T>
__host__ __device__ __forceinline__ T operator()(const T& a,
const T& b) const {
T min_val = std::min(a, b);
T max_val = std::max(a, b);
return std::log1p(std::exp(min_val - max_val)) + max_val;
}
};
struct ComplexSum {
template <typename T>
__host__ __device__ __forceinline__ T operator()(const T& a,
const T& b) const {
return a + b;
}
};
template <typename T, typename op>
struct Identity;
template <typename T>
struct Identity<T, cub::Sum> {
static constexpr T value = 0;
};
template <typename T>
struct Identity<T, LogAddExp> {
static constexpr T value = std::numeric_limits<T>::lowest();
};
template <typename T>
struct Identity<T, ComplexSum> {
static constexpr T value = {0, 0};
};
template <typename T, typename Op>
struct BlockPrefixCallbackOp {
// Running prefix
T running_total_;
T compensation_;
Op op_;
__device__ BlockPrefixCallbackOp(T identity, Op op)
: running_total_(identity), compensation_(identity), op_(op) {}
// Callback operator to be entered by the first warp of threads in the block.
// tid 0 is responsible for returning a value for seeding the block-wide scan.
__device__ T operator()(T block_aggregate) {
T old_prefix = running_total_;
// Kahan Summation
T y = op_(block_aggregate, static_cast<T>(-compensation_));
T t = op_(running_total_, y);
T y_high = op_(t, static_cast<T>(-running_total_));
compensation_ = op_(y_high, static_cast<T>(-y));
running_total_ = t;
return old_prefix;
}
};
template <typename T>
struct BlockPrefixCallbackOp<T, LogAddExp> {
T max_so_far_;
T scaled_sum_;
T compensation_;
LogAddExp op_;
__device__ BlockPrefixCallbackOp(T identity, LogAddExp op)
: max_so_far_(identity),
scaled_sum_(static_cast<T>(0.0)),
compensation_(static_cast<T>(0.0)),
op_(op) {}
__device__ T operator()(T block_aggregate) {
if (scaled_sum_ == 0.0) {
max_so_far_ = block_aggregate;
scaled_sum_ = static_cast<T>(1.0);
compensation_ = static_cast<T>(0.0);
return std::numeric_limits<T>::lowest();
}
// Online Scaling
T old_prefix = max_so_far_ + std::log(scaled_sum_);
T m_old = max_so_far_;
T m_new = std::max(m_old, block_aggregate);
if (m_new > m_old) {
T scale = std::exp(m_old - m_new);
scaled_sum_ *= scale;
compensation_ *= scale;
}
// Kahan Summation
T term = std::exp(block_aggregate - m_new);
T y = term - compensation_;
T t = scaled_sum_ + y;
compensation_ = (t - scaled_sum_) - y;
scaled_sum_ = t;
max_so_far_ = m_new;
return old_prefix;
}
};
template <typename T, int BLOCK_THREADS, int ITEMS_PER_THREAD, typename Op>
__global__ void BlockScanKernel(T* d_out,
const T* d_in,
int64_t grid_size,
int64_t scan_size,
bool exclusive,
Op op) {
using MT = typename MPTypeTrait<T>::Type;
using CallbackOp = BlockPrefixCallbackOp<MT, Op>;
// Specialize BlockLoad, BlockStore, and BlockRadixSort collective types
using BlockLoadT = cub::
BlockLoad<MT, BLOCK_THREADS, ITEMS_PER_THREAD, cub::BLOCK_LOAD_TRANSPOSE>;
using BlockStoreT = cub::BlockStore<MT,
BLOCK_THREADS,
ITEMS_PER_THREAD,
cub::BLOCK_STORE_TRANSPOSE>;
using BlockScanT = cub::BlockScan<MT, BLOCK_THREADS>;
// Allocate type-safe, repurposable shared memory for collectives
__shared__ union {
typename BlockLoadT::TempStorage load;
typename BlockStoreT::TempStorage store;
typename BlockScanT::TempStorage scan;
} temp_storage;
// Obtain this block's segment of consecutive keys (blocked across threads)
int64_t item_per_block = BLOCK_THREADS * ITEMS_PER_THREAD;
for (int64_t bx = blockIdx.x; bx < grid_size; bx += gridDim.x) {
CallbackOp prefix_op(Identity<MT, Op>::value, op);
for (int64_t block_offset = 0; block_offset < scan_size;
block_offset += item_per_block) {
int64_t valid_item = std::min(scan_size - block_offset, item_per_block);
int64_t offset = bx * scan_size + block_offset;
MT thread_keys[ITEMS_PER_THREAD];
BlockLoadT(temp_storage.load)
.Load(
d_in + offset, thread_keys, valid_item, Identity<MT, Op>::value);
__syncthreads();
if (exclusive) {
BlockScanT(temp_storage.scan)
.ExclusiveScan(thread_keys, thread_keys, op, prefix_op);
} else {
BlockScanT(temp_storage.scan)
.InclusiveScan(thread_keys, thread_keys, op, prefix_op);
}
__syncthreads();
BlockStoreT(temp_storage.store)
.Store(d_out + offset, thread_keys, valid_item);
}
}
}
template <typename Context, typename T>
void ThrustCumsumKernel(const Context& dev_ctx,
const T* in_data,
T* out_data,
int64_t size,
bool reverse,
bool exclusive) {
using MT = typename MPTypeTrait<T>::Type;
#ifdef __HIPCC__
const auto& policy = thrust::hip::par.on(dev_ctx.stream());
#else
memory_utils::ThrustAllocator<cudaStream_t> allocator(dev_ctx.GetPlace(),
dev_ctx.stream());
const auto& policy = thrust::cuda::par(allocator).on(dev_ctx.stream());
#endif
if constexpr (std::is_same_v<T, MT>) {
if (reverse) {
thrust::reverse_iterator<thrust::device_ptr<const T>> reversed_in(
thrust::device_pointer_cast(in_data) + size);
thrust::reverse_iterator<thrust::device_ptr<T>> reversed_out(
thrust::device_pointer_cast(out_data) + size);
if (exclusive) {
thrust::exclusive_scan(
policy, reversed_in, reversed_in + size, reversed_out);
} else {
thrust::inclusive_scan(
policy, reversed_in, reversed_in + size, reversed_out);
}
} else {
if (exclusive) {
thrust::exclusive_scan(policy, in_data, in_data + size, out_data);
} else {
thrust::inclusive_scan(policy, in_data, in_data + size, out_data);
}
}
} else {
thrust::device_vector<MT> tmp_in(size);
thrust::device_vector<MT> tmp_out(size);
thrust::copy(policy, in_data, in_data + size, tmp_in.begin());
auto tmp_in_begin = tmp_in.begin();
auto tmp_in_end = tmp_in.end();
auto tmp_out_begin = tmp_out.begin();
if (reverse) {
auto reversed_in = tmp_in.rbegin();
auto reversed_out = tmp_out.rbegin();
if (exclusive) {
thrust::exclusive_scan(
policy, reversed_in, reversed_in + size, reversed_out);
} else {
thrust::inclusive_scan(
policy, reversed_in, reversed_in + size, reversed_out);
}
} else {
if (exclusive) {
thrust::exclusive_scan(policy, tmp_in_begin, tmp_in_end, tmp_out_begin);
} else {
thrust::inclusive_scan(policy, tmp_in_begin, tmp_in_end, tmp_out_begin);
}
}
thrust::copy(policy, tmp_out.begin(), tmp_out.end(), out_data);
}
}
template <typename T, typename Context, typename Op>
void ScanKernel(const Context& dev_ctx,
const DenseTensor& x,
int axis,
bool flatten,
bool exclusive,
bool reverse,
Op op,
DenseTensor* out) {
if (out && out->numel() == 0) {
dev_ctx.template Alloc<T>(out);
return;
}
T* out_data = dev_ctx.template Alloc<T>(out);
// For 0D Tensor
if (out->numel() == 1) {
auto raw_dims = out->dims();
Copy<Context>(dev_ctx, x, dev_ctx.GetPlace(), false, out);
out->Resize(raw_dims);
return;
}
auto out_dims = out->dims();
PADDLE_ENFORCE_EQ(
axis < out_dims.size() && axis >= (0 - out_dims.size()),
true,
common::errors::OutOfRange(
"Attr(axis) is out of range, It's expected "
"to be in range of [-%d, %d]. But received Attr(axis) = %d.",
out_dims.size(),
out_dims.size() - 1,
axis));
if (axis < 0) {
axis += out_dims.size();
}
const T* in_data = x.data<T>();
// Use thrust for parallel acceleration when the input size is equal to the
// length of the 'axis' dimension (i.e., it's a 1D scan).
int64_t size = x.numel();
if (std::is_same_v<Op, cub::Sum> && size == out_dims[axis]) {
ThrustCumsumKernel<Context, T>(
dev_ctx, in_data, out_data, size, reverse, exclusive);
return;
}
size_t height = 1;
size_t width = 1;
for (size_t i = 0; i <= axis; i++) {
height *= out_dims[i];
}
for (size_t i = axis + 1; i < out_dims.size(); i++) {
width *= out_dims[i];
}
int64_t scan_size = out_dims[axis];
bool transpose = (axis != out_dims.size() - 1);
DenseTensor tmp_tensor;
tmp_tensor.Resize(out_dims);
auto* tmp_data = dev_ctx.template Alloc<T>(&tmp_tensor);
auto swap_ptr = [](T*& ptr1, T*& ptr2) {
T* tmp = ptr2;
ptr2 = ptr1;
ptr1 = tmp;
};
int64_t max_grid_x = dev_ctx.GetCUDAMaxGridDimSize()[0];
// Do pre-process transpose
int64_t tile_size = 32;
dim3 blocks(32, 8);
int64_t transpose_grids = ((width + tile_size - 1) / tile_size) *
((height + tile_size - 1) / tile_size);
transpose_grids = std::min(transpose_grids, max_grid_x);
T* next_in_data = out_data;
T* next_out_data = tmp_data;
if (transpose) {
MatrixTranspose<T, 32, 8><<<transpose_grids, blocks, 0, dev_ctx.stream()>>>(
out_data, in_data, height, width);
next_in_data = out_data;
next_out_data = tmp_data;
}
// Do pre-process reverse
int64_t outer_size = height / scan_size;
int64_t inner_size = width;
int64_t grid_size = outer_size * inner_size;
int64_t scan_grid = std::min(grid_size, max_grid_x);
if (reverse) {
if (transpose) {
MatrixRowReverse<T><<<scan_grid, 1024, 0, dev_ctx.stream()>>>(
next_in_data, next_out_data, grid_size, scan_size);
if (!transpose) next_in_data = tmp_data;
swap_ptr(next_in_data, next_out_data);
} else {
MatrixRowReverse<T><<<scan_grid, 1024, 0, dev_ctx.stream()>>>(
in_data, out_data, grid_size, scan_size);
}
}
// Do scan
if (!transpose && !reverse) {
BlockScanKernel<T, 128, 4, Op><<<scan_grid, 128, 0, dev_ctx.stream()>>>(
out_data, in_data, grid_size, scan_size, exclusive, op);
} else {
BlockScanKernel<T, 128, 4, Op><<<scan_grid, 128, 0, dev_ctx.stream()>>>(
next_out_data, next_in_data, grid_size, scan_size, exclusive, op);
}
swap_ptr(next_in_data, next_out_data);
// Do post-process reverse and transpose
if (reverse) {
MatrixRowReverse<T><<<scan_grid, 1024, 0, dev_ctx.stream()>>>(
next_in_data, next_out_data, grid_size, scan_size);
swap_ptr(next_in_data, next_out_data);
}
if (transpose) {
MatrixTranspose<T, 32, 8><<<transpose_grids, blocks, 0, dev_ctx.stream()>>>(
next_out_data, next_in_data, width, height);
}
}
template <typename T, typename Context>
void CumsumKernel(const Context& dev_ctx,
const DenseTensor& x,
const Scalar& axis,
bool flatten,
bool exclusive,
bool reverse,
DenseTensor* out) {
using Op = typename std::conditional<std::is_same<T, complex64>::value ||
std::is_same<T, complex128>::value,
ComplexSum,
cub::Sum>::type;
if (FLAGS_use_accuracy_compatible_kernel && !exclusive) {
if (out && out->numel() == 0) {
dev_ctx.template Alloc<T>(out);
return;
}
dev_ctx.template Alloc<T>(out);
size_t outer_dim = 1;
size_t mid_dim = 1;
size_t inner_dim = 1;
if (flatten) {
mid_dim = x.numel();
} else {
GetCumprodDimInfo(
x.dims(), axis.to<int>(), &outer_dim, &mid_dim, &inner_dim);
}
const T* x_data = x.data<T>();
T* out_data = out->data<T>();
funcs::InclusiveScan(x_data,
out_data,
outer_dim,
mid_dim,
inner_dim,
static_cast<T>(0),
funcs::AddFunctor<T>(),
/*reverse=*/reverse,
dev_ctx);
return;
}
auto op = Op();
ScanKernel<T, Context, Op>(
dev_ctx, x, axis.to<int>(), flatten, exclusive, reverse, op, out);
}
template <typename T, typename Context>
void LogcumsumexpKernel(const Context& dev_ctx,
const DenseTensor& x,
int axis,
bool flatten,
bool exclusive,
bool reverse,
DenseTensor* out) {
using Op = LogAddExp;
auto op = Op();
ScanKernel<T, Context, Op>(
dev_ctx, x, axis, flatten, exclusive, reverse, op, out);
}
} // namespace phi
#ifdef PADDLE_WITH_HIP
PD_REGISTER_KERNEL(cumsum,
GPU,
ALL_LAYOUT,
phi::CumsumKernel,
float,
phi::float16,
double,
int16_t,
int,
int64_t) {}
PD_REGISTER_KERNEL(
logcumsumexp, GPU, ALL_LAYOUT, phi::LogcumsumexpKernel, float, double) {}
#else
PD_REGISTER_KERNEL(cumsum,
GPU,
ALL_LAYOUT,
phi::CumsumKernel,
float,
double,
uint8_t,
int8_t,
int16_t,
int,
int64_t,
phi::float16,
phi::bfloat16,
phi::complex64,
phi::complex128) {}
PD_REGISTER_KERNEL(logcumsumexp,
GPU,
ALL_LAYOUT,
phi::LogcumsumexpKernel,
float,
double,
phi::float16,
phi::bfloat16) {}
#endif