#pragma once #include #include #include #include #include #include #include #include namespace device { namespace details { template struct LocalStorage { T data[N]; }; template inline constexpr auto get_mem_package() { if constexpr (kUnit == 16) { return uint4{}; } else if constexpr (kUnit == 8) { return uint2{}; } else if constexpr (kUnit == 4) { return uint1{}; } else { static_assert(kUnit == 16 || kUnit == 8 || kUnit == 4, "Unsupported memory package size"); } } template using PackageType = decltype(get_mem_package()); // NVIDIA exposes an explicit "do not allocate in L1" cache hint via PTX. ROCm // has no equivalent PTX, but non-temporal (streaming) loads/stores express the // same intent for one-shot HiCache write-back traffic that should not pollute // the cache. Guard the PTX behind USE_ROCM so the JIT module also compiles with // hipcc; see python/sglang/jit_kernel/utils.py for the ROCm build flags. #ifdef USE_ROCM // Native Clang vector types so a single __builtin_nontemporal_{load,store} maps // to one vectorized global_{load,store}_dwordx{2,4}. Issuing N independent // 32-bit nontemporal ops instead leaves merging to the LoadStoreVectorizer, // which is not guaranteed and may drop the nontemporal hint, throttling HiCache // bandwidth. uint2/uint4 already carry 8B/16B alignment matching the vector // types, so the pointer reinterpret_casts stay correctly aligned. typedef uint32_t native_uint2 __attribute__((ext_vector_type(2))); typedef uint32_t native_uint4 __attribute__((ext_vector_type(4))); #endif SGL_DEVICE uint1 load_nc(const uint1* __restrict__ src) { #ifndef USE_ROCM uint32_t tmp; asm volatile("ld.global.L1::no_allocate.b32 %0,[%1];" : "=r"(tmp) : "l"(src)); return uint1{tmp}; #else return uint1{__builtin_nontemporal_load(&src->x)}; #endif } SGL_DEVICE uint2 load_nc(const uint2* __restrict__ src) { #ifndef USE_ROCM uint32_t tmp0, tmp1; asm volatile("ld.global.L1::no_allocate.v2.b32 {%0,%1},[%2];" : "=r"(tmp0), "=r"(tmp1) : "l"(src)); return uint2{tmp0, tmp1}; #else native_uint2 tmp = __builtin_nontemporal_load(reinterpret_cast(src)); return __builtin_bit_cast(uint2, tmp); #endif } SGL_DEVICE uint4 load_nc(const uint4* __restrict__ src) { #ifndef USE_ROCM uint32_t tmp0, tmp1, tmp2, tmp3; asm volatile("ld.global.L1::no_allocate.v4.b32 {%0,%1,%2,%3},[%4];" : "=r"(tmp0), "=r"(tmp1), "=r"(tmp2), "=r"(tmp3) : "l"(src)); return uint4{tmp0, tmp1, tmp2, tmp3}; #else native_uint4 tmp = __builtin_nontemporal_load(reinterpret_cast(src)); return __builtin_bit_cast(uint4, tmp); #endif } SGL_DEVICE void store_nc(uint1* __restrict__ dst, const uint1& value) { #ifndef USE_ROCM uint32_t tmp = value.x; asm volatile("st.global.L1::no_allocate.b32 [%0],%1;" ::"l"(dst), "r"(tmp)); #else __builtin_nontemporal_store(value.x, &dst->x); #endif } SGL_DEVICE void store_nc(uint2* __restrict__ dst, const uint2& value) { #ifndef USE_ROCM uint32_t tmp0 = value.x; uint32_t tmp1 = value.y; asm volatile("st.global.L1::no_allocate.v2.b32 [%0],{%1,%2};" ::"l"(dst), "r"(tmp0), "r"(tmp1)); #else __builtin_nontemporal_store(__builtin_bit_cast(native_uint2, value), reinterpret_cast(dst)); #endif } SGL_DEVICE void store_nc(uint4* __restrict__ dst, const uint4& value) { #ifndef USE_ROCM uint32_t tmp0 = value.x; uint32_t tmp1 = value.y; uint32_t tmp2 = value.z; uint32_t tmp3 = value.w; asm volatile( "st.global.L1::no_allocate.v4.b32 [%0],{%1,%2,%3,%4};" ::"l"(dst), "r"(tmp0), "r"(tmp1), "r"(tmp2), "r"(tmp3)); #else __builtin_nontemporal_store(__builtin_bit_cast(native_uint4, value), reinterpret_cast(dst)); #endif } } // namespace details template SGL_DEVICE auto load_vec(const void* __restrict__ src) { static_assert(kBytes % 128 == 0, "kBytes must be multiple of 128 bytes"); static_assert(128 % kNumThreads == 0, "kNumThreads must divide 128 bytes"); constexpr uint32_t kLoopCount = kBytes / 128; using Package = details::PackageType<128 / kNumThreads>; using Storage = details::LocalStorage; const auto src_packed = static_cast(src); const auto lane_id = threadIdx.x % kNumThreads; Storage vec; #pragma unroll kLoopCount for (uint32_t i = 0; i < kLoopCount; ++i) { const auto j = i * kNumThreads + lane_id; vec.data[i] = details::load_nc(&src_packed[j]); } return vec; } template SGL_DEVICE void store_vec(void* __restrict__ dst, const Storage& vec) { using Package = std::decay_t; constexpr uint32_t kBytesPerLoop = sizeof(Package) * kNumThreads; constexpr uint32_t kLoopCount = kBytes / kBytesPerLoop; static_assert(kBytes % kBytesPerLoop == 0, "Invalid Storage configuration"); const auto dst_packed = static_cast(dst); const auto lane_id = threadIdx.x % kNumThreads; #pragma unroll kLoopCount for (uint32_t i = 0; i < kLoopCount; ++i) { const auto j = i * kNumThreads + lane_id; details::store_nc(&dst_packed[j], vec.data[i]); } } } // namespace device namespace { #define SGL_HICACHE_KERNEL __global__ __launch_bounds__(kBlockSize, 1) struct HicacheKernelParams { void* __restrict__ k_cache_dst; void* __restrict__ v_cache_dst; const void* __restrict__ indices_dst; void* __restrict__ k_cache_src; void* __restrict__ v_cache_src; const void* __restrict__ indices_src; int64_t kv_cache_src_stride; int64_t kv_cache_dst_stride; uint32_t length; uint32_t num_layers = 0; // only used in all_layer transfer }; template < typename T, int64_t kElementSize, uint32_t kUnroll, uint32_t kBlockQuota, uint32_t kBlockSize, bool kIsMLA = false> SGL_HICACHE_KERNEL void hicache_transfer_per_layer(const __grid_constant__ HicacheKernelParams params) { using namespace device; static_assert(kBlockSize % kWarpThreads == 0); static_assert(kWarpThreads % kUnroll == 0); constexpr uint32_t kNumThreads = kWarpThreads / kUnroll; constexpr uint32_t kWorkersPerBlock = kBlockSize / kNumThreads; constexpr uint32_t kNumWorkers = kWorkersPerBlock * kBlockQuota; const auto& [ k_cache_dst, v_cache_dst, indices_dst, // dst k_cache_src, v_cache_src, indices_src, // src kv_cache_src_stride, kv_cache_dst_stride, length, _ // metadata ] = params; const uint32_t work_id = blockIdx.x * kWorkersPerBlock + threadIdx.x / kNumThreads; for (uint32_t i = work_id; i < length; i += kNumWorkers) { const auto pos_src = static_cast(indices_src)[i]; const auto pos_dst = static_cast(indices_dst)[i]; const auto src_k = pointer::offset(k_cache_src, pos_src * kv_cache_src_stride); const auto dst_k = pointer::offset(k_cache_dst, pos_dst * kv_cache_dst_stride); const auto vec_k = load_vec(src_k); store_vec(dst_k, vec_k); if constexpr (!kIsMLA) { const auto src_v = pointer::offset(v_cache_src, pos_src * kv_cache_src_stride); const auto dst_v = pointer::offset(v_cache_dst, pos_dst * kv_cache_dst_stride); const auto vec_v = load_vec(src_v); store_vec(dst_v, vec_v); } } } template < typename T, int64_t kElementSize, uint32_t kUnroll, uint32_t kBlockQuota, uint32_t kBlockSize, bool kIsMLA = false> SGL_HICACHE_KERNEL void hicache_transfer_all_layer(const __grid_constant__ HicacheKernelParams params) { using namespace device; using src_ptr_t = const void*; using dst_ptr_t = void*; static_assert(kBlockSize % kWarpThreads == 0); static_assert(kWarpThreads % kUnroll == 0); constexpr uint32_t kNumThreads = kWarpThreads / kUnroll; constexpr uint32_t kWorkersPerBlock = kBlockSize / kNumThreads; constexpr uint32_t kNumWorkers = kWorkersPerBlock * kBlockQuota; const auto& [ k_ptr_dst, v_ptr_dst, indices_dst, // dst k_ptr_src, v_ptr_src, indices_src, // src kv_cache_src_stride, kv_cache_dst_stride, length, num_layers // metadata ] = params; const uint32_t work_id = blockIdx.x * kWorkersPerBlock + threadIdx.x / kNumThreads; for (uint32_t i = work_id; i < length; i += kNumWorkers) { const auto pos_src = static_cast(indices_src)[i]; const auto pos_dst = static_cast(indices_dst)[i]; for (uint32_t layer = 0; layer < num_layers; ++layer) { const auto k_cache_src = static_cast(k_ptr_src)[layer]; const auto k_cache_dst = static_cast(k_ptr_dst)[layer]; const auto src_k = pointer::offset(k_cache_src, pos_src * kv_cache_src_stride); const auto dst_k = pointer::offset(k_cache_dst, pos_dst * kv_cache_dst_stride); const auto vec_k = load_vec(src_k); store_vec(dst_k, vec_k); if constexpr (!kIsMLA) { const auto v_cache_src = static_cast(v_ptr_src)[layer]; const auto v_cache_dst = static_cast(v_ptr_dst)[layer]; const auto src_v = pointer::offset(v_cache_src, pos_src * kv_cache_src_stride); const auto dst_v = pointer::offset(v_cache_dst, pos_dst * kv_cache_dst_stride); const auto vec_v = load_vec(src_v); store_vec(dst_v, vec_v); } } } } template struct HiCacheKernel { template static constexpr auto kernel_one = hicache_transfer_per_layer; template static constexpr auto kernel_all = hicache_transfer_all_layer; template static constexpr auto kernel_one_mla = hicache_transfer_per_layer; template static constexpr auto kernel_all_mla = hicache_transfer_all_layer; static void run_one( const tvm::ffi::TensorView k_cache_dst, const tvm::ffi::TensorView v_cache_dst, const tvm::ffi::TensorView indices_dst, const tvm::ffi::TensorView k_cache_src, const tvm::ffi::TensorView v_cache_src, const tvm::ffi::TensorView indices_src) { using namespace host; auto D = SymbolicSize{"head dimension"}; auto N = SymbolicSize{"src kv stride"}; auto M = SymbolicSize{"dst kv stride"}; auto L = SymbolicSize{"indices length"}; auto cache_dtype = SymbolicDType{}; auto indices_dtype = SymbolicDType{}; auto indices_device = SymbolicDevice{}; TensorMatcher({-1, D}) // .with_strides({N, 1}) .with_dtype(cache_dtype) .with_device() .verify(k_cache_src) .verify(v_cache_src); TensorMatcher({-1, D}) // .with_strides({M, 1}) .with_dtype(cache_dtype) .with_device() .verify(k_cache_dst) .verify(v_cache_dst); TensorMatcher({L}) // .with_dtype(indices_dtype) .with_device(indices_device) .verify(indices_src) .verify(indices_dst); // verify dimension match const auto dtype_size = dtype_bytes(cache_dtype.unwrap()); const auto element_bytes = D.unwrap() * dtype_size; RuntimeCheck(kElementSize == element_bytes, "HicacheKernel: cache dimension mismatch."); const auto k_cache_dst_ptr = k_cache_dst.data_ptr(); const auto v_cache_dst_ptr = v_cache_dst.data_ptr(); const auto k_cache_src_ptr = k_cache_src.data_ptr(); const auto v_cache_src_ptr = v_cache_src.data_ptr(); const auto indices_dst_ptr = indices_dst.data_ptr(); const auto indices_src_ptr = indices_src.data_ptr(); const auto length = static_cast(L.unwrap()); const auto kv_cache_src_stride = static_cast(N.unwrap() * dtype_size); const auto kv_cache_dst_stride = static_cast(M.unwrap() * dtype_size); const auto use_int32 = indices_dtype.unwrap().bits == 32; const auto device = indices_device.unwrap(); constexpr auto kWorkersPerBlock = kBlockSize / (device::kWarpThreads / kUnroll); const auto num_blocks = std::min(div_ceil(length, kWorkersPerBlock), kBlockQuota); const auto params = HicacheKernelParams{ .k_cache_dst = k_cache_dst_ptr, .v_cache_dst = v_cache_dst_ptr, .indices_dst = indices_dst_ptr, .k_cache_src = k_cache_src_ptr, .v_cache_src = v_cache_src_ptr, .indices_src = indices_src_ptr, .kv_cache_src_stride = kv_cache_src_stride, .kv_cache_dst_stride = kv_cache_dst_stride, .length = length, }; const auto kernel = use_int32 ? kernel_one : kernel_one; LaunchKernel(num_blocks, kBlockSize, device)(kernel, params); } static void run_all( const tvm::ffi::TensorView k_ptr_dst, const tvm::ffi::TensorView v_ptr_dst, const tvm::ffi::TensorView indices_dst, const tvm::ffi::TensorView k_ptr_src, const tvm::ffi::TensorView v_ptr_src, const tvm::ffi::TensorView indices_src, const int64_t kv_src_stride_bytes, const int64_t kv_dst_stride_bytes) { using namespace host; auto N = SymbolicSize{"num_layers"}; auto L = SymbolicSize{"indices length"}; auto dtype_ = SymbolicDType{}; auto device_ = SymbolicDevice{}; TensorMatcher({N}) // .with_dtype() .with_device(device_) .verify(k_ptr_src) .verify(v_ptr_src) .verify(k_ptr_dst) .verify(v_ptr_dst); TensorMatcher({L}) // .with_dtype(dtype_) .with_device(device_) .verify(indices_src) .verify(indices_dst); // verify dimension match const auto k_cache_dst_ptr = k_ptr_dst.data_ptr(); const auto v_cache_dst_ptr = v_ptr_dst.data_ptr(); const auto k_cache_src_ptr = k_ptr_src.data_ptr(); const auto v_cache_src_ptr = v_ptr_src.data_ptr(); const auto indices_dst_ptr = indices_dst.data_ptr(); const auto indices_src_ptr = indices_src.data_ptr(); const auto length = static_cast(L.unwrap()); const auto use_int32 = dtype_.unwrap().bits == 32; const auto device = device_.unwrap(); constexpr auto kWorkersPerBlock = kBlockSize / (device::kWarpThreads / kUnroll); const auto num_blocks = std::min(div_ceil(length, kWorkersPerBlock), kBlockQuota); const auto params = HicacheKernelParams{ .k_cache_dst = k_cache_dst_ptr, .v_cache_dst = v_cache_dst_ptr, .indices_dst = indices_dst_ptr, .k_cache_src = k_cache_src_ptr, .v_cache_src = v_cache_src_ptr, .indices_src = indices_src_ptr, .kv_cache_src_stride = kv_src_stride_bytes, .kv_cache_dst_stride = kv_dst_stride_bytes, .length = length, .num_layers = static_cast(N.unwrap()), }; const auto kernel = use_int32 ? kernel_all : kernel_all; LaunchKernel(num_blocks, kBlockSize, device)(kernel, params); } static void run_one_mla( const tvm::ffi::TensorView cache_dst, const tvm::ffi::TensorView indices_dst, const tvm::ffi::TensorView cache_src, const tvm::ffi::TensorView indices_src) { using namespace host; auto D = SymbolicSize{"head dimension"}; auto N = SymbolicSize{"src stride"}; auto M = SymbolicSize{"dst stride"}; auto L = SymbolicSize{"indices length"}; auto cache_dtype = SymbolicDType{}; auto indices_dtype = SymbolicDType{}; auto indices_device = SymbolicDevice{}; TensorMatcher({-1, D}) // .with_strides({N, 1}) .with_dtype(cache_dtype) .with_device() .verify(cache_src); TensorMatcher({-1, D}) // .with_strides({M, 1}) .with_dtype(cache_dtype) .with_device() .verify(cache_dst); TensorMatcher({L}) // .with_dtype(indices_dtype) .with_device(indices_device) .verify(indices_src) .verify(indices_dst); const auto dtype_size = dtype_bytes(cache_dtype.unwrap()); const auto element_bytes = D.unwrap() * dtype_size; RuntimeCheck(kElementSize == element_bytes, "HicacheKernel MLA: cache dimension mismatch."); const auto cache_dst_ptr = cache_dst.data_ptr(); const auto cache_src_ptr = cache_src.data_ptr(); const auto indices_dst_ptr = indices_dst.data_ptr(); const auto indices_src_ptr = indices_src.data_ptr(); const auto length = static_cast(L.unwrap()); const auto cache_src_stride = static_cast(N.unwrap() * dtype_size); const auto cache_dst_stride = static_cast(M.unwrap() * dtype_size); const auto use_int32 = indices_dtype.unwrap().bits == 32; const auto device = indices_device.unwrap(); constexpr auto kWorkersPerBlock = kBlockSize / (device::kWarpThreads / kUnroll); const auto num_blocks = std::min(div_ceil(length, kWorkersPerBlock), kBlockQuota); const auto params = HicacheKernelParams{ .k_cache_dst = cache_dst_ptr, .v_cache_dst = nullptr, .indices_dst = indices_dst_ptr, .k_cache_src = cache_src_ptr, .v_cache_src = nullptr, .indices_src = indices_src_ptr, .kv_cache_src_stride = cache_src_stride, .kv_cache_dst_stride = cache_dst_stride, .length = length, }; const auto kernel = use_int32 ? kernel_one_mla : kernel_one_mla; LaunchKernel(num_blocks, kBlockSize, device)(kernel, params); } static void run_all_mla( const tvm::ffi::TensorView ptr_dst, const tvm::ffi::TensorView indices_dst, const tvm::ffi::TensorView ptr_src, const tvm::ffi::TensorView indices_src, const int64_t src_stride_bytes, const int64_t dst_stride_bytes) { using namespace host; auto N = SymbolicSize{"num_layers"}; auto L = SymbolicSize{"indices length"}; auto dtype_ = SymbolicDType{}; auto device_ = SymbolicDevice{}; TensorMatcher({N}) // .with_dtype() .with_device(device_) .verify(ptr_src) .verify(ptr_dst); TensorMatcher({L}) // .with_dtype(dtype_) .with_device(device_) .verify(indices_src) .verify(indices_dst); const auto cache_dst_ptr = ptr_dst.data_ptr(); const auto cache_src_ptr = ptr_src.data_ptr(); const auto indices_dst_ptr = indices_dst.data_ptr(); const auto indices_src_ptr = indices_src.data_ptr(); const auto length = static_cast(L.unwrap()); const auto use_int32 = dtype_.unwrap().bits == 32; const auto device = device_.unwrap(); constexpr auto kWorkersPerBlock = kBlockSize / (device::kWarpThreads / kUnroll); const auto num_blocks = std::min(div_ceil(length, kWorkersPerBlock), kBlockQuota); const auto params = HicacheKernelParams{ .k_cache_dst = cache_dst_ptr, .v_cache_dst = nullptr, .indices_dst = indices_dst_ptr, .k_cache_src = cache_src_ptr, .v_cache_src = nullptr, .indices_src = indices_src_ptr, .kv_cache_src_stride = src_stride_bytes, .kv_cache_dst_stride = dst_stride_bytes, .length = length, .num_layers = static_cast(N.unwrap()), }; const auto kernel = use_int32 ? kernel_all_mla : kernel_all_mla; LaunchKernel(num_blocks, kBlockSize, device)(kernel, params); } }; #undef SGL_HICACHE_KERNEL } // namespace