1646 lines
67 KiB
Plaintext
1646 lines
67 KiB
Plaintext
/*
|
|
* Copyright (c) 2023, NVIDIA CORPORATION.
|
|
*
|
|
* 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 <cooperative_groups.h>
|
|
|
|
#include <nv_gpu_cache.hpp>
|
|
|
|
namespace cg = cooperative_groups;
|
|
|
|
// Overload CUDA atomic for other 64bit unsigned/signed integer type
|
|
__forceinline__ __device__ long atomicAdd(long* address, long val) {
|
|
return (long)atomicAdd((unsigned long long*)address, (unsigned long long)val);
|
|
}
|
|
|
|
__forceinline__ __device__ long long atomicAdd(long long* address, long long val) {
|
|
return (long long)atomicAdd((unsigned long long*)address, (unsigned long long)val);
|
|
}
|
|
|
|
__forceinline__ __device__ unsigned long atomicAdd(unsigned long* address, unsigned long val) {
|
|
return (unsigned long)atomicAdd((unsigned long long*)address, (unsigned long long)val);
|
|
}
|
|
|
|
namespace gpu_cache {
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
template <int warp_size>
|
|
__forceinline__ __device__ void warp_tile_copy(const size_t lane_idx,
|
|
const size_t emb_vec_size_in_float, float* d_dst,
|
|
const float* d_src) {
|
|
#pragma unroll
|
|
for (size_t i = lane_idx; i < emb_vec_size_in_float; i += warp_size) {
|
|
d_dst[i] = d_src[i];
|
|
}
|
|
}
|
|
#else
|
|
template <int warp_size>
|
|
__forceinline__ __device__ void warp_tile_copy(const size_t lane_idx,
|
|
const size_t emb_vec_size_in_float,
|
|
volatile float* d_dst, volatile float* d_src) {
|
|
|
|
#pragma unroll
|
|
for (size_t i = lane_idx; i < emb_vec_size_in_float; i += warp_size) {
|
|
d_dst[i] = d_src[i];
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
// Will be called by multiple thread_block_tile((sub-)warp) on the same mutex
|
|
// Expect only one thread_block_tile return to execute critical section at any time
|
|
template <typename mutex, int warp_size>
|
|
__forceinline__ __device__ void warp_lock_mutex(const cg::thread_block_tile<warp_size>& warp_tile,
|
|
mutex& set_mutex) {
|
|
// The first thread of this (sub-)warp to acquire the lock
|
|
if (warp_tile.thread_rank() == 0) {
|
|
set_mutex.acquire();
|
|
}
|
|
warp_tile.sync(); // Synchronize the threads in the (sub-)warp. Execution barrier + memory fence
|
|
}
|
|
|
|
// The (sub-)warp holding the mutex will unlock the mutex after finishing the critical section on a
|
|
// set Expect any following (sub-)warp that acquire the mutex can see its modification done in the
|
|
// critical section
|
|
template <typename mutex, int warp_size>
|
|
__forceinline__ __device__ void warp_unlock_mutex(const cg::thread_block_tile<warp_size>& warp_tile,
|
|
mutex& set_mutex) {
|
|
warp_tile.sync(); // Synchronize the threads in the (sub-)warp. Execution barrier + memory fence
|
|
// The first thread of this (sub-)warp to release the lock
|
|
if (warp_tile.thread_rank() == 0) {
|
|
set_mutex.release();
|
|
}
|
|
}
|
|
#else
|
|
// Will be called by multiple thread_block_tile((sub-)warp) on the same mutex
|
|
// Expect only one thread_block_tile return to execute critical section at any time
|
|
template <int warp_size>
|
|
__forceinline__ __device__ void warp_lock_mutex(const cg::thread_block_tile<warp_size>& warp_tile,
|
|
volatile int& set_mutex) {
|
|
// The first thread of this (sub-)warp to acquire the lock
|
|
if (warp_tile.thread_rank() == 0) {
|
|
while (0 == atomicCAS((int*)&set_mutex, 1, 0))
|
|
;
|
|
}
|
|
__threadfence();
|
|
warp_tile.sync(); // Synchronize the threads in the (sub-)warp. Execution barrier + memory fence
|
|
}
|
|
|
|
// The (sub-)warp holding the mutex will unlock the mutex after finishing the critical section on a
|
|
// set Expect any following (sub-)warp that acquire the mutex can see its modification done in the
|
|
// critical section
|
|
template <int warp_size>
|
|
__forceinline__ __device__ void warp_unlock_mutex(const cg::thread_block_tile<warp_size>& warp_tile,
|
|
volatile int& set_mutex) {
|
|
__threadfence();
|
|
warp_tile.sync(); // Synchronize the threads in the (sub-)warp. Execution barrier + memory fence
|
|
// The first thread of this (sub-)warp to release the lock
|
|
if (warp_tile.thread_rank() == 0) {
|
|
atomicExch((int*)&set_mutex, 1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// The (sub-)warp doing all reduction to find the slot with min slot_counter
|
|
// The slot with min slot_counter is the LR slot.
|
|
template <typename ref_counter_type, int warp_size>
|
|
__forceinline__ __device__ void warp_min_reduction(
|
|
const cg::thread_block_tile<warp_size>& warp_tile, ref_counter_type& min_slot_counter_val,
|
|
size_t& slab_distance, size_t& slot_distance) {
|
|
const size_t lane_idx = warp_tile.thread_rank();
|
|
slot_distance = lane_idx;
|
|
|
|
for (size_t i = (warp_tile.size() >> 1); i > 0; i = i >> 1) {
|
|
ref_counter_type input_slot_counter_val = warp_tile.shfl_xor(min_slot_counter_val, (int)i);
|
|
size_t input_slab_distance = warp_tile.shfl_xor(slab_distance, (int)i);
|
|
size_t input_slot_distance = warp_tile.shfl_xor(slot_distance, (int)i);
|
|
|
|
if (input_slot_counter_val == min_slot_counter_val) {
|
|
if (input_slab_distance == slab_distance) {
|
|
if (input_slot_distance < slot_distance) {
|
|
slot_distance = input_slot_distance;
|
|
}
|
|
} else if (input_slab_distance < slab_distance) {
|
|
slab_distance = input_slab_distance;
|
|
slot_distance = input_slot_distance;
|
|
}
|
|
} else if (input_slot_counter_val < min_slot_counter_val) {
|
|
min_slot_counter_val = input_slot_counter_val;
|
|
slab_distance = input_slab_distance;
|
|
slot_distance = input_slot_distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
// Kernel to initialize the GPU cache
|
|
// Init every entry of the cache with <unused_key, value> pair
|
|
template <typename slabset, typename ref_counter_type, typename atomic_ref_counter_type,
|
|
typename key_type, typename mutex>
|
|
__global__ void init_cache(slabset* keys, ref_counter_type* slot_counter,
|
|
atomic_ref_counter_type* global_counter, const size_t num_slot,
|
|
const key_type empty_key, mutex* set_mutex,
|
|
const size_t capacity_in_set) {
|
|
const size_t idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
if (idx < num_slot) {
|
|
// Set the key of this slot to unused key
|
|
// Flatten the cache
|
|
key_type* key_slot = (key_type*)keys;
|
|
key_slot[idx] = empty_key;
|
|
|
|
// Clear the counter for this slot
|
|
slot_counter[idx] = 0;
|
|
}
|
|
// First CUDA thread clear the global counter
|
|
if (idx == 0) {
|
|
new (global_counter) atomic_ref_counter_type(0);
|
|
}
|
|
|
|
// First capacity_in_set CUDA thread initialize mutex
|
|
if (idx < capacity_in_set) {
|
|
new (set_mutex + idx) mutex(1);
|
|
}
|
|
}
|
|
|
|
template <typename atomic_ref_counter_type, typename mutex>
|
|
__global__ void destruct_kernel(atomic_ref_counter_type* global_counter, mutex* set_mutex,
|
|
const size_t capacity_in_set) {
|
|
const size_t idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
// First CUDA thread destruct the global_counter
|
|
if (idx == 0) {
|
|
global_counter->~atomic_ref_counter_type();
|
|
}
|
|
// First capacity_in_set CUDA thread destruct the set mutex
|
|
if (idx < capacity_in_set) {
|
|
(set_mutex + idx)->~mutex();
|
|
}
|
|
}
|
|
#else
|
|
// Kernel to initialize the GPU cache
|
|
// Init every entry of the cache with <unused_key, value> pair
|
|
template <typename slabset, typename ref_counter_type, typename key_type>
|
|
__global__ void init_cache(slabset* keys, ref_counter_type* slot_counter,
|
|
ref_counter_type* global_counter, const size_t num_slot,
|
|
const key_type empty_key, int* set_mutex, const size_t capacity_in_set) {
|
|
const size_t idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
if (idx < num_slot) {
|
|
// Set the key of this slot to unused key
|
|
// Flatten the cache
|
|
key_type* key_slot = (key_type*)keys;
|
|
key_slot[idx] = empty_key;
|
|
|
|
// Clear the counter for this slot
|
|
slot_counter[idx] = 0;
|
|
}
|
|
// First CUDA thread clear the global counter
|
|
if (idx == 0) {
|
|
global_counter[idx] = 0;
|
|
}
|
|
|
|
// First capacity_in_set CUDA thread initialize mutex
|
|
if (idx < capacity_in_set) {
|
|
set_mutex[idx] = 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Kernel to update global counter
|
|
// Resolve distance overflow issue as well
|
|
#ifdef LIBCUDACXX_VERSION
|
|
template <typename atomic_ref_counter_type>
|
|
__global__ void update_kernel_overflow_ignore(atomic_ref_counter_type* global_counter,
|
|
size_t* d_missing_len) {
|
|
// Update global counter
|
|
global_counter->fetch_add(1, cuda::std::memory_order_relaxed);
|
|
*d_missing_len = 0;
|
|
}
|
|
#else
|
|
template <typename ref_counter_type>
|
|
__global__ void update_kernel_overflow_ignore(ref_counter_type* global_counter,
|
|
size_t* d_missing_len) {
|
|
// Update global counter
|
|
atomicAdd(global_counter, 1);
|
|
*d_missing_len = 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
// Kernel to read from cache
|
|
// Also update locality information for touched slot
|
|
template <typename key_type, typename ref_counter_type, typename atomic_ref_counter_type,
|
|
typename slabset, typename set_hasher, typename slab_hasher, typename mutex,
|
|
key_type empty_key, int set_associativity, int warp_size>
|
|
__global__ void get_kernel(const key_type* d_keys, const size_t len, float* d_values,
|
|
const size_t embedding_vec_size, uint64_t* d_missing_index,
|
|
key_type* d_missing_keys, size_t* d_missing_len,
|
|
const atomic_ref_counter_type* global_counter,
|
|
ref_counter_type* slot_counter, const size_t capacity_in_set,
|
|
const slabset* keys, const float* vals, mutex* set_mutex,
|
|
const size_t task_per_warp_tile) {
|
|
// Lane(thread) ID within a warp_tile
|
|
cg::thread_block_tile<warp_size> warp_tile =
|
|
cg::tiled_partition<warp_size>(cg::this_thread_block());
|
|
const size_t lane_idx = warp_tile.thread_rank();
|
|
// Warp tile global ID
|
|
const size_t warp_tile_global_idx =
|
|
(blockIdx.x * (blockDim.x / warp_size)) + warp_tile.meta_group_rank();
|
|
// The index of key for this thread
|
|
const size_t key_idx = (warp_tile_global_idx * task_per_warp_tile) + lane_idx;
|
|
// The assigned key for this lane(thread)
|
|
key_type key;
|
|
// The dst slabset and the dst slab inside this set
|
|
size_t src_set;
|
|
size_t src_slab;
|
|
// The variable that contains the missing key
|
|
key_type missing_key;
|
|
// The variable that contains the index for the missing key
|
|
uint64_t missing_index;
|
|
// The counter for counting the missing key in this warp
|
|
uint8_t warp_missing_counter = 0;
|
|
// Active flag: whether current lane(thread) has unfinished task
|
|
bool active = false;
|
|
if (lane_idx < task_per_warp_tile) {
|
|
if (key_idx < len) {
|
|
active = true;
|
|
key = d_keys[key_idx];
|
|
src_set = set_hasher::hash(key) % capacity_in_set;
|
|
src_slab = slab_hasher::hash(key) % set_associativity;
|
|
}
|
|
}
|
|
|
|
// Lane participate in warp_tile ballot to produce warp-level work queue
|
|
unsigned active_mask = warp_tile.ballot(active);
|
|
|
|
// The warp-level outer loop: finish all the tasks within the work queue
|
|
while (active_mask != 0) {
|
|
// Next task in the work quere, start from lower index lane(thread)
|
|
int next_lane = __ffs(active_mask) - 1;
|
|
// Broadcast the task and the global index to all lane in the warp_tile
|
|
key_type next_key = warp_tile.shfl(key, next_lane);
|
|
size_t next_idx = warp_tile.shfl(key_idx, next_lane);
|
|
size_t next_set = warp_tile.shfl(src_set, next_lane);
|
|
size_t next_slab = warp_tile.shfl(src_slab, next_lane);
|
|
|
|
// Counter to record how many slab have been searched
|
|
size_t counter = 0;
|
|
|
|
// Working queue before task started
|
|
const unsigned old_active_mask = active_mask;
|
|
|
|
// Lock the slabset before operating the slabset
|
|
warp_lock_mutex<mutex, warp_size>(warp_tile, set_mutex[next_set]);
|
|
|
|
// The warp-level inner loop: finish a single task in the work queue
|
|
while (active_mask == old_active_mask) {
|
|
// When all the slabs inside a slabset have been searched, mark missing task, task is
|
|
// completed
|
|
if (counter >= set_associativity) {
|
|
if (lane_idx == warp_missing_counter) {
|
|
missing_key = next_key;
|
|
missing_index = next_idx;
|
|
}
|
|
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
warp_missing_counter++;
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// The warp_tile read out the slab
|
|
key_type read_key = keys[next_set].set_[next_slab].slab_[lane_idx];
|
|
|
|
// Compare the slab data with the target key
|
|
int found_lane = __ffs(warp_tile.ballot(read_key == next_key)) - 1;
|
|
|
|
// If found, mark hit task, copy the founded data, the task is completed
|
|
if (found_lane >= 0) {
|
|
size_t found_offset = (next_set * set_associativity + next_slab) * warp_size + found_lane;
|
|
if (lane_idx == (size_t)next_lane) {
|
|
slot_counter[found_offset] = global_counter->load(cuda::std::memory_order_relaxed);
|
|
active = false;
|
|
}
|
|
|
|
warp_tile_copy<warp_size>(lane_idx, embedding_vec_size,
|
|
d_values + next_idx * embedding_vec_size,
|
|
vals + found_offset * embedding_vec_size);
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// Compare the slab data with empty key, if found empty key, mark missing task, task is
|
|
// completed
|
|
if (warp_tile.ballot(read_key == empty_key) != 0) {
|
|
if (lane_idx == warp_missing_counter) {
|
|
missing_key = next_key;
|
|
missing_index = next_idx;
|
|
}
|
|
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
warp_missing_counter++;
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// Not found in this slab, the task is not completed, goto searching next slab
|
|
counter++;
|
|
next_slab = (next_slab + 1) % set_associativity;
|
|
}
|
|
|
|
// Unlock the slabset after operating the slabset
|
|
warp_unlock_mutex<mutex, warp_size>(warp_tile, set_mutex[next_set]);
|
|
}
|
|
|
|
// After warp_tile complete the working queue, save the result for output
|
|
// First thread of the warp_tile accumulate the missing length to global variable
|
|
size_t warp_position;
|
|
if (lane_idx == 0) {
|
|
warp_position = atomicAdd(d_missing_len, (size_t)warp_missing_counter);
|
|
}
|
|
warp_position = warp_tile.shfl(warp_position, 0);
|
|
|
|
if (lane_idx < warp_missing_counter) {
|
|
d_missing_keys[warp_position + lane_idx] = missing_key;
|
|
d_missing_index[warp_position + lane_idx] = missing_index;
|
|
}
|
|
}
|
|
#else
|
|
// Kernel to read from cache
|
|
// Also update locality information for touched slot
|
|
template <typename key_type, typename ref_counter_type, typename slabset, typename set_hasher,
|
|
typename slab_hasher, key_type empty_key, int set_associativity, int warp_size>
|
|
__global__ void get_kernel(const key_type* d_keys, const size_t len, float* d_values,
|
|
const size_t embedding_vec_size, uint64_t* d_missing_index,
|
|
key_type* d_missing_keys, size_t* d_missing_len,
|
|
ref_counter_type* global_counter,
|
|
volatile ref_counter_type* slot_counter, const size_t capacity_in_set,
|
|
volatile slabset* keys, volatile float* vals, volatile int* set_mutex,
|
|
const size_t task_per_warp_tile) {
|
|
// Lane(thread) ID within a warp_tile
|
|
cg::thread_block_tile<warp_size> warp_tile =
|
|
cg::tiled_partition<warp_size>(cg::this_thread_block());
|
|
const size_t lane_idx = warp_tile.thread_rank();
|
|
// Warp tile global ID
|
|
const size_t warp_tile_global_idx =
|
|
(blockIdx.x * (blockDim.x / warp_size)) + warp_tile.meta_group_rank();
|
|
// The index of key for this thread
|
|
const size_t key_idx = (warp_tile_global_idx * task_per_warp_tile) + lane_idx;
|
|
// The assigned key for this lane(thread)
|
|
key_type key;
|
|
// The dst slabset and the dst slab inside this set
|
|
size_t src_set;
|
|
size_t src_slab;
|
|
// The variable that contains the missing key
|
|
key_type missing_key;
|
|
// The variable that contains the index for the missing key
|
|
uint64_t missing_index;
|
|
// The counter for counting the missing key in this warp
|
|
uint8_t warp_missing_counter = 0;
|
|
// Active flag: whether current lane(thread) has unfinished task
|
|
bool active = false;
|
|
if (lane_idx < task_per_warp_tile) {
|
|
if (key_idx < len) {
|
|
active = true;
|
|
key = d_keys[key_idx];
|
|
src_set = set_hasher::hash(key) % capacity_in_set;
|
|
src_slab = slab_hasher::hash(key) % set_associativity;
|
|
}
|
|
}
|
|
|
|
// Lane participate in warp_tile ballot to produce warp-level work queue
|
|
unsigned active_mask = warp_tile.ballot(active);
|
|
|
|
// The warp-level outer loop: finish all the tasks within the work queue
|
|
while (active_mask != 0) {
|
|
// Next task in the work quere, start from lower index lane(thread)
|
|
int next_lane = __ffs(active_mask) - 1;
|
|
// Broadcast the task and the global index to all lane in the warp_tile
|
|
key_type next_key = warp_tile.shfl(key, next_lane);
|
|
size_t next_idx = warp_tile.shfl(key_idx, next_lane);
|
|
size_t next_set = warp_tile.shfl(src_set, next_lane);
|
|
size_t next_slab = warp_tile.shfl(src_slab, next_lane);
|
|
|
|
// Counter to record how many slab have been searched
|
|
size_t counter = 0;
|
|
|
|
// Working queue before task started
|
|
const unsigned old_active_mask = active_mask;
|
|
|
|
// Lock the slabset before operating the slabset
|
|
warp_lock_mutex<warp_size>(warp_tile, set_mutex[next_set]);
|
|
|
|
// The warp-level inner loop: finish a single task in the work queue
|
|
while (active_mask == old_active_mask) {
|
|
// When all the slabs inside a slabset have been searched, mark missing task, task is
|
|
// completed
|
|
if (counter >= set_associativity) {
|
|
if (lane_idx == warp_missing_counter) {
|
|
missing_key = next_key;
|
|
missing_index = next_idx;
|
|
}
|
|
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
warp_missing_counter++;
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// The warp_tile read out the slab
|
|
key_type read_key = ((volatile key_type*)(keys[next_set].set_[next_slab].slab_))[lane_idx];
|
|
|
|
// Compare the slab data with the target key
|
|
int found_lane = __ffs(warp_tile.ballot(read_key == next_key)) - 1;
|
|
|
|
// If found, mark hit task, copy the founded data, the task is completed
|
|
if (found_lane >= 0) {
|
|
size_t found_offset = (next_set * set_associativity + next_slab) * warp_size + found_lane;
|
|
if (lane_idx == (size_t)next_lane) {
|
|
slot_counter[found_offset] = atomicAdd(global_counter, 0);
|
|
active = false;
|
|
}
|
|
|
|
warp_tile_copy<warp_size>(lane_idx, embedding_vec_size,
|
|
(volatile float*)(d_values + next_idx * embedding_vec_size),
|
|
(volatile float*)(vals + found_offset * embedding_vec_size));
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// Compare the slab data with empty key, if found empty key, mark missing task, task is
|
|
// completed
|
|
if (warp_tile.ballot(read_key == empty_key) != 0) {
|
|
if (lane_idx == warp_missing_counter) {
|
|
missing_key = next_key;
|
|
missing_index = next_idx;
|
|
}
|
|
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
warp_missing_counter++;
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// Not found in this slab, the task is not completed, goto searching next slab
|
|
counter++;
|
|
next_slab = (next_slab + 1) % set_associativity;
|
|
}
|
|
|
|
// Unlock the slabset after operating the slabset
|
|
warp_unlock_mutex<warp_size>(warp_tile, set_mutex[next_set]);
|
|
}
|
|
|
|
// After warp_tile complete the working queue, save the result for output
|
|
// First thread of the warp_tile accumulate the missing length to global variable
|
|
size_t warp_position;
|
|
if (lane_idx == 0) {
|
|
warp_position = atomicAdd(d_missing_len, (size_t)warp_missing_counter);
|
|
}
|
|
warp_position = warp_tile.shfl(warp_position, 0);
|
|
|
|
if (lane_idx < warp_missing_counter) {
|
|
d_missing_keys[warp_position + lane_idx] = missing_key;
|
|
d_missing_index[warp_position + lane_idx] = missing_index;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
// Kernel to insert or replace the <k,v> pairs into the cache
|
|
template <typename key_type, typename slabset, typename ref_counter_type, typename mutex,
|
|
typename atomic_ref_counter_type, typename set_hasher, typename slab_hasher,
|
|
key_type empty_key, int set_associativity, int warp_size,
|
|
ref_counter_type max_ref_counter_type = std::numeric_limits<ref_counter_type>::max(),
|
|
size_t max_slab_distance = std::numeric_limits<size_t>::max()>
|
|
__global__ void insert_replace_kernel(const key_type* d_keys, const float* d_values,
|
|
const size_t embedding_vec_size, const size_t len,
|
|
slabset* keys, float* vals, ref_counter_type* slot_counter,
|
|
mutex* set_mutex,
|
|
const atomic_ref_counter_type* global_counter,
|
|
const size_t capacity_in_set,
|
|
const size_t task_per_warp_tile) {
|
|
// Lane(thread) ID within a warp_tile
|
|
cg::thread_block_tile<warp_size> warp_tile =
|
|
cg::tiled_partition<warp_size>(cg::this_thread_block());
|
|
const size_t lane_idx = warp_tile.thread_rank();
|
|
// Warp tile global ID
|
|
const size_t warp_tile_global_idx =
|
|
(blockIdx.x * (blockDim.x / warp_size)) + warp_tile.meta_group_rank();
|
|
// The index of key for this thread
|
|
const size_t key_idx = (warp_tile_global_idx * task_per_warp_tile) + lane_idx;
|
|
// The assigned key for this lane(thread)
|
|
key_type key;
|
|
// The dst slabset and the dst slab inside this set
|
|
size_t src_set;
|
|
size_t src_slab;
|
|
// Active flag: whether current lane(thread) has unfinished task
|
|
bool active = false;
|
|
if (lane_idx < task_per_warp_tile) {
|
|
if (key_idx < len) {
|
|
active = true;
|
|
key = d_keys[key_idx];
|
|
src_set = set_hasher::hash(key) % capacity_in_set;
|
|
src_slab = slab_hasher::hash(key) % set_associativity;
|
|
}
|
|
}
|
|
|
|
// Lane participate in warp_tile ballot to produce warp-level work queue
|
|
unsigned active_mask = warp_tile.ballot(active);
|
|
|
|
// The warp-level outer loop: finish all the tasks within the work queue
|
|
while (active_mask != 0) {
|
|
// Next task in the work quere, start from lower index lane(thread)
|
|
int next_lane = __ffs(active_mask) - 1;
|
|
// Broadcast the task, the global index and the src slabset and slab to all lane in a warp_tile
|
|
key_type next_key = warp_tile.shfl(key, next_lane);
|
|
size_t next_idx = warp_tile.shfl(key_idx, next_lane);
|
|
size_t next_set = warp_tile.shfl(src_set, next_lane);
|
|
size_t next_slab = warp_tile.shfl(src_slab, next_lane);
|
|
size_t first_slab = next_slab;
|
|
|
|
// Counter to record how many slab have been searched
|
|
size_t counter = 0;
|
|
|
|
// Variable to keep the min slot counter during the probing
|
|
ref_counter_type min_slot_counter_val = max_ref_counter_type;
|
|
// Variable to keep the slab distance for slot with min counter
|
|
size_t slab_distance = max_slab_distance;
|
|
// Variable to keep the slot distance for slot with min counter within the slab
|
|
size_t slot_distance;
|
|
// Working queue before task started
|
|
const unsigned old_active_mask = active_mask;
|
|
|
|
// Lock the slabset before operating the slabset
|
|
warp_lock_mutex<mutex, warp_size>(warp_tile, set_mutex[next_set]);
|
|
|
|
// The warp-level inner loop: finish a single task in the work queue
|
|
while (active_mask == old_active_mask) {
|
|
// When all the slabs inside a slabset have been searched
|
|
// and no empty slots or target slots are found. Replace with LRU
|
|
if (counter >= set_associativity) {
|
|
// (sub)Warp all-reduction, the reduction result store in all threads
|
|
warp_min_reduction<ref_counter_type, warp_size>(warp_tile, min_slot_counter_val,
|
|
slab_distance, slot_distance);
|
|
|
|
// Calculate the position of LR slot
|
|
size_t target_slab = (first_slab + slab_distance) % set_associativity;
|
|
size_t slot_index =
|
|
(next_set * set_associativity + target_slab) * warp_size + slot_distance;
|
|
|
|
// Replace the LR slot
|
|
if (lane_idx == (size_t)next_lane) {
|
|
keys[next_set].set_[target_slab].slab_[slot_distance] = key;
|
|
slot_counter[slot_index] = global_counter->load(cuda::std::memory_order_relaxed);
|
|
}
|
|
|
|
warp_tile_copy<warp_size>(lane_idx, embedding_vec_size,
|
|
vals + slot_index * embedding_vec_size,
|
|
d_values + next_idx * embedding_vec_size);
|
|
|
|
// Replace complete, mark this task completed
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// The warp_tile read out the slab
|
|
key_type read_key = keys[next_set].set_[next_slab].slab_[lane_idx];
|
|
|
|
// Compare the slab data with the target key
|
|
int found_lane = __ffs(warp_tile.ballot(read_key == next_key)) - 1;
|
|
|
|
// If found target key, the insertion/replace is no longer needed.
|
|
// Refresh the slot, the task is completed
|
|
if (found_lane >= 0) {
|
|
size_t found_offset = (next_set * set_associativity + next_slab) * warp_size + found_lane;
|
|
if (lane_idx == (size_t)next_lane) {
|
|
slot_counter[found_offset] = global_counter->load(cuda::std::memory_order_relaxed);
|
|
active = false;
|
|
}
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// Compare the slab data with empty key.
|
|
// If found empty key, do insertion,the task is complete
|
|
found_lane = __ffs(warp_tile.ballot(read_key == empty_key)) - 1;
|
|
if (found_lane >= 0) {
|
|
size_t found_offset = (next_set * set_associativity + next_slab) * warp_size + found_lane;
|
|
|
|
if (lane_idx == (size_t)next_lane) {
|
|
keys[next_set].set_[next_slab].slab_[found_lane] = key;
|
|
slot_counter[found_offset] = global_counter->load(cuda::std::memory_order_relaxed);
|
|
}
|
|
|
|
warp_tile_copy<warp_size>(lane_idx, embedding_vec_size,
|
|
vals + found_offset * embedding_vec_size,
|
|
d_values + next_idx * embedding_vec_size);
|
|
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// If no target or unused slot found in this slab,
|
|
// Refresh LR info, continue probing
|
|
ref_counter_type read_slot_counter =
|
|
slot_counter[(next_set * set_associativity + next_slab) * warp_size + lane_idx];
|
|
if (read_slot_counter < min_slot_counter_val) {
|
|
min_slot_counter_val = read_slot_counter;
|
|
slab_distance = counter;
|
|
}
|
|
|
|
counter++;
|
|
next_slab = (next_slab + 1) % set_associativity;
|
|
}
|
|
|
|
// Unlock the slabset after operating the slabset
|
|
warp_unlock_mutex<mutex, warp_size>(warp_tile, set_mutex[next_set]);
|
|
}
|
|
}
|
|
#else
|
|
// Kernel to insert or replace the <k,v> pairs into the cache
|
|
template <typename key_type, typename slabset, typename ref_counter_type, typename set_hasher,
|
|
typename slab_hasher, key_type empty_key, int set_associativity, int warp_size,
|
|
ref_counter_type max_ref_counter_type = std::numeric_limits<ref_counter_type>::max(),
|
|
size_t max_slab_distance = std::numeric_limits<size_t>::max()>
|
|
__global__ void insert_replace_kernel(const key_type* d_keys, const float* d_values,
|
|
const size_t embedding_vec_size, const size_t len,
|
|
volatile slabset* keys, volatile float* vals,
|
|
volatile ref_counter_type* slot_counter,
|
|
volatile int* set_mutex, ref_counter_type* global_counter,
|
|
const size_t capacity_in_set,
|
|
const size_t task_per_warp_tile) {
|
|
// Lane(thread) ID within a warp_tile
|
|
cg::thread_block_tile<warp_size> warp_tile =
|
|
cg::tiled_partition<warp_size>(cg::this_thread_block());
|
|
const size_t lane_idx = warp_tile.thread_rank();
|
|
// Warp tile global ID
|
|
const size_t warp_tile_global_idx =
|
|
(blockIdx.x * (blockDim.x / warp_size)) + warp_tile.meta_group_rank();
|
|
// The index of key for this thread
|
|
const size_t key_idx = (warp_tile_global_idx * task_per_warp_tile) + lane_idx;
|
|
// The assigned key for this lane(thread)
|
|
key_type key;
|
|
// The dst slabset and the dst slab inside this set
|
|
size_t src_set;
|
|
size_t src_slab;
|
|
// Active flag: whether current lane(thread) has unfinished task
|
|
bool active = false;
|
|
if (lane_idx < task_per_warp_tile) {
|
|
if (key_idx < len) {
|
|
active = true;
|
|
key = d_keys[key_idx];
|
|
src_set = set_hasher::hash(key) % capacity_in_set;
|
|
src_slab = slab_hasher::hash(key) % set_associativity;
|
|
}
|
|
}
|
|
|
|
// Lane participate in warp_tile ballot to produce warp-level work queue
|
|
unsigned active_mask = warp_tile.ballot(active);
|
|
|
|
// The warp-level outer loop: finish all the tasks within the work queue
|
|
while (active_mask != 0) {
|
|
// Next task in the work quere, start from lower index lane(thread)
|
|
int next_lane = __ffs(active_mask) - 1;
|
|
// Broadcast the task, the global index and the src slabset and slab to all lane in a warp_tile
|
|
key_type next_key = warp_tile.shfl(key, next_lane);
|
|
size_t next_idx = warp_tile.shfl(key_idx, next_lane);
|
|
size_t next_set = warp_tile.shfl(src_set, next_lane);
|
|
size_t next_slab = warp_tile.shfl(src_slab, next_lane);
|
|
size_t first_slab = next_slab;
|
|
|
|
// Counter to record how many slab have been searched
|
|
size_t counter = 0;
|
|
|
|
// Variable to keep the min slot counter during the probing
|
|
ref_counter_type min_slot_counter_val = max_ref_counter_type;
|
|
// Variable to keep the slab distance for slot with min counter
|
|
size_t slab_distance = max_slab_distance;
|
|
// Variable to keep the slot distance for slot with min counter within the slab
|
|
size_t slot_distance;
|
|
// Working queue before task started
|
|
const unsigned old_active_mask = active_mask;
|
|
|
|
// Lock the slabset before operating the slabset
|
|
warp_lock_mutex<warp_size>(warp_tile, set_mutex[next_set]);
|
|
|
|
// The warp-level inner loop: finish a single task in the work queue
|
|
while (active_mask == old_active_mask) {
|
|
// When all the slabs inside a slabset have been searched
|
|
// and no empty slots or target slots are found. Replace with LRU
|
|
if (counter >= set_associativity) {
|
|
// (sub)Warp all-reduction, the reduction result store in all threads
|
|
warp_min_reduction<ref_counter_type, warp_size>(warp_tile, min_slot_counter_val,
|
|
slab_distance, slot_distance);
|
|
|
|
// Calculate the position of LR slot
|
|
size_t target_slab = (first_slab + slab_distance) % set_associativity;
|
|
size_t slot_index =
|
|
(next_set * set_associativity + target_slab) * warp_size + slot_distance;
|
|
|
|
// Replace the LR slot
|
|
if (lane_idx == (size_t)next_lane) {
|
|
((volatile key_type*)(keys[next_set].set_[target_slab].slab_))[slot_distance] = key;
|
|
slot_counter[slot_index] = atomicAdd(global_counter, 0);
|
|
}
|
|
|
|
warp_tile_copy<warp_size>(lane_idx, embedding_vec_size,
|
|
(volatile float*)(vals + slot_index * embedding_vec_size),
|
|
(volatile float*)(d_values + next_idx * embedding_vec_size));
|
|
|
|
// Replace complete, mark this task completed
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// The warp_tile read out the slab
|
|
key_type read_key = ((volatile key_type*)(keys[next_set].set_[next_slab].slab_))[lane_idx];
|
|
|
|
// Compare the slab data with the target key
|
|
int found_lane = __ffs(warp_tile.ballot(read_key == next_key)) - 1;
|
|
|
|
// If found target key, the insertion/replace is no longer needed.
|
|
// Refresh the slot, the task is completed
|
|
if (found_lane >= 0) {
|
|
size_t found_offset = (next_set * set_associativity + next_slab) * warp_size + found_lane;
|
|
if (lane_idx == (size_t)next_lane) {
|
|
slot_counter[found_offset] = atomicAdd(global_counter, 0);
|
|
active = false;
|
|
}
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// Compare the slab data with empty key.
|
|
// If found empty key, do insertion,the task is complete
|
|
found_lane = __ffs(warp_tile.ballot(read_key == empty_key)) - 1;
|
|
if (found_lane >= 0) {
|
|
size_t found_offset = (next_set * set_associativity + next_slab) * warp_size + found_lane;
|
|
|
|
if (lane_idx == (size_t)next_lane) {
|
|
((volatile key_type*)(keys[next_set].set_[next_slab].slab_))[found_lane] = key;
|
|
slot_counter[found_offset] = atomicAdd(global_counter, 0);
|
|
}
|
|
|
|
warp_tile_copy<warp_size>(lane_idx, embedding_vec_size,
|
|
(volatile float*)(vals + found_offset * embedding_vec_size),
|
|
(volatile float*)(d_values + next_idx * embedding_vec_size));
|
|
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// If no target or unused slot found in this slab,
|
|
// Refresh LR info, continue probing
|
|
ref_counter_type read_slot_counter =
|
|
slot_counter[(next_set * set_associativity + next_slab) * warp_size + lane_idx];
|
|
if (read_slot_counter < min_slot_counter_val) {
|
|
min_slot_counter_val = read_slot_counter;
|
|
slab_distance = counter;
|
|
}
|
|
|
|
counter++;
|
|
next_slab = (next_slab + 1) % set_associativity;
|
|
}
|
|
|
|
// Unlock the slabset after operating the slabset
|
|
warp_unlock_mutex<warp_size>(warp_tile, set_mutex[next_set]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
// Kernel to update the existing keys in the cache
|
|
// Will not change the locality information
|
|
template <typename key_type, typename slabset, typename set_hasher, typename slab_hasher,
|
|
typename mutex, key_type empty_key, int set_associativity, int warp_size>
|
|
__global__ void update_kernel(const key_type* d_keys, const size_t len, const float* d_values,
|
|
const size_t embedding_vec_size, const size_t capacity_in_set,
|
|
const slabset* keys, float* vals, mutex* set_mutex,
|
|
const size_t task_per_warp_tile) {
|
|
// Lane(thread) ID within a warp_tile
|
|
cg::thread_block_tile<warp_size> warp_tile =
|
|
cg::tiled_partition<warp_size>(cg::this_thread_block());
|
|
const size_t lane_idx = warp_tile.thread_rank();
|
|
// Warp tile global ID
|
|
const size_t warp_tile_global_idx =
|
|
(blockIdx.x * (blockDim.x / warp_size)) + warp_tile.meta_group_rank();
|
|
// The index of key for this thread
|
|
const size_t key_idx = (warp_tile_global_idx * task_per_warp_tile) + lane_idx;
|
|
// The assigned key for this lane(thread)
|
|
key_type key;
|
|
// The dst slabset and the dst slab inside this set
|
|
size_t src_set;
|
|
size_t src_slab;
|
|
// Active flag: whether current lane(thread) has unfinished task
|
|
bool active = false;
|
|
if (lane_idx < task_per_warp_tile) {
|
|
if (key_idx < len) {
|
|
active = true;
|
|
key = d_keys[key_idx];
|
|
src_set = set_hasher::hash(key) % capacity_in_set;
|
|
src_slab = slab_hasher::hash(key) % set_associativity;
|
|
}
|
|
}
|
|
|
|
// Lane participate in warp_tile ballot to produce warp-level work queue
|
|
unsigned active_mask = warp_tile.ballot(active);
|
|
|
|
// The warp-level outer loop: finish all the tasks within the work queue
|
|
while (active_mask != 0) {
|
|
// Next task in the work quere, start from lower index lane(thread)
|
|
int next_lane = __ffs(active_mask) - 1;
|
|
// Broadcast the task and the global index to all lane in the warp_tile
|
|
key_type next_key = warp_tile.shfl(key, next_lane);
|
|
size_t next_idx = warp_tile.shfl(key_idx, next_lane);
|
|
size_t next_set = warp_tile.shfl(src_set, next_lane);
|
|
size_t next_slab = warp_tile.shfl(src_slab, next_lane);
|
|
|
|
// Counter to record how many slab have been searched
|
|
size_t counter = 0;
|
|
|
|
// Working queue before task started
|
|
const unsigned old_active_mask = active_mask;
|
|
|
|
// Lock the slabset before operating the slabset
|
|
warp_lock_mutex<mutex, warp_size>(warp_tile, set_mutex[next_set]);
|
|
|
|
// The warp-level inner loop: finish a single task in the work queue
|
|
while (active_mask == old_active_mask) {
|
|
// When all the slabs inside a slabset have been searched, mark missing task, do nothing, task
|
|
// complete
|
|
if (counter >= set_associativity) {
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// The warp_tile read out the slab
|
|
key_type read_key = keys[next_set].set_[next_slab].slab_[lane_idx];
|
|
|
|
// Compare the slab data with the target key
|
|
int found_lane = __ffs(warp_tile.ballot(read_key == next_key)) - 1;
|
|
|
|
// If found, mark hit task, update the value, the task is completed
|
|
if (found_lane >= 0) {
|
|
size_t found_offset = (next_set * set_associativity + next_slab) * warp_size + found_lane;
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
warp_tile_copy<warp_size>(lane_idx, embedding_vec_size,
|
|
vals + found_offset * embedding_vec_size,
|
|
d_values + next_idx * embedding_vec_size);
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// Compare the slab data with empty key, if found empty key, mark missing task, do nothing,
|
|
// task is completed
|
|
if (warp_tile.ballot(read_key == empty_key) != 0) {
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// Not found in this slab, the task is not completed, goto searching next slab
|
|
counter++;
|
|
next_slab = (next_slab + 1) % set_associativity;
|
|
}
|
|
|
|
// Unlock the slabset after operating the slabset
|
|
warp_unlock_mutex<mutex, warp_size>(warp_tile, set_mutex[next_set]);
|
|
}
|
|
}
|
|
#else
|
|
// Kernel to update the existing keys in the cache
|
|
// Will not change the locality information
|
|
template <typename key_type, typename slabset, typename set_hasher, typename slab_hasher,
|
|
key_type empty_key, int set_associativity, int warp_size>
|
|
__global__ void update_kernel(const key_type* d_keys, const size_t len, const float* d_values,
|
|
const size_t embedding_vec_size, const size_t capacity_in_set,
|
|
volatile slabset* keys, volatile float* vals, volatile int* set_mutex,
|
|
const size_t task_per_warp_tile) {
|
|
// Lane(thread) ID within a warp_tile
|
|
cg::thread_block_tile<warp_size> warp_tile =
|
|
cg::tiled_partition<warp_size>(cg::this_thread_block());
|
|
const size_t lane_idx = warp_tile.thread_rank();
|
|
// Warp tile global ID
|
|
const size_t warp_tile_global_idx =
|
|
(blockIdx.x * (blockDim.x / warp_size)) + warp_tile.meta_group_rank();
|
|
// The index of key for this thread
|
|
const size_t key_idx = (warp_tile_global_idx * task_per_warp_tile) + lane_idx;
|
|
// The assigned key for this lane(thread)
|
|
key_type key;
|
|
// The dst slabset and the dst slab inside this set
|
|
size_t src_set;
|
|
size_t src_slab;
|
|
// Active flag: whether current lane(thread) has unfinished task
|
|
bool active = false;
|
|
if (lane_idx < task_per_warp_tile) {
|
|
if (key_idx < len) {
|
|
active = true;
|
|
key = d_keys[key_idx];
|
|
src_set = set_hasher::hash(key) % capacity_in_set;
|
|
src_slab = slab_hasher::hash(key) % set_associativity;
|
|
}
|
|
}
|
|
|
|
// Lane participate in warp_tile ballot to produce warp-level work queue
|
|
unsigned active_mask = warp_tile.ballot(active);
|
|
|
|
// The warp-level outer loop: finish all the tasks within the work queue
|
|
while (active_mask != 0) {
|
|
// Next task in the work quere, start from lower index lane(thread)
|
|
int next_lane = __ffs(active_mask) - 1;
|
|
// Broadcast the task and the global index to all lane in the warp_tile
|
|
key_type next_key = warp_tile.shfl(key, next_lane);
|
|
size_t next_idx = warp_tile.shfl(key_idx, next_lane);
|
|
size_t next_set = warp_tile.shfl(src_set, next_lane);
|
|
size_t next_slab = warp_tile.shfl(src_slab, next_lane);
|
|
|
|
// Counter to record how many slab have been searched
|
|
size_t counter = 0;
|
|
|
|
// Working queue before task started
|
|
const unsigned old_active_mask = active_mask;
|
|
|
|
// Lock the slabset before operating the slabset
|
|
warp_lock_mutex<warp_size>(warp_tile, set_mutex[next_set]);
|
|
|
|
// The warp-level inner loop: finish a single task in the work queue
|
|
while (active_mask == old_active_mask) {
|
|
// When all the slabs inside a slabset have been searched, mark missing task, do nothing, task
|
|
// complete
|
|
if (counter >= set_associativity) {
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// The warp_tile read out the slab
|
|
key_type read_key = ((volatile key_type*)(keys[next_set].set_[next_slab].slab_))[lane_idx];
|
|
|
|
// Compare the slab data with the target key
|
|
int found_lane = __ffs(warp_tile.ballot(read_key == next_key)) - 1;
|
|
|
|
// If found, mark hit task, update the value, the task is completed
|
|
if (found_lane >= 0) {
|
|
size_t found_offset = (next_set * set_associativity + next_slab) * warp_size + found_lane;
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
warp_tile_copy<warp_size>(lane_idx, embedding_vec_size,
|
|
(volatile float*)(vals + found_offset * embedding_vec_size),
|
|
(volatile float*)(d_values + next_idx * embedding_vec_size));
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// Compare the slab data with empty key, if found empty key, mark missing task, do nothing,
|
|
// task is completed
|
|
if (warp_tile.ballot(read_key == empty_key) != 0) {
|
|
if (lane_idx == (size_t)next_lane) {
|
|
active = false;
|
|
}
|
|
|
|
active_mask = warp_tile.ballot(active);
|
|
break;
|
|
}
|
|
|
|
// Not found in this slab, the task is not completed, goto searching next slab
|
|
counter++;
|
|
next_slab = (next_slab + 1) % set_associativity;
|
|
}
|
|
|
|
// Unlock the slabset after operating the slabset
|
|
warp_unlock_mutex<warp_size>(warp_tile, set_mutex[next_set]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
template <typename key_type, typename slabset, typename mutex, key_type empty_key,
|
|
int set_associativity, int warp_size>
|
|
__global__ void dump_kernel(key_type* d_keys, size_t* d_dump_counter, const slabset* keys,
|
|
mutex* set_mutex, const size_t start_set_index,
|
|
const size_t end_set_index) {
|
|
// Block-level counter used by all warp tiles within a block
|
|
__shared__ uint32_t block_acc;
|
|
// Initialize block-level counter
|
|
if (threadIdx.x == 0) {
|
|
block_acc = 0;
|
|
}
|
|
__syncthreads();
|
|
// Lane(thread) ID within a warp tile
|
|
cg::thread_block_tile<warp_size> warp_tile =
|
|
cg::tiled_partition<warp_size>(cg::this_thread_block());
|
|
const size_t lane_idx = warp_tile.thread_rank();
|
|
// Warp tile target slabset id
|
|
const size_t set_idx =
|
|
((blockIdx.x * (blockDim.x / warp_size)) + warp_tile.meta_group_rank()) + start_set_index;
|
|
// Keys dump from cache
|
|
key_type read_key[set_associativity];
|
|
// Lane(thread) offset for storing each key
|
|
uint32_t thread_key_offset[set_associativity];
|
|
// Warp offset for storing each key
|
|
uint32_t warp_key_offset;
|
|
// Block offset for storing each key
|
|
__shared__ size_t block_key_offset;
|
|
|
|
// Warp tile dump target slabset
|
|
if (set_idx < end_set_index) {
|
|
// Lock the slabset before operating the slabset
|
|
warp_lock_mutex<mutex, warp_size>(warp_tile, set_mutex[set_idx]);
|
|
|
|
// The warp tile read out the slabset
|
|
for (unsigned slab_id = 0; slab_id < set_associativity; slab_id++) {
|
|
// The warp tile read out a slab
|
|
read_key[slab_id] = keys[set_idx].set_[slab_id].slab_[lane_idx];
|
|
}
|
|
|
|
// Finish dumping the slabset, unlock the slabset
|
|
warp_unlock_mutex<mutex, warp_size>(warp_tile, set_mutex[set_idx]);
|
|
|
|
// Each lane(thread) within the warp tile calculate the offset to store its keys
|
|
uint32_t warp_tile_total_keys = 0;
|
|
for (unsigned slab_id = 0; slab_id < set_associativity; slab_id++) {
|
|
unsigned valid_mask = warp_tile.ballot(read_key[slab_id] != empty_key);
|
|
thread_key_offset[slab_id] =
|
|
__popc(valid_mask & ((1U << lane_idx) - 1U)) + warp_tile_total_keys;
|
|
warp_tile_total_keys = warp_tile_total_keys + __popc(valid_mask);
|
|
}
|
|
|
|
// Each warp tile request a unique place from the block-level counter
|
|
if (lane_idx == 0) {
|
|
warp_key_offset = atomicAdd(&block_acc, warp_tile_total_keys);
|
|
}
|
|
warp_key_offset = warp_tile.shfl(warp_key_offset, 0);
|
|
}
|
|
|
|
// Each block request a unique place in global memory output buffer
|
|
__syncthreads();
|
|
if (threadIdx.x == 0) {
|
|
block_key_offset = atomicAdd(d_dump_counter, (size_t)block_acc);
|
|
}
|
|
__syncthreads();
|
|
|
|
// Warp tile store the (non-empty)keys back to output buffer
|
|
if (set_idx < end_set_index) {
|
|
for (unsigned slab_id = 0; slab_id < set_associativity; slab_id++) {
|
|
if (read_key[slab_id] != empty_key) {
|
|
d_keys[block_key_offset + warp_key_offset + thread_key_offset[slab_id]] = read_key[slab_id];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
template <typename key_type, typename slabset, key_type empty_key, int set_associativity,
|
|
int warp_size>
|
|
__global__ void dump_kernel(key_type* d_keys, size_t* d_dump_counter, volatile slabset* keys,
|
|
volatile int* set_mutex, const size_t start_set_index,
|
|
const size_t end_set_index) {
|
|
// Block-level counter used by all warp tiles within a block
|
|
__shared__ uint32_t block_acc;
|
|
// Initialize block-level counter
|
|
if (threadIdx.x == 0) {
|
|
block_acc = 0;
|
|
}
|
|
__syncthreads();
|
|
// Lane(thread) ID within a warp tile
|
|
cg::thread_block_tile<warp_size> warp_tile =
|
|
cg::tiled_partition<warp_size>(cg::this_thread_block());
|
|
const size_t lane_idx = warp_tile.thread_rank();
|
|
// Warp tile target slabset id
|
|
const size_t set_idx =
|
|
((blockIdx.x * (blockDim.x / warp_size)) + warp_tile.meta_group_rank()) + start_set_index;
|
|
// Keys dump from cache
|
|
key_type read_key[set_associativity];
|
|
// Lane(thread) offset for storing each key
|
|
uint32_t thread_key_offset[set_associativity];
|
|
// Warp offset for storing each key
|
|
uint32_t warp_key_offset;
|
|
// Block offset for storing each key
|
|
__shared__ size_t block_key_offset;
|
|
|
|
// Warp tile dump target slabset
|
|
if (set_idx < end_set_index) {
|
|
// Lock the slabset before operating the slabset
|
|
warp_lock_mutex<warp_size>(warp_tile, set_mutex[set_idx]);
|
|
|
|
// The warp tile read out the slabset
|
|
for (unsigned slab_id = 0; slab_id < set_associativity; slab_id++) {
|
|
// The warp tile read out a slab
|
|
read_key[slab_id] = ((volatile key_type*)(keys[set_idx].set_[slab_id].slab_))[lane_idx];
|
|
}
|
|
|
|
// Finish dumping the slabset, unlock the slabset
|
|
warp_unlock_mutex<warp_size>(warp_tile, set_mutex[set_idx]);
|
|
|
|
// Each lane(thread) within the warp tile calculate the offset to store its keys
|
|
uint32_t warp_tile_total_keys = 0;
|
|
for (unsigned slab_id = 0; slab_id < set_associativity; slab_id++) {
|
|
unsigned valid_mask = warp_tile.ballot(read_key[slab_id] != empty_key);
|
|
thread_key_offset[slab_id] =
|
|
__popc(valid_mask & ((1U << lane_idx) - 1U)) + warp_tile_total_keys;
|
|
warp_tile_total_keys = warp_tile_total_keys + __popc(valid_mask);
|
|
}
|
|
|
|
// Each warp tile request a unique place from the block-level counter
|
|
if (lane_idx == 0) {
|
|
warp_key_offset = atomicAdd(&block_acc, warp_tile_total_keys);
|
|
}
|
|
warp_key_offset = warp_tile.shfl(warp_key_offset, 0);
|
|
}
|
|
|
|
// Each block request a unique place in global memory output buffer
|
|
__syncthreads();
|
|
if (threadIdx.x == 0) {
|
|
block_key_offset = atomicAdd(d_dump_counter, (size_t)block_acc);
|
|
}
|
|
__syncthreads();
|
|
|
|
// Warp tile store the (non-empty)keys back to output buffer
|
|
if (set_idx < end_set_index) {
|
|
for (unsigned slab_id = 0; slab_id < set_associativity; slab_id++) {
|
|
if (read_key[slab_id] != empty_key) {
|
|
d_keys[block_key_offset + warp_key_offset + thread_key_offset[slab_id]] = read_key[slab_id];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::gpu_cache(const size_t capacity_in_set, const size_t embedding_vec_size)
|
|
: capacity_in_set_(capacity_in_set), embedding_vec_size_(embedding_vec_size) {
|
|
// Check parameter
|
|
if (capacity_in_set_ == 0) {
|
|
printf("Error: Invalid value for capacity_in_set.\n");
|
|
return;
|
|
}
|
|
if (embedding_vec_size_ == 0) {
|
|
printf("Error: Invalid value for embedding_vec_size.\n");
|
|
return;
|
|
}
|
|
if (set_associativity <= 0) {
|
|
printf("Error: Invalid value for set_associativity.\n");
|
|
return;
|
|
}
|
|
if (warp_size != 1 && warp_size != 2 && warp_size != 4 && warp_size != 8 && warp_size != 16 &&
|
|
warp_size != 32) {
|
|
printf("Error: Invalid value for warp_size.\n");
|
|
return;
|
|
}
|
|
|
|
// Get the current CUDA dev
|
|
CUDA_CHECK(cudaGetDevice(&dev_));
|
|
|
|
// Calculate # of slot
|
|
num_slot_ = capacity_in_set_ * set_associativity * warp_size;
|
|
|
|
// Allocate GPU memory for cache
|
|
CUDA_CHECK(cudaMalloc((void**)&keys_, sizeof(slabset) * capacity_in_set_));
|
|
CUDA_CHECK(cudaMalloc((void**)&vals_, sizeof(float) * embedding_vec_size_ * num_slot_));
|
|
CUDA_CHECK(cudaMalloc((void**)&slot_counter_, sizeof(ref_counter_type) * num_slot_));
|
|
CUDA_CHECK(cudaMalloc((void**)&global_counter_, sizeof(atomic_ref_counter_type)));
|
|
|
|
// Allocate GPU memory for set mutex
|
|
CUDA_CHECK(cudaMalloc((void**)&set_mutex_, sizeof(mutex) * capacity_in_set_));
|
|
|
|
// Initialize the cache, set all entry to unused <K,V>
|
|
init_cache<<<((num_slot_ - 1) / BLOCK_SIZE_) + 1, BLOCK_SIZE_>>>(
|
|
keys_, slot_counter_, global_counter_, num_slot_, empty_key, set_mutex_, capacity_in_set_);
|
|
|
|
// Wait for initialization to finish
|
|
CUDA_CHECK(cudaStreamSynchronize(0));
|
|
CUDA_CHECK(cudaGetLastError());
|
|
}
|
|
#else
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::gpu_cache(const size_t capacity_in_set, const size_t embedding_vec_size)
|
|
: capacity_in_set_(capacity_in_set), embedding_vec_size_(embedding_vec_size) {
|
|
// Check parameter
|
|
if (capacity_in_set_ == 0) {
|
|
printf("Error: Invalid value for capacity_in_set.\n");
|
|
return;
|
|
}
|
|
if (embedding_vec_size_ == 0) {
|
|
printf("Error: Invalid value for embedding_vec_size.\n");
|
|
return;
|
|
}
|
|
if (set_associativity <= 0) {
|
|
printf("Error: Invalid value for set_associativity.\n");
|
|
return;
|
|
}
|
|
if (warp_size != 1 && warp_size != 2 && warp_size != 4 && warp_size != 8 && warp_size != 16 &&
|
|
warp_size != 32) {
|
|
printf("Error: Invalid value for warp_size.\n");
|
|
return;
|
|
}
|
|
|
|
// Get the current CUDA dev
|
|
CUDA_CHECK(cudaGetDevice(&dev_));
|
|
|
|
// Calculate # of slot
|
|
num_slot_ = capacity_in_set_ * set_associativity * warp_size;
|
|
|
|
// Allocate GPU memory for cache
|
|
CUDA_CHECK(cudaMalloc((void**)&keys_, sizeof(slabset) * capacity_in_set_));
|
|
CUDA_CHECK(cudaMalloc((void**)&vals_, sizeof(float) * embedding_vec_size_ * num_slot_));
|
|
CUDA_CHECK(cudaMalloc((void**)&slot_counter_, sizeof(ref_counter_type) * num_slot_));
|
|
CUDA_CHECK(cudaMalloc((void**)&global_counter_, sizeof(ref_counter_type)));
|
|
|
|
// Allocate GPU memory for set mutex
|
|
CUDA_CHECK(cudaMalloc((void**)&set_mutex_, sizeof(int) * capacity_in_set_));
|
|
|
|
// Initialize the cache, set all entry to unused <K,V>
|
|
init_cache<<<((num_slot_ - 1) / BLOCK_SIZE_) + 1, BLOCK_SIZE_>>>(
|
|
keys_, slot_counter_, global_counter_, num_slot_, empty_key, set_mutex_, capacity_in_set_);
|
|
|
|
// Wait for initialization to finish
|
|
CUDA_CHECK(cudaStreamSynchronize(0));
|
|
CUDA_CHECK(cudaGetLastError());
|
|
}
|
|
#endif
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::~gpu_cache() {
|
|
// Device Restorer
|
|
nv::CudaDeviceRestorer dev_restorer;
|
|
|
|
// Check device
|
|
dev_restorer.check_device(dev_);
|
|
|
|
// Destruct CUDA std object
|
|
destruct_kernel<<<((capacity_in_set_ - 1) / BLOCK_SIZE_) + 1, BLOCK_SIZE_>>>(
|
|
global_counter_, set_mutex_, capacity_in_set_);
|
|
// Wait for destruction to finish
|
|
CUDA_CHECK(cudaStreamSynchronize(0));
|
|
|
|
// Free GPU memory for cache
|
|
CUDA_CHECK(cudaFree(keys_));
|
|
CUDA_CHECK(cudaFree(vals_));
|
|
CUDA_CHECK(cudaFree(slot_counter_));
|
|
CUDA_CHECK(cudaFree(global_counter_));
|
|
// Free GPU memory for set mutex
|
|
CUDA_CHECK(cudaFree(set_mutex_));
|
|
}
|
|
#else
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::~gpu_cache() noexcept(false) {
|
|
// Device Restorer
|
|
nv::CudaDeviceRestorer dev_restorer;
|
|
|
|
// Check device
|
|
dev_restorer.check_device(dev_);
|
|
|
|
// Free GPU memory for cache
|
|
CUDA_CHECK(cudaFree(keys_));
|
|
CUDA_CHECK(cudaFree(vals_));
|
|
CUDA_CHECK(cudaFree(slot_counter_));
|
|
CUDA_CHECK(cudaFree(global_counter_));
|
|
// Free GPU memory for set mutex
|
|
CUDA_CHECK(cudaFree(set_mutex_));
|
|
}
|
|
#endif
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
void gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::Query(const key_type* d_keys, const size_t len, float* d_values,
|
|
uint64_t* d_missing_index, key_type* d_missing_keys,
|
|
size_t* d_missing_len, cudaStream_t stream,
|
|
const size_t task_per_warp_tile) {
|
|
// Device Restorer
|
|
nv::CudaDeviceRestorer dev_restorer;
|
|
// Check device
|
|
dev_restorer.check_device(dev_);
|
|
|
|
// Check if it is a valid query
|
|
if (len == 0) {
|
|
// Set the d_missing_len to 0 before return
|
|
CUDA_CHECK(cudaMemsetAsync(d_missing_len, 0, sizeof(size_t), stream));
|
|
return;
|
|
}
|
|
|
|
// Update the global counter as user perform a new(most recent) read operation to the cache
|
|
// Resolve distance overflow issue as well.
|
|
update_kernel_overflow_ignore<atomic_ref_counter_type>
|
|
<<<1, 1, 0, stream>>>(global_counter_, d_missing_len);
|
|
|
|
// Read from the cache
|
|
// Touch and refresh the hitting slot
|
|
const size_t keys_per_block = (BLOCK_SIZE_ / warp_size) * task_per_warp_tile;
|
|
const size_t grid_size = ((len - 1) / keys_per_block) + 1;
|
|
get_kernel<key_type, ref_counter_type, atomic_ref_counter_type, slabset, set_hasher, slab_hasher,
|
|
mutex, empty_key, set_associativity, warp_size><<<grid_size, BLOCK_SIZE_, 0, stream>>>(
|
|
d_keys, len, d_values, embedding_vec_size_, d_missing_index, d_missing_keys, d_missing_len,
|
|
global_counter_, slot_counter_, capacity_in_set_, keys_, vals_, set_mutex_,
|
|
task_per_warp_tile);
|
|
|
|
// Check for GPU error before return
|
|
CUDA_CHECK(cudaGetLastError());
|
|
}
|
|
#else
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
void gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::Query(const key_type* d_keys, const size_t len, float* d_values,
|
|
uint64_t* d_missing_index, key_type* d_missing_keys,
|
|
size_t* d_missing_len, cudaStream_t stream,
|
|
const size_t task_per_warp_tile) {
|
|
// Device Restorer
|
|
nv::CudaDeviceRestorer dev_restorer;
|
|
// Check device
|
|
dev_restorer.check_device(dev_);
|
|
|
|
// Check if it is a valid query
|
|
if (len == 0) {
|
|
// Set the d_missing_len to 0 before return
|
|
CUDA_CHECK(cudaMemsetAsync(d_missing_len, 0, sizeof(size_t), stream));
|
|
return;
|
|
}
|
|
|
|
// Update the global counter as user perform a new(most recent) read operation to the cache
|
|
// Resolve distance overflow issue as well.
|
|
update_kernel_overflow_ignore<ref_counter_type>
|
|
<<<1, 1, 0, stream>>>(global_counter_, d_missing_len);
|
|
|
|
// Read from the cache
|
|
// Touch and refresh the hitting slot
|
|
const size_t keys_per_block = (BLOCK_SIZE_ / warp_size) * task_per_warp_tile;
|
|
const size_t grid_size = ((len - 1) / keys_per_block) + 1;
|
|
get_kernel<key_type, ref_counter_type, slabset, set_hasher, slab_hasher, empty_key,
|
|
set_associativity, warp_size><<<grid_size, BLOCK_SIZE_, 0, stream>>>(
|
|
d_keys, len, d_values, embedding_vec_size_, d_missing_index, d_missing_keys, d_missing_len,
|
|
global_counter_, slot_counter_, capacity_in_set_, keys_, vals_, set_mutex_,
|
|
task_per_warp_tile);
|
|
|
|
// Check for GPU error before return
|
|
CUDA_CHECK(cudaGetLastError());
|
|
}
|
|
#endif
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
void gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::Replace(const key_type* d_keys, const size_t len,
|
|
const float* d_values, cudaStream_t stream,
|
|
const size_t task_per_warp_tile) {
|
|
// Check if it is a valid replacement
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
|
|
// Device Restorer
|
|
nv::CudaDeviceRestorer dev_restorer;
|
|
// Check device
|
|
dev_restorer.check_device(dev_);
|
|
|
|
// Try to insert the <k,v> paris into the cache as long as there are unused slot
|
|
// Then replace the <k,v> pairs into the cache
|
|
const size_t keys_per_block = (BLOCK_SIZE_ / warp_size) * task_per_warp_tile;
|
|
const size_t grid_size = ((len - 1) / keys_per_block) + 1;
|
|
insert_replace_kernel<key_type, slabset, ref_counter_type, mutex, atomic_ref_counter_type,
|
|
set_hasher, slab_hasher, empty_key, set_associativity, warp_size>
|
|
<<<grid_size, BLOCK_SIZE_, 0, stream>>>(d_keys, d_values, embedding_vec_size_, len, keys_,
|
|
vals_, slot_counter_, set_mutex_, global_counter_,
|
|
capacity_in_set_, task_per_warp_tile);
|
|
|
|
// Check for GPU error before return
|
|
CUDA_CHECK(cudaGetLastError());
|
|
}
|
|
#else
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
void gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::Replace(const key_type* d_keys, const size_t len,
|
|
const float* d_values, cudaStream_t stream,
|
|
const size_t task_per_warp_tile) {
|
|
// Check if it is a valid replacement
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
|
|
// Device Restorer
|
|
nv::CudaDeviceRestorer dev_restorer;
|
|
// Check device
|
|
dev_restorer.check_device(dev_);
|
|
|
|
// Try to insert the <k,v> paris into the cache as long as there are unused slot
|
|
// Then replace the <k,v> pairs into the cache
|
|
const size_t keys_per_block = (BLOCK_SIZE_ / warp_size) * task_per_warp_tile;
|
|
const size_t grid_size = ((len - 1) / keys_per_block) + 1;
|
|
insert_replace_kernel<key_type, slabset, ref_counter_type, set_hasher, slab_hasher, empty_key,
|
|
set_associativity, warp_size><<<grid_size, BLOCK_SIZE_, 0, stream>>>(
|
|
d_keys, d_values, embedding_vec_size_, len, keys_, vals_, slot_counter_, set_mutex_,
|
|
global_counter_, capacity_in_set_, task_per_warp_tile);
|
|
|
|
// Check for GPU error before return
|
|
CUDA_CHECK(cudaGetLastError());
|
|
}
|
|
#endif
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
void gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::Update(const key_type* d_keys, const size_t len, const float* d_values,
|
|
cudaStream_t stream, const size_t task_per_warp_tile) {
|
|
// Check if it is a valid update request
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
|
|
// Device Restorer
|
|
nv::CudaDeviceRestorer dev_restorer;
|
|
// Check device
|
|
dev_restorer.check_device(dev_);
|
|
|
|
// Update the value of input keys that are existed in the cache
|
|
const size_t keys_per_block = (BLOCK_SIZE_ / warp_size) * task_per_warp_tile;
|
|
const size_t grid_size = ((len - 1) / keys_per_block) + 1;
|
|
update_kernel<key_type, slabset, set_hasher, slab_hasher, mutex, empty_key, set_associativity,
|
|
warp_size><<<grid_size, BLOCK_SIZE_, 0, stream>>>(
|
|
d_keys, len, d_values, embedding_vec_size_, capacity_in_set_, keys_, vals_, set_mutex_,
|
|
task_per_warp_tile);
|
|
|
|
// Check for GPU error before return
|
|
CUDA_CHECK(cudaGetLastError());
|
|
}
|
|
#else
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
void gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::Update(const key_type* d_keys, const size_t len, const float* d_values,
|
|
cudaStream_t stream, const size_t task_per_warp_tile) {
|
|
// Check if it is a valid update request
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
|
|
// Device Restorer
|
|
nv::CudaDeviceRestorer dev_restorer;
|
|
// Check device
|
|
dev_restorer.check_device(dev_);
|
|
|
|
// Update the value of input keys that are existed in the cache
|
|
const size_t keys_per_block = (BLOCK_SIZE_ / warp_size) * task_per_warp_tile;
|
|
const size_t grid_size = ((len - 1) / keys_per_block) + 1;
|
|
update_kernel<key_type, slabset, set_hasher, slab_hasher, empty_key, set_associativity, warp_size>
|
|
<<<grid_size, BLOCK_SIZE_, 0, stream>>>(d_keys, len, d_values, embedding_vec_size_,
|
|
capacity_in_set_, keys_, vals_, set_mutex_,
|
|
task_per_warp_tile);
|
|
|
|
// Check for GPU error before return
|
|
CUDA_CHECK(cudaGetLastError());
|
|
}
|
|
#endif
|
|
|
|
#ifdef LIBCUDACXX_VERSION
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
void gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::Dump(key_type* d_keys, size_t* d_dump_counter,
|
|
const size_t start_set_index, const size_t end_set_index,
|
|
cudaStream_t stream) {
|
|
// Check if it is a valid dump request
|
|
if (start_set_index >= capacity_in_set_) {
|
|
printf("Error: Invalid value for start_set_index. Nothing dumped.\n");
|
|
return;
|
|
}
|
|
if (end_set_index <= start_set_index || end_set_index > capacity_in_set_) {
|
|
printf("Error: Invalid value for end_set_index. Nothing dumped.\n");
|
|
return;
|
|
}
|
|
|
|
// Device Restorer
|
|
nv::CudaDeviceRestorer dev_restorer;
|
|
// Check device
|
|
dev_restorer.check_device(dev_);
|
|
|
|
// Set the global counter to 0 first
|
|
CUDA_CHECK(cudaMemsetAsync(d_dump_counter, 0, sizeof(size_t), stream));
|
|
|
|
// Dump keys from the cache
|
|
const size_t grid_size =
|
|
(((end_set_index - start_set_index) - 1) / (BLOCK_SIZE_ / warp_size)) + 1;
|
|
dump_kernel<key_type, slabset, mutex, empty_key, set_associativity, warp_size>
|
|
<<<grid_size, BLOCK_SIZE_, 0, stream>>>(d_keys, d_dump_counter, keys_, set_mutex_,
|
|
start_set_index, end_set_index);
|
|
|
|
// Check for GPU error before return
|
|
CUDA_CHECK(cudaGetLastError());
|
|
}
|
|
#else
|
|
template <typename key_type, typename ref_counter_type, key_type empty_key, int set_associativity,
|
|
int warp_size, typename set_hasher, typename slab_hasher>
|
|
void gpu_cache<key_type, ref_counter_type, empty_key, set_associativity, warp_size, set_hasher,
|
|
slab_hasher>::Dump(key_type* d_keys, size_t* d_dump_counter,
|
|
const size_t start_set_index, const size_t end_set_index,
|
|
cudaStream_t stream) {
|
|
// Check if it is a valid dump request
|
|
if (start_set_index >= capacity_in_set_) {
|
|
printf("Error: Invalid value for start_set_index. Nothing dumped.\n");
|
|
return;
|
|
}
|
|
if (end_set_index <= start_set_index || end_set_index > capacity_in_set_) {
|
|
printf("Error: Invalid value for end_set_index. Nothing dumped.\n");
|
|
return;
|
|
}
|
|
|
|
// Device Restorer
|
|
nv::CudaDeviceRestorer dev_restorer;
|
|
// Check device
|
|
dev_restorer.check_device(dev_);
|
|
|
|
// Set the global counter to 0 first
|
|
CUDA_CHECK(cudaMemsetAsync(d_dump_counter, 0, sizeof(size_t), stream));
|
|
|
|
// Dump keys from the cache
|
|
const size_t grid_size =
|
|
(((end_set_index - start_set_index) - 1) / (BLOCK_SIZE_ / warp_size)) + 1;
|
|
dump_kernel<key_type, slabset, empty_key, set_associativity, warp_size>
|
|
<<<grid_size, BLOCK_SIZE_, 0, stream>>>(d_keys, d_dump_counter, keys_, set_mutex_,
|
|
start_set_index, end_set_index);
|
|
|
|
// Check for GPU error before return
|
|
CUDA_CHECK(cudaGetLastError());
|
|
}
|
|
#endif
|
|
|
|
template class gpu_cache<unsigned int, uint64_t, std::numeric_limits<unsigned int>::max(),
|
|
SET_ASSOCIATIVITY, SLAB_SIZE>;
|
|
template class gpu_cache<long long, uint64_t, std::numeric_limits<long long>::max(),
|
|
SET_ASSOCIATIVITY, SLAB_SIZE>;
|
|
} // namespace gpu_cache
|