/* * 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 #include 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 __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 __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 __forceinline__ __device__ void warp_lock_mutex(const cg::thread_block_tile& 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 __forceinline__ __device__ void warp_unlock_mutex(const cg::thread_block_tile& 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 __forceinline__ __device__ void warp_lock_mutex(const cg::thread_block_tile& 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 __forceinline__ __device__ void warp_unlock_mutex(const cg::thread_block_tile& 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 __forceinline__ __device__ void warp_min_reduction( const cg::thread_block_tile& 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 pair template __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 __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 pair template __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 __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 __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 __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_tile = cg::tiled_partition(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_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(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(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 __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_tile = cg::tiled_partition(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_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(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_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 pairs into the cache template ::max(), size_t max_slab_distance = std::numeric_limits::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_tile = cg::tiled_partition(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_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(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(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(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(warp_tile, set_mutex[next_set]); } } #else // Kernel to insert or replace the pairs into the cache template ::max(), size_t max_slab_distance = std::numeric_limits::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_tile = cg::tiled_partition(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_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(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(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(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_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 __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_tile = cg::tiled_partition(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_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(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(warp_tile, set_mutex[next_set]); } } #else // Kernel to update the existing keys in the cache // Will not change the locality information template __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_tile = cg::tiled_partition(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_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(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_tile, set_mutex[next_set]); } } #endif #ifdef LIBCUDACXX_VERSION template __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_tile = cg::tiled_partition(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_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(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 __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_tile = cg::tiled_partition(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_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_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 gpu_cache::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 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 gpu_cache::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 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 gpu_cache::~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 gpu_cache::~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 void gpu_cache::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 <<<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<<>>( 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 void gpu_cache::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 <<<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<<>>( 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 void gpu_cache::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 paris into the cache as long as there are unused slot // Then replace the 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 <<>>(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 void gpu_cache::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 paris into the cache as long as there are unused slot // Then replace the 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<<>>( 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 void gpu_cache::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<<>>( 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 void gpu_cache::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 <<>>(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 void gpu_cache::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 <<>>(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 void gpu_cache::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 <<>>(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::max(), SET_ASSOCIATIVITY, SLAB_SIZE>; template class gpu_cache::max(), SET_ASSOCIATIVITY, SLAB_SIZE>; } // namespace gpu_cache