[DSA] Use cos_sin_cache for DSA indexer fusion (#29613)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
// DeepSeek-V3.2 only.
|
// DeepSeek-V3.2 only.
|
||||||
//
|
//
|
||||||
// DSA indexer K kernels: single-head LayerNorm (not RMS), ropes the leading
|
// DSA indexer K kernels: single-head LayerNorm (not RMS), ropes the leading
|
||||||
// kRopeDim dims, and fp8-quantizes the un-rotated activations. V3.2 drops the
|
// kRopeDim dims, and fp8-quantizes the rotated activations. V3.2 drops the
|
||||||
// Hadamard incoherence rotation; it is logit-preserving (see main_norm_rope.cuh).
|
// 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`
|
// Independent of the wk + weights_proj GEMM fusion (dsa_indexer.py): `k_input`
|
||||||
@@ -33,13 +33,27 @@ constexpr uint32_t kFusedKIndexerNumWarps = kFusedKIndexerBlockSize / device::kW
|
|||||||
|
|
||||||
#define K_INDEXER_KERNEL __global__ __launch_bounds__(kFusedKIndexerBlockSize, 16)
|
#define K_INDEXER_KERNEL __global__ __launch_bounds__(kFusedKIndexerBlockSize, 16)
|
||||||
|
|
||||||
|
template <int64_t kRopeDim>
|
||||||
|
SGL_DEVICE device::AlignedVector<float, 4>
|
||||||
|
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<float, 4> 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.
|
// Indexer K: LayerNorm + RoPE -> bf16.
|
||||||
struct FusedKIndexerNormRopeParams {
|
struct FusedKIndexerNormRopeParams {
|
||||||
const void* __restrict__ k_input; // (B, 128) DType
|
const void* __restrict__ k_input; // (B, 128) DType
|
||||||
void* __restrict__ k_out; // (B, 128) DType
|
void* __restrict__ k_out; // (B, 128) DType
|
||||||
const float* __restrict__ weight; // (128,) fp32 -- LayerNorm gamma
|
const float* __restrict__ weight; // (128,) fp32 -- LayerNorm gamma
|
||||||
const float* __restrict__ bias; // (128,) fp32 -- LayerNorm beta
|
const float* __restrict__ bias; // (128,) fp32 -- LayerNorm beta
|
||||||
const float* __restrict__ freqs_cis; // (max_pos, 64) fp32
|
const float* __restrict__ cos_sin_cache; // (max_pos, 64) fp32 [cos..., sin...]
|
||||||
const void* __restrict__ positions; // (B,) PosT
|
const void* __restrict__ positions; // (B,) PosT
|
||||||
// Row stride for `k_input` in elements (caller passes the wk slice directly).
|
// Row stride for `k_input` in elements (caller passes the wk slice directly).
|
||||||
int64_t k_input_stride_batch;
|
int64_t k_input_stride_batch;
|
||||||
@@ -71,7 +85,7 @@ K_INDEXER_KERNEL void fused_k_indexer_norm_rope(const __grid_constant__ FusedKIn
|
|||||||
|
|
||||||
const auto input_ptr = static_cast<const DType*>(params.k_input) + work_id * params.k_input_stride_batch;
|
const auto input_ptr = static_cast<const DType*>(params.k_input) + work_id * params.k_input_stride_batch;
|
||||||
const auto position = static_cast<int32_t>(static_cast<const PosT*>(params.positions)[work_id]);
|
const auto position = static_cast<int32_t>(static_cast<const PosT*>(params.positions)[work_id]);
|
||||||
const auto freqs_cis = params.freqs_cis + position * kRopeDim;
|
const auto cos_sin_cache = params.cos_sin_cache + position * kRopeDim;
|
||||||
|
|
||||||
PDLWaitPrimary<kUsePDL>();
|
PDLWaitPrimary<kUsePDL>();
|
||||||
Float4 data, freq, gamma, beta;
|
Float4 data, freq, gamma, beta;
|
||||||
@@ -82,7 +96,7 @@ K_INDEXER_KERNEL void fused_k_indexer_norm_rope(const __grid_constant__ FusedKIn
|
|||||||
input_vec.load(input_ptr, lane_id);
|
input_vec.load(input_ptr, lane_id);
|
||||||
gamma.load(params.weight, lane_id);
|
gamma.load(params.weight, lane_id);
|
||||||
beta.load(params.bias, lane_id);
|
beta.load(params.bias, lane_id);
|
||||||
if (is_rope_lane) freq.load(freqs_cis, lane_id);
|
if (is_rope_lane) freq = load_rope_first_cos_sin<kRopeDim>(cos_sin_cache, lane_id);
|
||||||
|
|
||||||
float sum = 0.0f;
|
float sum = 0.0f;
|
||||||
#pragma unroll
|
#pragma unroll
|
||||||
@@ -144,7 +158,7 @@ struct FusedKIndexerNormRopeKernel {
|
|||||||
const tvm::ffi::TensorView k_out,
|
const tvm::ffi::TensorView k_out,
|
||||||
const tvm::ffi::TensorView weight,
|
const tvm::ffi::TensorView weight,
|
||||||
const tvm::ffi::TensorView bias,
|
const tvm::ffi::TensorView bias,
|
||||||
const tvm::ffi::TensorView freqs_cis,
|
const tvm::ffi::TensorView cos_sin_cache,
|
||||||
const tvm::ffi::TensorView positions,
|
const tvm::ffi::TensorView positions,
|
||||||
double eps) {
|
double eps) {
|
||||||
using namespace host;
|
using namespace host;
|
||||||
@@ -176,7 +190,7 @@ struct FusedKIndexerNormRopeKernel {
|
|||||||
TensorMatcher({-1, kRopeDim}) //
|
TensorMatcher({-1, kRopeDim}) //
|
||||||
.with_dtype<float>()
|
.with_dtype<float>()
|
||||||
.with_device(device_)
|
.with_device(device_)
|
||||||
.verify(freqs_cis);
|
.verify(cos_sin_cache);
|
||||||
auto pos_dtype = SymbolicDType{};
|
auto pos_dtype = SymbolicDType{};
|
||||||
TensorMatcher({B}) //
|
TensorMatcher({B}) //
|
||||||
.with_dtype<int32_t, int64_t>(pos_dtype)
|
.with_dtype<int32_t, int64_t>(pos_dtype)
|
||||||
@@ -191,7 +205,7 @@ struct FusedKIndexerNormRopeKernel {
|
|||||||
.k_out = k_out.data_ptr(),
|
.k_out = k_out.data_ptr(),
|
||||||
.weight = static_cast<const float*>(weight.data_ptr()),
|
.weight = static_cast<const float*>(weight.data_ptr()),
|
||||||
.bias = static_cast<const float*>(bias.data_ptr()),
|
.bias = static_cast<const float*>(bias.data_ptr()),
|
||||||
.freqs_cis = static_cast<const float*>(freqs_cis.data_ptr()),
|
.cos_sin_cache = static_cast<const float*>(cos_sin_cache.data_ptr()),
|
||||||
.positions = positions.data_ptr(),
|
.positions = positions.data_ptr(),
|
||||||
.k_input_stride_batch = k_input.stride(0),
|
.k_input_stride_batch = k_input.stride(0),
|
||||||
.batch_size = batch_size,
|
.batch_size = batch_size,
|
||||||
@@ -215,7 +229,7 @@ struct FusedKIndexerNormRopeStoreParams {
|
|||||||
const void* __restrict__ indices; // (B,) int64 -- out_cache_loc
|
const void* __restrict__ indices; // (B,) int64 -- out_cache_loc
|
||||||
const float* __restrict__ weight; // (128,) fp32 -- LayerNorm gamma
|
const float* __restrict__ weight; // (128,) fp32 -- LayerNorm gamma
|
||||||
const float* __restrict__ bias; // (128,) fp32 -- LayerNorm beta
|
const float* __restrict__ bias; // (128,) fp32 -- LayerNorm beta
|
||||||
const float* __restrict__ freqs_cis; // (max_pos, 64) fp32
|
const float* __restrict__ cos_sin_cache; // (max_pos, 64) fp32 [cos..., sin...]
|
||||||
const void* __restrict__ positions; // (B,) PosT
|
const void* __restrict__ positions; // (B,) PosT
|
||||||
// Row stride for `k_input` (caller passes the non-contiguous wk slice directly).
|
// Row stride for `k_input` (caller passes the non-contiguous wk slice directly).
|
||||||
int64_t k_input_stride_batch;
|
int64_t k_input_stride_batch;
|
||||||
@@ -249,7 +263,7 @@ K_INDEXER_KERNEL void fused_k_indexer_norm_rope_store(const __grid_constant__ Fu
|
|||||||
|
|
||||||
const auto input_ptr = static_cast<const DType*>(params.k_input) + work_id * params.k_input_stride_batch;
|
const auto input_ptr = static_cast<const DType*>(params.k_input) + work_id * params.k_input_stride_batch;
|
||||||
const auto position = static_cast<int32_t>(static_cast<const PosT*>(params.positions)[work_id]);
|
const auto position = static_cast<int32_t>(static_cast<const PosT*>(params.positions)[work_id]);
|
||||||
const auto freqs_cis = params.freqs_cis + position * kRopeDim;
|
const auto cos_sin_cache = params.cos_sin_cache + position * kRopeDim;
|
||||||
|
|
||||||
PDLWaitPrimary<kUsePDL>();
|
PDLWaitPrimary<kUsePDL>();
|
||||||
Float4 data, freq, gamma, beta;
|
Float4 data, freq, gamma, beta;
|
||||||
@@ -260,7 +274,7 @@ K_INDEXER_KERNEL void fused_k_indexer_norm_rope_store(const __grid_constant__ Fu
|
|||||||
input_vec.load(input_ptr, lane_id);
|
input_vec.load(input_ptr, lane_id);
|
||||||
gamma.load(params.weight, lane_id);
|
gamma.load(params.weight, lane_id);
|
||||||
beta.load(params.bias, lane_id);
|
beta.load(params.bias, lane_id);
|
||||||
if (is_rope_lane) freq.load(freqs_cis, lane_id);
|
if (is_rope_lane) freq = load_rope_first_cos_sin<kRopeDim>(cos_sin_cache, lane_id);
|
||||||
|
|
||||||
float sum = 0.0f;
|
float sum = 0.0f;
|
||||||
#pragma unroll
|
#pragma unroll
|
||||||
@@ -345,7 +359,7 @@ struct FusedKIndexerNormRopeStoreKernel {
|
|||||||
const tvm::ffi::TensorView indices,
|
const tvm::ffi::TensorView indices,
|
||||||
const tvm::ffi::TensorView weight,
|
const tvm::ffi::TensorView weight,
|
||||||
const tvm::ffi::TensorView bias,
|
const tvm::ffi::TensorView bias,
|
||||||
const tvm::ffi::TensorView freqs_cis,
|
const tvm::ffi::TensorView cos_sin_cache,
|
||||||
const tvm::ffi::TensorView positions,
|
const tvm::ffi::TensorView positions,
|
||||||
double eps) {
|
double eps) {
|
||||||
using namespace host;
|
using namespace host;
|
||||||
@@ -381,7 +395,7 @@ struct FusedKIndexerNormRopeStoreKernel {
|
|||||||
TensorMatcher({-1, kRopeDim}) //
|
TensorMatcher({-1, kRopeDim}) //
|
||||||
.with_dtype<float>()
|
.with_dtype<float>()
|
||||||
.with_device(device_)
|
.with_device(device_)
|
||||||
.verify(freqs_cis);
|
.verify(cos_sin_cache);
|
||||||
auto pos_dtype = SymbolicDType{};
|
auto pos_dtype = SymbolicDType{};
|
||||||
TensorMatcher({B}) //
|
TensorMatcher({B}) //
|
||||||
.with_dtype<int32_t, int64_t>(pos_dtype)
|
.with_dtype<int32_t, int64_t>(pos_dtype)
|
||||||
@@ -397,7 +411,7 @@ struct FusedKIndexerNormRopeStoreKernel {
|
|||||||
.indices = indices.data_ptr(),
|
.indices = indices.data_ptr(),
|
||||||
.weight = static_cast<const float*>(weight.data_ptr()),
|
.weight = static_cast<const float*>(weight.data_ptr()),
|
||||||
.bias = static_cast<const float*>(bias.data_ptr()),
|
.bias = static_cast<const float*>(bias.data_ptr()),
|
||||||
.freqs_cis = static_cast<const float*>(freqs_cis.data_ptr()),
|
.cos_sin_cache = static_cast<const float*>(cos_sin_cache.data_ptr()),
|
||||||
.positions = positions.data_ptr(),
|
.positions = positions.data_ptr(),
|
||||||
.k_input_stride_batch = k_input.stride(0),
|
.k_input_stride_batch = k_input.stride(0),
|
||||||
.batch_size = batch_size,
|
.batch_size = batch_size,
|
||||||
|
|||||||
@@ -46,6 +46,20 @@ constexpr uint32_t kFusedKNumWarps = kFusedKBlockSize / device::kWarpThreads;
|
|||||||
#define Q_KERNEL __global__ __launch_bounds__(kFusedQBlockSize, 16)
|
#define Q_KERNEL __global__ __launch_bounds__(kFusedQBlockSize, 16)
|
||||||
#define K_KERNEL __global__ __launch_bounds__(kFusedKBlockSize, 8)
|
#define K_KERNEL __global__ __launch_bounds__(kFusedKBlockSize, 8)
|
||||||
|
|
||||||
|
template <int64_t kRopeDim>
|
||||||
|
SGL_DEVICE device::AlignedVector<float, 4>
|
||||||
|
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<float, 4> 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;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Q kernel: warp-per-(token, head) rmsnorm-self + RoPE + write to q_out.
|
// Q kernel: warp-per-(token, head) rmsnorm-self + RoPE + write to q_out.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -423,7 +437,10 @@ struct FusedQIndexerRopeHadamardQuantParams {
|
|||||||
const void* __restrict__ weight; // (B, num_heads) DType
|
const void* __restrict__ weight; // (B, num_heads) DType
|
||||||
float* __restrict__ weights_out; // (B, num_heads) fp32 (== (B, H, 1) flat)
|
float* __restrict__ weights_out; // (B, num_heads) fp32 (== (B, H, 1) flat)
|
||||||
float weight_scale; // scalar c4_indexer.weight_scale
|
float weight_scale; // scalar c4_indexer.weight_scale
|
||||||
const float* __restrict__ freqs_cis; // (max_pos, 64) fp32
|
// Template-dependent layout:
|
||||||
|
// kRopeFirst=false: (max_pos, 64) fp32 interleaved [cos0, sin0, ...]
|
||||||
|
// kRopeFirst=true : (max_pos, 64) fp32 halves [cos..., sin...]
|
||||||
|
const float* __restrict__ rope_cache;
|
||||||
const void* __restrict__ positions; // (B,) PosT
|
const void* __restrict__ positions; // (B,) PosT
|
||||||
// Row stride for `weight` (caller passes the non-contiguous wk slice directly).
|
// Row stride for `weight` (caller passes the non-contiguous wk slice directly).
|
||||||
int64_t weight_stride_batch;
|
int64_t weight_stride_batch;
|
||||||
@@ -460,7 +477,7 @@ Q_KERNEL void fused_q_indexer_rope_hadamard_quant(const __grid_constant__ FusedQ
|
|||||||
const uint32_t batch_id = work_id / params.num_heads;
|
const uint32_t batch_id = work_id / params.num_heads;
|
||||||
const auto input_ptr = static_cast<const DType*>(params.q_input) + work_id * kHeadDim;
|
const auto input_ptr = static_cast<const DType*>(params.q_input) + work_id * kHeadDim;
|
||||||
const auto position = static_cast<int32_t>(static_cast<const PosT*>(params.positions)[batch_id]);
|
const auto position = static_cast<int32_t>(static_cast<const PosT*>(params.positions)[batch_id]);
|
||||||
const auto freqs_cis = params.freqs_cis + position * kRopeDim;
|
const auto rope_cache = params.rope_cache + position * kRopeDim;
|
||||||
|
|
||||||
// Lane 0 prefetches the weight scalar for this (token, head) work item.
|
// Lane 0 prefetches the weight scalar for this (token, head) work item.
|
||||||
// Weight is (B, num_heads) DType; we need one scalar per warp -- offload
|
// Weight is (B, num_heads) DType; we need one scalar per warp -- offload
|
||||||
@@ -477,7 +494,13 @@ Q_KERNEL void fused_q_indexer_rope_hadamard_quant(const __grid_constant__ FusedQ
|
|||||||
{
|
{
|
||||||
Storage input_vec;
|
Storage input_vec;
|
||||||
input_vec.load(input_ptr, lane_id);
|
input_vec.load(input_ptr, lane_id);
|
||||||
if (is_rope_lane) freq.load(freqs_cis, kRopeFirst ? lane_id : (lane_id - (kWarpThreads - kRopeSize)));
|
if (is_rope_lane) {
|
||||||
|
if constexpr (kRopeFirst) {
|
||||||
|
freq = load_rope_first_cos_sin<kRopeDim>(rope_cache, lane_id);
|
||||||
|
} else {
|
||||||
|
freq.load(rope_cache, lane_id - (kWarpThreads - kRopeSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
#pragma unroll
|
#pragma unroll
|
||||||
for (int i = 0; i < kVecSize; ++i) {
|
for (int i = 0; i < kVecSize; ++i) {
|
||||||
data[i] = cast<float>(input_vec[i]);
|
data[i] = cast<float>(input_vec[i]);
|
||||||
@@ -567,7 +590,7 @@ struct FusedQIndexerRopeHadamardQuantKernel {
|
|||||||
const tvm::ffi::TensorView weight,
|
const tvm::ffi::TensorView weight,
|
||||||
const tvm::ffi::TensorView weights_out,
|
const tvm::ffi::TensorView weights_out,
|
||||||
double weight_scale,
|
double weight_scale,
|
||||||
const tvm::ffi::TensorView freqs_cis,
|
const tvm::ffi::TensorView rope_cache,
|
||||||
const tvm::ffi::TensorView positions) {
|
const tvm::ffi::TensorView positions) {
|
||||||
using namespace host;
|
using namespace host;
|
||||||
constexpr int64_t kHeadDim = 128;
|
constexpr int64_t kHeadDim = 128;
|
||||||
@@ -603,7 +626,7 @@ struct FusedQIndexerRopeHadamardQuantKernel {
|
|||||||
TensorMatcher({-1, kRopeDim}) //
|
TensorMatcher({-1, kRopeDim}) //
|
||||||
.with_dtype<float>()
|
.with_dtype<float>()
|
||||||
.with_device(device_)
|
.with_device(device_)
|
||||||
.verify(freqs_cis);
|
.verify(rope_cache);
|
||||||
auto pos_dtype = SymbolicDType{};
|
auto pos_dtype = SymbolicDType{};
|
||||||
TensorMatcher({B}) //
|
TensorMatcher({B}) //
|
||||||
.with_dtype<int32_t, int64_t>(pos_dtype)
|
.with_dtype<int32_t, int64_t>(pos_dtype)
|
||||||
@@ -632,7 +655,7 @@ struct FusedQIndexerRopeHadamardQuantKernel {
|
|||||||
.weight = weight.data_ptr(),
|
.weight = weight.data_ptr(),
|
||||||
.weights_out = static_cast<float*>(weights_out.data_ptr()),
|
.weights_out = static_cast<float*>(weights_out.data_ptr()),
|
||||||
.weight_scale = static_cast<float>(weight_scale),
|
.weight_scale = static_cast<float>(weight_scale),
|
||||||
.freqs_cis = static_cast<const float*>(freqs_cis.data_ptr()),
|
.rope_cache = static_cast<const float*>(rope_cache.data_ptr()),
|
||||||
.positions = positions.data_ptr(),
|
.positions = positions.data_ptr(),
|
||||||
.weight_stride_batch = weight.stride(0),
|
.weight_stride_batch = weight.stride(0),
|
||||||
.batch_size = batch_size,
|
.batch_size = batch_size,
|
||||||
|
|||||||
@@ -43,11 +43,10 @@ def fused_k_indexer_norm_rope(
|
|||||||
weight: torch.Tensor,
|
weight: torch.Tensor,
|
||||||
bias: torch.Tensor,
|
bias: torch.Tensor,
|
||||||
eps: float,
|
eps: float,
|
||||||
freqs_cis: torch.Tensor,
|
cos_sin_cache: torch.Tensor,
|
||||||
positions: torch.Tensor,
|
positions: torch.Tensor,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""V3.2 indexer K: LayerNorm + RoPE on leading dims -> bf16. CUDA only."""
|
"""V3.2 indexer K: LayerNorm + RoPE on leading dims -> bf16. CUDA only."""
|
||||||
freqs_real = torch.view_as_real(freqs_cis).flatten(-2)
|
|
||||||
# k_input may be a non-contiguous wk slice; output is always contiguous.
|
# k_input may be a non-contiguous wk slice; output is always contiguous.
|
||||||
k_out = torch.empty(k_input.shape, dtype=k_input.dtype, device=k_input.device)
|
k_out = torch.empty(k_input.shape, dtype=k_input.dtype, device=k_input.device)
|
||||||
module = _jit_k_indexer_norm_rope_module(k_input.dtype)
|
module = _jit_k_indexer_norm_rope_module(k_input.dtype)
|
||||||
@@ -56,7 +55,7 @@ def fused_k_indexer_norm_rope(
|
|||||||
k_out,
|
k_out,
|
||||||
weight,
|
weight,
|
||||||
bias,
|
bias,
|
||||||
freqs_real,
|
cos_sin_cache,
|
||||||
positions,
|
positions,
|
||||||
float(eps),
|
float(eps),
|
||||||
)
|
)
|
||||||
@@ -70,13 +69,12 @@ def fused_k_indexer_norm_rope_store(
|
|||||||
weight: torch.Tensor,
|
weight: torch.Tensor,
|
||||||
bias: torch.Tensor,
|
bias: torch.Tensor,
|
||||||
eps: float,
|
eps: float,
|
||||||
freqs_cis: torch.Tensor,
|
cos_sin_cache: torch.Tensor,
|
||||||
positions: torch.Tensor,
|
positions: torch.Tensor,
|
||||||
page_size: int,
|
page_size: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""V3.2 indexer K + fused store: LayerNorm + RoPE on leading dims + fp8
|
"""V3.2 indexer K + fused store: LayerNorm + RoPE on leading dims + fp8
|
||||||
act-quant + paged index-k cache write, in one launch. CUDA only."""
|
act-quant + paged index-k cache write, in one launch. CUDA only."""
|
||||||
freqs_real = torch.view_as_real(freqs_cis).flatten(-2)
|
|
||||||
if not out_cache_loc.is_contiguous():
|
if not out_cache_loc.is_contiguous():
|
||||||
out_cache_loc = out_cache_loc.contiguous()
|
out_cache_loc = out_cache_loc.contiguous()
|
||||||
module = _jit_k_indexer_norm_rope_store_module(k_input.dtype, page_size)
|
module = _jit_k_indexer_norm_rope_store_module(k_input.dtype, page_size)
|
||||||
@@ -86,7 +84,7 @@ def fused_k_indexer_norm_rope_store(
|
|||||||
out_cache_loc,
|
out_cache_loc,
|
||||||
weight,
|
weight,
|
||||||
bias,
|
bias,
|
||||||
freqs_real,
|
cos_sin_cache,
|
||||||
positions,
|
positions,
|
||||||
float(eps),
|
float(eps),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -188,11 +188,10 @@ def fused_q_indexer_rope_first_quant(
|
|||||||
q_input: torch.Tensor,
|
q_input: torch.Tensor,
|
||||||
weight: torch.Tensor,
|
weight: torch.Tensor,
|
||||||
weight_scale: float,
|
weight_scale: float,
|
||||||
freqs_cis: torch.Tensor,
|
cos_sin_cache: torch.Tensor,
|
||||||
positions: torch.Tensor,
|
positions: torch.Tensor,
|
||||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||||
"""DeepSeek-V3.2 only. Indexer Q: RoPE on the leading dims + fp8 act-quant. CUDA only."""
|
"""DeepSeek-V3.2 only. Indexer Q: RoPE on the leading dims + fp8 act-quant. CUDA only."""
|
||||||
freqs_real = torch.view_as_real(freqs_cis).flatten(-2)
|
|
||||||
q_fp8 = torch.empty(q_input.shape, dtype=torch.float8_e4m3fn, device=q_input.device)
|
q_fp8 = torch.empty(q_input.shape, dtype=torch.float8_e4m3fn, device=q_input.device)
|
||||||
weights_out = torch.empty(
|
weights_out = torch.empty(
|
||||||
(*q_input.shape[:-1], 1), dtype=torch.float32, device=q_input.device
|
(*q_input.shape[:-1], 1), dtype=torch.float32, device=q_input.device
|
||||||
@@ -204,7 +203,7 @@ def fused_q_indexer_rope_first_quant(
|
|||||||
weight,
|
weight,
|
||||||
weights_out,
|
weights_out,
|
||||||
float(weight_scale),
|
float(weight_scale),
|
||||||
freqs_real,
|
cos_sin_cache,
|
||||||
positions,
|
positions,
|
||||||
)
|
)
|
||||||
return q_fp8, weights_out
|
return q_fp8, weights_out
|
||||||
|
|||||||
@@ -327,16 +327,6 @@ def rotate_activation(x: torch.Tensor) -> torch.Tensor:
|
|||||||
return hadamard_transform(x, scale=hidden_size**-0.5)
|
return hadamard_transform(x, scale=hidden_size**-0.5)
|
||||||
|
|
||||||
|
|
||||||
def _shared_indexer_freqs_cis(rotary_emb: torch.nn.Module) -> torch.Tensor:
|
|
||||||
cached = getattr(rotary_emb, "_dsa_indexer_freqs_cis", None)
|
|
||||||
if cached is None:
|
|
||||||
c = rotary_emb.cos_sin_cache.to(torch.float32)
|
|
||||||
half = c.shape[-1] // 2
|
|
||||||
cached = torch.complex(c[:, :half].contiguous(), c[:, half:].contiguous())
|
|
||||||
rotary_emb._dsa_indexer_freqs_cis = cached
|
|
||||||
return cached
|
|
||||||
|
|
||||||
|
|
||||||
class Indexer(MultiPlatformOp):
|
class Indexer(MultiPlatformOp):
|
||||||
_MQA_LOGITS_BYTES_PER_ELEM = 4
|
_MQA_LOGITS_BYTES_PER_ELEM = 4
|
||||||
_MQA_LOGITS_STATIC_SKIP_ELEMS = 8_000_000
|
_MQA_LOGITS_STATIC_SKIP_ELEMS = 8_000_000
|
||||||
@@ -437,10 +427,6 @@ class Indexer(MultiPlatformOp):
|
|||||||
self.scale_fmt = scale_fmt
|
self.scale_fmt = scale_fmt
|
||||||
self.softmax_scale = self.head_dim**-0.5
|
self.softmax_scale = self.head_dim**-0.5
|
||||||
|
|
||||||
self._indexer_freqs_cis: Optional[torch.Tensor] = None
|
|
||||||
if _use_dsa_indexer_fusion:
|
|
||||||
self._indexer_freqs_cis = _shared_indexer_freqs_cis(self.rotary_emb)
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _with_real_sm_count(self):
|
def _with_real_sm_count(self):
|
||||||
# When pipeline parallelism is enabled, each PP rank initiates a recv operation after the _pp_launch_batch
|
# When pipeline parallelism is enabled, each PP rank initiates a recv operation after the _pp_launch_batch
|
||||||
@@ -456,6 +442,10 @@ class Indexer(MultiPlatformOp):
|
|||||||
else:
|
else:
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _indexer_cos_sin_cache(self) -> torch.Tensor:
|
||||||
|
return self.rotary_emb.cos_sin_cache
|
||||||
|
|
||||||
def _weights_proj_bf16_in_fp32_out(
|
def _weights_proj_bf16_in_fp32_out(
|
||||||
self, x: Union[torch.Tensor, Tuple[torch.Tensor, ...]]
|
self, x: Union[torch.Tensor, Tuple[torch.Tensor, ...]]
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
@@ -662,7 +652,7 @@ class Indexer(MultiPlatformOp):
|
|||||||
self.k_norm.weight,
|
self.k_norm.weight,
|
||||||
self.k_norm.bias,
|
self.k_norm.bias,
|
||||||
self.k_norm.variance_epsilon,
|
self.k_norm.variance_epsilon,
|
||||||
self._indexer_freqs_cis,
|
self._indexer_cos_sin_cache,
|
||||||
positions,
|
positions,
|
||||||
page_size,
|
page_size,
|
||||||
)
|
)
|
||||||
@@ -674,7 +664,7 @@ class Indexer(MultiPlatformOp):
|
|||||||
self.k_norm.weight,
|
self.k_norm.weight,
|
||||||
self.k_norm.bias,
|
self.k_norm.bias,
|
||||||
self.k_norm.variance_epsilon,
|
self.k_norm.variance_epsilon,
|
||||||
self._indexer_freqs_cis,
|
self._indexer_cos_sin_cache,
|
||||||
positions,
|
positions,
|
||||||
)
|
)
|
||||||
self._store_index_k_cache(
|
self._store_index_k_cache(
|
||||||
@@ -726,7 +716,7 @@ class Indexer(MultiPlatformOp):
|
|||||||
q.contiguous(),
|
q.contiguous(),
|
||||||
weights_raw,
|
weights_raw,
|
||||||
q_scale_gate,
|
q_scale_gate,
|
||||||
self._indexer_freqs_cis,
|
self._indexer_cos_sin_cache,
|
||||||
positions,
|
positions,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -763,7 +753,7 @@ class Indexer(MultiPlatformOp):
|
|||||||
q.contiguous(),
|
q.contiguous(),
|
||||||
weights_raw,
|
weights_raw,
|
||||||
q_scale_gate,
|
q_scale_gate,
|
||||||
self._indexer_freqs_cis,
|
self._indexer_cos_sin_cache,
|
||||||
positions,
|
positions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ def _make_inputs(B, seed=0, pos_dtype=torch.int32):
|
|||||||
dev = "cuda"
|
dev = "cuda"
|
||||||
cos = torch.randn(MAX_POS, HALF, device=dev, generator=g)
|
cos = torch.randn(MAX_POS, HALF, device=dev, generator=g)
|
||||||
sin = torch.randn(MAX_POS, HALF, device=dev, generator=g)
|
sin = torch.randn(MAX_POS, HALF, device=dev, generator=g)
|
||||||
freqs_cis = torch.complex(cos, sin)
|
cos_sin_cache = torch.cat((cos, sin), dim=-1)
|
||||||
positions = torch.randint(0, 4096, (B,), device=dev, dtype=pos_dtype, generator=g)
|
positions = torch.randint(0, 4096, (B,), device=dev, dtype=pos_dtype, generator=g)
|
||||||
return cos, sin, freqs_cis, positions
|
return cos, sin, cos_sin_cache, positions
|
||||||
|
|
||||||
|
|
||||||
def _rope_first(x, cos_p, sin_p):
|
def _rope_first(x, cos_p, sin_p):
|
||||||
@@ -78,12 +78,12 @@ def test_k_norm_rope_matches_reference():
|
|||||||
_skip_if_unavailable()
|
_skip_if_unavailable()
|
||||||
dev = "cuda"
|
dev = "cuda"
|
||||||
B = 37
|
B = 37
|
||||||
cos, sin, freqs_cis, positions = _make_inputs(B)
|
cos, sin, cos_sin_cache, positions = _make_inputs(B)
|
||||||
key = torch.randn(B, HEAD_DIM, dtype=torch.bfloat16, device=dev)
|
key = torch.randn(B, HEAD_DIM, dtype=torch.bfloat16, device=dev)
|
||||||
weight = torch.randn(HEAD_DIM, dtype=torch.float32, device=dev)
|
weight = torch.randn(HEAD_DIM, dtype=torch.float32, device=dev)
|
||||||
bias = torch.randn(HEAD_DIM, dtype=torch.float32, device=dev)
|
bias = torch.randn(HEAD_DIM, dtype=torch.float32, device=dev)
|
||||||
|
|
||||||
out = fused_k_indexer_norm_rope(key, weight, bias, EPS, freqs_cis, positions)
|
out = fused_k_indexer_norm_rope(key, weight, bias, EPS, cos_sin_cache, positions)
|
||||||
torch.cuda.synchronize()
|
torch.cuda.synchronize()
|
||||||
|
|
||||||
normed = torch.nn.functional.layer_norm(
|
normed = torch.nn.functional.layer_norm(
|
||||||
@@ -106,7 +106,7 @@ def test_k_store_matches_unfused(strided):
|
|||||||
pytest.skip("fused store JIT unavailable")
|
pytest.skip("fused store JIT unavailable")
|
||||||
dev = "cuda"
|
dev = "cuda"
|
||||||
B, n_heads = 41, 64
|
B, n_heads = 41, 64
|
||||||
cos, sin, freqs_cis, positions = _make_inputs(B)
|
cos, sin, cos_sin_cache, positions = _make_inputs(B)
|
||||||
weight = torch.randn(HEAD_DIM, dtype=torch.float32, device=dev)
|
weight = torch.randn(HEAD_DIM, dtype=torch.float32, device=dev)
|
||||||
bias = torch.randn(HEAD_DIM, dtype=torch.float32, device=dev)
|
bias = torch.randn(HEAD_DIM, dtype=torch.float32, device=dev)
|
||||||
|
|
||||||
@@ -125,11 +125,13 @@ def test_k_store_matches_unfused(strided):
|
|||||||
)
|
)
|
||||||
buf_fused = torch.zeros_like(buf_ref)
|
buf_fused = torch.zeros_like(buf_ref)
|
||||||
|
|
||||||
key_bf16 = fused_k_indexer_norm_rope(key, weight, bias, EPS, freqs_cis, positions)
|
key_bf16 = fused_k_indexer_norm_rope(
|
||||||
|
key, weight, bias, EPS, cos_sin_cache, positions
|
||||||
|
)
|
||||||
fused_store_index_k_cache(key_bf16, buf_ref, loc, PAGE_SIZE)
|
fused_store_index_k_cache(key_bf16, buf_ref, loc, PAGE_SIZE)
|
||||||
|
|
||||||
fused_k_indexer_norm_rope_store(
|
fused_k_indexer_norm_rope_store(
|
||||||
key, buf_fused, loc, weight, bias, EPS, freqs_cis, positions, PAGE_SIZE
|
key, buf_fused, loc, weight, bias, EPS, cos_sin_cache, positions, PAGE_SIZE
|
||||||
)
|
)
|
||||||
torch.cuda.synchronize()
|
torch.cuda.synchronize()
|
||||||
|
|
||||||
@@ -144,13 +146,13 @@ def test_q_rope_quant_matches_reference(pos_dtype):
|
|||||||
_skip_if_unavailable()
|
_skip_if_unavailable()
|
||||||
dev = "cuda"
|
dev = "cuda"
|
||||||
B, n_heads = 37, 64
|
B, n_heads = 37, 64
|
||||||
cos, sin, freqs_cis, positions = _make_inputs(B, pos_dtype=pos_dtype)
|
cos, sin, cos_sin_cache, positions = _make_inputs(B, pos_dtype=pos_dtype)
|
||||||
q = torch.randn(B, n_heads, HEAD_DIM, dtype=torch.bfloat16, device=dev)
|
q = torch.randn(B, n_heads, HEAD_DIM, dtype=torch.bfloat16, device=dev)
|
||||||
weight = torch.randn(B, n_heads, dtype=torch.bfloat16, device=dev)
|
weight = torch.randn(B, n_heads, dtype=torch.bfloat16, device=dev)
|
||||||
weight_scale = 0.137
|
weight_scale = 0.137
|
||||||
|
|
||||||
q_fp8, weights_out = fused_q_indexer_rope_first_quant(
|
q_fp8, weights_out = fused_q_indexer_rope_first_quant(
|
||||||
q, weight, weight_scale, freqs_cis, positions
|
q, weight, weight_scale, cos_sin_cache, positions
|
||||||
)
|
)
|
||||||
torch.cuda.synchronize()
|
torch.cuda.synchronize()
|
||||||
|
|
||||||
@@ -181,7 +183,7 @@ def test_q_strided_weight_matches_contiguous():
|
|||||||
_skip_if_unavailable()
|
_skip_if_unavailable()
|
||||||
dev = "cuda"
|
dev = "cuda"
|
||||||
B, n_heads = 29, 64
|
B, n_heads = 29, 64
|
||||||
cos, sin, freqs_cis, positions = _make_inputs(B)
|
cos, sin, cos_sin_cache, positions = _make_inputs(B)
|
||||||
q = torch.randn(B, n_heads, HEAD_DIM, dtype=torch.bfloat16, device=dev)
|
q = torch.randn(B, n_heads, HEAD_DIM, dtype=torch.bfloat16, device=dev)
|
||||||
# weights_raw = kw[:, head_dim:] is a non-contiguous slice.
|
# weights_raw = kw[:, head_dim:] is a non-contiguous slice.
|
||||||
kw = torch.randn(B, HEAD_DIM + n_heads, dtype=torch.bfloat16, device=dev)
|
kw = torch.randn(B, HEAD_DIM + n_heads, dtype=torch.bfloat16, device=dev)
|
||||||
@@ -190,16 +192,77 @@ def test_q_strided_weight_matches_contiguous():
|
|||||||
assert not w_strided.is_contiguous()
|
assert not w_strided.is_contiguous()
|
||||||
|
|
||||||
a_fp8, a_w = fused_q_indexer_rope_first_quant(
|
a_fp8, a_w = fused_q_indexer_rope_first_quant(
|
||||||
q, w_strided, 0.137, freqs_cis, positions
|
q, w_strided, 0.137, cos_sin_cache, positions
|
||||||
)
|
)
|
||||||
b_fp8, b_w = fused_q_indexer_rope_first_quant(
|
b_fp8, b_w = fused_q_indexer_rope_first_quant(
|
||||||
q, w_contig, 0.137, freqs_cis, positions
|
q, w_contig, 0.137, cos_sin_cache, positions
|
||||||
)
|
)
|
||||||
torch.cuda.synchronize()
|
torch.cuda.synchronize()
|
||||||
assert torch.equal(a_fp8, b_fp8)
|
assert torch.equal(a_fp8, b_fp8)
|
||||||
assert torch.equal(a_w, b_w)
|
assert torch.equal(a_w, b_w)
|
||||||
|
|
||||||
|
|
||||||
|
def test_indexer_uses_replaced_rope_cache_for_fused_kernels():
|
||||||
|
_skip_if_unavailable()
|
||||||
|
from sglang.srt.layers.attention.dsa.dsa_indexer import Indexer
|
||||||
|
|
||||||
|
dev = "cuda"
|
||||||
|
B, n_heads = 7, 64
|
||||||
|
old_len = 16
|
||||||
|
new_len = 128
|
||||||
|
g = torch.Generator(device=dev).manual_seed(123)
|
||||||
|
old_cache = torch.randn(old_len, ROPE_DIM, dtype=torch.float32, device=dev)
|
||||||
|
cos = torch.randn(new_len, HALF, dtype=torch.float32, device=dev, generator=g)
|
||||||
|
sin = torch.randn(new_len, HALF, dtype=torch.float32, device=dev, generator=g)
|
||||||
|
grown_cache = torch.cat((cos, sin), dim=-1)
|
||||||
|
positions = torch.arange(old_len, old_len + B, device=dev, dtype=torch.int32)
|
||||||
|
|
||||||
|
class DummyRotary:
|
||||||
|
pass
|
||||||
|
|
||||||
|
rotary_emb = DummyRotary()
|
||||||
|
rotary_emb.cos_sin_cache = old_cache
|
||||||
|
indexer = Indexer.__new__(Indexer)
|
||||||
|
indexer.rotary_emb = rotary_emb
|
||||||
|
assert indexer._indexer_cos_sin_cache.data_ptr() == old_cache.data_ptr()
|
||||||
|
|
||||||
|
rotary_emb.cos_sin_cache = grown_cache
|
||||||
|
assert indexer._indexer_cos_sin_cache.data_ptr() == grown_cache.data_ptr()
|
||||||
|
|
||||||
|
key = torch.randn(B, HEAD_DIM, dtype=torch.bfloat16, device=dev, generator=g)
|
||||||
|
k_weight = torch.randn(HEAD_DIM, dtype=torch.float32, device=dev, generator=g)
|
||||||
|
k_bias = torch.randn(HEAD_DIM, dtype=torch.float32, device=dev, generator=g)
|
||||||
|
k_out = fused_k_indexer_norm_rope(
|
||||||
|
key, k_weight, k_bias, EPS, indexer._indexer_cos_sin_cache, positions
|
||||||
|
)
|
||||||
|
|
||||||
|
normed = torch.nn.functional.layer_norm(
|
||||||
|
key.float(), (HEAD_DIM,), weight=k_weight, bias=k_bias, eps=EPS
|
||||||
|
)
|
||||||
|
k_ref = _rope_first(normed, cos[positions.long()], sin[positions.long()])
|
||||||
|
torch.testing.assert_close(k_out.float(), k_ref, atol=0.06, rtol=0.0)
|
||||||
|
|
||||||
|
q = torch.randn(B, n_heads, HEAD_DIM, dtype=torch.bfloat16, device=dev, generator=g)
|
||||||
|
q_weight = torch.randn(B, n_heads, dtype=torch.bfloat16, device=dev, generator=g)
|
||||||
|
weight_scale = 0.137
|
||||||
|
q_fp8, weights_out = fused_q_indexer_rope_first_quant(
|
||||||
|
q, q_weight, weight_scale, indexer._indexer_cos_sin_cache, positions
|
||||||
|
)
|
||||||
|
torch.cuda.synchronize()
|
||||||
|
|
||||||
|
q_ref = _rope_first(
|
||||||
|
q.float(), cos[positions.long()][:, None, :], sin[positions.long()][:, None, :]
|
||||||
|
)
|
||||||
|
scale = torch.clamp(q_ref.abs().amax(dim=-1, keepdim=True), min=1e-4) / FP8_MAX
|
||||||
|
torch.testing.assert_close(
|
||||||
|
weights_out.squeeze(-1),
|
||||||
|
q_weight.float() * weight_scale * scale.squeeze(-1),
|
||||||
|
atol=1e-3,
|
||||||
|
rtol=1e-3,
|
||||||
|
)
|
||||||
|
assert ((q_fp8.float() * scale - q_ref).abs() <= 0.0625 * q_ref.abs() + scale).all()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user