// DeepSeek-V3.2 only. // // DSA indexer K kernels: single-head LayerNorm (not RMS), ropes the leading // kRopeDim dims, and fp8-quantizes the rotated activations. V3.2 drops the // Hadamard incoherence rotation; it is logit-preserving (see main_norm_rope.cuh). // // Independent of the wk + weights_proj GEMM fusion (dsa_indexer.py): `k_input` // here is the non-contiguous wk slice kw[:, :head_dim] read via // k_input_stride_batch (no copy). #include #include #include #include #include #include #include #include #include #include #include #include namespace { using deepseek_v4::fp8::pack_fp8; constexpr uint32_t kFusedKIndexerBlockSize = 128; constexpr uint32_t kFusedKIndexerNumWarps = kFusedKIndexerBlockSize / device::kWarpThreads; #define K_INDEXER_KERNEL __global__ __launch_bounds__(kFusedKIndexerBlockSize, 16) template SGL_DEVICE device::AlignedVector load_rope_first_cos_sin(const float* __restrict__ cos_sin_cache, int32_t lane_id) { constexpr int64_t kHalfRopeDim = kRopeDim / 2; const int32_t pair0 = lane_id * 2; const int32_t pair1 = pair0 + 1; device::AlignedVector freq; freq[0] = cos_sin_cache[pair0]; freq[1] = cos_sin_cache[kHalfRopeDim + pair0]; freq[2] = cos_sin_cache[pair1]; freq[3] = cos_sin_cache[kHalfRopeDim + pair1]; return freq; } // Indexer K: LayerNorm + RoPE -> bf16. struct FusedKIndexerNormRopeParams { const void* __restrict__ k_input; // (B, 128) DType void* __restrict__ k_out; // (B, 128) DType const float* __restrict__ weight; // (128,) fp32 -- LayerNorm gamma const float* __restrict__ bias; // (128,) fp32 -- LayerNorm beta const float* __restrict__ cos_sin_cache; // (max_pos, 64) fp32 [cos..., sin...] const void* __restrict__ positions; // (B,) PosT // Row stride for `k_input` in elements (caller passes the wk slice directly). int64_t k_input_stride_batch; uint32_t batch_size; float eps; }; template K_INDEXER_KERNEL void fused_k_indexer_norm_rope(const __grid_constant__ FusedKIndexerNormRopeParams params) { using namespace device; constexpr int64_t kHeadDim = 128; constexpr int64_t kRopeDim = 64; constexpr int64_t kVecSize = 4; constexpr uint32_t kRopeSize = kRopeDim / kVecSize; // = 16 static_assert(kHeadDim == kWarpThreads * kVecSize); static_assert(kRopeDim == kWarpThreads * 2); static_assert(kRopeSize <= kWarpThreads); using Storage = AlignedVector; using Float4 = AlignedVector; const auto warp_id = threadIdx.x / kWarpThreads; const auto lane_id = threadIdx.x % kWarpThreads; const auto work_id = blockIdx.x * kFusedKIndexerNumWarps + warp_id; const bool is_rope_lane = lane_id < kRopeSize; if (work_id >= params.batch_size) return; const auto input_ptr = static_cast(params.k_input) + work_id * params.k_input_stride_batch; const auto position = static_cast(static_cast(params.positions)[work_id]); const auto cos_sin_cache = params.cos_sin_cache + position * kRopeDim; PDLWaitPrimary(); Float4 data, freq, gamma, beta; // part 1: LayerNorm { Storage input_vec; input_vec.load(input_ptr, lane_id); gamma.load(params.weight, lane_id); beta.load(params.bias, lane_id); if (is_rope_lane) freq = load_rope_first_cos_sin(cos_sin_cache, lane_id); float sum = 0.0f; #pragma unroll for (int i = 0; i < kVecSize; ++i) { data[i] = cast(input_vec[i]); sum += data[i]; } const float mean = warp::reduce_sum(sum) / kHeadDim; float var = 0.0f; #pragma unroll for (int i = 0; i < kVecSize; ++i) { const float centered = data[i] - mean; var += centered * centered; } const float inv_std = math::rsqrt(warp::reduce_sum(var) / kHeadDim + params.eps); #pragma unroll for (int i = 0; i < kVecSize; ++i) { data[i] = (data[i] - mean) * inv_std * gamma[i] + beta[i]; } } // part 2: rope on rope lanes if (is_rope_lane) { const auto x_real = data[0]; const auto x_imag = data[1]; const auto y_real = data[2]; const auto y_imag = data[3]; const auto fxr = freq[0]; const auto fxi = freq[1]; const auto fyr = freq[2]; const auto fyi = freq[3]; data[0] = x_real * fxr - x_imag * fxi; data[1] = x_real * fxi + x_imag * fxr; data[2] = y_real * fyr - y_imag * fyi; data[3] = y_real * fyi + y_imag * fyr; } PDLTriggerSecondary(); { Storage out_vec; #pragma unroll for (int i = 0; i < kVecSize; ++i) out_vec[i] = cast(data[i]); auto out_row = static_cast(params.k_out) + work_id * kHeadDim; out_vec.store(out_row, lane_id); } } template struct FusedKIndexerNormRopeKernel { template static constexpr auto kernel = fused_k_indexer_norm_rope; static void forward( const tvm::ffi::TensorView k_input, const tvm::ffi::TensorView k_out, const tvm::ffi::TensorView weight, const tvm::ffi::TensorView bias, const tvm::ffi::TensorView cos_sin_cache, const tvm::ffi::TensorView positions, double eps) { using namespace host; constexpr int64_t kHeadDim = 128; constexpr int64_t kRopeDim = 64; auto B = SymbolicSize{"batch_size"}; auto device_ = SymbolicDevice{}; device_.set_options(); TensorMatcher({B, kHeadDim}) // .with_strides({-1, 1}) .with_dtype() .with_device(device_) .verify(k_input); TensorMatcher({B, kHeadDim}) // .with_strides({kHeadDim, 1}) .with_dtype() .with_device(device_) .verify(k_out); TensorMatcher({kHeadDim}) // .with_dtype() .with_device(device_) .verify(weight); TensorMatcher({kHeadDim}) // .with_dtype() .with_device(device_) .verify(bias); TensorMatcher({-1, kRopeDim}) // .with_dtype() .with_device(device_) .verify(cos_sin_cache); auto pos_dtype = SymbolicDType{}; TensorMatcher({B}) // .with_dtype(pos_dtype) .with_device(device_) .verify(positions); const auto batch_size = static_cast(B.unwrap()); if (batch_size == 0) return; const auto params = FusedKIndexerNormRopeParams{ .k_input = k_input.data_ptr(), .k_out = k_out.data_ptr(), .weight = static_cast(weight.data_ptr()), .bias = static_cast(bias.data_ptr()), .cos_sin_cache = static_cast(cos_sin_cache.data_ptr()), .positions = positions.data_ptr(), .k_input_stride_batch = k_input.stride(0), .batch_size = batch_size, .eps = static_cast(eps), }; const auto num_blocks = div_ceil(batch_size, kFusedKIndexerNumWarps); const auto k_int32 = kernel; const auto k_int64 = kernel; const auto k = pos_dtype.is_type() ? k_int32 : k_int64; LaunchKernel(num_blocks, kFusedKIndexerBlockSize, device_.unwrap()) // .enable_pdl(kUsePDL)(k, params); } }; // Indexer K + fused store: LayerNorm + RoPE + fp8 quant + paged store in one // launch. Page layout matches fused_store_index_cache.cuh: each page is // 132*page_size bytes (128*page_size fp8 keys, then 4*page_size fp32 scales). struct FusedKIndexerNormRopeStoreParams { const void* __restrict__ k_input; // (B, 128) DType void* __restrict__ cache; // (num_pages, 132*page_size) uint8 const void* __restrict__ indices; // (B,) int64 -- out_cache_loc const float* __restrict__ weight; // (128,) fp32 -- LayerNorm gamma const float* __restrict__ bias; // (128,) fp32 -- LayerNorm beta const float* __restrict__ cos_sin_cache; // (max_pos, 64) fp32 [cos..., sin...] const void* __restrict__ positions; // (B,) PosT // Row stride for `k_input` (caller passes the non-contiguous wk slice directly). int64_t k_input_stride_batch; uint32_t batch_size; float eps; }; template K_INDEXER_KERNEL void fused_k_indexer_norm_rope_store(const __grid_constant__ FusedKIndexerNormRopeStoreParams params) { using namespace device; constexpr int64_t kHeadDim = 128; constexpr int64_t kRopeDim = 64; constexpr int64_t kVecSize = 4; constexpr uint32_t kRopeSize = kRopeDim / kVecSize; // = 16 constexpr int64_t kPageBytes = 132ll << kPageBits; static_assert(kHeadDim == kWarpThreads * kVecSize); static_assert(kRopeDim == kWarpThreads * 2); static_assert(kRopeSize <= kWarpThreads); using Storage = AlignedVector; using Float4 = AlignedVector; using OutStorage = AlignedVector; // 4 fp8 / lane const auto warp_id = threadIdx.x / kWarpThreads; const auto lane_id = threadIdx.x % kWarpThreads; const auto work_id = blockIdx.x * kFusedKIndexerNumWarps + warp_id; const bool is_rope_lane = lane_id < kRopeSize; if (work_id >= params.batch_size) return; const auto input_ptr = static_cast(params.k_input) + work_id * params.k_input_stride_batch; const auto position = static_cast(static_cast(params.positions)[work_id]); const auto cos_sin_cache = params.cos_sin_cache + position * kRopeDim; PDLWaitPrimary(); Float4 data, freq, gamma, beta; // part 1: LayerNorm { Storage input_vec; input_vec.load(input_ptr, lane_id); gamma.load(params.weight, lane_id); beta.load(params.bias, lane_id); if (is_rope_lane) freq = load_rope_first_cos_sin(cos_sin_cache, lane_id); float sum = 0.0f; #pragma unroll for (int i = 0; i < kVecSize; ++i) { data[i] = cast(input_vec[i]); sum += data[i]; } const float mean = warp::reduce_sum(sum) / kHeadDim; float var = 0.0f; #pragma unroll for (int i = 0; i < kVecSize; ++i) { const float centered = data[i] - mean; var += centered * centered; } const float inv_std = math::rsqrt(warp::reduce_sum(var) / kHeadDim + params.eps); #pragma unroll for (int i = 0; i < kVecSize; ++i) { data[i] = (data[i] - mean) * inv_std * gamma[i] + beta[i]; } } // part 2: rope on rope lanes if (is_rope_lane) { const auto x_real = data[0]; const auto x_imag = data[1]; const auto y_real = data[2]; const auto y_imag = data[3]; const auto fxr = freq[0]; const auto fxi = freq[1]; const auto fyr = freq[2]; const auto fyi = freq[3]; data[0] = x_real * fxr - x_imag * fxi; data[1] = x_real * fxi + x_imag * fxr; data[2] = y_real * fyr - y_imag * fyi; data[3] = y_real * fyi + y_imag * fyr; } PDLTriggerSecondary(); // part 3: fp8 act-quant + paged store. Round through bf16 first so the fp8 // scale matches the un-fused path. #pragma unroll for (int i = 0; i < kVecSize; ++i) data[i] = cast(cast(data[i])); float local_max = math::abs(data[0]); #pragma unroll for (int i = 1; i < kVecSize; ++i) local_max = math::max(local_max, math::abs(data[i])); const auto abs_max = warp::reduce_max(local_max); const auto scale = fmaxf(1e-4f, abs_max) / math::FP8_E4M3_MAX; const auto inv_scale = 1.0f / scale; const auto index = static_cast(params.indices)[work_id]; const int32_t page = static_cast(index >> kPageBits); const int32_t offset = static_cast(index & ((1 << kPageBits) - 1)); const auto page_ptr = static_cast(params.cache) + page * kPageBytes; const auto value_ptr = page_ptr + offset * kHeadDim; const auto scale_ptr = page_ptr + (kHeadDim << kPageBits) + offset * 4; OutStorage result; result[0] = pack_fp8(data[0] * inv_scale, data[1] * inv_scale); result[1] = pack_fp8(data[2] * inv_scale, data[3] * inv_scale); reinterpret_cast(value_ptr)[lane_id] = result; if (lane_id == 0) *reinterpret_cast(scale_ptr) = scale; } template struct FusedKIndexerNormRopeStoreKernel { static constexpr int32_t kPageBits = std::countr_zero(kPageSize); static constexpr int64_t kPageBytes = 132ll * kPageSize; static_assert(std::has_single_bit(kPageSize), "kPageSize must be a power of 2"); template static constexpr auto kernel = fused_k_indexer_norm_rope_store; static void forward( const tvm::ffi::TensorView k_input, const tvm::ffi::TensorView cache, const tvm::ffi::TensorView indices, const tvm::ffi::TensorView weight, const tvm::ffi::TensorView bias, const tvm::ffi::TensorView cos_sin_cache, const tvm::ffi::TensorView positions, double eps) { using namespace host; constexpr int64_t kHeadDim = 128; constexpr int64_t kRopeDim = 64; auto B = SymbolicSize{"batch_size"}; auto device_ = SymbolicDevice{}; device_.set_options(); TensorMatcher({B, kHeadDim}) // .with_strides({-1, 1}) .with_dtype() .with_device(device_) .verify(k_input); TensorMatcher({-1, -1}) // .with_strides({kPageBytes, 1}) .with_dtype() .with_device(device_) .verify(cache); TensorMatcher({B}) // .with_dtype() .with_device(device_) .verify(indices); TensorMatcher({kHeadDim}) // .with_dtype() .with_device(device_) .verify(weight); TensorMatcher({kHeadDim}) // .with_dtype() .with_device(device_) .verify(bias); TensorMatcher({-1, kRopeDim}) // .with_dtype() .with_device(device_) .verify(cos_sin_cache); auto pos_dtype = SymbolicDType{}; TensorMatcher({B}) // .with_dtype(pos_dtype) .with_device(device_) .verify(positions); const auto batch_size = static_cast(B.unwrap()); if (batch_size == 0) return; const auto params = FusedKIndexerNormRopeStoreParams{ .k_input = k_input.data_ptr(), .cache = cache.data_ptr(), .indices = indices.data_ptr(), .weight = static_cast(weight.data_ptr()), .bias = static_cast(bias.data_ptr()), .cos_sin_cache = static_cast(cos_sin_cache.data_ptr()), .positions = positions.data_ptr(), .k_input_stride_batch = k_input.stride(0), .batch_size = batch_size, .eps = static_cast(eps), }; const auto num_blocks = div_ceil(batch_size, kFusedKIndexerNumWarps); const auto k_int32 = kernel; const auto k_int64 = kernel; const auto k = pos_dtype.is_type() ? k_int32 : k_int64; LaunchKernel(num_blocks, kFusedKIndexerBlockSize, device_.unwrap()) // .enable_pdl(kUsePDL)(k, params); } }; } // namespace