542 lines
21 KiB
C++
542 lines
21 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
|
|
#include "paddle/phi/kernels/sparse/conv_kernel.h"
|
|
|
|
#include "paddle/phi/backends/gpu/gpu_context.h"
|
|
#include "paddle/phi/backends/gpu/gpu_info.h"
|
|
#include "paddle/phi/backends/gpu/gpu_launch_config.h"
|
|
#include "paddle/phi/core/enforce.h"
|
|
#include "paddle/phi/core/tensor_utils.h"
|
|
#include "paddle/phi/kernels/funcs/aligned_vector.h"
|
|
#include "paddle/phi/kernels/funcs/index_impl.cu.h"
|
|
#include "paddle/phi/kernels/funcs/math_function.h"
|
|
#include "paddle/phi/kernels/funcs/sparse/scatter.cu.h"
|
|
#include "paddle/phi/kernels/funcs/sparse/utils.cu.h"
|
|
#include "paddle/phi/kernels/primitive/compute_primitives.h"
|
|
#include "paddle/phi/kernels/sparse/gpu/conv_host_buffer.h"
|
|
|
|
namespace phi {
|
|
namespace sparse {
|
|
using Dims4D = funcs::sparse::Dims4D;
|
|
|
|
inline __device__ uint32_t BitCount(const uint32_t data) {
|
|
uint32_t count = data;
|
|
count = (count & 0x55555555) + ((count >> 1) & 0x55555555);
|
|
count = (count & 0x33333333) + ((count >> 2) & 0x33333333);
|
|
count = (count & 0x0f0f0f0f) + ((count >> 4) & 0x0f0f0f0f);
|
|
count = (count & 0x00ff00ff) + ((count >> 8) & 0x00ff00ff);
|
|
count = (count & 0x0000ffff) + ((count >> 16) & 0x0000ffff);
|
|
return count;
|
|
}
|
|
|
|
static __global__ void GetOutIndicesCounter(const int* flags,
|
|
const int n,
|
|
int* out) {
|
|
int tid = threadIdx.x + blockDim.x * blockIdx.x;
|
|
__shared__ int block_count;
|
|
if (threadIdx.x == 0) {
|
|
block_count = 0;
|
|
}
|
|
__syncthreads();
|
|
|
|
if (tid < n) {
|
|
// get the count of 1 in flags[tid]
|
|
uint32_t count = BitCount(static_cast<uint32_t>(flags[tid]));
|
|
// add to block_count
|
|
// TODO(zhangkaihuo): replace with block reduce_sum
|
|
atomicAdd(&block_count, static_cast<int>(count));
|
|
}
|
|
__syncthreads();
|
|
// write to out
|
|
if (threadIdx.x == 0) {
|
|
out[blockIdx.x] = block_count;
|
|
}
|
|
}
|
|
|
|
// unique the out indices in rulebook
|
|
template <typename IntT>
|
|
__global__ void UniqueKernel(const IntT* in_indices,
|
|
int* rulebook_len,
|
|
int* index_flags,
|
|
int* out_indices,
|
|
int* nnz) {
|
|
extern __shared__ int cache[];
|
|
__shared__ int count, start;
|
|
if (threadIdx.x == 0) {
|
|
count = 0;
|
|
start = 0;
|
|
}
|
|
__syncthreads();
|
|
int rulebook_len_num = rulebook_len[0] / 2;
|
|
|
|
int i = threadIdx.x + blockDim.x * blockIdx.x;
|
|
if (i < rulebook_len_num) {
|
|
// atomicOr only support int
|
|
int index = static_cast<int>((in_indices + rulebook_len_num)[i]);
|
|
const bool flag = funcs::sparse::SetBits(index, index_flags);
|
|
if (!flag) {
|
|
int j = atomicAdd(&count, 1);
|
|
cache[j] = index;
|
|
}
|
|
}
|
|
__syncthreads();
|
|
|
|
if (threadIdx.x == 0) {
|
|
start = atomicAdd(nnz, count);
|
|
}
|
|
__syncthreads();
|
|
for (int i = threadIdx.x; i < count; i += blockDim.x) {
|
|
out_indices[start + i] = cache[i];
|
|
}
|
|
}
|
|
|
|
struct is_equal {
|
|
int value;
|
|
__host__ __device__ bool operator()(int x) const { return x == value; }
|
|
};
|
|
|
|
template <typename T, typename Pred>
|
|
__global__ void mark_kernel(const T* input, int* flags, Pred pred, int n) {
|
|
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
if (idx < n) {
|
|
flags[idx] = !pred(input[idx]);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
__global__ void compact_kernel(const T* input,
|
|
T* output,
|
|
const int* indices,
|
|
const int* flags,
|
|
int n,
|
|
int* out_num) {
|
|
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
if (idx < n) {
|
|
if (flags[idx]) output[indices[idx]] = input[idx];
|
|
if (idx == n - 1) out_num[0] = indices[idx] + flags[idx];
|
|
}
|
|
}
|
|
|
|
template <typename T, typename Pred>
|
|
void cuda_remove(const GPUContext& dev_ctx,
|
|
DenseTensor& input, // NOLINT
|
|
int n,
|
|
Pred pred,
|
|
int* out_num_ptr) {
|
|
const int block_size = 256;
|
|
const int grid_size = (n + block_size - 1) / block_size;
|
|
DenseTensor flags = Empty<int>(dev_ctx, {n});
|
|
DenseTensor indices = Empty<int>(dev_ctx, {n});
|
|
DenseTensor out = Empty<T>(dev_ctx, {n});
|
|
|
|
mark_kernel<<<grid_size, block_size, 0, dev_ctx.stream()>>>(
|
|
input.data<T>(), flags.data<int>(), pred, n);
|
|
|
|
size_t temp_size = 0;
|
|
cub::DeviceScan::ExclusiveSum(NULL,
|
|
temp_size,
|
|
flags.data<int>(),
|
|
indices.data<int>(),
|
|
n,
|
|
dev_ctx.stream());
|
|
phi::Allocator* allocator =
|
|
const_cast<phi::Allocator*>(&(dev_ctx.GetAllocator()));
|
|
auto ws = allocator->Allocate(temp_size)->ptr();
|
|
cub::DeviceScan::ExclusiveSum(ws,
|
|
temp_size,
|
|
flags.data<int>(),
|
|
indices.data<int>(),
|
|
n,
|
|
dev_ctx.stream());
|
|
|
|
compact_kernel<<<grid_size, block_size, 0, dev_ctx.stream()>>>(
|
|
input.data<T>(),
|
|
out.data<T>(),
|
|
indices.data<int>(),
|
|
flags.data<int>(),
|
|
n,
|
|
out_num_ptr);
|
|
|
|
backends::gpu::GpuMemcpyAsync(input.data<T>(),
|
|
out.data<T>(),
|
|
sizeof(T) * n,
|
|
gpuMemcpyDeviceToDevice,
|
|
dev_ctx.stream());
|
|
}
|
|
|
|
template <int BS>
|
|
__global__ void GetOutIndices(const int* flags,
|
|
const int n,
|
|
const int* offsets,
|
|
const int out_nnz,
|
|
int* out) {
|
|
int tid = threadIdx.x + blockDim.x * blockIdx.x;
|
|
__shared__ int block_counts[BS];
|
|
__shared__ int block_outs[BS * 32];
|
|
|
|
int count = 0;
|
|
|
|
if (tid < n) {
|
|
// get the count of 1 in flags[tid]
|
|
int flag = flags[tid];
|
|
count = BitCount(static_cast<uint32_t>(flag));
|
|
}
|
|
|
|
// call block prefix_sum
|
|
// using namespace cub;
|
|
typedef cub::BlockScan<int, BS> BlockScan;
|
|
__shared__ typename BlockScan::TempStorage temp_storage;
|
|
BlockScan(temp_storage).ExclusiveSum(count, count);
|
|
__syncthreads();
|
|
|
|
// write index to out
|
|
if (tid < n) {
|
|
// get the count of 1 in flags[tid]
|
|
int flag = flags[tid];
|
|
// int j = block_counts[threadIdx.x];
|
|
int j = count;
|
|
// TODO(zhangkaihuo): opt the loop
|
|
for (int i = 0; i < 32; ++i) {
|
|
if ((1 & (flag >> i)) == 1) {
|
|
block_outs[j++] = (tid << 5) + i;
|
|
}
|
|
}
|
|
}
|
|
|
|
__syncthreads();
|
|
// write to block_outs
|
|
int start = offsets[blockIdx.x];
|
|
int end = blockIdx.x == gridDim.x - 1 ? out_nnz : offsets[blockIdx.x + 1];
|
|
for (int i = threadIdx.x; i < end - start; i += blockDim.x) {
|
|
out[start + i] = block_outs[i];
|
|
}
|
|
}
|
|
|
|
template <typename IntT>
|
|
__global__ void GroupIndices(const int* out_index_table,
|
|
const int n,
|
|
const int kernel_size,
|
|
IntT* out_indices,
|
|
int* out_index_counts,
|
|
int* out_index_groups) {
|
|
CUDA_KERNEL_LOOP_TYPE(i, n, int64_t) {
|
|
IntT index = out_indices[i];
|
|
int real_index = out_index_table[index];
|
|
out_indices[i] = real_index;
|
|
|
|
// kernel_size at most
|
|
int j = atomicAdd(out_index_counts + real_index, 1);
|
|
// nnz * kernel_size
|
|
out_index_groups[real_index * kernel_size + j] = i;
|
|
}
|
|
}
|
|
|
|
template <typename IntT>
|
|
__global__ void GetOutIndexTable(int* indices,
|
|
const int non_zero_num,
|
|
const Dims4D out_dims,
|
|
const bool is2D,
|
|
int* out_index_table,
|
|
IntT* out_indices) {
|
|
CUDA_KERNEL_LOOP_TYPE(i, non_zero_num, int64_t) {
|
|
IntT index = static_cast<IntT>(indices[i]);
|
|
out_index_table[index] = i;
|
|
IntT batch, x, y, z;
|
|
funcs::sparse::IndexToPoint<Dims4D>(index, out_dims, &batch, &x, &y, &z);
|
|
// get out indices
|
|
out_indices[i] = batch;
|
|
if (is2D) {
|
|
out_indices[i + non_zero_num] = y;
|
|
out_indices[i + non_zero_num * 2] = x;
|
|
} else {
|
|
out_indices[i + non_zero_num] = z;
|
|
out_indices[i + non_zero_num * 2] = y;
|
|
out_indices[i + non_zero_num * 3] = x;
|
|
}
|
|
indices[i] = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief product rulebook
|
|
* for input_i in x_indices:
|
|
* if input_i participate in the convolution calculation:
|
|
* infer the output_i by input_i and kernel_i
|
|
* save output_i
|
|
*
|
|
* x_indices: the indices of input features
|
|
* x_dims: the input dims
|
|
* kernel_dims: the kernel dims
|
|
* out_dims: the output dims
|
|
* non_zero_num: the number of input features
|
|
* rulebook: the rulebook to save the kernel index, input index and output index
|
|
* counter: save the number of times each location in the kernel participates in
|
|
* the calculation
|
|
**/
|
|
template <typename T>
|
|
__global__ void ProductRuleBookKernel(const T* x_indices,
|
|
const Dims4D x_dims,
|
|
const Dims4D kernel_dims,
|
|
const Dims4D out_dims,
|
|
const int64_t non_zero_num,
|
|
const Dims4D paddings,
|
|
const Dims4D dilations,
|
|
const Dims4D strides,
|
|
const bool is2D,
|
|
T* rulebook,
|
|
int* counter) {
|
|
int tid = threadIdx.x + blockIdx.x * blockDim.x;
|
|
extern __shared__ int counter_buf[]; // kernel_size
|
|
const int kernel_size = kernel_dims[3] * kernel_dims[2] * kernel_dims[1];
|
|
const int offset = kernel_size * non_zero_num;
|
|
for (int i = threadIdx.x; i < kernel_size; i += blockDim.x) {
|
|
counter_buf[i] = 0;
|
|
}
|
|
__syncthreads();
|
|
|
|
for (int i = tid; i < non_zero_num; i += gridDim.x * blockDim.x) {
|
|
int kernel_index = 0;
|
|
T batch = x_indices[i];
|
|
T in_z = is2D ? 0 : x_indices[i + non_zero_num];
|
|
T in_y =
|
|
is2D ? x_indices[i + non_zero_num] : x_indices[i + 2 * non_zero_num];
|
|
T in_x = is2D ? x_indices[i + 2 * non_zero_num]
|
|
: x_indices[i + 3 * non_zero_num];
|
|
for (int kz = 0; kz < kernel_dims[1]; kz++) {
|
|
for (int ky = 0; ky < kernel_dims[2]; ky++) {
|
|
for (int kx = 0; kx < kernel_dims[3]; kx++) {
|
|
int in_i = -1, out_index = -1, kernel_i = -1;
|
|
if (funcs::sparse::Check(x_dims,
|
|
kernel_dims,
|
|
paddings,
|
|
dilations,
|
|
strides,
|
|
in_x,
|
|
in_y,
|
|
in_z,
|
|
kx,
|
|
ky,
|
|
kz)) {
|
|
T out_z =
|
|
is2D ? 0
|
|
: (in_z + paddings[1] - kz * dilations[1]) / strides[1];
|
|
T out_y = (in_y + paddings[2] - ky * dilations[2]) / strides[2];
|
|
T out_x = (in_x + paddings[3] - kx * dilations[3]) / strides[3];
|
|
in_i = i;
|
|
out_index = funcs::sparse::PointToIndex<Dims4D>(
|
|
batch, out_x, out_y, out_z, out_dims);
|
|
atomicAdd(&counter_buf[kernel_index], 1);
|
|
kernel_i = kernel_index;
|
|
}
|
|
rulebook[kernel_index * non_zero_num + i] = in_i;
|
|
rulebook[kernel_index * non_zero_num + offset + i] = out_index;
|
|
++kernel_index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
__syncthreads();
|
|
for (int i = threadIdx.x; i < kernel_size; i += blockDim.x) {
|
|
atomicAdd(&counter[i], counter_buf[i]);
|
|
}
|
|
}
|
|
|
|
template <typename T, typename Context, typename IntT = int>
|
|
int ProductRuleBookWithBuffer(const Context& dev_ctx,
|
|
const IntT* indices_ptr,
|
|
const Dims4D& d_x_dims,
|
|
const Dims4D& d_kernel_dims,
|
|
const Dims4D& d_out_dims,
|
|
const Dims4D& d_paddings,
|
|
const Dims4D& d_strides,
|
|
const Dims4D& d_dilations,
|
|
const DDim& out_dims,
|
|
const std::vector<int>& kernel_sizes,
|
|
const int64_t& non_zero_num,
|
|
const int& kernel_size,
|
|
const int& rulebook_rows,
|
|
const int& rulebook_cols,
|
|
IntT* rulebook_ptr,
|
|
int* counter_ptr,
|
|
int* offsets_ptr,
|
|
DenseTensor* index_flags,
|
|
DenseTensor* out_index_table,
|
|
DenseTensor* rulebook,
|
|
DenseTensor* out_index,
|
|
DenseTensor* unique_value,
|
|
SparseCooTensor* out,
|
|
int* h_buffer) {
|
|
DenseTensor d_buffer = Empty<int>(dev_ctx, {2 * kernel_size + 3});
|
|
const bool is2D = out_dims.size() == 4 ? true : false;
|
|
auto config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, non_zero_num, 1);
|
|
ProductRuleBookKernel<IntT><<<config.block_per_grid.x,
|
|
config.thread_per_block.x,
|
|
kernel_size * sizeof(int),
|
|
dev_ctx.stream()>>>(indices_ptr,
|
|
d_x_dims,
|
|
d_kernel_dims,
|
|
d_out_dims,
|
|
non_zero_num,
|
|
d_paddings,
|
|
d_dilations,
|
|
d_strides,
|
|
is2D,
|
|
rulebook_ptr,
|
|
counter_ptr);
|
|
|
|
DenseTensor rulebook_len_tensor = Empty<int>(dev_ctx, {1});
|
|
cuda_remove<IntT>(dev_ctx,
|
|
*rulebook,
|
|
rulebook_rows * rulebook_cols,
|
|
is_equal{-1},
|
|
rulebook_len_tensor.data<int>());
|
|
|
|
size_t temp_size = 0;
|
|
cub::DeviceScan::ExclusiveSum(
|
|
NULL, temp_size, counter_ptr, offsets_ptr, kernel_size, dev_ctx.stream());
|
|
phi::Allocator* allocator =
|
|
const_cast<phi::Allocator*>(&(dev_ctx.GetAllocator()));
|
|
auto ws = allocator->Allocate(temp_size)->ptr();
|
|
cub::DeviceScan::ExclusiveSum(
|
|
ws, temp_size, counter_ptr, offsets_ptr, kernel_size, dev_ctx.stream());
|
|
|
|
int64_t max_nnz =
|
|
phi::sparse::ConvHostBuffer::getInstance().get_max_bound() * non_zero_num;
|
|
rulebook->Resize({rulebook_rows, static_cast<int>(max_nnz)});
|
|
// 3. sorted or merge the out index
|
|
|
|
out_index->ResizeAndAllocate({static_cast<int>(max_nnz)});
|
|
DenseTensor unique_key = Empty<int>(dev_ctx, {static_cast<int>(max_nnz)});
|
|
int* out_index_ptr = out_index->data<int>();
|
|
int* unique_key_ptr = unique_key.data<int>();
|
|
|
|
backends::gpu::GpuMemsetAsync(
|
|
unique_key_ptr, 0, sizeof(int), dev_ctx.stream());
|
|
config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, max_nnz, 1);
|
|
size_t cache_size = sizeof(int) * config.thread_per_block.x;
|
|
int* index_flags_ptr = index_flags->data<int>();
|
|
UniqueKernel<IntT><<<config.block_per_grid,
|
|
config.thread_per_block,
|
|
cache_size,
|
|
dev_ctx.stream()>>>(rulebook_ptr,
|
|
rulebook_len_tensor.data<int>(),
|
|
index_flags_ptr,
|
|
out_index_ptr,
|
|
unique_key_ptr);
|
|
|
|
backends::gpu::GpuMemcpyAsync(d_buffer.data<int>(),
|
|
counter_ptr,
|
|
kernel_size * sizeof(int),
|
|
gpuMemcpyDeviceToDevice,
|
|
dev_ctx.stream());
|
|
backends::gpu::GpuMemcpyAsync(d_buffer.data<int>() + kernel_size,
|
|
offsets_ptr,
|
|
kernel_size * sizeof(int),
|
|
gpuMemcpyDeviceToDevice,
|
|
dev_ctx.stream());
|
|
backends::gpu::GpuMemcpyAsync(d_buffer.data<int>() + 2 * kernel_size + 1,
|
|
rulebook_len_tensor.data<int>(),
|
|
sizeof(int),
|
|
gpuMemcpyDeviceToDevice,
|
|
dev_ctx.stream());
|
|
backends::gpu::GpuMemcpyAsync(d_buffer.data<int>() + 2 * kernel_size + 2,
|
|
unique_key_ptr,
|
|
sizeof(int),
|
|
gpuMemcpyDeviceToDevice,
|
|
dev_ctx.stream());
|
|
backends::gpu::GpuMemcpyAsync(h_buffer,
|
|
d_buffer.data<int>(),
|
|
(2 * kernel_size + 3) * sizeof(int),
|
|
gpuMemcpyDeviceToHost,
|
|
dev_ctx.stream());
|
|
|
|
dev_ctx.Wait();
|
|
int rulebook_len = h_buffer[2 * kernel_size + 1] / 2;
|
|
int out_nnz = h_buffer[2 * kernel_size + 2];
|
|
|
|
rulebook->Resize({rulebook_rows, static_cast<int>(rulebook_len)});
|
|
out_index->Resize({static_cast<int>(rulebook_len)});
|
|
|
|
const int threads = 256;
|
|
const int blocks = (index_flags->numel() + threads - 1) / threads;
|
|
int* out_index_table_ptr = out_index_table->data<int>();
|
|
|
|
GetOutIndicesCounter<<<blocks, threads, 0, dev_ctx.stream()>>>(
|
|
index_flags_ptr, index_flags->numel(), out_index_table_ptr);
|
|
|
|
size_t temp_size1 = 0;
|
|
cub::DeviceScan::ExclusiveSum(NULL,
|
|
temp_size1,
|
|
out_index_table_ptr,
|
|
out_index_table_ptr,
|
|
blocks,
|
|
dev_ctx.stream());
|
|
|
|
phi::Allocator* allocator1 =
|
|
const_cast<phi::Allocator*>(&(dev_ctx.GetAllocator()));
|
|
auto ws1 = allocator->Allocate(temp_size)->ptr();
|
|
cub::DeviceScan::ExclusiveSum(ws1,
|
|
temp_size1,
|
|
out_index_table_ptr,
|
|
out_index_table_ptr,
|
|
blocks,
|
|
dev_ctx.stream());
|
|
|
|
GetOutIndices<threads>
|
|
<<<blocks, threads, 0, dev_ctx.stream()>>>(index_flags_ptr,
|
|
index_flags->numel(),
|
|
out_index_table_ptr,
|
|
out_nnz,
|
|
out_index_ptr);
|
|
|
|
const int64_t sparse_dim = is2D ? 3 : 4;
|
|
DenseTensor out_indices = Empty<IntT>(dev_ctx, {sparse_dim, out_nnz});
|
|
|
|
DenseTensor out_values =
|
|
Empty<T>(dev_ctx, {out_nnz, kernel_sizes[sparse_dim]});
|
|
out->SetMember(out_indices, out_values, out_dims, false);
|
|
|
|
IntT* out_indices_ptr = out_indices.data<IntT>();
|
|
|
|
config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, out_nnz, 1);
|
|
GetOutIndexTable<IntT>
|
|
<<<config.block_per_grid, config.thread_per_block, 0, dev_ctx.stream()>>>(
|
|
out_index_ptr,
|
|
out_nnz,
|
|
d_out_dims,
|
|
is2D,
|
|
out_index_table_ptr,
|
|
out_indices_ptr);
|
|
|
|
config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, rulebook_len, 1);
|
|
unique_value->ResizeAndAllocate({static_cast<int>(out_nnz * kernel_size)});
|
|
int* unique_value_ptr = unique_value->data<int>();
|
|
|
|
GroupIndices<<<config.block_per_grid,
|
|
config.thread_per_block,
|
|
0,
|
|
dev_ctx.stream()>>>(out_index_table_ptr,
|
|
rulebook_len,
|
|
kernel_size,
|
|
rulebook_ptr + rulebook_len,
|
|
out_index_ptr,
|
|
unique_value_ptr);
|
|
|
|
return rulebook_len;
|
|
}
|
|
} // namespace sparse
|
|
} // namespace phi
|