// 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 #include #include #include #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 __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(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 __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(threadIdx.x); int64_t y = block_y * TILE_DIM + static_cast(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 __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 __host__ __device__ __forceinline__ T operator()(const T& a, const T& b) const { return a + b; } }; template struct Identity; template struct Identity { static constexpr T value = 0; }; template struct Identity { static constexpr T value = std::numeric_limits::lowest(); }; template struct Identity { static constexpr T value = {0, 0}; }; template 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(-compensation_)); T t = op_(running_total_, y); T y_high = op_(t, static_cast(-running_total_)); compensation_ = op_(y_high, static_cast(-y)); running_total_ = t; return old_prefix; } }; template struct BlockPrefixCallbackOp { 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(0.0)), compensation_(static_cast(0.0)), op_(op) {} __device__ T operator()(T block_aggregate) { if (scaled_sum_ == 0.0) { max_so_far_ = block_aggregate; scaled_sum_ = static_cast(1.0); compensation_ = static_cast(0.0); return std::numeric_limits::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 __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::Type; using CallbackOp = BlockPrefixCallbackOp; // Specialize BlockLoad, BlockStore, and BlockRadixSort collective types using BlockLoadT = cub:: BlockLoad; using BlockStoreT = cub::BlockStore; using BlockScanT = cub::BlockScan; // 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::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::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 void ThrustCumsumKernel(const Context& dev_ctx, const T* in_data, T* out_data, int64_t size, bool reverse, bool exclusive) { using MT = typename MPTypeTrait::Type; #ifdef __HIPCC__ const auto& policy = thrust::hip::par.on(dev_ctx.stream()); #else memory_utils::ThrustAllocator 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) { if (reverse) { thrust::reverse_iterator> reversed_in( thrust::device_pointer_cast(in_data) + size); thrust::reverse_iterator> 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 tmp_in(size); thrust::device_vector 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 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(out); return; } T* out_data = dev_ctx.template Alloc(out); // For 0D Tensor if (out->numel() == 1) { auto raw_dims = out->dims(); Copy(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(); // 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 && size == out_dims[axis]) { ThrustCumsumKernel( 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(&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<<>>( 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<<>>( 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<<>>( in_data, out_data, grid_size, scan_size); } } // Do scan if (!transpose && !reverse) { BlockScanKernel<<>>( out_data, in_data, grid_size, scan_size, exclusive, op); } else { BlockScanKernel<<>>( 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<<>>( next_in_data, next_out_data, grid_size, scan_size); swap_ptr(next_in_data, next_out_data); } if (transpose) { MatrixTranspose<<>>( next_out_data, next_in_data, width, height); } } template 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::value || std::is_same::value, ComplexSum, cub::Sum>::type; if (FLAGS_use_accuracy_compatible_kernel && !exclusive) { if (out && out->numel() == 0) { dev_ctx.template Alloc(out); return; } dev_ctx.template Alloc(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(), &outer_dim, &mid_dim, &inner_dim); } const T* x_data = x.data(); T* out_data = out->data(); funcs::InclusiveScan(x_data, out_data, outer_dim, mid_dim, inner_dim, static_cast(0), funcs::AddFunctor(), /*reverse=*/reverse, dev_ctx); return; } auto op = Op(); ScanKernel( dev_ctx, x, axis.to(), flatten, exclusive, reverse, op, out); } template 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( 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