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

429 lines
16 KiB
Plaintext

// 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 <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <utility>
#include <vector>
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/sparse_coo_tensor.h"
#include "paddle/phi/kernels/empty_kernel.h"
#include "paddle/phi/kernels/funcs/math_function_impl.h"
#include "paddle/phi/kernels/funcs/transpose_function.cuh"
/** Reserved value for indicating "empty". */
#define EMPTY_CELL (0)
/** CUDA naive thread block size. */
#define BLOCK_SIZE (256)
__inline__ __device__ int8_t atomicCAS(int8_t* address,
int8_t compare,
int8_t val) {
auto address_value = reinterpret_cast<uintptr_t>(address);
int32_t* base_address = reinterpret_cast<int32_t*>(
reinterpret_cast<char*>(address) - (address_value & 3));
int32_t int_val = static_cast<int32_t>(val) << ((address_value & 3) * 8);
int32_t int_comp = static_cast<int32_t>(compare) << ((address_value & 3) * 8);
return static_cast<int8_t>(atomicCAS(base_address, int_comp, int_val));
}
// TODO(ShigureNyako): can we do this more efficiently?
__inline__ __device__ int16_t atomicCAS(int16_t* address,
int16_t compare,
int16_t val) {
auto address_value = reinterpret_cast<uintptr_t>(address);
int32_t* base_address = reinterpret_cast<int32_t*>(
reinterpret_cast<char*>(address) - (address_value & 2));
int32_t int_val = static_cast<int32_t>(val) << ((address_value & 2) * 8);
int32_t int_comp = static_cast<int32_t>(compare) << ((address_value & 2) * 8);
return static_cast<int16_t>(atomicCAS(base_address, int_comp, int_val));
}
__inline__ __device__ int64_t atomicCAS(int64_t* address,
int64_t compare,
int64_t val) {
using AtomicCAS64Type = unsigned long long; // NOLINT(runtime/int)
return static_cast<int64_t>(
atomicCAS(reinterpret_cast<AtomicCAS64Type*>(address),
static_cast<AtomicCAS64Type>(compare),
static_cast<AtomicCAS64Type>(val)));
}
namespace phi {
namespace sparse {
template <typename dtype = int>
__device__ uint64_t hash_func_64b(dtype* data, int n = 4) {
uint64_t hash = UINT64_C(14695981039346656037);
for (int j = 0; j < n; j++) {
hash ^= static_cast<unsigned int>(data[j]);
hash *= UINT64_C(1099511628211);
}
// hash = (hash >> 60) ^ (hash & 0xFFFFFFFFFFFFFFF);
return hash;
}
template <typename key_type>
__device__ int hash(key_type key, int _capacity) {
return static_cast<uint64_t>(key) % _capacity;
}
template <typename key_type, typename val_type>
class GPUHashTable {
private:
// public:
bool free_pointers;
const int _capacity;
const int _divisor;
const int _width;
key_type* table_keys;
val_type* table_vals;
void insert_many_coords(const GPUContext& dev_ctx,
const int* coords,
const int n);
void lookup_many_coords(const GPUContext& dev_ctx,
const int* coords,
val_type* results,
const int* kernel_sizes,
const int* tensor_strides,
const int n,
const int kernel_volume);
public:
GPUHashTable(DenseTensor* table_keys,
DenseTensor* table_vals,
const int divisor,
const int width)
: _capacity(table_keys->dims()[0]),
free_pointers(false),
table_keys(table_keys->data<key_type>()),
table_vals(table_vals->data<val_type>()),
_divisor(divisor),
_width(width) {}
~GPUHashTable() {
if (free_pointers) {
cudaFree(table_keys);
cudaFree(table_vals);
}
}
void insert_coords(const GPUContext& dev_ctx, const DenseTensor& coords);
void lookup_coords(const GPUContext& dev_ctx,
const DenseTensor& coords,
const int* kernel_sizes,
const int* tensor_strides,
int kernel_volume,
DenseTensor* results);
int get_divisor() { return _divisor; }
int get_capacity() { return _capacity; }
};
using hashtable = GPUHashTable<int64_t, int>;
using hashtable32 = GPUHashTable<int, int>;
template <typename key_type = int64_t, typename val_type = int>
__global__ void insert_coords_kernel(key_type* table_keys,
val_type* table_vals,
const int* coords,
int n,
int _capacity,
int _width) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
key_type key = (key_type)(hash_func_64b(coords + idx * _width, _width));
int value = idx + 1;
int slot = hash(key, _capacity);
while (true) {
key_type prev = atomicCAS(&table_keys[slot], EMPTY_CELL, key);
if (prev == EMPTY_CELL || prev == key) {
table_vals[slot] = value;
return;
}
slot = (slot + 1) % _capacity;
}
}
}
template <typename key_type = int64_t, typename val_type = int, bool odd>
__global__ void lookup_coords_kernel(key_type* table_keys,
val_type* table_vals,
const int* coords,
val_type* vals,
const int* kernel_sizes,
const int* strides,
int n,
int _capacity,
int kernel_volume,
int _width) {
int tidx = blockIdx.x * blockDim.x + threadIdx.x;
int idx = tidx / kernel_volume;
if (idx >= n) return;
int _kernel_idx = tidx % kernel_volume;
int kernel_idx = _kernel_idx;
const int* in_coords = coords + _width * idx;
int coords_out[4];
// coords_out[2] = in_coords[2];
// coords_out[3] = in_coords[3];
coords_out[0] = in_coords[0];
if constexpr (odd) {
#pragma unroll
for (int i = 0; i <= _width - 2; i++) {
int cur_offset = _kernel_idx % kernel_sizes[i];
cur_offset -= (kernel_sizes[i] - 1) / 2;
coords_out[i + 1] = in_coords[i + 1] * strides[i] + cur_offset;
_kernel_idx /= kernel_sizes[i];
}
} else {
#pragma unroll
for (int i = _width - 2; i >= 0; i--) {
int cur_offset = _kernel_idx % kernel_sizes[i];
cur_offset -= (kernel_sizes[i] - 1) / 2;
coords_out[i + 1] = in_coords[i + 1] * strides[i] + cur_offset;
_kernel_idx /= kernel_sizes[i];
}
}
if (idx < n) {
key_type key = (key_type)(hash_func_64b(coords_out, _width));
int slot = hash(key, _capacity);
while (true) {
key_type cur_key = table_keys[slot];
if (key == cur_key) {
vals[idx * kernel_volume + kernel_idx] =
table_vals[slot] -
1; // need to subtract 1 to avoid extra operations in python
}
if (table_keys[slot] == EMPTY_CELL) {
return;
}
slot = (slot + 1) % _capacity;
}
}
}
__global__ void set_kernel_sizes_and_strides_tensor(int* kernel_size_tensor,
int* strides_tensor,
int kernel_size0,
int kernel_size1,
int kernel_size2,
int stride0,
int stride1,
int stride2) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < 6) {
switch (idx) {
case 0:
kernel_size_tensor[idx] = kernel_size0;
break;
case 1:
kernel_size_tensor[idx] = kernel_size1;
break;
case 2:
kernel_size_tensor[idx] = kernel_size2;
break;
case 3:
strides_tensor[idx - 3] = stride0;
break;
case 4:
strides_tensor[idx - 3] = stride1;
break;
case 5:
strides_tensor[idx - 3] = stride2;
break;
default:
break;
}
}
}
template <typename key_type, typename val_type>
void GPUHashTable<key_type, val_type>::insert_many_coords(
const GPUContext& dev_ctx, const int* coords, const int n) {
insert_coords_kernel<key_type, val_type>
<<<(n + BLOCK_SIZE - 1) / BLOCK_SIZE, BLOCK_SIZE, 0, dev_ctx.stream()>>>(
table_keys, table_vals, coords, n, _capacity, _width);
}
template <typename key_type, typename val_type>
void GPUHashTable<key_type, val_type>::insert_coords(
const GPUContext& dev_ctx, const DenseTensor& coords) {
insert_many_coords(dev_ctx, coords.data<int>(), coords.dims()[0]);
}
template <typename key_type, typename val_type>
void GPUHashTable<key_type, val_type>::lookup_many_coords(
const GPUContext& dev_ctx,
const int* coords,
val_type* results,
const int* kernel_sizes,
const int* strides,
const int n,
const int kernel_volume) {
if (kernel_volume % 2)
lookup_coords_kernel<key_type, val_type, true>
<<<(n * kernel_volume + BLOCK_SIZE - 1) / BLOCK_SIZE,
BLOCK_SIZE,
0,
dev_ctx.stream()>>>(table_keys,
table_vals,
coords,
results,
kernel_sizes,
strides,
n,
_capacity,
kernel_volume,
_width);
else
lookup_coords_kernel<key_type, val_type, false>
<<<(n * kernel_volume + BLOCK_SIZE - 1) / BLOCK_SIZE,
BLOCK_SIZE,
0,
dev_ctx.stream()>>>(table_keys,
table_vals,
coords,
results,
kernel_sizes,
strides,
n,
_capacity,
kernel_volume,
_width);
}
template <typename key_type, typename val_type>
void GPUHashTable<key_type, val_type>::lookup_coords(const GPUContext& dev_ctx,
const DenseTensor& coords,
const int* kernel_sizes,
const int* strides,
const int kernel_volume,
DenseTensor* results) {
int32_t* results_data = results->data<int32_t>();
lookup_many_coords(dev_ctx,
coords.data<int>(),
results_data,
kernel_sizes,
strides,
coords.dims()[0],
kernel_volume);
}
template <typename IntT>
void build_sparse_conv_kmap(const GPUContext& dev_ctx,
const SparseCooTensor& x,
const std::string& key,
const std::vector<int>& kernel_sizes,
const std::vector<int>& strides,
const int kernel_volume,
const bool is2D,
SparseCooTensor* out) {
int nnz = x.nnz();
const KmapCache* in_kmap_cache_ptr = x.GetKmapCache(key);
out->ClearKmaps();
KmapCache* out_kmap_cache_ptr = nullptr;
bool to_insert = false;
if (in_kmap_cache_ptr == nullptr) {
KmapCache kmap_cache;
out_kmap_cache_ptr = out->SetKmapCache(key, kmap_cache);
if (out_kmap_cache_ptr->hashmap_keys == nullptr) {
DenseTensor* tmp_hashmap_keys = new DenseTensor();
tmp_hashmap_keys->Resize({2 * x.nnz()});
dev_ctx.template Alloc<IntT>(tmp_hashmap_keys);
funcs::SetConstant<GPUContext, IntT> set_zero;
set_zero(dev_ctx, tmp_hashmap_keys, static_cast<IntT>(0));
out_kmap_cache_ptr->hashmap_keys = tmp_hashmap_keys;
to_insert = true;
}
if (out_kmap_cache_ptr->hashmap_values == nullptr) {
DenseTensor* tmp_hashmap_values = new DenseTensor();
tmp_hashmap_values->Resize({2 * x.nnz()});
dev_ctx.template Alloc<int32_t>(tmp_hashmap_values);
funcs::SetConstant<GPUContext, int32_t> set_zero;
set_zero(dev_ctx, tmp_hashmap_values, static_cast<int32_t>(0));
out_kmap_cache_ptr->hashmap_values = tmp_hashmap_values;
}
if (out_kmap_cache_ptr->coords == nullptr) {
DenseTensor* tmp_indices = new DenseTensor();
tmp_indices->Resize({x.indices().dims()[1], x.indices().dims()[0]});
dev_ctx.template Alloc<int32_t>(tmp_indices);
// transpose indices
std::vector<int> perm = {1, 0};
funcs::TransposeGPUKernelDriver<int32_t>(
dev_ctx, x.indices(), perm, tmp_indices);
out_kmap_cache_ptr->coords = tmp_indices;
}
const int divisor = 128;
const int width = is2D ? 3 : 4;
auto hashmap =
GPUHashTable<IntT, int32_t>(out_kmap_cache_ptr->hashmap_keys,
out_kmap_cache_ptr->hashmap_values,
divisor,
width);
if (to_insert) {
hashmap.insert_coords(dev_ctx, *(out_kmap_cache_ptr->coords));
}
DenseTensor* tmp_out_in_map = new DenseTensor();
tmp_out_in_map->Resize(
{(x.nnz() + divisor - 1) / divisor * divisor, kernel_volume});
dev_ctx.template Alloc<int32_t>(tmp_out_in_map);
out_kmap_cache_ptr->out_in_map = tmp_out_in_map;
funcs::SetConstant<GPUContext, int32_t> set_neg_one;
set_neg_one(
dev_ctx, out_kmap_cache_ptr->out_in_map, static_cast<int32_t>(-1));
// need to put kernel_sizes and strides to GPU
auto kernel_sizes_tensor = phi::Empty<int32_t>(dev_ctx, {3});
auto strides_tensor = phi::Empty<int32_t>(dev_ctx, {3});
set_kernel_sizes_and_strides_tensor<<<1, 32, 0, dev_ctx.stream()>>>(
kernel_sizes_tensor.data<int32_t>(),
strides_tensor.data<int32_t>(),
kernel_sizes[0],
kernel_sizes[1],
kernel_sizes[2],
strides[0],
strides[1],
strides[2]);
hashmap.lookup_coords(dev_ctx,
*(out_kmap_cache_ptr->coords),
kernel_sizes_tensor.data<int32_t>(),
strides_tensor.data<int32_t>(),
kernel_volume,
out_kmap_cache_ptr->out_in_map);
} else {
// out tensor takes the kmaps from x
out->SetKmaps(x.GetKmaps());
// force clear the kmaps of x
const_cast<SparseCooTensor&>(x).ClearKmaps();
}
const KmapCache* new_out_kmap_cache_ptr = out->GetKmapCache(key);
assert(new_out_kmap_cache_ptr != nullptr);
assert(new_out_kmap_cache_ptr->hashmap_keys != nullptr);
assert(new_out_kmap_cache_ptr->hashmap_values != nullptr);
assert(new_out_kmap_cache_ptr->coords != nullptr);
assert(new_out_kmap_cache_ptr->out_in_map != nullptr);
return;
}
} // namespace sparse
} // namespace phi