// 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 #include #include #include #include #include #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(address); int32_t* base_address = reinterpret_cast( reinterpret_cast(address) - (address_value & 3)); int32_t int_val = static_cast(val) << ((address_value & 3) * 8); int32_t int_comp = static_cast(compare) << ((address_value & 3) * 8); return static_cast(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(address); int32_t* base_address = reinterpret_cast( reinterpret_cast(address) - (address_value & 2)); int32_t int_val = static_cast(val) << ((address_value & 2) * 8); int32_t int_comp = static_cast(compare) << ((address_value & 2) * 8); return static_cast(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( atomicCAS(reinterpret_cast(address), static_cast(compare), static_cast(val))); } namespace phi { namespace sparse { template __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(data[j]); hash *= UINT64_C(1099511628211); } // hash = (hash >> 60) ^ (hash & 0xFFFFFFFFFFFFFFF); return hash; } template __device__ int hash(key_type key, int _capacity) { return static_cast(key) % _capacity; } template 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()), table_vals(table_vals->data()), _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; using hashtable32 = GPUHashTable; template __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 __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 void GPUHashTable::insert_many_coords( const GPUContext& dev_ctx, const int* coords, const int n) { insert_coords_kernel <<<(n + BLOCK_SIZE - 1) / BLOCK_SIZE, BLOCK_SIZE, 0, dev_ctx.stream()>>>( table_keys, table_vals, coords, n, _capacity, _width); } template void GPUHashTable::insert_coords( const GPUContext& dev_ctx, const DenseTensor& coords) { insert_many_coords(dev_ctx, coords.data(), coords.dims()[0]); } template void GPUHashTable::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 <<<(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 <<<(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 void GPUHashTable::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(); lookup_many_coords(dev_ctx, coords.data(), results_data, kernel_sizes, strides, coords.dims()[0], kernel_volume); } template void build_sparse_conv_kmap(const GPUContext& dev_ctx, const SparseCooTensor& x, const std::string& key, const std::vector& kernel_sizes, const std::vector& 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(tmp_hashmap_keys); funcs::SetConstant set_zero; set_zero(dev_ctx, tmp_hashmap_keys, static_cast(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(tmp_hashmap_values); funcs::SetConstant set_zero; set_zero(dev_ctx, tmp_hashmap_values, static_cast(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(tmp_indices); // transpose indices std::vector perm = {1, 0}; funcs::TransposeGPUKernelDriver( 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(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(tmp_out_in_map); out_kmap_cache_ptr->out_in_map = tmp_out_in_map; funcs::SetConstant set_neg_one; set_neg_one( dev_ctx, out_kmap_cache_ptr->out_in_map, static_cast(-1)); // need to put kernel_sizes and strides to GPU auto kernel_sizes_tensor = phi::Empty(dev_ctx, {3}); auto strides_tensor = phi::Empty(dev_ctx, {3}); set_kernel_sizes_and_strides_tensor<<<1, 32, 0, dev_ctx.stream()>>>( kernel_sizes_tensor.data(), strides_tensor.data(), 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(), strides_tensor.data(), 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(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