[Refactor] Rename NSA → DSA: user-facing aliases, file/class/import rename (#25821)
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
da6d549ab2
commit
8131641bc6
@@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Fused metadata copy kernel for NSA backend CUDA graph replay.
|
||||
* Fused metadata copy kernel for DSA backend CUDA graph replay.
|
||||
* JIT-compiled version for python/sglang/jit_kernel.
|
||||
*
|
||||
* OVERVIEW:
|
||||
* This kernel fuses multiple tensor copy operations (cache_seqlens, cu_seqlens_k,
|
||||
* page_table, nsa metadata, and optional FlashMLA metadata) into single kernel
|
||||
* page_table, dsa metadata, and optional FlashMLA metadata) into single kernel
|
||||
* launches, significantly reducing kernel launch overhead and improving CUDA
|
||||
* graph replay performance during inference.
|
||||
*
|
||||
@@ -37,7 +37,7 @@
|
||||
#include <algorithm> // for std::min
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
// Forward mode enum (must match Python ForwardMode in sglang/srt/layers/attention/nsa_backend.py)
|
||||
// Forward mode enum (must match Python ForwardMode in sglang/srt/layers/attention/dsa_backend.py)
|
||||
enum ForwardModeEnum { DECODE = 0, TARGET_VERIFY = 1, DRAFT_EXTEND = 2 };
|
||||
|
||||
/**
|
||||
@@ -49,9 +49,9 @@ struct SourcePointers {
|
||||
const int32_t* __restrict__ cache_seqlens; // [bs] sequence lengths in cache
|
||||
const int32_t* __restrict__ cu_seqlens_k; // [bs+1] cumulative sequence lengths
|
||||
const int32_t* __restrict__ page_indices; // page table indices
|
||||
const int32_t* __restrict__ nsa_cache_seqlens; // NSA-specific cache lengths
|
||||
const int32_t* __restrict__ dsa_cache_seqlens; // DSA-specific cache lengths
|
||||
const int32_t* __restrict__ seqlens_expanded; // expanded sequence lengths (TARGET_VERIFY/DRAFT_EXTEND only)
|
||||
const int32_t* __restrict__ nsa_cu_seqlens_k; // NSA cumulative sequence lengths
|
||||
const int32_t* __restrict__ dsa_cu_seqlens_k; // DSA cumulative sequence lengths
|
||||
const int32_t* __restrict__ real_page_table; // optional real page table
|
||||
const int32_t* __restrict__ flashmla_num_splits; // optional FlashMLA split counts
|
||||
const int32_t* __restrict__ flashmla_metadata; // optional FlashMLA metadata
|
||||
@@ -66,9 +66,9 @@ struct DestinationPointers {
|
||||
int32_t* __restrict__ cache_seqlens; // [bs] sequence lengths in cache
|
||||
int32_t* __restrict__ cu_seqlens_k; // [bs+1] cumulative sequence lengths
|
||||
int32_t* __restrict__ page_table_1; // page table (note: different name from source)
|
||||
int32_t* __restrict__ nsa_cache_seqlens; // NSA-specific cache lengths
|
||||
int32_t* __restrict__ dsa_cache_seqlens; // DSA-specific cache lengths
|
||||
int32_t* __restrict__ seqlens_expanded; // expanded sequence lengths (TARGET_VERIFY/DRAFT_EXTEND only)
|
||||
int32_t* __restrict__ nsa_cu_seqlens_k; // NSA cumulative sequence lengths
|
||||
int32_t* __restrict__ dsa_cu_seqlens_k; // DSA cumulative sequence lengths
|
||||
int32_t* __restrict__ real_page_table; // optional real page table
|
||||
int32_t* __restrict__ flashmla_num_splits; // optional FlashMLA split counts
|
||||
int32_t* __restrict__ flashmla_metadata; // optional FlashMLA metadata
|
||||
@@ -189,26 +189,26 @@ __global__ void fused_metadata_copy_kernel(const FusedMetadataCopyParams __grid_
|
||||
}
|
||||
}
|
||||
|
||||
// Branch 3: NSA metadata copy (different loop sizes per mode)
|
||||
// Branch 3: DSA metadata copy (different loop sizes per mode)
|
||||
if (forward_mode == 0) { // DECODE
|
||||
#pragma unroll 8
|
||||
for (int i = tid; i < bs; i += total_threads) {
|
||||
dst.nsa_cache_seqlens[i] = src.nsa_cache_seqlens[i];
|
||||
dst.dsa_cache_seqlens[i] = src.dsa_cache_seqlens[i];
|
||||
}
|
||||
|
||||
#pragma unroll 8
|
||||
for (int i = tid; i < bs; i += total_threads) {
|
||||
dst.nsa_cu_seqlens_k[i + 1] = src.nsa_cu_seqlens_k[i + 1];
|
||||
dst.dsa_cu_seqlens_k[i + 1] = src.dsa_cu_seqlens_k[i + 1];
|
||||
}
|
||||
} else { // TARGET_VERIFY or DRAFT_EXTEND
|
||||
#pragma unroll 4
|
||||
for (int i = tid; i < seqlens_expanded_size; i += total_threads) {
|
||||
dst.nsa_cache_seqlens[i] = src.nsa_cache_seqlens[i];
|
||||
dst.dsa_cache_seqlens[i] = src.dsa_cache_seqlens[i];
|
||||
}
|
||||
|
||||
#pragma unroll 4
|
||||
for (int i = tid; i < seqlens_expanded_size; i += total_threads) {
|
||||
dst.nsa_cu_seqlens_k[i + 1] = src.nsa_cu_seqlens_k[i + 1];
|
||||
dst.dsa_cu_seqlens_k[i + 1] = src.dsa_cu_seqlens_k[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,22 +309,22 @@ __global__ void fused_metadata_copy_multi_kernel(const FusedMetadataCopyMultiPar
|
||||
dst2.page_table_1[row * page_table_1_stride + col] = val;
|
||||
}
|
||||
|
||||
// Copy nsa_cache_seqlens to all 3 backends
|
||||
// Copy dsa_cache_seqlens to all 3 backends
|
||||
#pragma unroll 8
|
||||
for (int i = tid; i < bs; i += total_threads) {
|
||||
int32_t val = src.nsa_cache_seqlens[i];
|
||||
dst0.nsa_cache_seqlens[i] = val;
|
||||
dst1.nsa_cache_seqlens[i] = val;
|
||||
dst2.nsa_cache_seqlens[i] = val;
|
||||
int32_t val = src.dsa_cache_seqlens[i];
|
||||
dst0.dsa_cache_seqlens[i] = val;
|
||||
dst1.dsa_cache_seqlens[i] = val;
|
||||
dst2.dsa_cache_seqlens[i] = val;
|
||||
}
|
||||
|
||||
// Copy NSA cu_seqlens to all 3 backends
|
||||
// Copy DSA cu_seqlens to all 3 backends
|
||||
#pragma unroll 8
|
||||
for (int i = tid; i < bs; i += total_threads) {
|
||||
int32_t val = src.nsa_cu_seqlens_k[i + 1];
|
||||
dst0.nsa_cu_seqlens_k[i + 1] = val;
|
||||
dst1.nsa_cu_seqlens_k[i + 1] = val;
|
||||
dst2.nsa_cu_seqlens_k[i + 1] = val;
|
||||
int32_t val = src.dsa_cu_seqlens_k[i + 1];
|
||||
dst0.dsa_cu_seqlens_k[i + 1] = val;
|
||||
dst1.dsa_cu_seqlens_k[i + 1] = val;
|
||||
dst2.dsa_cu_seqlens_k[i + 1] = val;
|
||||
}
|
||||
|
||||
// Copy real page table to all 3 backends
|
||||
@@ -493,18 +493,18 @@ struct FusedMetadataCopyKernel {
|
||||
run(const tvm::ffi::TensorView cache_seqlens_src,
|
||||
const tvm::ffi::TensorView cu_seqlens_k_src,
|
||||
const tvm::ffi::TensorView page_indices_src,
|
||||
const tvm::ffi::TensorView nsa_cache_seqlens_src,
|
||||
const tvm::ffi::TensorView dsa_cache_seqlens_src,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> seqlens_expanded_src,
|
||||
const tvm::ffi::TensorView nsa_cu_seqlens_k_src,
|
||||
const tvm::ffi::TensorView dsa_cu_seqlens_k_src,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> real_page_table_src,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_num_splits_src,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_metadata_src,
|
||||
const tvm::ffi::TensorView cache_seqlens_dst,
|
||||
const tvm::ffi::TensorView cu_seqlens_k_dst,
|
||||
const tvm::ffi::TensorView page_table_1_dst,
|
||||
const tvm::ffi::TensorView nsa_cache_seqlens_dst,
|
||||
const tvm::ffi::TensorView dsa_cache_seqlens_dst,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> seqlens_expanded_dst,
|
||||
const tvm::ffi::TensorView nsa_cu_seqlens_k_dst,
|
||||
const tvm::ffi::TensorView dsa_cu_seqlens_k_dst,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> real_page_table_dst,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_num_splits_dst,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_metadata_dst,
|
||||
@@ -522,9 +522,9 @@ struct FusedMetadataCopyKernel {
|
||||
.cache_seqlens = unwrap_data_ptr<int32_t>(cache_seqlens_src, "cache_seqlens_src"),
|
||||
.cu_seqlens_k = unwrap_data_ptr<int32_t>(cu_seqlens_k_src, "cu_seqlens_k_src"),
|
||||
.page_indices = unwrap_data_ptr<int32_t>(page_indices_src, "page_indices_src"),
|
||||
.nsa_cache_seqlens = unwrap_data_ptr<int32_t>(nsa_cache_seqlens_src, "nsa_cache_seqlens_src"),
|
||||
.dsa_cache_seqlens = unwrap_data_ptr<int32_t>(dsa_cache_seqlens_src, "dsa_cache_seqlens_src"),
|
||||
.seqlens_expanded = unwrap_optional_data_ptr<int32_t>(seqlens_expanded_src, "seqlens_expanded_src"),
|
||||
.nsa_cu_seqlens_k = unwrap_data_ptr<int32_t>(nsa_cu_seqlens_k_src, "nsa_cu_seqlens_k_src"),
|
||||
.dsa_cu_seqlens_k = unwrap_data_ptr<int32_t>(dsa_cu_seqlens_k_src, "dsa_cu_seqlens_k_src"),
|
||||
.real_page_table = unwrap_optional_data_ptr<int32_t>(real_page_table_src, "real_page_table_src"),
|
||||
.flashmla_num_splits =
|
||||
unwrap_optional_data_ptr<int32_t>(flashmla_num_splits_src, "flashmla_num_splits_src"),
|
||||
@@ -535,9 +535,9 @@ struct FusedMetadataCopyKernel {
|
||||
.cache_seqlens = unwrap_data_ptr_mut<int32_t>(cache_seqlens_dst, "cache_seqlens_dst"),
|
||||
.cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(cu_seqlens_k_dst, "cu_seqlens_k_dst"),
|
||||
.page_table_1 = unwrap_data_ptr_mut<int32_t>(page_table_1_dst, "page_table_1_dst"),
|
||||
.nsa_cache_seqlens = unwrap_data_ptr_mut<int32_t>(nsa_cache_seqlens_dst, "nsa_cache_seqlens_dst"),
|
||||
.dsa_cache_seqlens = unwrap_data_ptr_mut<int32_t>(dsa_cache_seqlens_dst, "dsa_cache_seqlens_dst"),
|
||||
.seqlens_expanded = unwrap_optional_data_ptr_mut<int32_t>(seqlens_expanded_dst, "seqlens_expanded_dst"),
|
||||
.nsa_cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(nsa_cu_seqlens_k_dst, "nsa_cu_seqlens_k_dst"),
|
||||
.dsa_cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(dsa_cu_seqlens_k_dst, "dsa_cu_seqlens_k_dst"),
|
||||
.real_page_table = unwrap_optional_data_ptr_mut<int32_t>(real_page_table_dst, "real_page_table_dst"),
|
||||
.flashmla_num_splits =
|
||||
unwrap_optional_data_ptr_mut<int32_t>(flashmla_num_splits_dst, "flashmla_num_splits_dst"),
|
||||
@@ -605,32 +605,32 @@ struct FusedMetadataCopyMultiKernel {
|
||||
run(const tvm::ffi::TensorView cache_seqlens_src,
|
||||
const tvm::ffi::TensorView cu_seqlens_k_src,
|
||||
const tvm::ffi::TensorView page_indices_src,
|
||||
const tvm::ffi::TensorView nsa_cache_seqlens_src,
|
||||
const tvm::ffi::TensorView nsa_cu_seqlens_k_src,
|
||||
const tvm::ffi::TensorView dsa_cache_seqlens_src,
|
||||
const tvm::ffi::TensorView dsa_cu_seqlens_k_src,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> real_page_table_src,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_num_splits_src,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_metadata_src,
|
||||
const tvm::ffi::TensorView cache_seqlens_dst0,
|
||||
const tvm::ffi::TensorView cu_seqlens_k_dst0,
|
||||
const tvm::ffi::TensorView page_table_1_dst0,
|
||||
const tvm::ffi::TensorView nsa_cache_seqlens_dst0,
|
||||
const tvm::ffi::TensorView nsa_cu_seqlens_k_dst0,
|
||||
const tvm::ffi::TensorView dsa_cache_seqlens_dst0,
|
||||
const tvm::ffi::TensorView dsa_cu_seqlens_k_dst0,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> real_page_table_dst0,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_num_splits_dst0,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_metadata_dst0,
|
||||
const tvm::ffi::TensorView cache_seqlens_dst1,
|
||||
const tvm::ffi::TensorView cu_seqlens_k_dst1,
|
||||
const tvm::ffi::TensorView page_table_1_dst1,
|
||||
const tvm::ffi::TensorView nsa_cache_seqlens_dst1,
|
||||
const tvm::ffi::TensorView nsa_cu_seqlens_k_dst1,
|
||||
const tvm::ffi::TensorView dsa_cache_seqlens_dst1,
|
||||
const tvm::ffi::TensorView dsa_cu_seqlens_k_dst1,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> real_page_table_dst1,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_num_splits_dst1,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_metadata_dst1,
|
||||
const tvm::ffi::TensorView cache_seqlens_dst2,
|
||||
const tvm::ffi::TensorView cu_seqlens_k_dst2,
|
||||
const tvm::ffi::TensorView page_table_1_dst2,
|
||||
const tvm::ffi::TensorView nsa_cache_seqlens_dst2,
|
||||
const tvm::ffi::TensorView nsa_cu_seqlens_k_dst2,
|
||||
const tvm::ffi::TensorView dsa_cache_seqlens_dst2,
|
||||
const tvm::ffi::TensorView dsa_cu_seqlens_k_dst2,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> real_page_table_dst2,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_num_splits_dst2,
|
||||
const tvm::ffi::Optional<tvm::ffi::TensorView> flashmla_metadata_dst2,
|
||||
@@ -647,9 +647,9 @@ struct FusedMetadataCopyMultiKernel {
|
||||
.cache_seqlens = unwrap_data_ptr<int32_t>(cache_seqlens_src, "cache_seqlens_src"),
|
||||
.cu_seqlens_k = unwrap_data_ptr<int32_t>(cu_seqlens_k_src, "cu_seqlens_k_src"),
|
||||
.page_indices = unwrap_data_ptr<int32_t>(page_indices_src, "page_indices_src"),
|
||||
.nsa_cache_seqlens = unwrap_data_ptr<int32_t>(nsa_cache_seqlens_src, "nsa_cache_seqlens_src"),
|
||||
.dsa_cache_seqlens = unwrap_data_ptr<int32_t>(dsa_cache_seqlens_src, "dsa_cache_seqlens_src"),
|
||||
.seqlens_expanded = nullptr, // Not used in multi-backend DECODE mode
|
||||
.nsa_cu_seqlens_k = unwrap_data_ptr<int32_t>(nsa_cu_seqlens_k_src, "nsa_cu_seqlens_k_src"),
|
||||
.dsa_cu_seqlens_k = unwrap_data_ptr<int32_t>(dsa_cu_seqlens_k_src, "dsa_cu_seqlens_k_src"),
|
||||
.real_page_table = unwrap_optional_data_ptr<int32_t>(real_page_table_src, "real_page_table_src"),
|
||||
.flashmla_num_splits =
|
||||
unwrap_optional_data_ptr<int32_t>(flashmla_num_splits_src, "flashmla_num_splits_src"),
|
||||
@@ -660,9 +660,9 @@ struct FusedMetadataCopyMultiKernel {
|
||||
.cache_seqlens = unwrap_data_ptr_mut<int32_t>(cache_seqlens_dst0, "cache_seqlens_dst0"),
|
||||
.cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(cu_seqlens_k_dst0, "cu_seqlens_k_dst0"),
|
||||
.page_table_1 = unwrap_data_ptr_mut<int32_t>(page_table_1_dst0, "page_table_1_dst0"),
|
||||
.nsa_cache_seqlens = unwrap_data_ptr_mut<int32_t>(nsa_cache_seqlens_dst0, "nsa_cache_seqlens_dst0"),
|
||||
.dsa_cache_seqlens = unwrap_data_ptr_mut<int32_t>(dsa_cache_seqlens_dst0, "dsa_cache_seqlens_dst0"),
|
||||
.seqlens_expanded = nullptr,
|
||||
.nsa_cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(nsa_cu_seqlens_k_dst0, "nsa_cu_seqlens_k_dst0"),
|
||||
.dsa_cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(dsa_cu_seqlens_k_dst0, "dsa_cu_seqlens_k_dst0"),
|
||||
.real_page_table = unwrap_optional_data_ptr_mut<int32_t>(real_page_table_dst0, "real_page_table_dst0"),
|
||||
.flashmla_num_splits =
|
||||
unwrap_optional_data_ptr_mut<int32_t>(flashmla_num_splits_dst0, "flashmla_num_splits_dst0"),
|
||||
@@ -674,9 +674,9 @@ struct FusedMetadataCopyMultiKernel {
|
||||
.cache_seqlens = unwrap_data_ptr_mut<int32_t>(cache_seqlens_dst1, "cache_seqlens_dst1"),
|
||||
.cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(cu_seqlens_k_dst1, "cu_seqlens_k_dst1"),
|
||||
.page_table_1 = unwrap_data_ptr_mut<int32_t>(page_table_1_dst1, "page_table_1_dst1"),
|
||||
.nsa_cache_seqlens = unwrap_data_ptr_mut<int32_t>(nsa_cache_seqlens_dst1, "nsa_cache_seqlens_dst1"),
|
||||
.dsa_cache_seqlens = unwrap_data_ptr_mut<int32_t>(dsa_cache_seqlens_dst1, "dsa_cache_seqlens_dst1"),
|
||||
.seqlens_expanded = nullptr,
|
||||
.nsa_cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(nsa_cu_seqlens_k_dst1, "nsa_cu_seqlens_k_dst1"),
|
||||
.dsa_cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(dsa_cu_seqlens_k_dst1, "dsa_cu_seqlens_k_dst1"),
|
||||
.real_page_table = unwrap_optional_data_ptr_mut<int32_t>(real_page_table_dst1, "real_page_table_dst1"),
|
||||
.flashmla_num_splits =
|
||||
unwrap_optional_data_ptr_mut<int32_t>(flashmla_num_splits_dst1, "flashmla_num_splits_dst1"),
|
||||
@@ -688,9 +688,9 @@ struct FusedMetadataCopyMultiKernel {
|
||||
.cache_seqlens = unwrap_data_ptr_mut<int32_t>(cache_seqlens_dst2, "cache_seqlens_dst2"),
|
||||
.cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(cu_seqlens_k_dst2, "cu_seqlens_k_dst2"),
|
||||
.page_table_1 = unwrap_data_ptr_mut<int32_t>(page_table_1_dst2, "page_table_1_dst2"),
|
||||
.nsa_cache_seqlens = unwrap_data_ptr_mut<int32_t>(nsa_cache_seqlens_dst2, "nsa_cache_seqlens_dst2"),
|
||||
.dsa_cache_seqlens = unwrap_data_ptr_mut<int32_t>(dsa_cache_seqlens_dst2, "dsa_cache_seqlens_dst2"),
|
||||
.seqlens_expanded = nullptr,
|
||||
.nsa_cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(nsa_cu_seqlens_k_dst2, "nsa_cu_seqlens_k_dst2"),
|
||||
.dsa_cu_seqlens_k = unwrap_data_ptr_mut<int32_t>(dsa_cu_seqlens_k_dst2, "dsa_cu_seqlens_k_dst2"),
|
||||
.real_page_table = unwrap_optional_data_ptr_mut<int32_t>(real_page_table_dst2, "real_page_table_dst2"),
|
||||
.flashmla_num_splits =
|
||||
unwrap_optional_data_ptr_mut<int32_t>(flashmla_num_splits_dst2, "flashmla_num_splits_dst2"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Fused metadata copy kernel for NSA backend CUDA graph replay.
|
||||
Fused metadata copy kernel for DSA backend CUDA graph replay.
|
||||
|
||||
This module provides JIT-compiled CUDA kernels for fusing multiple tensor
|
||||
copy operations into single kernel launches, reducing kernel launch overhead
|
||||
@@ -98,18 +98,18 @@ def fused_metadata_copy_cuda(
|
||||
cache_seqlens_src: torch.Tensor,
|
||||
cu_seqlens_k_src: torch.Tensor,
|
||||
page_indices_src: torch.Tensor,
|
||||
nsa_cache_seqlens_src: torch.Tensor,
|
||||
dsa_cache_seqlens_src: torch.Tensor,
|
||||
seqlens_expanded_src: Optional[torch.Tensor],
|
||||
nsa_cu_seqlens_k_src: torch.Tensor,
|
||||
dsa_cu_seqlens_k_src: torch.Tensor,
|
||||
real_page_table_src: Optional[torch.Tensor],
|
||||
flashmla_num_splits_src: Optional[torch.Tensor],
|
||||
flashmla_metadata_src: Optional[torch.Tensor],
|
||||
cache_seqlens_dst: torch.Tensor,
|
||||
cu_seqlens_k_dst: torch.Tensor,
|
||||
page_table_1_dst: torch.Tensor,
|
||||
nsa_cache_seqlens_dst: torch.Tensor,
|
||||
dsa_cache_seqlens_dst: torch.Tensor,
|
||||
seqlens_expanded_dst: Optional[torch.Tensor],
|
||||
nsa_cu_seqlens_k_dst: torch.Tensor,
|
||||
dsa_cu_seqlens_k_dst: torch.Tensor,
|
||||
real_page_table_dst: Optional[torch.Tensor],
|
||||
flashmla_num_splits_dst: Optional[torch.Tensor],
|
||||
flashmla_metadata_dst: Optional[torch.Tensor],
|
||||
@@ -120,7 +120,7 @@ def fused_metadata_copy_cuda(
|
||||
seqlens_expanded_size: int,
|
||||
) -> None:
|
||||
"""
|
||||
Fused metadata copy kernel for NSA backend CUDA graph replay.
|
||||
Fused metadata copy kernel for DSA backend CUDA graph replay.
|
||||
|
||||
This function fuses multiple tensor copy operations into a single kernel launch,
|
||||
reducing kernel launch overhead and improving performance.
|
||||
@@ -129,18 +129,18 @@ def fused_metadata_copy_cuda(
|
||||
cache_seqlens_src: Source cache sequence lengths [bs]
|
||||
cu_seqlens_k_src: Source cumulative sequence lengths [bs+1]
|
||||
page_indices_src: Source page indices [rows, max_len]
|
||||
nsa_cache_seqlens_src: Source NSA cache sequence lengths [size]
|
||||
dsa_cache_seqlens_src: Source DSA cache sequence lengths [size]
|
||||
seqlens_expanded_src: Optional source expanded sequence lengths [size] (required for TARGET_VERIFY/DRAFT_EXTEND)
|
||||
nsa_cu_seqlens_k_src: Source NSA cumulative sequence lengths [size+1]
|
||||
dsa_cu_seqlens_k_src: Source DSA cumulative sequence lengths [size+1]
|
||||
real_page_table_src: Optional source real page table [rows, cols]
|
||||
flashmla_num_splits_src: Optional source FlashMLA num_splits [size+1]
|
||||
flashmla_metadata_src: Optional source FlashMLA metadata tensor
|
||||
cache_seqlens_dst: Destination cache sequence lengths [bs]
|
||||
cu_seqlens_k_dst: Destination cumulative sequence lengths [bs+1]
|
||||
page_table_1_dst: Destination page table [rows, stride]
|
||||
nsa_cache_seqlens_dst: Destination NSA cache sequence lengths [size]
|
||||
dsa_cache_seqlens_dst: Destination DSA cache sequence lengths [size]
|
||||
seqlens_expanded_dst: Optional destination expanded sequence lengths [size] (required for TARGET_VERIFY/DRAFT_EXTEND)
|
||||
nsa_cu_seqlens_k_dst: Destination NSA cumulative sequence lengths [size+1]
|
||||
dsa_cu_seqlens_k_dst: Destination DSA cumulative sequence lengths [size+1]
|
||||
real_page_table_dst: Optional destination real page table [rows, cols]
|
||||
flashmla_num_splits_dst: Optional destination FlashMLA num_splits [size+1]
|
||||
flashmla_metadata_dst: Optional destination FlashMLA metadata tensor
|
||||
@@ -164,28 +164,28 @@ def fused_metadata_copy_cuda(
|
||||
cache_seqlens_src = cache_seqlens_src.contiguous()
|
||||
cu_seqlens_k_src = cu_seqlens_k_src.contiguous()
|
||||
page_indices_src = page_indices_src.contiguous()
|
||||
nsa_cache_seqlens_src = nsa_cache_seqlens_src.contiguous()
|
||||
dsa_cache_seqlens_src = dsa_cache_seqlens_src.contiguous()
|
||||
if seqlens_expanded_src is not None:
|
||||
seqlens_expanded_src = seqlens_expanded_src.contiguous()
|
||||
nsa_cu_seqlens_k_src = nsa_cu_seqlens_k_src.contiguous()
|
||||
dsa_cu_seqlens_k_src = dsa_cu_seqlens_k_src.contiguous()
|
||||
|
||||
# Call JIT-compiled kernel (None values are passed as Optional with no value)
|
||||
module.fused_metadata_copy(
|
||||
cache_seqlens_src,
|
||||
cu_seqlens_k_src,
|
||||
page_indices_src,
|
||||
nsa_cache_seqlens_src,
|
||||
dsa_cache_seqlens_src,
|
||||
seqlens_expanded_src,
|
||||
nsa_cu_seqlens_k_src,
|
||||
dsa_cu_seqlens_k_src,
|
||||
real_page_table_src,
|
||||
flashmla_num_splits_src,
|
||||
flashmla_metadata_src,
|
||||
cache_seqlens_dst,
|
||||
cu_seqlens_k_dst,
|
||||
page_table_1_dst,
|
||||
nsa_cache_seqlens_dst,
|
||||
dsa_cache_seqlens_dst,
|
||||
seqlens_expanded_dst,
|
||||
nsa_cu_seqlens_k_dst,
|
||||
dsa_cu_seqlens_k_dst,
|
||||
real_page_table_dst,
|
||||
flashmla_num_splits_dst,
|
||||
flashmla_metadata_dst,
|
||||
@@ -200,32 +200,32 @@ def fused_metadata_copy_multi_cuda(
|
||||
cache_seqlens_src: torch.Tensor,
|
||||
cu_seqlens_k_src: torch.Tensor,
|
||||
page_indices_src: torch.Tensor,
|
||||
nsa_cache_seqlens_src: torch.Tensor,
|
||||
nsa_cu_seqlens_k_src: torch.Tensor,
|
||||
dsa_cache_seqlens_src: torch.Tensor,
|
||||
dsa_cu_seqlens_k_src: torch.Tensor,
|
||||
real_page_table_src: Optional[torch.Tensor],
|
||||
flashmla_num_splits_src: Optional[torch.Tensor],
|
||||
flashmla_metadata_src: Optional[torch.Tensor],
|
||||
cache_seqlens_dst0: torch.Tensor,
|
||||
cu_seqlens_k_dst0: torch.Tensor,
|
||||
page_table_1_dst0: torch.Tensor,
|
||||
nsa_cache_seqlens_dst0: torch.Tensor,
|
||||
nsa_cu_seqlens_k_dst0: torch.Tensor,
|
||||
dsa_cache_seqlens_dst0: torch.Tensor,
|
||||
dsa_cu_seqlens_k_dst0: torch.Tensor,
|
||||
real_page_table_dst0: Optional[torch.Tensor],
|
||||
flashmla_num_splits_dst0: Optional[torch.Tensor],
|
||||
flashmla_metadata_dst0: Optional[torch.Tensor],
|
||||
cache_seqlens_dst1: torch.Tensor,
|
||||
cu_seqlens_k_dst1: torch.Tensor,
|
||||
page_table_1_dst1: torch.Tensor,
|
||||
nsa_cache_seqlens_dst1: torch.Tensor,
|
||||
nsa_cu_seqlens_k_dst1: torch.Tensor,
|
||||
dsa_cache_seqlens_dst1: torch.Tensor,
|
||||
dsa_cu_seqlens_k_dst1: torch.Tensor,
|
||||
real_page_table_dst1: Optional[torch.Tensor],
|
||||
flashmla_num_splits_dst1: Optional[torch.Tensor],
|
||||
flashmla_metadata_dst1: Optional[torch.Tensor],
|
||||
cache_seqlens_dst2: torch.Tensor,
|
||||
cu_seqlens_k_dst2: torch.Tensor,
|
||||
page_table_1_dst2: torch.Tensor,
|
||||
nsa_cache_seqlens_dst2: torch.Tensor,
|
||||
nsa_cu_seqlens_k_dst2: torch.Tensor,
|
||||
dsa_cache_seqlens_dst2: torch.Tensor,
|
||||
dsa_cu_seqlens_k_dst2: torch.Tensor,
|
||||
real_page_table_dst2: Optional[torch.Tensor],
|
||||
flashmla_num_splits_dst2: Optional[torch.Tensor],
|
||||
flashmla_metadata_dst2: Optional[torch.Tensor],
|
||||
@@ -234,7 +234,7 @@ def fused_metadata_copy_multi_cuda(
|
||||
seqlens_expanded_size: int,
|
||||
) -> None:
|
||||
"""
|
||||
Multi-backend fused metadata copy kernel for NSA backend CUDA graph replay.
|
||||
Multi-backend fused metadata copy kernel for DSA backend CUDA graph replay.
|
||||
|
||||
This function copies metadata from one source to THREE destinations in a single
|
||||
kernel launch, eliminating the overhead of 3 separate kernel calls. Currently
|
||||
@@ -244,16 +244,16 @@ def fused_metadata_copy_multi_cuda(
|
||||
cache_seqlens_src: Source cache sequence lengths [bs]
|
||||
cu_seqlens_k_src: Source cumulative sequence lengths [bs+1]
|
||||
page_indices_src: Source page indices [bs, max_len]
|
||||
nsa_cache_seqlens_src: Source NSA cache sequence lengths [bs]
|
||||
nsa_cu_seqlens_k_src: Source NSA cumulative sequence lengths [bs+1]
|
||||
dsa_cache_seqlens_src: Source DSA cache sequence lengths [bs]
|
||||
dsa_cu_seqlens_k_src: Source DSA cumulative sequence lengths [bs+1]
|
||||
real_page_table_src: Optional source real page table [bs, cols]
|
||||
flashmla_num_splits_src: Optional source FlashMLA num_splits [bs+1]
|
||||
flashmla_metadata_src: Optional source FlashMLA metadata tensor
|
||||
cache_seqlens_dst0-2: Destination cache sequence lengths for backends 0-2
|
||||
cu_seqlens_k_dst0-2: Destination cumulative sequence lengths for backends 0-2
|
||||
page_table_1_dst0-2: Destination page tables for backends 0-2
|
||||
nsa_cache_seqlens_dst0-2: Destination NSA cache sequence lengths for backends 0-2
|
||||
nsa_cu_seqlens_k_dst0-2: Destination NSA cumulative sequence lengths for backends 0-2
|
||||
dsa_cache_seqlens_dst0-2: Destination DSA cache sequence lengths for backends 0-2
|
||||
dsa_cu_seqlens_k_dst0-2: Destination DSA cumulative sequence lengths for backends 0-2
|
||||
real_page_table_dst0-2: Optional destination real page tables for backends 0-2
|
||||
flashmla_num_splits_dst0-2: Optional destination FlashMLA num_splits for backends 0-2
|
||||
flashmla_metadata_dst0-2: Optional destination FlashMLA metadata tensors for backends 0-2
|
||||
@@ -273,40 +273,40 @@ def fused_metadata_copy_multi_cuda(
|
||||
cache_seqlens_src = cache_seqlens_src.contiguous()
|
||||
cu_seqlens_k_src = cu_seqlens_k_src.contiguous()
|
||||
page_indices_src = page_indices_src.contiguous()
|
||||
nsa_cache_seqlens_src = nsa_cache_seqlens_src.contiguous()
|
||||
nsa_cu_seqlens_k_src = nsa_cu_seqlens_k_src.contiguous()
|
||||
dsa_cache_seqlens_src = dsa_cache_seqlens_src.contiguous()
|
||||
dsa_cu_seqlens_k_src = dsa_cu_seqlens_k_src.contiguous()
|
||||
|
||||
# Call JIT-compiled kernel (None values are passed as Optional with no value)
|
||||
module.fused_metadata_copy_multi(
|
||||
cache_seqlens_src,
|
||||
cu_seqlens_k_src,
|
||||
page_indices_src,
|
||||
nsa_cache_seqlens_src,
|
||||
nsa_cu_seqlens_k_src,
|
||||
dsa_cache_seqlens_src,
|
||||
dsa_cu_seqlens_k_src,
|
||||
real_page_table_src,
|
||||
flashmla_num_splits_src,
|
||||
flashmla_metadata_src,
|
||||
cache_seqlens_dst0,
|
||||
cu_seqlens_k_dst0,
|
||||
page_table_1_dst0,
|
||||
nsa_cache_seqlens_dst0,
|
||||
nsa_cu_seqlens_k_dst0,
|
||||
dsa_cache_seqlens_dst0,
|
||||
dsa_cu_seqlens_k_dst0,
|
||||
real_page_table_dst0,
|
||||
flashmla_num_splits_dst0,
|
||||
flashmla_metadata_dst0,
|
||||
cache_seqlens_dst1,
|
||||
cu_seqlens_k_dst1,
|
||||
page_table_1_dst1,
|
||||
nsa_cache_seqlens_dst1,
|
||||
nsa_cu_seqlens_k_dst1,
|
||||
dsa_cache_seqlens_dst1,
|
||||
dsa_cu_seqlens_k_dst1,
|
||||
real_page_table_dst1,
|
||||
flashmla_num_splits_dst1,
|
||||
flashmla_metadata_dst1,
|
||||
cache_seqlens_dst2,
|
||||
cu_seqlens_k_dst2,
|
||||
page_table_1_dst2,
|
||||
nsa_cache_seqlens_dst2,
|
||||
nsa_cu_seqlens_k_dst2,
|
||||
dsa_cache_seqlens_dst2,
|
||||
dsa_cu_seqlens_k_dst2,
|
||||
real_page_table_dst2,
|
||||
flashmla_num_splits_dst2,
|
||||
flashmla_metadata_dst2,
|
||||
|
||||
@@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@cache_once
|
||||
def _jit_nsa_fused_store_module(
|
||||
def _jit_dsa_fused_store_module(
|
||||
key_dtype: torch.dtype, indices_dtype: torch.dtype, page_size: int
|
||||
) -> Module:
|
||||
"""
|
||||
@@ -39,13 +39,13 @@ def _jit_nsa_fused_store_module(
|
||||
return load_jit(
|
||||
"fused_store_index_k_cache",
|
||||
*args,
|
||||
cuda_files=["nsa/fused_store_index_cache.cuh"],
|
||||
cuda_files=["dsa/fused_store_index_cache.cuh"],
|
||||
cuda_wrappers=[
|
||||
(
|
||||
"fused_store_index_k_cache",
|
||||
# - Float = bf16_t (sgl_kernel/type.cuh)
|
||||
# - IndicesT = int64_t (out_cache_loc is int64 in SGLang SetKAndS)
|
||||
# - kPageSize = 64 (CUDA NSA)
|
||||
# - kPageSize = 64 (CUDA DSA)
|
||||
f"FusedStoreCacheIndexerKernel<{args}>::run",
|
||||
)
|
||||
],
|
||||
@@ -53,15 +53,15 @@ def _jit_nsa_fused_store_module(
|
||||
|
||||
|
||||
@cache_once
|
||||
def can_use_nsa_fused_store(
|
||||
def can_use_dsa_fused_store(
|
||||
key_dtype: torch.dtype, indices_dtype: torch.dtype, page_size: int
|
||||
) -> bool:
|
||||
logger = logging.getLogger(__name__)
|
||||
try:
|
||||
_jit_nsa_fused_store_module(key_dtype, indices_dtype, page_size)
|
||||
_jit_dsa_fused_store_module(key_dtype, indices_dtype, page_size)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load nsa fused store JIT kernel: {e}")
|
||||
logger.warning(f"Failed to load dsa fused store JIT kernel: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ def fused_store_index_k_cache(
|
||||
page_size: int = 64,
|
||||
) -> None:
|
||||
"""
|
||||
Fused: quantize bf16 key (N,128) -> fp8 + fp32 scale and write into NSATokenToKVPool.index_k_with_scale_buffer.
|
||||
Fused: quantize bf16 key (N,128) -> fp8 + fp32 scale and write into DSATokenToKVPool.index_k_with_scale_buffer.
|
||||
|
||||
key: (num_tokens, 128) bf16 (or reshapeable to it)
|
||||
index_k_with_scale: (num_pages, 64*(128+4)) uint8
|
||||
@@ -101,5 +101,5 @@ def fused_store_index_k_cache(
|
||||
if not index_k_with_scale.is_contiguous():
|
||||
index_k_with_scale = index_k_with_scale.contiguous()
|
||||
|
||||
module = _jit_nsa_fused_store_module(key.dtype, out_cache_loc.dtype, page_size)
|
||||
module = _jit_dsa_fused_store_module(key.dtype, out_cache_loc.dtype, page_size)
|
||||
module.fused_store_index_k_cache(key, index_k_with_scale, out_cache_loc)
|
||||
|
||||
@@ -33,7 +33,7 @@ def create_test_metadata(
|
||||
has_flashmla: bool = False,
|
||||
device: str = "cuda",
|
||||
):
|
||||
"""Create test metadata tensors matching NSA backend structure."""
|
||||
"""Create test metadata tensors matching DSA backend structure."""
|
||||
# Basic tensors (always present)
|
||||
cache_seqlens_src = torch.randint(
|
||||
1, max_len, (bs,), dtype=torch.int32, device=device
|
||||
@@ -44,28 +44,28 @@ def create_test_metadata(
|
||||
page_indices_src = torch.randint(
|
||||
0, 1000, (bs, max_len), dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cache_seqlens_src = torch.randint(
|
||||
dsa_cache_seqlens_src = torch.randint(
|
||||
1, max_len, (seqlens_expanded_size,), dtype=torch.int32, device=device
|
||||
)
|
||||
seqlens_expanded_src = torch.randint(
|
||||
1, max_seqlen_k, (seqlens_expanded_size,), dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cu_seqlens_k_src = torch.zeros(
|
||||
dsa_cu_seqlens_k_src = torch.zeros(
|
||||
seqlens_expanded_size + 1, dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cu_seqlens_k_src[1:] = torch.cumsum(nsa_cache_seqlens_src, dim=0)
|
||||
dsa_cu_seqlens_k_src[1:] = torch.cumsum(dsa_cache_seqlens_src, dim=0)
|
||||
|
||||
# Destination tensors
|
||||
cache_seqlens_dst = torch.zeros(bs, dtype=torch.int32, device=device)
|
||||
cu_seqlens_k_dst = torch.zeros(bs + 1, dtype=torch.int32, device=device)
|
||||
page_table_1_dst = torch.zeros((bs, max_len + 16), dtype=torch.int32, device=device)
|
||||
nsa_cache_seqlens_dst = torch.zeros(
|
||||
dsa_cache_seqlens_dst = torch.zeros(
|
||||
seqlens_expanded_size, dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_seqlens_expanded_dst = torch.zeros(
|
||||
dsa_seqlens_expanded_dst = torch.zeros(
|
||||
seqlens_expanded_size, dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cu_seqlens_k_dst = torch.zeros(
|
||||
dsa_cu_seqlens_k_dst = torch.zeros(
|
||||
seqlens_expanded_size + 1, dtype=torch.int32, device=device
|
||||
)
|
||||
|
||||
@@ -107,9 +107,9 @@ def create_test_metadata(
|
||||
"cache_seqlens": cache_seqlens_src,
|
||||
"cu_seqlens_k": cu_seqlens_k_src,
|
||||
"page_indices": page_indices_src,
|
||||
"nsa_cache_seqlens": nsa_cache_seqlens_src,
|
||||
"dsa_cache_seqlens": dsa_cache_seqlens_src,
|
||||
"seqlens_expanded": seqlens_expanded_src,
|
||||
"nsa_cu_seqlens_k": nsa_cu_seqlens_k_src,
|
||||
"dsa_cu_seqlens_k": dsa_cu_seqlens_k_src,
|
||||
"real_page_table": real_page_table_src,
|
||||
"flashmla_num_splits": flashmla_num_splits_src,
|
||||
"flashmla_metadata": flashmla_metadata_src,
|
||||
@@ -118,9 +118,9 @@ def create_test_metadata(
|
||||
"cache_seqlens": cache_seqlens_dst,
|
||||
"cu_seqlens_k": cu_seqlens_k_dst,
|
||||
"page_table_1": page_table_1_dst,
|
||||
"nsa_cache_seqlens": nsa_cache_seqlens_dst,
|
||||
"nsa_seqlens_expanded": nsa_seqlens_expanded_dst,
|
||||
"nsa_cu_seqlens_k": nsa_cu_seqlens_k_dst,
|
||||
"dsa_cache_seqlens": dsa_cache_seqlens_dst,
|
||||
"dsa_seqlens_expanded": dsa_seqlens_expanded_dst,
|
||||
"dsa_cu_seqlens_k": dsa_cu_seqlens_k_dst,
|
||||
"real_page_table": real_page_table_dst,
|
||||
"flashmla_num_splits": flashmla_num_splits_dst,
|
||||
"flashmla_metadata": flashmla_metadata_dst,
|
||||
@@ -134,8 +134,8 @@ def reference_copy_decode(src, dst, max_len):
|
||||
dst["cache_seqlens"].copy_(src["cache_seqlens"])
|
||||
dst["cu_seqlens_k"][1:].copy_(src["cu_seqlens_k"][1:])
|
||||
dst["page_table_1"][:, :max_len].copy_(src["page_indices"])
|
||||
dst["nsa_cache_seqlens"].copy_(src["nsa_cache_seqlens"])
|
||||
dst["nsa_cu_seqlens_k"][1 : bs + 1].copy_(src["nsa_cu_seqlens_k"][1 : bs + 1])
|
||||
dst["dsa_cache_seqlens"].copy_(src["dsa_cache_seqlens"])
|
||||
dst["dsa_cu_seqlens_k"][1 : bs + 1].copy_(src["dsa_cu_seqlens_k"][1 : bs + 1])
|
||||
|
||||
if src["real_page_table"] is not None:
|
||||
rows, cols = src["real_page_table"].shape
|
||||
@@ -159,10 +159,10 @@ def reference_copy_target_verify(src, dst, max_seqlen_k, seqlens_expanded_size):
|
||||
|
||||
rows, cols = src["page_indices"].shape
|
||||
dst["page_table_1"][:rows, :cols].copy_(src["page_indices"])
|
||||
dst["nsa_seqlens_expanded"][:seqlens_expanded_size].copy_(src["seqlens_expanded"])
|
||||
dst["nsa_cache_seqlens"][:seqlens_expanded_size].copy_(src["nsa_cache_seqlens"])
|
||||
dst["nsa_cu_seqlens_k"][1 : seqlens_expanded_size + 1].copy_(
|
||||
src["nsa_cu_seqlens_k"][1 : seqlens_expanded_size + 1]
|
||||
dst["dsa_seqlens_expanded"][:seqlens_expanded_size].copy_(src["seqlens_expanded"])
|
||||
dst["dsa_cache_seqlens"][:seqlens_expanded_size].copy_(src["dsa_cache_seqlens"])
|
||||
dst["dsa_cu_seqlens_k"][1 : seqlens_expanded_size + 1].copy_(
|
||||
src["dsa_cu_seqlens_k"][1 : seqlens_expanded_size + 1]
|
||||
)
|
||||
|
||||
if src["real_page_table"] is not None:
|
||||
@@ -187,10 +187,10 @@ def reference_copy_draft_extend(src, dst, max_seqlen_k, seqlens_expanded_size):
|
||||
|
||||
rows, cols = src["page_indices"].shape
|
||||
dst["page_table_1"][:rows, :cols].copy_(src["page_indices"])
|
||||
dst["nsa_seqlens_expanded"][:seqlens_expanded_size].copy_(src["seqlens_expanded"])
|
||||
dst["nsa_cache_seqlens"][:seqlens_expanded_size].copy_(src["nsa_cache_seqlens"])
|
||||
dst["nsa_cu_seqlens_k"][1 : seqlens_expanded_size + 1].copy_(
|
||||
src["nsa_cu_seqlens_k"][1 : seqlens_expanded_size + 1]
|
||||
dst["dsa_seqlens_expanded"][:seqlens_expanded_size].copy_(src["seqlens_expanded"])
|
||||
dst["dsa_cache_seqlens"][:seqlens_expanded_size].copy_(src["dsa_cache_seqlens"])
|
||||
dst["dsa_cu_seqlens_k"][1 : seqlens_expanded_size + 1].copy_(
|
||||
src["dsa_cu_seqlens_k"][1 : seqlens_expanded_size + 1]
|
||||
)
|
||||
|
||||
if src["real_page_table"] is not None:
|
||||
@@ -233,13 +233,13 @@ def test_fused_metadata_copy_dtype_validation():
|
||||
page_indices_src = torch.randint(
|
||||
0, 1000, (bs, max_len), dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cache_seqlens_src = torch.randint(
|
||||
dsa_cache_seqlens_src = torch.randint(
|
||||
1, max_len, (seqlens_expanded_size,), dtype=torch.int32, device=device
|
||||
)
|
||||
seqlens_expanded_src = torch.randint(
|
||||
1, max_seqlen_k, (seqlens_expanded_size,), dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cu_seqlens_k_src = torch.zeros(
|
||||
dsa_cu_seqlens_k_src = torch.zeros(
|
||||
seqlens_expanded_size + 1, dtype=torch.int32, device=device
|
||||
)
|
||||
|
||||
@@ -247,13 +247,13 @@ def test_fused_metadata_copy_dtype_validation():
|
||||
cache_seqlens_dst = torch.zeros(bs, dtype=torch.int32, device=device)
|
||||
cu_seqlens_k_dst = torch.zeros(bs + 1, dtype=torch.int32, device=device)
|
||||
page_table_1_dst = torch.zeros((bs, max_len + 16), dtype=torch.int32, device=device)
|
||||
nsa_cache_seqlens_dst = torch.zeros(
|
||||
dsa_cache_seqlens_dst = torch.zeros(
|
||||
seqlens_expanded_size, dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_seqlens_expanded_dst = torch.zeros(
|
||||
dsa_seqlens_expanded_dst = torch.zeros(
|
||||
seqlens_expanded_size, dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cu_seqlens_k_dst = torch.zeros(
|
||||
dsa_cu_seqlens_k_dst = torch.zeros(
|
||||
seqlens_expanded_size + 1, dtype=torch.int32, device=device
|
||||
)
|
||||
|
||||
@@ -263,18 +263,18 @@ def test_fused_metadata_copy_dtype_validation():
|
||||
cache_seqlens_src_wrong, # Wrong dtype: int64
|
||||
cu_seqlens_k_src,
|
||||
page_indices_src,
|
||||
nsa_cache_seqlens_src,
|
||||
dsa_cache_seqlens_src,
|
||||
seqlens_expanded_src,
|
||||
nsa_cu_seqlens_k_src,
|
||||
dsa_cu_seqlens_k_src,
|
||||
None, # real_page_table_src
|
||||
None, # flashmla_num_splits_src
|
||||
None, # flashmla_metadata_src
|
||||
cache_seqlens_dst,
|
||||
cu_seqlens_k_dst,
|
||||
page_table_1_dst,
|
||||
nsa_cache_seqlens_dst,
|
||||
nsa_seqlens_expanded_dst,
|
||||
nsa_cu_seqlens_k_dst,
|
||||
dsa_cache_seqlens_dst,
|
||||
dsa_seqlens_expanded_dst,
|
||||
dsa_cu_seqlens_k_dst,
|
||||
None, # real_page_table_dst
|
||||
None, # flashmla_num_splits_dst
|
||||
None, # flashmla_metadata_dst
|
||||
@@ -296,18 +296,18 @@ def test_fused_metadata_copy_dtype_validation():
|
||||
cache_seqlens_src,
|
||||
cu_seqlens_k_src,
|
||||
page_indices_src,
|
||||
nsa_cache_seqlens_src,
|
||||
dsa_cache_seqlens_src,
|
||||
seqlens_expanded_src,
|
||||
nsa_cu_seqlens_k_src,
|
||||
dsa_cu_seqlens_k_src,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
cache_seqlens_dst_wrong, # Wrong dtype: int64
|
||||
cu_seqlens_k_dst,
|
||||
page_table_1_dst,
|
||||
nsa_cache_seqlens_dst,
|
||||
nsa_seqlens_expanded_dst,
|
||||
nsa_cu_seqlens_k_dst,
|
||||
dsa_cache_seqlens_dst,
|
||||
dsa_seqlens_expanded_dst,
|
||||
dsa_cu_seqlens_k_dst,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -369,18 +369,18 @@ def test_fused_metadata_copy(bs, forward_mode, has_real_page_table, has_flashmla
|
||||
data["src"]["cache_seqlens"],
|
||||
data["src"]["cu_seqlens_k"],
|
||||
data["src"]["page_indices"],
|
||||
data["src"]["nsa_cache_seqlens"],
|
||||
data["src"]["dsa_cache_seqlens"],
|
||||
data["src"]["seqlens_expanded"],
|
||||
data["src"]["nsa_cu_seqlens_k"],
|
||||
data["src"]["dsa_cu_seqlens_k"],
|
||||
data["src"]["real_page_table"],
|
||||
data["src"]["flashmla_num_splits"],
|
||||
data["src"]["flashmla_metadata"],
|
||||
dst_fused["cache_seqlens"],
|
||||
dst_fused["cu_seqlens_k"],
|
||||
dst_fused["page_table_1"],
|
||||
dst_fused["nsa_cache_seqlens"],
|
||||
dst_fused["nsa_seqlens_expanded"],
|
||||
dst_fused["nsa_cu_seqlens_k"],
|
||||
dst_fused["dsa_cache_seqlens"],
|
||||
dst_fused["dsa_seqlens_expanded"],
|
||||
dst_fused["dsa_cu_seqlens_k"],
|
||||
dst_fused["real_page_table"],
|
||||
dst_fused["flashmla_num_splits"],
|
||||
dst_fused["flashmla_metadata"],
|
||||
@@ -402,14 +402,14 @@ def test_fused_metadata_copy(bs, forward_mode, has_real_page_table, has_flashmla
|
||||
dst_ref["page_table_1"], dst_fused["page_table_1"]
|
||||
), "page_table_1 mismatch"
|
||||
assert torch.equal(
|
||||
dst_ref["nsa_cache_seqlens"], dst_fused["nsa_cache_seqlens"]
|
||||
), "nsa_cache_seqlens mismatch"
|
||||
dst_ref["dsa_cache_seqlens"], dst_fused["dsa_cache_seqlens"]
|
||||
), "dsa_cache_seqlens mismatch"
|
||||
assert torch.equal(
|
||||
dst_ref["nsa_seqlens_expanded"], dst_fused["nsa_seqlens_expanded"]
|
||||
), "nsa_seqlens_expanded mismatch"
|
||||
dst_ref["dsa_seqlens_expanded"], dst_fused["dsa_seqlens_expanded"]
|
||||
), "dsa_seqlens_expanded mismatch"
|
||||
assert torch.equal(
|
||||
dst_ref["nsa_cu_seqlens_k"], dst_fused["nsa_cu_seqlens_k"]
|
||||
), "nsa_cu_seqlens_k mismatch"
|
||||
dst_ref["dsa_cu_seqlens_k"], dst_fused["dsa_cu_seqlens_k"]
|
||||
), "dsa_cu_seqlens_k mismatch"
|
||||
|
||||
if has_real_page_table:
|
||||
assert torch.equal(
|
||||
@@ -458,18 +458,18 @@ def test_fused_metadata_copy_large_batch(bs):
|
||||
data["src"]["cache_seqlens"],
|
||||
data["src"]["cu_seqlens_k"],
|
||||
data["src"]["page_indices"],
|
||||
data["src"]["nsa_cache_seqlens"],
|
||||
data["src"]["dsa_cache_seqlens"],
|
||||
data["src"]["seqlens_expanded"],
|
||||
data["src"]["nsa_cu_seqlens_k"],
|
||||
data["src"]["dsa_cu_seqlens_k"],
|
||||
data["src"]["real_page_table"],
|
||||
data["src"]["flashmla_num_splits"],
|
||||
data["src"]["flashmla_metadata"],
|
||||
dst_fused["cache_seqlens"],
|
||||
dst_fused["cu_seqlens_k"],
|
||||
dst_fused["page_table_1"],
|
||||
dst_fused["nsa_cache_seqlens"],
|
||||
dst_fused["nsa_seqlens_expanded"],
|
||||
dst_fused["nsa_cu_seqlens_k"],
|
||||
dst_fused["dsa_cache_seqlens"],
|
||||
dst_fused["dsa_seqlens_expanded"],
|
||||
dst_fused["dsa_cu_seqlens_k"],
|
||||
dst_fused["real_page_table"],
|
||||
dst_fused["flashmla_num_splits"],
|
||||
dst_fused["flashmla_metadata"],
|
||||
@@ -510,13 +510,13 @@ def create_test_metadata_multi(
|
||||
page_indices_src = torch.randint(
|
||||
0, 1000, (bs, max_len), dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cache_seqlens_src = torch.randint(
|
||||
dsa_cache_seqlens_src = torch.randint(
|
||||
1, max_len, (seqlens_expanded_size,), dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cu_seqlens_k_src = torch.zeros(
|
||||
dsa_cu_seqlens_k_src = torch.zeros(
|
||||
seqlens_expanded_size + 1, dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cu_seqlens_k_src[1:] = torch.cumsum(nsa_cache_seqlens_src, dim=0)
|
||||
dsa_cu_seqlens_k_src[1:] = torch.cumsum(dsa_cache_seqlens_src, dim=0)
|
||||
|
||||
# Optional tensors
|
||||
real_page_table_src = None
|
||||
@@ -544,10 +544,10 @@ def create_test_metadata_multi(
|
||||
page_table_1_dst = torch.zeros(
|
||||
(bs, max_len + 16), dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cache_seqlens_dst = torch.zeros(
|
||||
dsa_cache_seqlens_dst = torch.zeros(
|
||||
seqlens_expanded_size, dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cu_seqlens_k_dst = torch.zeros(
|
||||
dsa_cu_seqlens_k_dst = torch.zeros(
|
||||
seqlens_expanded_size + 1, dtype=torch.int32, device=device
|
||||
)
|
||||
|
||||
@@ -573,8 +573,8 @@ def create_test_metadata_multi(
|
||||
"cache_seqlens_int32": cache_seqlens_dst,
|
||||
"cu_seqlens_k": cu_seqlens_k_dst,
|
||||
"page_table_1": page_table_1_dst,
|
||||
"nsa_cache_seqlens_int32": nsa_cache_seqlens_dst,
|
||||
"nsa_cu_seqlens_k": nsa_cu_seqlens_k_dst,
|
||||
"dsa_cache_seqlens_int32": dsa_cache_seqlens_dst,
|
||||
"dsa_cu_seqlens_k": dsa_cu_seqlens_k_dst,
|
||||
"real_page_table": real_page_table_dst,
|
||||
"flashmla_num_splits": flashmla_num_splits_dst,
|
||||
"flashmla_metadata": flashmla_metadata_dst,
|
||||
@@ -585,8 +585,8 @@ def create_test_metadata_multi(
|
||||
"cache_seqlens": cache_seqlens_src,
|
||||
"cu_seqlens_k": cu_seqlens_k_src,
|
||||
"page_indices": page_indices_src,
|
||||
"nsa_cache_seqlens": nsa_cache_seqlens_src,
|
||||
"nsa_cu_seqlens_k": nsa_cu_seqlens_k_src,
|
||||
"dsa_cache_seqlens": dsa_cache_seqlens_src,
|
||||
"dsa_cu_seqlens_k": dsa_cu_seqlens_k_src,
|
||||
"real_page_table": real_page_table_src,
|
||||
"flashmla_num_splits": flashmla_num_splits_src,
|
||||
"flashmla_metadata": flashmla_metadata_src,
|
||||
@@ -604,8 +604,8 @@ def reference_copy_for_loop(src, dst_list, bs, max_len):
|
||||
dst["cache_seqlens_int32"].copy_(src["cache_seqlens"])
|
||||
dst["cu_seqlens_k"][1:].copy_(src["cu_seqlens_k"][1:])
|
||||
dst["page_table_1"][:, :max_len].copy_(src["page_indices"])
|
||||
dst["nsa_cache_seqlens_int32"].copy_(src["nsa_cache_seqlens"])
|
||||
dst["nsa_cu_seqlens_k"][1 : bs + 1].copy_(src["nsa_cu_seqlens_k"][1 : bs + 1])
|
||||
dst["dsa_cache_seqlens_int32"].copy_(src["dsa_cache_seqlens"])
|
||||
dst["dsa_cu_seqlens_k"][1 : bs + 1].copy_(src["dsa_cu_seqlens_k"][1 : bs + 1])
|
||||
|
||||
if src["real_page_table"] is not None:
|
||||
rows, cols = src["real_page_table"].shape
|
||||
@@ -641,10 +641,10 @@ def test_fused_metadata_copy_multi_dtype_validation():
|
||||
page_indices_src = torch.randint(
|
||||
0, 1000, (bs, max_len), dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cache_seqlens_src = torch.randint(
|
||||
dsa_cache_seqlens_src = torch.randint(
|
||||
1, max_len, (seqlens_expanded_size,), dtype=torch.int32, device=device
|
||||
)
|
||||
nsa_cu_seqlens_k_src = torch.zeros(
|
||||
dsa_cu_seqlens_k_src = torch.zeros(
|
||||
seqlens_expanded_size + 1, dtype=torch.int32, device=device
|
||||
)
|
||||
|
||||
@@ -656,10 +656,10 @@ def test_fused_metadata_copy_multi_dtype_validation():
|
||||
"page_table_1": torch.zeros(
|
||||
(bs, max_len + 16), dtype=torch.int32, device=device
|
||||
),
|
||||
"nsa_cache_seqlens": torch.zeros(
|
||||
"dsa_cache_seqlens": torch.zeros(
|
||||
seqlens_expanded_size, dtype=torch.int32, device=device
|
||||
),
|
||||
"nsa_cu_seqlens_k": torch.zeros(
|
||||
"dsa_cu_seqlens_k": torch.zeros(
|
||||
seqlens_expanded_size + 1, dtype=torch.int32, device=device
|
||||
),
|
||||
}
|
||||
@@ -674,8 +674,8 @@ def test_fused_metadata_copy_multi_dtype_validation():
|
||||
cache_seqlens_src_wrong, # Wrong dtype: int64
|
||||
cu_seqlens_k_src,
|
||||
page_indices_src,
|
||||
nsa_cache_seqlens_src,
|
||||
nsa_cu_seqlens_k_src,
|
||||
dsa_cache_seqlens_src,
|
||||
dsa_cu_seqlens_k_src,
|
||||
None, # real_page_table_src
|
||||
None, # flashmla_num_splits_src
|
||||
None, # flashmla_metadata_src
|
||||
@@ -683,8 +683,8 @@ def test_fused_metadata_copy_multi_dtype_validation():
|
||||
dst0["cache_seqlens"],
|
||||
dst0["cu_seqlens_k"],
|
||||
dst0["page_table_1"],
|
||||
dst0["nsa_cache_seqlens"],
|
||||
dst0["nsa_cu_seqlens_k"],
|
||||
dst0["dsa_cache_seqlens"],
|
||||
dst0["dsa_cu_seqlens_k"],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -692,8 +692,8 @@ def test_fused_metadata_copy_multi_dtype_validation():
|
||||
dst1["cache_seqlens"],
|
||||
dst1["cu_seqlens_k"],
|
||||
dst1["page_table_1"],
|
||||
dst1["nsa_cache_seqlens"],
|
||||
dst1["nsa_cu_seqlens_k"],
|
||||
dst1["dsa_cache_seqlens"],
|
||||
dst1["dsa_cu_seqlens_k"],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -701,8 +701,8 @@ def test_fused_metadata_copy_multi_dtype_validation():
|
||||
dst2["cache_seqlens"],
|
||||
dst2["cu_seqlens_k"],
|
||||
dst2["page_table_1"],
|
||||
dst2["nsa_cache_seqlens"],
|
||||
dst2["nsa_cu_seqlens_k"],
|
||||
dst2["dsa_cache_seqlens"],
|
||||
dst2["dsa_cu_seqlens_k"],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -772,8 +772,8 @@ def test_fused_metadata_copy_multi(bs, has_real_page_table, has_flashmla):
|
||||
data["src"]["cache_seqlens"],
|
||||
data["src"]["cu_seqlens_k"],
|
||||
data["src"]["page_indices"],
|
||||
data["src"]["nsa_cache_seqlens"],
|
||||
data["src"]["nsa_cu_seqlens_k"],
|
||||
data["src"]["dsa_cache_seqlens"],
|
||||
data["src"]["dsa_cu_seqlens_k"],
|
||||
data["src"]["real_page_table"],
|
||||
data["src"]["flashmla_num_splits"],
|
||||
data["src"]["flashmla_metadata"],
|
||||
@@ -781,8 +781,8 @@ def test_fused_metadata_copy_multi(bs, has_real_page_table, has_flashmla):
|
||||
dst_fused_0["cache_seqlens_int32"],
|
||||
dst_fused_0["cu_seqlens_k"],
|
||||
dst_fused_0["page_table_1"],
|
||||
dst_fused_0["nsa_cache_seqlens_int32"],
|
||||
dst_fused_0["nsa_cu_seqlens_k"],
|
||||
dst_fused_0["dsa_cache_seqlens_int32"],
|
||||
dst_fused_0["dsa_cu_seqlens_k"],
|
||||
dst_fused_0["real_page_table"],
|
||||
dst_fused_0["flashmla_num_splits"],
|
||||
dst_fused_0["flashmla_metadata"],
|
||||
@@ -790,8 +790,8 @@ def test_fused_metadata_copy_multi(bs, has_real_page_table, has_flashmla):
|
||||
dst_fused_1["cache_seqlens_int32"],
|
||||
dst_fused_1["cu_seqlens_k"],
|
||||
dst_fused_1["page_table_1"],
|
||||
dst_fused_1["nsa_cache_seqlens_int32"],
|
||||
dst_fused_1["nsa_cu_seqlens_k"],
|
||||
dst_fused_1["dsa_cache_seqlens_int32"],
|
||||
dst_fused_1["dsa_cu_seqlens_k"],
|
||||
dst_fused_1["real_page_table"],
|
||||
dst_fused_1["flashmla_num_splits"],
|
||||
dst_fused_1["flashmla_metadata"],
|
||||
@@ -799,8 +799,8 @@ def test_fused_metadata_copy_multi(bs, has_real_page_table, has_flashmla):
|
||||
dst_fused_2["cache_seqlens_int32"],
|
||||
dst_fused_2["cu_seqlens_k"],
|
||||
dst_fused_2["page_table_1"],
|
||||
dst_fused_2["nsa_cache_seqlens_int32"],
|
||||
dst_fused_2["nsa_cu_seqlens_k"],
|
||||
dst_fused_2["dsa_cache_seqlens_int32"],
|
||||
dst_fused_2["dsa_cu_seqlens_k"],
|
||||
dst_fused_2["real_page_table"],
|
||||
dst_fused_2["flashmla_num_splits"],
|
||||
dst_fused_2["flashmla_metadata"],
|
||||
@@ -836,8 +836,8 @@ def test_fused_metadata_copy_multi(bs, has_real_page_table, has_flashmla):
|
||||
"cache_seqlens_int32",
|
||||
"cu_seqlens_k",
|
||||
"page_table_1",
|
||||
"nsa_cache_seqlens_int32",
|
||||
"nsa_cu_seqlens_k",
|
||||
"dsa_cache_seqlens_int32",
|
||||
"dsa_cu_seqlens_k",
|
||||
]:
|
||||
if not torch.equal(dst_ref[key], dst_fused[key]):
|
||||
diff = (
|
||||
@@ -965,32 +965,32 @@ def test_fused_metadata_copy_multi_large_batch(bs):
|
||||
data["src"]["cache_seqlens"],
|
||||
data["src"]["cu_seqlens_k"],
|
||||
data["src"]["page_indices"],
|
||||
data["src"]["nsa_cache_seqlens"],
|
||||
data["src"]["nsa_cu_seqlens_k"],
|
||||
data["src"]["dsa_cache_seqlens"],
|
||||
data["src"]["dsa_cu_seqlens_k"],
|
||||
data["src"]["real_page_table"],
|
||||
data["src"]["flashmla_num_splits"],
|
||||
data["src"]["flashmla_metadata"],
|
||||
dst_fused_0["cache_seqlens_int32"],
|
||||
dst_fused_0["cu_seqlens_k"],
|
||||
dst_fused_0["page_table_1"],
|
||||
dst_fused_0["nsa_cache_seqlens_int32"],
|
||||
dst_fused_0["nsa_cu_seqlens_k"],
|
||||
dst_fused_0["dsa_cache_seqlens_int32"],
|
||||
dst_fused_0["dsa_cu_seqlens_k"],
|
||||
dst_fused_0["real_page_table"],
|
||||
dst_fused_0["flashmla_num_splits"],
|
||||
dst_fused_0["flashmla_metadata"],
|
||||
dst_fused_1["cache_seqlens_int32"],
|
||||
dst_fused_1["cu_seqlens_k"],
|
||||
dst_fused_1["page_table_1"],
|
||||
dst_fused_1["nsa_cache_seqlens_int32"],
|
||||
dst_fused_1["nsa_cu_seqlens_k"],
|
||||
dst_fused_1["dsa_cache_seqlens_int32"],
|
||||
dst_fused_1["dsa_cu_seqlens_k"],
|
||||
dst_fused_1["real_page_table"],
|
||||
dst_fused_1["flashmla_num_splits"],
|
||||
dst_fused_1["flashmla_metadata"],
|
||||
dst_fused_2["cache_seqlens_int32"],
|
||||
dst_fused_2["cu_seqlens_k"],
|
||||
dst_fused_2["page_table_1"],
|
||||
dst_fused_2["nsa_cache_seqlens_int32"],
|
||||
dst_fused_2["nsa_cu_seqlens_k"],
|
||||
dst_fused_2["dsa_cache_seqlens_int32"],
|
||||
dst_fused_2["dsa_cu_seqlens_k"],
|
||||
dst_fused_2["real_page_table"],
|
||||
dst_fused_2["flashmla_num_splits"],
|
||||
dst_fused_2["flashmla_metadata"],
|
||||
@@ -1013,32 +1013,32 @@ def test_fused_metadata_copy_multi_large_batch(bs):
|
||||
data["src"]["cache_seqlens"],
|
||||
data["src"]["cu_seqlens_k"],
|
||||
data["src"]["page_indices"],
|
||||
data["src"]["nsa_cache_seqlens"],
|
||||
data["src"]["nsa_cu_seqlens_k"],
|
||||
data["src"]["dsa_cache_seqlens"],
|
||||
data["src"]["dsa_cu_seqlens_k"],
|
||||
data["src"]["real_page_table"],
|
||||
data["src"]["flashmla_num_splits"],
|
||||
data["src"]["flashmla_metadata"],
|
||||
dst_fused_0["cache_seqlens_int32"],
|
||||
dst_fused_0["cu_seqlens_k"],
|
||||
dst_fused_0["page_table_1"],
|
||||
dst_fused_0["nsa_cache_seqlens_int32"],
|
||||
dst_fused_0["nsa_cu_seqlens_k"],
|
||||
dst_fused_0["dsa_cache_seqlens_int32"],
|
||||
dst_fused_0["dsa_cu_seqlens_k"],
|
||||
dst_fused_0["real_page_table"],
|
||||
dst_fused_0["flashmla_num_splits"],
|
||||
dst_fused_0["flashmla_metadata"],
|
||||
dst_fused_1["cache_seqlens_int32"],
|
||||
dst_fused_1["cu_seqlens_k"],
|
||||
dst_fused_1["page_table_1"],
|
||||
dst_fused_1["nsa_cache_seqlens_int32"],
|
||||
dst_fused_1["nsa_cu_seqlens_k"],
|
||||
dst_fused_1["dsa_cache_seqlens_int32"],
|
||||
dst_fused_1["dsa_cu_seqlens_k"],
|
||||
dst_fused_1["real_page_table"],
|
||||
dst_fused_1["flashmla_num_splits"],
|
||||
dst_fused_1["flashmla_metadata"],
|
||||
dst_fused_2["cache_seqlens_int32"],
|
||||
dst_fused_2["cu_seqlens_k"],
|
||||
dst_fused_2["page_table_1"],
|
||||
dst_fused_2["nsa_cache_seqlens_int32"],
|
||||
dst_fused_2["nsa_cu_seqlens_k"],
|
||||
dst_fused_2["dsa_cache_seqlens_int32"],
|
||||
dst_fused_2["dsa_cu_seqlens_k"],
|
||||
dst_fused_2["real_page_table"],
|
||||
dst_fused_2["flashmla_num_splits"],
|
||||
dst_fused_2["flashmla_metadata"],
|
||||
|
||||
@@ -26,7 +26,7 @@ from sglang.test.ci.ci_register import register_cuda_ci
|
||||
|
||||
try:
|
||||
from sglang.jit_kernel.fused_store_index_cache import (
|
||||
can_use_nsa_fused_store,
|
||||
can_use_dsa_fused_store,
|
||||
fused_store_index_k_cache,
|
||||
)
|
||||
|
||||
@@ -70,7 +70,7 @@ def _skip_if_unavailable(page_size: int = PAGE_SIZE):
|
||||
pytest.skip("torch.float8_e4m3fn not available")
|
||||
if not HAS_FUSED:
|
||||
pytest.skip("fused_store_index_cache not importable")
|
||||
if not can_use_nsa_fused_store(torch.bfloat16, torch.int64, page_size):
|
||||
if not can_use_dsa_fused_store(torch.bfloat16, torch.int64, page_size):
|
||||
pytest.skip("JIT kernel unavailable / failed to compile")
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ def _reference_quantize_and_store(
|
||||
|
||||
def _import_act_quant():
|
||||
try:
|
||||
from sglang.srt.layers.attention.nsa.triton_kernel import act_quant
|
||||
from sglang.srt.layers.attention.dsa.triton_kernel import act_quant
|
||||
|
||||
return act_quant
|
||||
except Exception:
|
||||
|
||||
@@ -75,7 +75,7 @@ def test_set_mla_kv_buffer_loc_dtypes(loc_dtype):
|
||||
|
||||
|
||||
def test_set_mla_kv_buffer_uint8_byte_layout():
|
||||
"""FP8 NSA byte-layout: cache_k_nope is uint8 with [fp8(512) | scales(16)] = 528,
|
||||
"""FP8 DSA byte-layout: cache_k_nope is uint8 with [fp8(512) | scales(16)] = 528,
|
||||
cache_k_rope is uint8 [128]; total payload = 656 bytes."""
|
||||
nope_bytes, rope_bytes = 528, 128
|
||||
batch_size = 64
|
||||
|
||||
@@ -54,13 +54,13 @@ def apply_deepseek_v4_defaults(server_args: "ServerArgs", model_arch: str) -> No
|
||||
|
||||
def validate_deepseek_v4_cp(server_args: "ServerArgs") -> None:
|
||||
"""Validate DeepSeek V4 context-parallel configuration."""
|
||||
if not server_args.enable_nsa_prefill_context_parallel:
|
||||
if not server_args.enable_dsa_prefill_context_parallel:
|
||||
return
|
||||
|
||||
if server_args.nsa_prefill_cp_mode != "round-robin-split":
|
||||
if server_args.dsa_prefill_cp_mode != "round-robin-split":
|
||||
raise ValueError(
|
||||
f"DeepSeekV4 only supports round-robin-split CP mode, "
|
||||
f"got {server_args.nsa_prefill_cp_mode}"
|
||||
f"got {server_args.dsa_prefill_cp_mode}"
|
||||
)
|
||||
|
||||
server_args.enable_dp_attention = True
|
||||
|
||||
@@ -20,13 +20,13 @@ def _hisparse_default_backend(kv_cache_dtype: str) -> str:
|
||||
return "flashmla_kv" if kv_cache_dtype == "fp8_e4m3" else "flashmla_sparse"
|
||||
|
||||
|
||||
def apply_hisparse_nsa_backend_defaults(
|
||||
def apply_hisparse_dsa_backend_defaults(
|
||||
server_args: "ServerArgs",
|
||||
user_set_prefill: bool,
|
||||
user_set_decode: bool,
|
||||
kv_cache_dtype: str,
|
||||
) -> bool:
|
||||
"""Pick NSA backends for --enable-hisparse based on KV dtype.
|
||||
"""Pick DSA backends for --enable-hisparse based on KV dtype.
|
||||
|
||||
BF16 KV -> flashmla_sparse, FP8 KV -> flashmla_kv. Returns True if hisparse
|
||||
handled backend selection (caller should skip its own default logic).
|
||||
@@ -36,29 +36,29 @@ def apply_hisparse_nsa_backend_defaults(
|
||||
|
||||
backend = _hisparse_default_backend(kv_cache_dtype)
|
||||
if not user_set_prefill:
|
||||
server_args.nsa_prefill_backend = backend
|
||||
server_args.dsa_prefill_backend = backend
|
||||
if not user_set_decode:
|
||||
server_args.nsa_decode_backend = backend
|
||||
server_args.dsa_decode_backend = backend
|
||||
logger.warning(
|
||||
f"HiSparse enabled ({kv_cache_dtype}): using NSA backends "
|
||||
f"prefill={server_args.nsa_prefill_backend}, decode={server_args.nsa_decode_backend}."
|
||||
f"HiSparse enabled ({kv_cache_dtype}): using DSA backends "
|
||||
f"prefill={server_args.dsa_prefill_backend}, decode={server_args.dsa_decode_backend}."
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def validate_hisparse(server_args: "ServerArgs") -> None:
|
||||
"""Validate --enable-hisparse constraints (model class, radix cache, NSA backend)."""
|
||||
"""Validate --enable-hisparse constraints (model class, radix cache, DSA backend)."""
|
||||
if not server_args.enable_hisparse:
|
||||
return
|
||||
|
||||
from sglang.srt.configs.model_config import (
|
||||
is_deepseek_nsa,
|
||||
is_deepseek_dsa,
|
||||
is_deepseek_v4,
|
||||
)
|
||||
|
||||
hf_config = server_args.get_model_config().hf_config
|
||||
is_v4_hisparse = is_deepseek_v4(hf_config)
|
||||
assert is_deepseek_nsa(hf_config) or is_v4_hisparse, (
|
||||
assert is_deepseek_dsa(hf_config) or is_v4_hisparse, (
|
||||
"--enable-hisparse is only supported for DSA (DeepSeek Sparse Attention) "
|
||||
"models (e.g., DeepSeek V3.2, GLM-5) and DeepSeek V4 now. "
|
||||
)
|
||||
@@ -83,13 +83,13 @@ def validate_hisparse(server_args: "ServerArgs") -> None:
|
||||
server_args.kv_cache_dtype, {"flashmla_sparse", "flashmla_kv"}
|
||||
)
|
||||
for attr, label in [
|
||||
("nsa_prefill_backend", "prefill"),
|
||||
("nsa_decode_backend", "decode"),
|
||||
("dsa_prefill_backend", "prefill"),
|
||||
("dsa_decode_backend", "decode"),
|
||||
]:
|
||||
backend = getattr(server_args, attr)
|
||||
if backend is not None and backend not in allowed_backends:
|
||||
raise ValueError(
|
||||
f"HiSparse with --kv-cache-dtype={server_args.kv_cache_dtype} requires "
|
||||
f"--nsa-{label}-backend in {sorted(allowed_backends)}, "
|
||||
f"--dsa-{label}-backend in {sorted(allowed_backends)}, "
|
||||
f"but got {backend}."
|
||||
)
|
||||
|
||||
@@ -99,7 +99,7 @@ def _hf_attr(config, name):
|
||||
return getattr(config, name, None)
|
||||
|
||||
|
||||
def is_deepseek_nsa(config) -> bool:
|
||||
def is_deepseek_dsa(config) -> bool:
|
||||
return (
|
||||
_hf_arch(config)
|
||||
in (
|
||||
@@ -121,31 +121,31 @@ def is_deepseek_v4(config) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def get_nsa_index_head_dim(config: PretrainedConfig) -> int:
|
||||
assert is_deepseek_nsa(config) or is_deepseek_v4(config)
|
||||
def get_dsa_index_head_dim(config: PretrainedConfig) -> int:
|
||||
assert is_deepseek_dsa(config) or is_deepseek_v4(config)
|
||||
return config.index_head_dim
|
||||
|
||||
|
||||
def get_nsa_index_topk(config: PretrainedConfig) -> int:
|
||||
assert is_deepseek_nsa(config)
|
||||
def get_dsa_index_topk(config: PretrainedConfig) -> int:
|
||||
assert is_deepseek_dsa(config)
|
||||
return config.index_topk
|
||||
|
||||
|
||||
def get_nsa_index_n_heads(config: PretrainedConfig) -> int:
|
||||
assert is_deepseek_nsa(config)
|
||||
def get_dsa_index_n_heads(config: PretrainedConfig) -> int:
|
||||
assert is_deepseek_dsa(config)
|
||||
return config.index_n_heads
|
||||
|
||||
|
||||
def get_num_indexer_layers(config) -> int:
|
||||
"""Layer count for the global indexer-topk capturer's host buffer.
|
||||
|
||||
NSA models (V3.2) instantiate an Indexer on every transformer layer.
|
||||
DSA models (V3.2) instantiate an Indexer on every transformer layer.
|
||||
With index_topk_freq > 1 some layers reuse prev layer's topk; those still
|
||||
get a slot (mirrored at the MLA call site). DSv4 has C4 indexers only on
|
||||
layers whose compress_ratio == 4. Other architectures: set
|
||||
num_indexer_layers on hf_text_config; 0 disables the capturer.
|
||||
"""
|
||||
if is_deepseek_nsa(config):
|
||||
if is_deepseek_dsa(config):
|
||||
return config.num_hidden_layers
|
||||
if is_deepseek_v4(config):
|
||||
compress_ratios = getattr(config, "compress_ratios", None) or []
|
||||
@@ -329,7 +329,7 @@ class ModelConfig:
|
||||
self.use_ngram_embedding = getattr(self.hf_config, "use_ngram_embedding", False)
|
||||
self.is_piecewise_cuda_graph_disabled_model = (
|
||||
is_piecewise_cuda_graph_disabled_model(self.hf_config.architectures)
|
||||
or is_deepseek_nsa(self.hf_text_config)
|
||||
or is_deepseek_dsa(self.hf_text_config)
|
||||
)
|
||||
self.dtype = _get_and_verify_dtype(self.hf_text_config, dtype)
|
||||
|
||||
@@ -622,8 +622,8 @@ class ModelConfig:
|
||||
self.qk_rope_head_dim = self.hf_text_config.qk_rope_head_dim
|
||||
self.v_head_dim = self.hf_text_config.v_head_dim
|
||||
self.index_head_dim = (
|
||||
get_nsa_index_head_dim(self.hf_text_config)
|
||||
if is_deepseek_nsa(self.hf_text_config)
|
||||
get_dsa_index_head_dim(self.hf_text_config)
|
||||
if is_deepseek_dsa(self.hf_text_config)
|
||||
else None
|
||||
)
|
||||
# Handle rope scaling
|
||||
|
||||
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
||||
class StateType(str, enum.Enum):
|
||||
MAMBA = "mamba"
|
||||
SWA = "swa"
|
||||
NSA = "nsa"
|
||||
DSA = "dsa"
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
||||
@@ -954,7 +954,7 @@ class DecodePreallocQueue:
|
||||
window_kv_indices_swa.cpu().numpy(), page_size
|
||||
)
|
||||
|
||||
def _nsa_payload():
|
||||
def _dsa_payload():
|
||||
kv_indices_full = self.req_to_token_pool.req_to_token[
|
||||
decode_req.req.req_pool_idx, :seq_len
|
||||
]
|
||||
@@ -971,8 +971,8 @@ class DecodePreallocQueue:
|
||||
state_indices.append(_mamba_payload())
|
||||
elif st == StateType.SWA:
|
||||
state_indices.append(_swa_payload())
|
||||
elif st == StateType.NSA:
|
||||
state_indices.append(_nsa_payload())
|
||||
elif st == StateType.DSA:
|
||||
state_indices.append(_dsa_payload())
|
||||
else:
|
||||
state_indices.append(None)
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ class DecodeKVCacheOffloadManager:
|
||||
kv_committed_len = req.pop_committed_kv_cache()
|
||||
start = start_offset
|
||||
end = kv_committed_len
|
||||
# Free the incremental part of the request (NSA-aware)
|
||||
# Free the incremental part of the request (DSA-aware)
|
||||
kv_indices = self.req_to_token_pool.req_to_token[req.req_pool_idx, start:end]
|
||||
self.token_to_kv_pool_allocator.free(kv_indices)
|
||||
|
||||
|
||||
@@ -1012,7 +1012,7 @@ class MooncakeKVManager(CommonKVManager):
|
||||
)
|
||||
or rc
|
||||
)
|
||||
elif st in (StateType.SWA, StateType.NSA):
|
||||
elif st in (StateType.SWA, StateType.DSA):
|
||||
if (
|
||||
target_rank_registration_info is not None
|
||||
and not self.is_mla_backend
|
||||
|
||||
@@ -960,8 +960,8 @@ class MoriKVManager(CommonKVManager):
|
||||
return self._send_mamba_state(
|
||||
peer_info, src_state_indices, dst_state_indices
|
||||
)
|
||||
elif state_type in ("swa", "nsa"):
|
||||
return self._send_swa_nsa_state(
|
||||
elif state_type in ("swa", "dsa"):
|
||||
return self._send_swa_dsa_state(
|
||||
peer_info, src_state_indices, dst_state_indices, state_type
|
||||
)
|
||||
else:
|
||||
@@ -1056,7 +1056,7 @@ class MoriKVManager(CommonKVManager):
|
||||
|
||||
return statuses
|
||||
|
||||
def _send_swa_nsa_state(
|
||||
def _send_swa_dsa_state(
|
||||
self,
|
||||
peer_info: KVArgsRegisterInfo,
|
||||
src_state_indices: npt.NDArray[np.int32],
|
||||
|
||||
@@ -1541,7 +1541,7 @@ class NixlKVManager(CommonKVManager):
|
||||
dst_gpu_id,
|
||||
comp_notif,
|
||||
)
|
||||
elif st in (StateType.SWA, StateType.NSA):
|
||||
elif st in (StateType.SWA, StateType.DSA):
|
||||
if not self.is_mla_backend and self.attn_tp_size != decode_tp_size:
|
||||
raise RuntimeError(
|
||||
f"PD Disaggregation does NOT support PD different TP sizes for non-MLA {st.upper()} hybrid models yet."
|
||||
|
||||
@@ -818,7 +818,7 @@ class SchedulerDisaggregationPrefillMixin:
|
||||
window_kv_indices_swa.cpu().numpy(), page_size
|
||||
)
|
||||
|
||||
def _nsa_payload():
|
||||
def _dsa_payload():
|
||||
kv_indices_full = self.req_to_token_pool.req_to_token[
|
||||
req.req_pool_idx, :seq_len
|
||||
]
|
||||
@@ -833,8 +833,8 @@ class SchedulerDisaggregationPrefillMixin:
|
||||
state_indices.append(_mamba_payload())
|
||||
elif st == StateType.SWA:
|
||||
state_indices.append(_swa_payload())
|
||||
elif st == StateType.NSA:
|
||||
state_indices.append(_nsa_payload())
|
||||
elif st == StateType.DSA:
|
||||
state_indices.append(_dsa_payload())
|
||||
else:
|
||||
state_indices.append(None)
|
||||
|
||||
|
||||
@@ -567,7 +567,7 @@ def setup_state_kv_args(
|
||||
from sglang.srt.disaggregation.base.conn import StateType
|
||||
from sglang.srt.hardware_backend.npu.memory_pool_npu import NPUMLATokenToKVPool
|
||||
from sglang.srt.mem_cache.base_swa_memory_pool import BaseSWAKVPool
|
||||
from sglang.srt.mem_cache.memory_pool import HybridLinearKVPool, NSATokenToKVPool
|
||||
from sglang.srt.mem_cache.memory_pool import DSATokenToKVPool, HybridLinearKVPool
|
||||
|
||||
kv_args.state_types = []
|
||||
kv_args.state_data_ptrs = []
|
||||
@@ -593,9 +593,9 @@ def setup_state_kv_args(
|
||||
append_state_component(
|
||||
kv_args, StateType.MAMBA, data_ptrs, data_lens, item_lens, dim
|
||||
)
|
||||
elif isinstance(token_to_kv_pool, (NSATokenToKVPool, NPUMLATokenToKVPool)):
|
||||
elif isinstance(token_to_kv_pool, (DSATokenToKVPool, NPUMLATokenToKVPool)):
|
||||
if draft_token_to_kv_pool is not None and isinstance(
|
||||
draft_token_to_kv_pool, NSATokenToKVPool
|
||||
draft_token_to_kv_pool, DSATokenToKVPool
|
||||
):
|
||||
(
|
||||
draft_data_ptrs,
|
||||
@@ -612,7 +612,7 @@ def setup_state_kv_args(
|
||||
kv_args.total_kv_layers = total_kv_layers
|
||||
else:
|
||||
append_state_component(
|
||||
kv_args, StateType.NSA, data_ptrs, data_lens, item_lens
|
||||
kv_args, StateType.DSA, data_ptrs, data_lens, item_lens
|
||||
)
|
||||
|
||||
if (
|
||||
|
||||
@@ -134,6 +134,41 @@ class EnvInt(EnvField):
|
||||
raise ValueError(f'"{value}" is not a valid integer value')
|
||||
|
||||
|
||||
class _DeprecatedEnvFallback:
|
||||
"""Mixin for EnvField subclasses: if the canonical env var is not set,
|
||||
check *deprecated_name* and emit DeprecationWarning before reading it.
|
||||
|
||||
Usage:
|
||||
SGLANG_DSA_FUSE_TOPK = EnvBoolWithAlias(True, deprecated_name="SGLANG_NSA_FUSE_TOPK")
|
||||
"""
|
||||
|
||||
def __init__(self, default: Any, deprecated_name: str):
|
||||
super().__init__(default)
|
||||
self.deprecated_name = deprecated_name
|
||||
|
||||
def get(self) -> Any:
|
||||
if os.getenv(self.name) is None:
|
||||
fallback = os.getenv(self.deprecated_name)
|
||||
if fallback is not None:
|
||||
warnings.warn(
|
||||
f"Environment variable '{self.deprecated_name}' is deprecated; "
|
||||
f"use '{self.name}' instead. "
|
||||
"The alias will be removed in a future release.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
os.environ[self.name] = fallback
|
||||
return super().get()
|
||||
|
||||
|
||||
class EnvBoolWithAlias(_DeprecatedEnvFallback, EnvBool):
|
||||
pass
|
||||
|
||||
|
||||
class EnvIntWithAlias(_DeprecatedEnvFallback, EnvInt):
|
||||
pass
|
||||
|
||||
|
||||
class EnvFloat(EnvField):
|
||||
def parse(self, value: str) -> float:
|
||||
try:
|
||||
@@ -428,11 +463,18 @@ class Envs:
|
||||
SGLANG_NIXL_EP_BF16_DISPATCH = EnvBool(False)
|
||||
SGLANG_NIXL_EP_NUM_MAX_DISPATCH_TOKENS_PER_RANK = EnvInt(128)
|
||||
|
||||
# NSA Backend
|
||||
SGLANG_NSA_FUSE_TOPK = EnvBool(True)
|
||||
SGLANG_NSA_ENABLE_MTP_PRECOMPUTE_METADATA = EnvBool(True)
|
||||
# DSA Backend (canonical names; fall back to SGLANG_NSA_* with deprecation warning)
|
||||
SGLANG_DSA_FUSE_TOPK = EnvBoolWithAlias(True, deprecated_name="SGLANG_NSA_FUSE_TOPK")
|
||||
SGLANG_DSA_ENABLE_MTP_PRECOMPUTE_METADATA = EnvBoolWithAlias(
|
||||
True, deprecated_name="SGLANG_NSA_ENABLE_MTP_PRECOMPUTE_METADATA"
|
||||
)
|
||||
SGLANG_DSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD = EnvIntWithAlias(
|
||||
2048, deprecated_name="SGLANG_NSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD"
|
||||
)
|
||||
SGLANG_DSA_HIP_DISABLE_PRESHUFFLE = EnvBoolWithAlias(
|
||||
False, deprecated_name="SGLANG_NSA_HIP_DISABLE_PRESHUFFLE"
|
||||
)
|
||||
SGLANG_USE_FUSED_METADATA_COPY = EnvBool(True)
|
||||
SGLANG_NSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD = EnvInt(2048)
|
||||
|
||||
# sgl-kernel
|
||||
SGLANG_SKIP_SGL_KERNEL_VERSION_CHECK = EnvBool(False)
|
||||
|
||||
@@ -20,7 +20,7 @@ from sglang.srt.hardware_backend.npu.attention.mla_preprocess import (
|
||||
is_mla_preprocess_enabled,
|
||||
)
|
||||
from sglang.srt.layers.attention.base_attn_backend import AttentionBackend
|
||||
from sglang.srt.layers.attention.nsa.utils import is_nsa_enable_prefill_cp
|
||||
from sglang.srt.layers.attention.dsa.utils import is_dsa_enable_prefill_cp
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
from sglang.srt.layers.radix_attention import AttentionType
|
||||
from sglang.srt.layers.utils.cp_utils import cp_all_gather_rerange_kv_cache
|
||||
@@ -932,7 +932,7 @@ class AscendAttnBackend(AttentionBackend):
|
||||
|
||||
if (
|
||||
is_prefill
|
||||
and is_nsa_enable_prefill_cp()
|
||||
and is_dsa_enable_prefill_cp()
|
||||
and forward_batch.attn_cp_metadata is not None
|
||||
):
|
||||
attn_out = self.do_cp_balance_attn(
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.configs.model_config import is_deepseek_nsa
|
||||
from sglang.srt.configs.model_config import is_deepseek_dsa
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
from sglang.srt.speculative.eagle_draft_extend_cuda_graph_runner import (
|
||||
EAGLEDraftExtendCudaGraphRunner,
|
||||
@@ -59,7 +59,7 @@ class EAGLEDraftExtendNpuGraphRunner(EAGLEDraftExtendCudaGraphRunner):
|
||||
)
|
||||
|
||||
def _replay(self, forward_batch: ForwardBatch):
|
||||
if not is_deepseek_nsa(self.model_runner.model_config.hf_config):
|
||||
if not is_deepseek_dsa(self.model_runner.model_config.hf_config):
|
||||
seq_lens = forward_batch.seq_lens_cpu.tolist() + [0] * (
|
||||
self.bs - self.raw_bs
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Dict, Union
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from sglang.srt.configs.model_config import AttentionArch, is_deepseek_nsa
|
||||
from sglang.srt.configs.model_config import AttentionArch, is_deepseek_dsa
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
from sglang.srt.speculative.eagle_draft_cuda_graph_runner import (
|
||||
EAGLEDraftCudaGraphRunner,
|
||||
@@ -96,7 +96,7 @@ class EAGLEDraftNpuGraphRunner(EAGLEDraftCudaGraphRunner):
|
||||
def _replay(self, forward_batch: ForwardBatch):
|
||||
self.update_attr_name = self._get_update_attr_name()
|
||||
self.update_attr_type = self._get_update_attr_type()
|
||||
if not is_deepseek_nsa(self.model_runner.model_config.hf_config):
|
||||
if not is_deepseek_dsa(self.model_runner.model_config.hf_config):
|
||||
seq_lens_for_each_draft_step = []
|
||||
for speculative_step_id in range(self.speculative_num_steps - 1):
|
||||
seq_lens_cpu = forward_batch.seq_lens_cpu + speculative_step_id + 1
|
||||
|
||||
@@ -26,7 +26,7 @@ import numpy as np
|
||||
import torch
|
||||
|
||||
import sglang
|
||||
from sglang.srt.configs.model_config import AttentionArch, is_deepseek_nsa
|
||||
from sglang.srt.configs.model_config import AttentionArch, is_deepseek_dsa
|
||||
from sglang.srt.distributed.parallel_state import GroupCoordinator
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.model_executor.cuda_graph_runner import CudaGraphRunner
|
||||
@@ -188,7 +188,7 @@ class NPUGraphRunner(CudaGraphRunner):
|
||||
self.update_attr_name = self._get_update_attr_name()
|
||||
self.update_attr_type = self._get_update_attr_type()
|
||||
# Replay
|
||||
if not is_deepseek_nsa(self.model_runner.model_config.hf_config):
|
||||
if not is_deepseek_dsa(self.model_runner.model_config.hf_config):
|
||||
if forward_batch.forward_mode.is_target_verify():
|
||||
seq_lens_cpu = forward_batch.seq_lens.cpu() + self.num_tokens_per_bs
|
||||
seq_lens = seq_lens_cpu.tolist() + [0] * (self.bs - self.raw_bs)
|
||||
|
||||
@@ -11,9 +11,9 @@ from sglang.srt.hardware_backend.npu.attention.mla_preprocess import (
|
||||
is_fia_nz,
|
||||
is_mla_preprocess_enabled,
|
||||
)
|
||||
from sglang.srt.layers.attention.nsa.nsa_indexer import scattered_to_tp_attn_full
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
nsa_use_prefill_cp,
|
||||
from sglang.srt.layers.attention.dsa.dsa_indexer import scattered_to_tp_attn_full
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
dsa_use_prefill_cp,
|
||||
)
|
||||
from sglang.srt.layers.communicator import ScatterMode, get_attn_tp_context
|
||||
|
||||
@@ -43,9 +43,9 @@ def forward_mha_prepare_npu(
|
||||
)
|
||||
)
|
||||
|
||||
# NSA Indexer: cache quantized keys, auto-skip topk for sequences <= nsa_index_topk
|
||||
# DSA Indexer: cache quantized keys, auto-skip topk for sequences <= dsa_index_topk
|
||||
|
||||
if m.use_nsa:
|
||||
if m.use_dsa:
|
||||
q_lora = m.q_a_layernorm(q)
|
||||
q = m.q_b_proj(q_lora)[0].view(-1, m.num_local_heads, m.qk_head_dim)
|
||||
_ = m.indexer(
|
||||
@@ -206,7 +206,7 @@ def forward_mla_prepare_npu(
|
||||
k_nope = m.kv_a_layernorm(k_nope)
|
||||
|
||||
# q_lora needed by indexer
|
||||
if m.use_nsa:
|
||||
if m.use_dsa:
|
||||
q_lora = q
|
||||
|
||||
k_nope = k_nope.unsqueeze(1)
|
||||
@@ -226,7 +226,7 @@ def forward_mla_prepare_npu(
|
||||
|
||||
q_pe, k_pe = m.rotary_emb(positions, q_pe, k_pe)
|
||||
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
# support allgather+rerrange
|
||||
k_nope, k_pe = m.rebuild_cp_kv_cache(
|
||||
latent_cache, forward_batch, k_nope, k_pe
|
||||
@@ -359,7 +359,7 @@ def forward_dsa_prepare_npu(
|
||||
if q_event is not None:
|
||||
torch.npu.current_stream().wait_event(q_event)
|
||||
else:
|
||||
if fused_qkv_a_proj_out.shape[0] < 65535 and not nsa_use_prefill_cp(
|
||||
if fused_qkv_a_proj_out.shape[0] < 65535 and not dsa_use_prefill_cp(
|
||||
forward_batch
|
||||
):
|
||||
q_lora, k_nope, k_pe = fused_split_qk_norm(
|
||||
@@ -398,7 +398,7 @@ def forward_dsa_prepare_npu(
|
||||
|
||||
q_pe, k_pe = m.rotary_emb(positions, q_pe, k_pe)
|
||||
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
# support allgather+rerrange
|
||||
k_nope, k_pe = m.rebuild_cp_kv_cache(
|
||||
latent_cache, forward_batch, k_nope, k_pe
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sglang.srt.configs.linear_attn_model_registry import (
|
||||
@@ -96,11 +97,22 @@ def create_ascend_backend(runner):
|
||||
return AscendAttnBackend(runner)
|
||||
|
||||
|
||||
@register_attention_backend("nsa")
|
||||
def create_nsa_backend(runner):
|
||||
from sglang.srt.layers.attention.nsa_backend import NativeSparseAttnBackend
|
||||
@register_attention_backend("dsa")
|
||||
def create_dsa_backend(runner):
|
||||
from sglang.srt.layers.attention.dsa_backend import DeepseekSparseAttnBackend
|
||||
|
||||
return NativeSparseAttnBackend(runner)
|
||||
return DeepseekSparseAttnBackend(runner)
|
||||
|
||||
|
||||
@register_attention_backend("nsa")
|
||||
def _create_nsa_compat(runner):
|
||||
warnings.warn(
|
||||
"attention-backend='nsa' is deprecated; use 'dsa' instead. "
|
||||
"The alias will be removed in a future release.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return create_dsa_backend(runner)
|
||||
|
||||
|
||||
@register_attention_backend("dsv4")
|
||||
|
||||
@@ -9,7 +9,7 @@ from sglang.kernel_api_logging import debug_kernel_api
|
||||
from sglang.srt.utils.common import is_npu
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.layers.attention.nsa.nsa_indexer import BaseIndexerMetadata
|
||||
from sglang.srt.layers.attention.dsa.dsa_indexer import BaseIndexerMetadata
|
||||
from sglang.srt.layers.radix_attention import RadixAttention
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch, ForwardMode
|
||||
from sglang.srt.speculative.spec_info import SpecInput
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
|
||||
def dequantize_k_cache(quant_k_cache):
|
||||
return _dequantize_k_cache_fast_wrapped(quant_k_cache)
|
||||
|
||||
|
||||
def _dequantize_k_cache_ref(
|
||||
quant_k_cache: torch.Tensor, # (num_blocks, block_size, 1, bytes_per_token)
|
||||
dv: int = 512,
|
||||
tile_size: int = 128,
|
||||
d: int = 576,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
De-quantize the k-cache
|
||||
"""
|
||||
assert dv % tile_size == 0
|
||||
original_ndim = quant_k_cache.ndim
|
||||
if original_ndim == 3:
|
||||
# set block_size = 1
|
||||
quant_k_cache = quant_k_cache.unsqueeze(1)
|
||||
num_tiles = dv // tile_size
|
||||
num_blocks, block_size, h_k, _ = quant_k_cache.shape
|
||||
assert h_k == 1
|
||||
result = torch.empty(
|
||||
(num_blocks, block_size, d), dtype=torch.bfloat16, device=quant_k_cache.device
|
||||
)
|
||||
|
||||
quant_k_cache = quant_k_cache.view(num_blocks, block_size, -1)
|
||||
|
||||
input_nope = quant_k_cache[..., :dv]
|
||||
input_scale = quant_k_cache[..., dv : dv + num_tiles * 4].view(torch.float32)
|
||||
input_rope = quant_k_cache[..., dv + num_tiles * 4 :].view(torch.bfloat16)
|
||||
result[..., dv:] = input_rope
|
||||
|
||||
for tile_idx in range(0, num_tiles):
|
||||
cur_nope = input_nope[
|
||||
..., tile_idx * tile_size : (tile_idx + 1) * tile_size
|
||||
].to(torch.float32)
|
||||
cur_scales = input_scale[..., tile_idx].unsqueeze(-1)
|
||||
result[..., tile_idx * tile_size : (tile_idx + 1) * tile_size] = (
|
||||
cur_nope * cur_scales
|
||||
)
|
||||
|
||||
if original_ndim == 3:
|
||||
return result.view(num_blocks, 1, -1)
|
||||
else:
|
||||
return result.view(num_blocks, block_size, 1, -1)
|
||||
|
||||
|
||||
def _dequantize_k_cache_fast_wrapped(
|
||||
quant_k_cache: torch.Tensor,
|
||||
dv: int = 512,
|
||||
tile_size: int = 128,
|
||||
) -> torch.Tensor:
|
||||
original_ndim = quant_k_cache.ndim
|
||||
if original_ndim == 3:
|
||||
# set block_size = 1
|
||||
quant_k_cache = quant_k_cache.unsqueeze(1)
|
||||
num_blocks, block_size, _, dim_quant = quant_k_cache.shape
|
||||
assert dv == 512
|
||||
assert dim_quant == 656
|
||||
assert tile_size == 128
|
||||
quant_k_cache = quant_k_cache.view((-1, dim_quant))
|
||||
|
||||
output = _dequantize_k_cache_fast(quant_k_cache)
|
||||
|
||||
if original_ndim == 3:
|
||||
return output.view(num_blocks, 1, -1)
|
||||
else:
|
||||
return output.view(num_blocks, block_size, 1, -1)
|
||||
|
||||
|
||||
def _dequantize_k_cache_fast(quant_k_cache, group_size: int = 128):
|
||||
num_tokens, dim_quant = quant_k_cache.shape
|
||||
|
||||
assert quant_k_cache.dtype == torch.float8_e4m3fn
|
||||
dim_nope = 512
|
||||
dim_rope = 64
|
||||
num_tiles = dim_nope // group_size
|
||||
assert dim_quant == 656
|
||||
|
||||
output = torch.empty(
|
||||
(num_tokens, dim_nope + dim_rope),
|
||||
dtype=torch.bfloat16,
|
||||
device=quant_k_cache.device,
|
||||
)
|
||||
|
||||
num_blocks_per_token = triton.cdiv(dim_nope + dim_rope, group_size)
|
||||
assert num_blocks_per_token == 5
|
||||
|
||||
assert dim_nope % group_size == 0
|
||||
|
||||
input_nope_q = quant_k_cache[:, :dim_nope]
|
||||
input_nope_s = quant_k_cache[:, dim_nope : dim_nope + num_tiles * 4].view(
|
||||
torch.float32
|
||||
)
|
||||
input_rope = quant_k_cache[:, dim_nope + num_tiles * 4 :].view(torch.bfloat16)
|
||||
|
||||
_dequantize_k_cache_fast_kernel[(num_tokens, num_blocks_per_token)](
|
||||
output,
|
||||
input_nope_q,
|
||||
input_nope_s,
|
||||
input_rope,
|
||||
output.stride(0),
|
||||
input_nope_q.stride(0),
|
||||
input_nope_s.stride(0),
|
||||
input_rope.stride(0),
|
||||
NUM_NOPE_BLOCKS=num_tiles,
|
||||
GROUP_SIZE=group_size,
|
||||
DIM_NOPE=dim_nope,
|
||||
DIM_ROPE=dim_rope,
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _dequantize_k_cache_fast_kernel(
|
||||
output_ptr,
|
||||
input_nope_q_ptr,
|
||||
input_nope_s_ptr,
|
||||
input_rope_ptr,
|
||||
output_stride_0: int,
|
||||
input_nope_q_stride_0: int,
|
||||
input_nope_s_stride_0: int,
|
||||
input_rope_stride_0: int,
|
||||
NUM_NOPE_BLOCKS: tl.constexpr,
|
||||
GROUP_SIZE: tl.constexpr,
|
||||
DIM_NOPE: tl.constexpr,
|
||||
DIM_ROPE: tl.constexpr,
|
||||
):
|
||||
token_id = tl.program_id(0)
|
||||
raw_block_id = tl.program_id(1)
|
||||
|
||||
if raw_block_id < NUM_NOPE_BLOCKS:
|
||||
# a. dequant nope
|
||||
effective_block_id = raw_block_id
|
||||
|
||||
offs_q = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs_q < DIM_NOPE
|
||||
ptr_q = input_nope_q_ptr + token_id * input_nope_q_stride_0 + offs_q
|
||||
ptr_s = input_nope_s_ptr + token_id * input_nope_s_stride_0 + effective_block_id
|
||||
|
||||
y_q = tl.load(ptr_q, mask=mask, other=0.0).to(tl.float32)
|
||||
y_s = tl.load(ptr_s)
|
||||
|
||||
y = (y_q * y_s).to(output_ptr.dtype.element_ty)
|
||||
|
||||
dst_ptr = output_ptr + token_id * output_stride_0 + offs_q
|
||||
tl.store(dst_ptr, y, mask=mask)
|
||||
else:
|
||||
# b. copy rope
|
||||
effective_block_id = raw_block_id - NUM_NOPE_BLOCKS
|
||||
|
||||
offs = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs < DIM_ROPE
|
||||
|
||||
src_ptr = input_rope_ptr + token_id * input_rope_stride_0 + offs
|
||||
dst_ptr = output_ptr + token_id * output_stride_0 + DIM_NOPE + offs
|
||||
|
||||
data = tl.load(src_ptr, mask=mask).to(tl.bfloat16)
|
||||
tl.store(dst_ptr, data, mask=mask)
|
||||
|
||||
|
||||
def dequantize_k_cache_paged(
|
||||
quant_k_cache: torch.Tensor,
|
||||
page_table_1_flattened: torch.Tensor,
|
||||
group_size: int = 128,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
De-quantize the k-cache with paged layout
|
||||
Args:
|
||||
quant_k_cache: [total_num_tokens, 1, dim_quant] or [num_blocks, block_size, 1, dim_quant], the quantized k-cache in paged layout
|
||||
page_table_1_flattened: [num_tokens], the flattened page_table_1 with the page indices in each requests concatenated together
|
||||
Returns:
|
||||
output: [num_tokens, 1, dim_nope + dim_rope], the de-quantized k-cache
|
||||
"""
|
||||
dim_quant = quant_k_cache.shape[-1]
|
||||
assert (
|
||||
dim_quant == 656
|
||||
), f"dim_quant: {dim_quant} != 656 detected in dequantize_k_cache_paged"
|
||||
quant_k_cache = quant_k_cache.view((-1, dim_quant))
|
||||
|
||||
# num_tokens can exceed kv_cache_size due to prefix sharing (multiple seqs share same KV slots)
|
||||
# Index bounds validated in dsa_backend.init_forward_metadata
|
||||
num_tokens = page_table_1_flattened.shape[0]
|
||||
assert quant_k_cache.dtype == torch.float8_e4m3fn
|
||||
dim_nope = 512
|
||||
dim_rope = 64
|
||||
num_tiles = dim_nope // group_size # 512 // 128 = 4
|
||||
|
||||
output = torch.empty(
|
||||
(num_tokens, 1, dim_nope + dim_rope),
|
||||
dtype=torch.bfloat16,
|
||||
device=quant_k_cache.device,
|
||||
)
|
||||
|
||||
# cdiv(512 + 64, 128) = 5
|
||||
num_blocks_per_token = triton.cdiv(dim_nope + dim_rope, group_size)
|
||||
assert num_blocks_per_token == 5
|
||||
|
||||
assert dim_nope % group_size == 0
|
||||
|
||||
input_nope_q = quant_k_cache[:, :dim_nope]
|
||||
# [:, 512:512+4*4] = [:, 512:528]
|
||||
input_nope_s = quant_k_cache[:, dim_nope : dim_nope + num_tiles * 4].view(
|
||||
torch.float32
|
||||
)
|
||||
# [:, 528:]
|
||||
input_rope = quant_k_cache[:, dim_nope + num_tiles * 4 :].view(torch.bfloat16)
|
||||
|
||||
_dequantize_k_cache_paged_kernel[(num_tokens, num_blocks_per_token)](
|
||||
output,
|
||||
input_nope_q,
|
||||
input_nope_s,
|
||||
input_rope,
|
||||
page_table_1_flattened,
|
||||
output.stride(0),
|
||||
input_nope_q.stride(0),
|
||||
input_nope_s.stride(0),
|
||||
input_rope.stride(0),
|
||||
NUM_NOPE_BLOCKS=num_tiles,
|
||||
GROUP_SIZE=group_size,
|
||||
DIM_NOPE=dim_nope,
|
||||
DIM_ROPE=dim_rope,
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _dequantize_k_cache_paged_kernel(
|
||||
output_ptr,
|
||||
input_nope_q_ptr,
|
||||
input_nope_s_ptr,
|
||||
input_rope_ptr,
|
||||
page_table_1_ptr,
|
||||
output_stride_0: int,
|
||||
input_nope_q_stride_0: int,
|
||||
input_nope_s_stride_0: int,
|
||||
input_rope_stride_0: int,
|
||||
NUM_NOPE_BLOCKS: tl.constexpr,
|
||||
GROUP_SIZE: tl.constexpr,
|
||||
DIM_NOPE: tl.constexpr,
|
||||
DIM_ROPE: tl.constexpr,
|
||||
):
|
||||
token_id = tl.program_id(0)
|
||||
token_id_paged = tl.load(page_table_1_ptr + token_id).to(tl.int32)
|
||||
raw_block_id = tl.program_id(1)
|
||||
|
||||
if raw_block_id < NUM_NOPE_BLOCKS:
|
||||
# a. dequant nope
|
||||
effective_block_id = raw_block_id
|
||||
|
||||
offs_q = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs_q < DIM_NOPE
|
||||
ptr_q = input_nope_q_ptr + token_id_paged * input_nope_q_stride_0 + offs_q
|
||||
ptr_s = (
|
||||
input_nope_s_ptr
|
||||
+ token_id_paged * input_nope_s_stride_0
|
||||
+ effective_block_id
|
||||
)
|
||||
|
||||
y_q = tl.load(ptr_q, mask=mask, other=0.0).to(tl.float32)
|
||||
y_s = tl.load(ptr_s)
|
||||
|
||||
y = (y_q * y_s).to(output_ptr.dtype.element_ty)
|
||||
|
||||
dst_ptr = output_ptr + token_id * output_stride_0 + offs_q
|
||||
tl.store(dst_ptr, y, mask=mask)
|
||||
else:
|
||||
# b. copy rope
|
||||
effective_block_id = raw_block_id - NUM_NOPE_BLOCKS
|
||||
|
||||
offs = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs < DIM_ROPE
|
||||
|
||||
src_ptr = input_rope_ptr + token_id_paged * input_rope_stride_0 + offs
|
||||
dst_ptr = output_ptr + token_id * output_stride_0 + DIM_NOPE + offs
|
||||
|
||||
data = tl.load(src_ptr, mask=mask).to(tl.bfloat16)
|
||||
tl.store(dst_ptr, data, mask=mask)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise Exception("UT is in quant_k_cache.py")
|
||||
@@ -0,0 +1,331 @@
|
||||
"""Multi-step precompute utilities for Native Sparse Attention backend.
|
||||
|
||||
This module provides optimization utilities for multi-step speculative decoding
|
||||
by precomputing shared metadata once and copying it to multiple backend instances.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.layers.attention.dsa.utils import compute_dsa_seqlens
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardMode
|
||||
from sglang.srt.speculative.spec_info import SpecInput
|
||||
|
||||
|
||||
@dataclass
|
||||
class PrecomputedMetadata:
|
||||
"""Precomputed metadata shared across multiple backend instances.
|
||||
|
||||
Used for multi-step speculative decoding where multiple backends
|
||||
need identical metadata. Precomputing once and copying N times
|
||||
is much faster than computing N times.
|
||||
|
||||
"""
|
||||
|
||||
# Basic seqlens
|
||||
cache_seqlens: torch.Tensor # int32, [bs]
|
||||
cu_seqlens_k: torch.Tensor # int32, [bs+1]
|
||||
|
||||
# Page table
|
||||
page_indices: torch.Tensor # int32, [bs, max_len] or [expanded_bs, max_len]
|
||||
real_page_table: Optional[torch.Tensor] # int32, transformed version
|
||||
|
||||
# DSA seqlens
|
||||
seqlens_expanded: torch.Tensor # int32, [expanded_size]
|
||||
dsa_cache_seqlens: torch.Tensor # int32, [expanded_size]
|
||||
dsa_cu_seqlens_k: torch.Tensor # int32, [expanded_size+1]
|
||||
seqlens_expanded_size: int
|
||||
|
||||
# Dimensions
|
||||
max_len: int # for decode/draft_extend
|
||||
max_seqlen_k: int # for target_verify
|
||||
|
||||
# FlashMLA (optional)
|
||||
flashmla_metadata: Optional[torch.Tensor] = None
|
||||
|
||||
|
||||
def compute_cu_seqlens(seqlens: torch.Tensor) -> torch.Tensor:
|
||||
"""Compute cumulative sequence lengths with padding."""
|
||||
assert seqlens.dtype == torch.int32
|
||||
return torch.nn.functional.pad(
|
||||
torch.cumsum(seqlens, dim=0, dtype=torch.int32), (1, 0)
|
||||
)
|
||||
|
||||
|
||||
class DeepseekSparseAttnBackendMTPPrecomputeMixin:
|
||||
"""Mixin class providing metadata precomputation for multi-step speculative decoding.
|
||||
|
||||
This mixin provides the _precompute_replay_metadata method and its helpers,
|
||||
which are used to optimize CUDA graph replay in multi-step scenarios.
|
||||
"""
|
||||
|
||||
def _precompute_replay_metadata(
|
||||
self,
|
||||
bs: int,
|
||||
req_pool_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
seq_lens_cpu: torch.Tensor,
|
||||
forward_mode: "ForwardMode",
|
||||
spec_info: Optional["SpecInput"],
|
||||
) -> PrecomputedMetadata:
|
||||
"""Precompute all shared metadata for multi-step backends.
|
||||
|
||||
This function extracts and computes all operations that are
|
||||
identical across different backend instances in multi-step
|
||||
speculative decoding.
|
||||
|
||||
Args:
|
||||
bs: Batch size
|
||||
req_pool_indices: Request pool indices [bs]
|
||||
seq_lens: Sequence lengths [bs]
|
||||
seq_lens_cpu: Sequence lengths on CPU [bs]
|
||||
forward_mode: Forward mode (decode/target_verify/draft_extend)
|
||||
spec_info: Speculative decoding info (for draft_extend mode)
|
||||
|
||||
Returns:
|
||||
PrecomputedMetadata containing all shared intermediate results
|
||||
"""
|
||||
# Slice inputs to batch size
|
||||
seq_lens = seq_lens[:bs]
|
||||
seq_lens_cpu = seq_lens_cpu[:bs]
|
||||
req_pool_indices = req_pool_indices[:bs]
|
||||
|
||||
# Dispatch to mode-specific precomputation
|
||||
if forward_mode.is_decode_or_idle():
|
||||
return self._precompute_decode_mode(
|
||||
bs, req_pool_indices, seq_lens, seq_lens_cpu
|
||||
)
|
||||
elif forward_mode.is_target_verify():
|
||||
return self._precompute_target_verify_mode(
|
||||
bs, req_pool_indices, seq_lens, seq_lens_cpu
|
||||
)
|
||||
elif forward_mode.is_draft_extend():
|
||||
return self._precompute_draft_extend_mode(
|
||||
bs, req_pool_indices, seq_lens, seq_lens_cpu, spec_info
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported forward mode: {forward_mode}")
|
||||
|
||||
def _precompute_decode_mode(
|
||||
self,
|
||||
bs: int,
|
||||
req_pool_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
seq_lens_cpu: torch.Tensor,
|
||||
) -> PrecomputedMetadata:
|
||||
"""Precompute metadata for normal decode mode."""
|
||||
max_len = int(seq_lens_cpu.max().item())
|
||||
|
||||
# Convert to int32 and compute cumsum
|
||||
cache_seqlens = seq_lens.to(torch.int32)
|
||||
cu_seqlens_k = compute_cu_seqlens(cache_seqlens)
|
||||
|
||||
# Get page indices from cache
|
||||
page_indices = self.req_to_token[req_pool_indices, :max_len].contiguous()
|
||||
|
||||
# Compute DSA seqlens
|
||||
dsa_cache_seqlens = compute_dsa_seqlens(
|
||||
cache_seqlens, dsa_index_topk=self.dsa_index_topk
|
||||
)
|
||||
seqlens_expanded = cache_seqlens
|
||||
seqlens_expanded_size = seqlens_expanded.shape[0]
|
||||
|
||||
# Compute DSA cumsum
|
||||
dsa_cu_seqlens_k = compute_cu_seqlens(dsa_cache_seqlens)
|
||||
|
||||
# Transform page table if needed
|
||||
if self.real_page_size > 1:
|
||||
real_page_table = self._transform_table_1_to_real(page_indices)
|
||||
else:
|
||||
real_page_table = None # Will use page_indices directly
|
||||
|
||||
# Compute FlashMLA metadata if needed
|
||||
flashmla_metadata = None
|
||||
if self.dsa_decode_impl == "flashmla_kv":
|
||||
flashmla_metadata = self._compute_flashmla_metadata(
|
||||
cache_seqlens=dsa_cache_seqlens,
|
||||
seq_len_q=1,
|
||||
)
|
||||
|
||||
return PrecomputedMetadata(
|
||||
cache_seqlens=cache_seqlens,
|
||||
cu_seqlens_k=cu_seqlens_k,
|
||||
page_indices=page_indices,
|
||||
real_page_table=real_page_table,
|
||||
seqlens_expanded=seqlens_expanded,
|
||||
dsa_cache_seqlens=dsa_cache_seqlens,
|
||||
dsa_cu_seqlens_k=dsa_cu_seqlens_k,
|
||||
seqlens_expanded_size=seqlens_expanded_size,
|
||||
max_len=max_len,
|
||||
max_seqlen_k=max_len,
|
||||
flashmla_metadata=flashmla_metadata,
|
||||
)
|
||||
|
||||
def _precompute_target_verify_mode(
|
||||
self,
|
||||
bs: int,
|
||||
req_pool_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
seq_lens_cpu: torch.Tensor,
|
||||
) -> PrecomputedMetadata:
|
||||
"""Precompute metadata for target verify mode."""
|
||||
max_seqlen_k = int(
|
||||
seq_lens_cpu.max().item() + self.speculative_num_draft_tokens
|
||||
)
|
||||
|
||||
# Cache seqlens with draft tokens
|
||||
cache_seqlens = (seq_lens + self.speculative_num_draft_tokens).to(torch.int32)
|
||||
cu_seqlens_k = compute_cu_seqlens(cache_seqlens)
|
||||
|
||||
# Page indices (repeated for each draft token)
|
||||
page_indices = self.req_to_token[req_pool_indices, :max_seqlen_k]
|
||||
page_indices = torch.repeat_interleave(
|
||||
page_indices, repeats=self.speculative_num_draft_tokens, dim=0
|
||||
).contiguous()
|
||||
|
||||
# Generate expanded seqlens
|
||||
extend_seq_lens_cpu = [self.speculative_num_draft_tokens] * bs
|
||||
seqlens_int32_cpu = [
|
||||
self.speculative_num_draft_tokens + kv_len
|
||||
for kv_len in seq_lens_cpu.tolist()
|
||||
]
|
||||
seqlens_expanded = torch.cat(
|
||||
[
|
||||
torch.arange(
|
||||
kv_len - qo_len + 1,
|
||||
kv_len + 1,
|
||||
dtype=torch.int32,
|
||||
device=self.device,
|
||||
)
|
||||
for qo_len, kv_len in zip(
|
||||
extend_seq_lens_cpu,
|
||||
seqlens_int32_cpu,
|
||||
strict=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Compute DSA seqlens
|
||||
dsa_cache_seqlens = compute_dsa_seqlens(seqlens_expanded, self.dsa_index_topk)
|
||||
seqlens_expanded_size = seqlens_expanded.shape[0]
|
||||
|
||||
# DSA cumsum
|
||||
dsa_cu_seqlens_k = compute_cu_seqlens(dsa_cache_seqlens)
|
||||
|
||||
# Transform page table
|
||||
if self.real_page_size > 1:
|
||||
real_page_table = self._transform_table_1_to_real(page_indices)
|
||||
else:
|
||||
real_page_table = None
|
||||
|
||||
# FlashMLA metadata
|
||||
flashmla_metadata = None
|
||||
if self.dsa_decode_impl == "flashmla_kv":
|
||||
flashmla_metadata = self._compute_flashmla_metadata(
|
||||
cache_seqlens=dsa_cache_seqlens,
|
||||
seq_len_q=1,
|
||||
)
|
||||
|
||||
return PrecomputedMetadata(
|
||||
cache_seqlens=cache_seqlens,
|
||||
cu_seqlens_k=cu_seqlens_k,
|
||||
page_indices=page_indices,
|
||||
real_page_table=real_page_table,
|
||||
seqlens_expanded=seqlens_expanded,
|
||||
dsa_cache_seqlens=dsa_cache_seqlens,
|
||||
dsa_cu_seqlens_k=dsa_cu_seqlens_k,
|
||||
seqlens_expanded_size=seqlens_expanded_size,
|
||||
max_len=-1, # Not used in this mode
|
||||
max_seqlen_k=max_seqlen_k,
|
||||
flashmla_metadata=flashmla_metadata,
|
||||
)
|
||||
|
||||
def _precompute_draft_extend_mode(
|
||||
self,
|
||||
bs: int,
|
||||
req_pool_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
seq_lens_cpu: torch.Tensor,
|
||||
spec_info: "SpecInput",
|
||||
) -> PrecomputedMetadata:
|
||||
"""Precompute metadata for draft extend mode."""
|
||||
max_seqlen_k = int(seq_lens_cpu.max().item())
|
||||
|
||||
# Cache seqlens
|
||||
cache_seqlens = seq_lens.to(torch.int32)
|
||||
cu_seqlens_k = compute_cu_seqlens(cache_seqlens)
|
||||
|
||||
# Extend seqlens from spec_info: num_accept_tokens already includes
|
||||
# the bonus token (drafts + 1).
|
||||
extend_seq_lens = spec_info.num_accept_tokens[:bs]
|
||||
extend_seq_lens_cpu = extend_seq_lens.tolist()
|
||||
|
||||
# Page indices (repeated per accept length)
|
||||
page_indices = self.req_to_token[req_pool_indices, :max_seqlen_k]
|
||||
page_indices = torch.repeat_interleave(
|
||||
page_indices, repeats=extend_seq_lens, dim=0
|
||||
).contiguous()
|
||||
|
||||
# Generate expanded seqlens
|
||||
seqlens_expanded = torch.cat(
|
||||
[
|
||||
torch.arange(
|
||||
kv_len - qo_len + 1,
|
||||
kv_len + 1,
|
||||
dtype=torch.int32,
|
||||
device=self.device,
|
||||
)
|
||||
for qo_len, kv_len in zip(
|
||||
extend_seq_lens_cpu,
|
||||
seq_lens_cpu.tolist(),
|
||||
strict=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Compute DSA seqlens
|
||||
dsa_cache_seqlens = compute_dsa_seqlens(seqlens_expanded, self.dsa_index_topk)
|
||||
seqlens_expanded_size = seqlens_expanded.shape[0]
|
||||
|
||||
# DSA cumsum
|
||||
dsa_cu_seqlens_k = compute_cu_seqlens(dsa_cache_seqlens)
|
||||
|
||||
# Transform page table
|
||||
if self.real_page_size > 1:
|
||||
real_page_table = self._transform_table_1_to_real(page_indices)
|
||||
else:
|
||||
real_page_table = None
|
||||
|
||||
# FlashMLA metadata
|
||||
flashmla_metadata = None
|
||||
if self.dsa_decode_impl == "flashmla_kv":
|
||||
flashmla_metadata = self._compute_flashmla_metadata(
|
||||
cache_seqlens=dsa_cache_seqlens,
|
||||
seq_len_q=1,
|
||||
)
|
||||
|
||||
return PrecomputedMetadata(
|
||||
cache_seqlens=cache_seqlens,
|
||||
cu_seqlens_k=cu_seqlens_k,
|
||||
page_indices=page_indices,
|
||||
real_page_table=real_page_table,
|
||||
seqlens_expanded=seqlens_expanded,
|
||||
dsa_cache_seqlens=dsa_cache_seqlens,
|
||||
dsa_cu_seqlens_k=dsa_cu_seqlens_k,
|
||||
seqlens_expanded_size=seqlens_expanded_size,
|
||||
max_len=max_seqlen_k,
|
||||
max_seqlen_k=max_seqlen_k,
|
||||
flashmla_metadata=flashmla_metadata,
|
||||
)
|
||||
|
||||
|
||||
# Backward-compat alias
|
||||
DeepseekSparseAttnBackendMTPPrecomputeMixin = (
|
||||
DeepseekSparseAttnBackendMTPPrecomputeMixin
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,407 @@
|
||||
"""
|
||||
Verification utilities for DSA backend fused metadata copy operations.
|
||||
|
||||
This module contains verification code to ensure that fused metadata copy kernels
|
||||
produce the same results as individual copy operations.
|
||||
"""
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
def verify_single_backend_fused_metadata_copy(
|
||||
metadata,
|
||||
precomputed,
|
||||
forward_mode,
|
||||
bs,
|
||||
flashmla_num_splits_src=None,
|
||||
flashmla_metadata_src=None,
|
||||
flashmla_num_splits_dst=None,
|
||||
flashmla_metadata_dst=None,
|
||||
):
|
||||
"""
|
||||
Verify that the fused metadata copy kernel produces the same results as individual copies.
|
||||
|
||||
Args:
|
||||
metadata: The DSA metadata object containing destination tensors
|
||||
precomputed: The precomputed metadata containing source tensors
|
||||
forward_mode: The forward mode (decode, target_verify, or draft_extend)
|
||||
bs: Batch size
|
||||
flashmla_num_splits_src: Source FlashMLA num_splits tensor (optional)
|
||||
flashmla_metadata_src: Source FlashMLA metadata tensor (optional)
|
||||
flashmla_num_splits_dst: Destination FlashMLA num_splits tensor (optional)
|
||||
flashmla_metadata_dst: Destination FlashMLA metadata tensor (optional)
|
||||
|
||||
Raises:
|
||||
RuntimeError: If verification fails (tensors don't match)
|
||||
"""
|
||||
# Clone destination tensors to preserve fused kernel results
|
||||
fused_cache_seqlens = metadata.cache_seqlens_int32.clone()
|
||||
fused_cu_seqlens_k = metadata.cu_seqlens_k.clone()
|
||||
fused_page_table_1 = metadata.page_table_1.clone()
|
||||
fused_dsa_cache_seqlens = metadata.dsa_cache_seqlens_int32.clone()
|
||||
fused_dsa_seqlens_expanded = metadata.dsa_seqlens_expanded.clone()
|
||||
fused_dsa_cu_seqlens_k = metadata.dsa_cu_seqlens_k.clone()
|
||||
fused_real_page_table = (
|
||||
metadata.real_page_table.clone()
|
||||
if precomputed.real_page_table is not None
|
||||
else None
|
||||
)
|
||||
fused_flashmla_num_splits = None
|
||||
fused_flashmla_metadata = None
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
fused_flashmla_num_splits = flashmla_num_splits_dst.clone()
|
||||
fused_flashmla_metadata = flashmla_metadata_dst.clone()
|
||||
|
||||
# Create reference tensors (zeroed out)
|
||||
ref_cache_seqlens = torch.zeros_like(metadata.cache_seqlens_int32)
|
||||
ref_cu_seqlens_k = torch.zeros_like(metadata.cu_seqlens_k)
|
||||
ref_page_table_1 = torch.zeros_like(metadata.page_table_1)
|
||||
ref_dsa_cache_seqlens = torch.zeros_like(metadata.dsa_cache_seqlens_int32)
|
||||
ref_dsa_seqlens_expanded = torch.zeros_like(metadata.dsa_seqlens_expanded)
|
||||
ref_dsa_cu_seqlens_k = torch.zeros_like(metadata.dsa_cu_seqlens_k)
|
||||
ref_real_page_table = (
|
||||
torch.zeros_like(metadata.real_page_table)
|
||||
if precomputed.real_page_table is not None
|
||||
else None
|
||||
)
|
||||
ref_flashmla_num_splits = None
|
||||
ref_flashmla_metadata = None
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
ref_flashmla_num_splits = torch.zeros_like(flashmla_num_splits_dst)
|
||||
ref_flashmla_metadata = torch.zeros_like(flashmla_metadata_dst)
|
||||
|
||||
# Run individual copy operations (reference implementation)
|
||||
ref_cache_seqlens.copy_(precomputed.cache_seqlens)
|
||||
ref_cu_seqlens_k[1:].copy_(precomputed.cu_seqlens_k[1:])
|
||||
|
||||
if forward_mode.is_decode_or_idle():
|
||||
# Decode mode
|
||||
ref_page_table_1[:, : precomputed.max_len].copy_(precomputed.page_indices)
|
||||
ref_dsa_cache_seqlens.copy_(precomputed.dsa_cache_seqlens)
|
||||
elif forward_mode.is_target_verify():
|
||||
# Target verify mode
|
||||
ref_page_table_1[:, : precomputed.max_seqlen_k].copy_(precomputed.page_indices)
|
||||
ref_dsa_seqlens_expanded.copy_(precomputed.seqlens_expanded)
|
||||
ref_dsa_cache_seqlens.copy_(precomputed.dsa_cache_seqlens)
|
||||
elif forward_mode.is_draft_extend():
|
||||
# Draft extend mode
|
||||
rows = precomputed.page_indices.shape[0]
|
||||
cols = precomputed.max_seqlen_k
|
||||
ref_page_table_1[:rows, :cols].copy_(precomputed.page_indices)
|
||||
size = precomputed.seqlens_expanded_size
|
||||
ref_dsa_seqlens_expanded[:size].copy_(precomputed.seqlens_expanded)
|
||||
ref_dsa_cache_seqlens[:size].copy_(precomputed.dsa_cache_seqlens)
|
||||
|
||||
# Copy DSA cu_seqlens
|
||||
size = precomputed.seqlens_expanded_size
|
||||
ref_dsa_cu_seqlens_k[1 : 1 + size].copy_(precomputed.dsa_cu_seqlens_k[1 : 1 + size])
|
||||
|
||||
# Copy real page table
|
||||
if precomputed.real_page_table is not None:
|
||||
rows, cols = precomputed.real_page_table.shape
|
||||
ref_real_page_table[:rows, :cols].copy_(precomputed.real_page_table)
|
||||
|
||||
# Copy FlashMLA metadata
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
size = precomputed.seqlens_expanded_size
|
||||
ref_flashmla_num_splits[: size + 1].copy_(flashmla_num_splits_src[: size + 1])
|
||||
ref_flashmla_metadata.copy_(flashmla_metadata_src)
|
||||
|
||||
# Compare results and crash if inconsistent
|
||||
def check_tensor_equal(name, fused, ref):
|
||||
if not torch.equal(fused, ref):
|
||||
max_diff = (fused.float() - ref.float()).abs().max().item()
|
||||
mismatched_elements = (fused != ref).sum().item()
|
||||
total_elements = fused.numel()
|
||||
raise RuntimeError(
|
||||
f"FUSED METADATA COPY VERIFICATION FAILED!\n"
|
||||
f"Tensor: {name}\n"
|
||||
f"Max difference: {max_diff}\n"
|
||||
f"Mismatched elements: {mismatched_elements}/{total_elements}\n"
|
||||
f"Fused shape: {fused.shape}, Ref shape: {ref.shape}\n"
|
||||
f"Forward mode: {forward_mode}, bs={bs}\n"
|
||||
f"The fused kernel produces different results than individual copies.\n"
|
||||
f"This indicates a bug in the fused metadata copy kernel."
|
||||
)
|
||||
|
||||
# Verify all tensors (only compare the slices that were actually updated)
|
||||
check_tensor_equal("cache_seqlens", fused_cache_seqlens, ref_cache_seqlens)
|
||||
check_tensor_equal("cu_seqlens_k", fused_cu_seqlens_k, ref_cu_seqlens_k)
|
||||
|
||||
# Compare page_table_1 only for the region that was updated
|
||||
if forward_mode.is_decode_or_idle():
|
||||
check_tensor_equal(
|
||||
"page_table_1",
|
||||
fused_page_table_1[:, : precomputed.max_len],
|
||||
ref_page_table_1[:, : precomputed.max_len],
|
||||
)
|
||||
elif forward_mode.is_target_verify():
|
||||
check_tensor_equal(
|
||||
"page_table_1",
|
||||
fused_page_table_1[:, : precomputed.max_seqlen_k],
|
||||
ref_page_table_1[:, : precomputed.max_seqlen_k],
|
||||
)
|
||||
elif forward_mode.is_draft_extend():
|
||||
rows = precomputed.page_indices.shape[0]
|
||||
cols = precomputed.max_seqlen_k
|
||||
check_tensor_equal(
|
||||
"page_table_1",
|
||||
fused_page_table_1[:rows, :cols],
|
||||
ref_page_table_1[:rows, :cols],
|
||||
)
|
||||
|
||||
# Compare dsa_cache_seqlens only for the region that was updated
|
||||
if forward_mode.is_decode_or_idle():
|
||||
check_tensor_equal(
|
||||
"dsa_cache_seqlens",
|
||||
fused_dsa_cache_seqlens,
|
||||
ref_dsa_cache_seqlens,
|
||||
)
|
||||
else: # TARGET_VERIFY or DRAFT_EXTEND
|
||||
size = precomputed.seqlens_expanded_size
|
||||
check_tensor_equal(
|
||||
"dsa_cache_seqlens",
|
||||
fused_dsa_cache_seqlens[:size],
|
||||
ref_dsa_cache_seqlens[:size],
|
||||
)
|
||||
|
||||
# Compare dsa_seqlens_expanded only for TARGET_VERIFY and DRAFT_EXTEND
|
||||
if forward_mode.is_target_verify() or forward_mode.is_draft_extend():
|
||||
size = precomputed.seqlens_expanded_size
|
||||
check_tensor_equal(
|
||||
"dsa_seqlens_expanded",
|
||||
fused_dsa_seqlens_expanded[:size],
|
||||
ref_dsa_seqlens_expanded[:size],
|
||||
)
|
||||
|
||||
# Compare dsa_cu_seqlens_k only for the region that was updated
|
||||
size = precomputed.seqlens_expanded_size
|
||||
check_tensor_equal(
|
||||
"dsa_cu_seqlens_k",
|
||||
fused_dsa_cu_seqlens_k[: 1 + size],
|
||||
ref_dsa_cu_seqlens_k[: 1 + size],
|
||||
)
|
||||
|
||||
if precomputed.real_page_table is not None:
|
||||
rows, cols = precomputed.real_page_table.shape
|
||||
check_tensor_equal(
|
||||
"real_page_table",
|
||||
fused_real_page_table[:rows, :cols],
|
||||
ref_real_page_table[:rows, :cols],
|
||||
)
|
||||
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
size = precomputed.seqlens_expanded_size
|
||||
check_tensor_equal(
|
||||
"flashmla_num_splits",
|
||||
fused_flashmla_num_splits[: size + 1],
|
||||
ref_flashmla_num_splits[: size + 1],
|
||||
)
|
||||
check_tensor_equal(
|
||||
"flashmla_metadata",
|
||||
fused_flashmla_metadata,
|
||||
ref_flashmla_metadata,
|
||||
)
|
||||
|
||||
|
||||
def verify_multi_backend_fused_metadata_copy(
|
||||
metadata0,
|
||||
metadata1,
|
||||
metadata2,
|
||||
precomputed,
|
||||
bs,
|
||||
flashmla_num_splits_src=None,
|
||||
flashmla_metadata_src=None,
|
||||
):
|
||||
"""
|
||||
Verify that the multi-backend fused metadata copy kernel produces the same results
|
||||
as individual copies for all three backends.
|
||||
|
||||
Args:
|
||||
metadata0: The DSA metadata object for backend 0
|
||||
metadata1: The DSA metadata object for backend 1
|
||||
metadata2: The DSA metadata object for backend 2
|
||||
precomputed: The precomputed metadata containing source tensors
|
||||
bs: Batch size
|
||||
flashmla_num_splits_src: Source FlashMLA num_splits tensor (optional)
|
||||
flashmla_metadata_src: Source FlashMLA metadata tensor (optional)
|
||||
|
||||
Raises:
|
||||
RuntimeError: If verification fails (tensors don't match)
|
||||
"""
|
||||
# Clone destination tensors to preserve fused kernel results
|
||||
fused_results = []
|
||||
for idx, metadata in enumerate([metadata0, metadata1, metadata2]):
|
||||
fused_cache_seqlens = metadata.cache_seqlens_int32.clone()
|
||||
fused_cu_seqlens_k = metadata.cu_seqlens_k.clone()
|
||||
fused_page_table_1 = metadata.page_table_1.clone()
|
||||
fused_dsa_cache_seqlens = metadata.dsa_cache_seqlens_int32.clone()
|
||||
fused_dsa_cu_seqlens_k = metadata.dsa_cu_seqlens_k.clone()
|
||||
fused_real_page_table = (
|
||||
metadata.real_page_table.clone()
|
||||
if precomputed.real_page_table is not None
|
||||
else None
|
||||
)
|
||||
fused_flashmla_num_splits = None
|
||||
fused_flashmla_metadata = None
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
fused_flashmla_num_splits = metadata.flashmla_metadata.num_splits.clone()
|
||||
fused_flashmla_metadata = (
|
||||
metadata.flashmla_metadata.flashmla_metadata.clone()
|
||||
)
|
||||
|
||||
fused_results.append(
|
||||
{
|
||||
"cache_seqlens": fused_cache_seqlens,
|
||||
"cu_seqlens_k": fused_cu_seqlens_k,
|
||||
"page_table_1": fused_page_table_1,
|
||||
"dsa_cache_seqlens": fused_dsa_cache_seqlens,
|
||||
"dsa_cu_seqlens_k": fused_dsa_cu_seqlens_k,
|
||||
"real_page_table": fused_real_page_table,
|
||||
"flashmla_num_splits": fused_flashmla_num_splits,
|
||||
"flashmla_metadata": fused_flashmla_metadata,
|
||||
}
|
||||
)
|
||||
|
||||
# Run individual copy operations for each backend (reference implementation)
|
||||
ref_results = []
|
||||
for idx in range(3):
|
||||
metadata = [metadata0, metadata1, metadata2][idx]
|
||||
|
||||
# Create reference tensors (zeroed out)
|
||||
ref_cache_seqlens = torch.zeros_like(metadata.cache_seqlens_int32)
|
||||
ref_cu_seqlens_k = torch.zeros_like(metadata.cu_seqlens_k)
|
||||
ref_page_table_1 = torch.zeros_like(metadata.page_table_1)
|
||||
ref_dsa_cache_seqlens = torch.zeros_like(metadata.dsa_cache_seqlens_int32)
|
||||
ref_dsa_cu_seqlens_k = torch.zeros_like(metadata.dsa_cu_seqlens_k)
|
||||
ref_real_page_table = (
|
||||
torch.zeros_like(metadata.real_page_table)
|
||||
if precomputed.real_page_table is not None
|
||||
else None
|
||||
)
|
||||
ref_flashmla_num_splits = None
|
||||
ref_flashmla_metadata = None
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
ref_flashmla_num_splits = torch.zeros_like(
|
||||
metadata.flashmla_metadata.num_splits
|
||||
)
|
||||
ref_flashmla_metadata = torch.zeros_like(
|
||||
metadata.flashmla_metadata.flashmla_metadata
|
||||
)
|
||||
|
||||
# Copy operations (decode mode)
|
||||
ref_cache_seqlens.copy_(precomputed.cache_seqlens)
|
||||
ref_cu_seqlens_k[1:].copy_(precomputed.cu_seqlens_k[1:])
|
||||
ref_page_table_1[:, : precomputed.max_len].copy_(precomputed.page_indices)
|
||||
ref_dsa_cache_seqlens.copy_(precomputed.dsa_cache_seqlens)
|
||||
|
||||
# Copy DSA cu_seqlens
|
||||
size = precomputed.seqlens_expanded_size
|
||||
ref_dsa_cu_seqlens_k[1 : 1 + size].copy_(
|
||||
precomputed.dsa_cu_seqlens_k[1 : 1 + size]
|
||||
)
|
||||
|
||||
# Copy real page table
|
||||
if precomputed.real_page_table is not None:
|
||||
rows, cols = precomputed.real_page_table.shape
|
||||
ref_real_page_table[:rows, :cols].copy_(precomputed.real_page_table)
|
||||
|
||||
# Copy FlashMLA metadata
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
ref_flashmla_num_splits[: size + 1].copy_(
|
||||
flashmla_num_splits_src[: size + 1]
|
||||
)
|
||||
ref_flashmla_metadata.copy_(flashmla_metadata_src)
|
||||
|
||||
ref_results.append(
|
||||
{
|
||||
"cache_seqlens": ref_cache_seqlens,
|
||||
"cu_seqlens_k": ref_cu_seqlens_k,
|
||||
"page_table_1": ref_page_table_1,
|
||||
"dsa_cache_seqlens": ref_dsa_cache_seqlens,
|
||||
"dsa_cu_seqlens_k": ref_dsa_cu_seqlens_k,
|
||||
"real_page_table": ref_real_page_table,
|
||||
"flashmla_num_splits": ref_flashmla_num_splits,
|
||||
"flashmla_metadata": ref_flashmla_metadata,
|
||||
}
|
||||
)
|
||||
|
||||
# Compare results for all 3 backends
|
||||
def check_tensor_equal(backend_idx, name, fused, ref):
|
||||
if not torch.equal(fused, ref):
|
||||
max_diff = (fused.float() - ref.float()).abs().max().item()
|
||||
mismatched_elements = (fused != ref).sum().item()
|
||||
total_elements = fused.numel()
|
||||
raise RuntimeError(
|
||||
f"MULTI-BACKEND FUSED METADATA COPY VERIFICATION FAILED!\n"
|
||||
f"Backend: {backend_idx}\n"
|
||||
f"Tensor: {name}\n"
|
||||
f"Max difference: {max_diff}\n"
|
||||
f"Mismatched elements: {mismatched_elements}/{total_elements}\n"
|
||||
f"Fused shape: {fused.shape}, Ref shape: {ref.shape}\n"
|
||||
f"Batch size: {bs}\n"
|
||||
f"The multi-backend fused kernel produces different results than individual copies.\n"
|
||||
f"This indicates a bug in the fused metadata copy kernel."
|
||||
)
|
||||
|
||||
# Verify all tensors for all 3 backends (multi-backend is DECODE mode only)
|
||||
for idx in range(3):
|
||||
fused = fused_results[idx]
|
||||
ref = ref_results[idx]
|
||||
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"cache_seqlens",
|
||||
fused["cache_seqlens"],
|
||||
ref["cache_seqlens"],
|
||||
)
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"cu_seqlens_k",
|
||||
fused["cu_seqlens_k"],
|
||||
ref["cu_seqlens_k"],
|
||||
)
|
||||
# Multi-backend is DECODE mode only, so compare only [:, :max_len]
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"page_table_1",
|
||||
fused["page_table_1"][:, : precomputed.max_len],
|
||||
ref["page_table_1"][:, : precomputed.max_len],
|
||||
)
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"dsa_cache_seqlens",
|
||||
fused["dsa_cache_seqlens"],
|
||||
ref["dsa_cache_seqlens"],
|
||||
)
|
||||
# DECODE mode uses bs for dsa_cu_seqlens_k size
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"dsa_cu_seqlens_k",
|
||||
fused["dsa_cu_seqlens_k"][: bs + 1],
|
||||
ref["dsa_cu_seqlens_k"][: bs + 1],
|
||||
)
|
||||
|
||||
if precomputed.real_page_table is not None:
|
||||
rows, cols = precomputed.real_page_table.shape
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"real_page_table",
|
||||
fused["real_page_table"][:rows, :cols],
|
||||
ref["real_page_table"][:rows, :cols],
|
||||
)
|
||||
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
# DECODE mode uses bs + 1 for flashmla_num_splits
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"flashmla_num_splits",
|
||||
fused["flashmla_num_splits"][: bs + 1],
|
||||
ref["flashmla_num_splits"][: bs + 1],
|
||||
)
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"flashmla_metadata",
|
||||
fused["flashmla_metadata"],
|
||||
ref["flashmla_metadata"],
|
||||
)
|
||||
@@ -0,0 +1,814 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.srt.layers.attention.dsa.utils import aiter_can_use_preshuffle_paged_mqa
|
||||
from sglang.srt.layers.quantization.fp8_kernel import is_fp8_fnuz
|
||||
from sglang.srt.utils import get_bool_env_var, is_hip
|
||||
|
||||
_is_hip = is_hip()
|
||||
_is_fp8_fnuz = is_fp8_fnuz()
|
||||
_use_aiter = get_bool_env_var("SGLANG_USE_AITER") and _is_hip
|
||||
# aiter cp_gather kernel with preshuffle=True is only valid when the indexer
|
||||
# uses the page_size=64 preshuffle layout (i.e. when the matching MQA gluon path
|
||||
# is also enabled).
|
||||
_use_aiter_preshuffle = aiter_can_use_preshuffle_paged_mqa()
|
||||
|
||||
if _use_aiter_preshuffle:
|
||||
from aiter.ops.cache import cp_gather_indexer_k_quant_cache
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.mem_cache.memory_pool import DSATokenToKVPool
|
||||
|
||||
"""
|
||||
k: data, 128 item per token, fp8
|
||||
s: scale, 1 item per token, fp32
|
||||
"""
|
||||
|
||||
|
||||
class GetK:
|
||||
@classmethod
|
||||
def execute(cls, *args, **kwargs):
|
||||
return cls.triton(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def slow(
|
||||
cls, pool: "DSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
num_pages = (seq_len + pool.page_size - 1) // pool.page_size
|
||||
seq_len_ = num_pages * pool.page_size
|
||||
index_k_fp8 = torch.empty(
|
||||
(seq_len_, pool.index_head_dim),
|
||||
dtype=torch.uint8,
|
||||
device=pool.device,
|
||||
)
|
||||
for i in range(num_pages):
|
||||
page_index = page_indices[i]
|
||||
index_k_fp8[i * pool.page_size : (i + 1) * pool.page_size] = buf[
|
||||
page_index
|
||||
][: pool.page_size * pool.index_head_dim].view(-1, pool.index_head_dim)
|
||||
|
||||
return index_k_fp8[:seq_len]
|
||||
|
||||
@classmethod
|
||||
def torch_fast(
|
||||
cls, pool: "DSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
"""
|
||||
:param page_indices: (num_pages,), int32
|
||||
:return: (seq_len, index_head_dim), uint8
|
||||
"""
|
||||
|
||||
# can handle per 128B instead of per element
|
||||
|
||||
# page_indices: (num_pages,), element := a page index
|
||||
buf_numel_per_page = buf.shape[1]
|
||||
|
||||
num_k_bytes_per_page = pool.page_size * pool.index_head_dim
|
||||
num_k_bytes_per_token = pool.index_head_dim
|
||||
|
||||
# buf: (num_pages, page_size 64 * head_dim 128 + page_size 64 * fp32_nbytes 4), uint8
|
||||
# flat_buf: (whatever,), uint8
|
||||
flat_buf = buf.flatten()
|
||||
|
||||
# flat_indices: (num_pages, num_k_bytes_per_page), int32, element := an index into flat_buf that we want to access
|
||||
flat_indices = (page_indices * buf_numel_per_page)[:, None] + torch.arange(
|
||||
num_k_bytes_per_page, dtype=torch.int32, device="cuda"
|
||||
)[None, :]
|
||||
flat_indices = flat_indices.flatten()[: seq_len * num_k_bytes_per_token]
|
||||
|
||||
out = flat_buf[flat_indices]
|
||||
return out.view(-1, 128)
|
||||
|
||||
@classmethod
|
||||
def triton(
|
||||
cls, pool: "DSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
"""
|
||||
Triton implementation for gathering K data from paged buffer.
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:return: (seq_len, index_head_dim), uint8
|
||||
"""
|
||||
return _get_k_triton(
|
||||
buf=buf,
|
||||
page_indices=page_indices,
|
||||
seq_len=seq_len,
|
||||
page_size=pool.page_size,
|
||||
index_head_dim=pool.index_head_dim,
|
||||
)
|
||||
|
||||
|
||||
class GetS:
|
||||
@classmethod
|
||||
def execute(cls, *args, **kwargs):
|
||||
return cls.triton(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def slow(
|
||||
cls, pool: "DSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
num_pages = (seq_len + pool.page_size - 1) // pool.page_size
|
||||
seq_len_ = num_pages * pool.page_size
|
||||
assert pool.index_head_dim // pool.quant_block_size == 1
|
||||
index_k_scale_fp8 = torch.empty(
|
||||
(seq_len_, 4),
|
||||
dtype=torch.uint8,
|
||||
device=pool.device,
|
||||
)
|
||||
for i in range(num_pages):
|
||||
page_index = page_indices[i]
|
||||
index_k_scale_fp8[i * pool.page_size : (i + 1) * pool.page_size] = buf[
|
||||
page_index
|
||||
][pool.page_size * pool.index_head_dim :].view(-1, 4)
|
||||
return index_k_scale_fp8[:seq_len]
|
||||
|
||||
@classmethod
|
||||
def torch_fast(
|
||||
cls, pool: "DSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
"""
|
||||
:param page_indices: (num_pages,), int32
|
||||
:return: (seq_len, index_head_dim // quant_block_size), uint8
|
||||
"""
|
||||
buf_numel_per_page = buf.shape[1]
|
||||
|
||||
num_s_bytes_per_page = buf.shape[1] - pool.page_size * pool.index_head_dim
|
||||
num_s_bytes_per_token = pool.index_head_dim // pool.quant_block_size * 4
|
||||
s_offset_in_page = pool.page_size * pool.index_head_dim
|
||||
|
||||
flat_buf = buf.flatten()
|
||||
flat_indices = (
|
||||
(page_indices * buf_numel_per_page)[:, None]
|
||||
+ torch.arange(num_s_bytes_per_page, dtype=torch.int32, device="cuda")[
|
||||
None, :
|
||||
]
|
||||
+ s_offset_in_page
|
||||
)
|
||||
flat_indices = flat_indices.flatten()[: seq_len * num_s_bytes_per_token]
|
||||
|
||||
out = flat_buf[flat_indices]
|
||||
return out.view(-1, 4)
|
||||
|
||||
@classmethod
|
||||
def triton(
|
||||
cls, pool: "DSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
"""
|
||||
Triton implementation for gathering S (scale) data from paged buffer.
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:return: (seq_len, 4), uint8
|
||||
"""
|
||||
return _get_s_triton(
|
||||
buf=buf,
|
||||
page_indices=page_indices,
|
||||
seq_len=seq_len,
|
||||
page_size=pool.page_size,
|
||||
index_head_dim=pool.index_head_dim,
|
||||
)
|
||||
|
||||
|
||||
class GetKAndS:
|
||||
@classmethod
|
||||
def execute(cls, *args, **kwargs):
|
||||
# The aiter path uses cp_gather_indexer_k_quant_cache(preshuffle=True),
|
||||
# which only matches the layout produced when the rest of the indexer
|
||||
# is on the page_size=64 preshuffle path. Otherwise fall back to the
|
||||
# triton implementation (which works on the page_size=1 legacy layout).
|
||||
if _use_aiter_preshuffle:
|
||||
return cls.aiter(*args, **kwargs)
|
||||
return cls.triton(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def aiter(
|
||||
cls,
|
||||
pool: "DSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_len_tensor: torch.Tensor,
|
||||
seq_len_sum: int,
|
||||
max_seq_len: int,
|
||||
):
|
||||
from sglang.srt.layers.quantization.fp8_kernel import fp8_dtype
|
||||
|
||||
page_size = pool.page_size
|
||||
index_head_dim = pool.index_head_dim
|
||||
quant_block_size = pool.quant_block_size
|
||||
scale_elems = index_head_dim // quant_block_size
|
||||
|
||||
kv_cache = buf.view(-1, page_size, index_head_dim + scale_elems * 4).view(
|
||||
fp8_dtype
|
||||
)
|
||||
dst_k = torch.empty(
|
||||
(seq_len_sum, index_head_dim), dtype=torch.uint8, device=buf.device
|
||||
)
|
||||
dst_scale = torch.empty(
|
||||
(seq_len_sum, scale_elems * 4), dtype=torch.uint8, device=buf.device
|
||||
)
|
||||
|
||||
cu_seq_lens = torch.zeros(
|
||||
seq_len_tensor.shape[0] + 1, dtype=torch.int32, device=buf.device
|
||||
)
|
||||
torch.cumsum(seq_len_tensor.to(torch.int32), dim=0, out=cu_seq_lens[1:])
|
||||
|
||||
cp_gather_indexer_k_quant_cache(
|
||||
kv_cache,
|
||||
dst_k.view(fp8_dtype),
|
||||
dst_scale,
|
||||
page_indices.to(torch.int32),
|
||||
cu_seq_lens,
|
||||
preshuffle=True,
|
||||
)
|
||||
return dst_k, dst_scale
|
||||
|
||||
@classmethod
|
||||
def triton(
|
||||
cls,
|
||||
pool: "DSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_len_tensor: torch.Tensor,
|
||||
seq_len_sum: int,
|
||||
max_seq_len: int,
|
||||
):
|
||||
"""
|
||||
Triton implementation for gathering both K and S data from paged buffer in a single call.
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:param seq_len_tensor: (num_pages,), int32/int64
|
||||
:param seq_len_sum: sum of all sequence len, int32
|
||||
:param max_seq_len: max of all sequence len, int32
|
||||
:return: tuple of (k_fp8, k_scale) where
|
||||
k_fp8: (seq_len, index_head_dim), uint8
|
||||
k_scale: (seq_len, 4), uint8
|
||||
"""
|
||||
return _get_k_and_s_triton(
|
||||
buf=buf,
|
||||
page_indices=page_indices,
|
||||
seq_lens=seq_len_tensor,
|
||||
seq_len_sum=seq_len_sum,
|
||||
max_seq_len=max_seq_len,
|
||||
page_size=pool.page_size,
|
||||
index_head_dim=pool.index_head_dim,
|
||||
)
|
||||
|
||||
|
||||
class SetK:
|
||||
@classmethod
|
||||
def execute(cls, *args, buf, **kwargs):
|
||||
return cls.torch_fast(*args, **kwargs, buf=buf)
|
||||
|
||||
@classmethod
|
||||
def slow(
|
||||
cls,
|
||||
pool: "DSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
loc: torch.Tensor,
|
||||
index_k: torch.Tensor,
|
||||
):
|
||||
for i in range(len(loc)):
|
||||
page_index = loc[i] // pool.page_size
|
||||
offset = loc[i] % pool.page_size
|
||||
buf[
|
||||
page_index,
|
||||
offset * pool.index_head_dim : (offset + 1) * pool.index_head_dim,
|
||||
] = index_k[i].view(torch.uint8)
|
||||
|
||||
@classmethod
|
||||
def torch_fast(
|
||||
cls,
|
||||
pool: "DSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
loc: torch.Tensor,
|
||||
index_k: torch.Tensor,
|
||||
):
|
||||
(num_tokens_to_write,) = loc.shape
|
||||
buf_numel_per_page = buf.shape[1]
|
||||
num_k_bytes_per_token = pool.index_head_dim
|
||||
|
||||
# loc: (num_tokens_to_write,), int32, element := the token index to write to
|
||||
loc_page_index = loc // pool.page_size
|
||||
loc_token_offset_in_page = loc % pool.page_size
|
||||
|
||||
flat_buf = buf.flatten()
|
||||
flat_indices = (
|
||||
(loc_page_index * buf_numel_per_page)[:, None]
|
||||
+ (loc_token_offset_in_page * num_k_bytes_per_token)[:, None]
|
||||
+ torch.arange(num_k_bytes_per_token, dtype=torch.int32, device="cuda")[
|
||||
None, :
|
||||
]
|
||||
)
|
||||
num_k_bytes_total = num_tokens_to_write * num_k_bytes_per_token
|
||||
flat_indices = flat_indices.flatten()[:num_k_bytes_total]
|
||||
flat_buf[flat_indices] = index_k.view(torch.uint8).flatten()
|
||||
|
||||
|
||||
class SetS:
|
||||
@classmethod
|
||||
def execute(cls, *args, buf, **kwargs):
|
||||
return cls.torch_fast(*args, **kwargs, buf=buf)
|
||||
|
||||
@classmethod
|
||||
def slow(
|
||||
cls,
|
||||
pool: "DSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
loc: torch.Tensor,
|
||||
index_k_scale: torch.Tensor,
|
||||
):
|
||||
for i in range(len(loc)):
|
||||
page_index = loc[i] // pool.page_size
|
||||
offset = loc[i] % pool.page_size
|
||||
start = pool.page_size * pool.index_head_dim
|
||||
buf[page_index, start + offset * 4 : start + (offset + 1) * 4] = (
|
||||
index_k_scale[i].view(torch.uint8)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def torch_fast(
|
||||
cls,
|
||||
pool: "DSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
loc: torch.Tensor,
|
||||
index_k_scale: torch.Tensor,
|
||||
):
|
||||
(num_tokens_to_write,) = loc.shape
|
||||
buf_numel_per_page = buf.shape[1]
|
||||
num_s_bytes_per_token = 4
|
||||
s_offset_in_page = pool.page_size * pool.index_head_dim
|
||||
|
||||
# loc: (num_tokens_to_write,), int32, element := the token index to write to
|
||||
loc_page_index = loc // pool.page_size
|
||||
loc_token_offset_in_page = loc % pool.page_size
|
||||
|
||||
flat_buf = buf.flatten()
|
||||
flat_indices = (
|
||||
(loc_page_index * buf_numel_per_page)[:, None]
|
||||
+ s_offset_in_page
|
||||
+ (loc_token_offset_in_page * num_s_bytes_per_token)[:, None]
|
||||
+ torch.arange(num_s_bytes_per_token, dtype=torch.int32, device="cuda")[
|
||||
None, :
|
||||
]
|
||||
)
|
||||
number_s_bytes_total = num_tokens_to_write * num_s_bytes_per_token
|
||||
flat_indices = flat_indices.flatten()[:number_s_bytes_total]
|
||||
flat_buf[flat_indices] = index_k_scale.view(torch.uint8).flatten()
|
||||
|
||||
|
||||
class SetKAndS:
|
||||
@classmethod
|
||||
def execute(cls, *args, buf, **kwargs):
|
||||
if 0:
|
||||
# print("SetK, SetS comparison test")
|
||||
buf_cloned = buf.clone()
|
||||
cls.vanilla(*args, **kwargs, buf=buf)
|
||||
cls.triton(*args, **kwargs, buf=buf_cloned)
|
||||
|
||||
def _clear_token_0(target):
|
||||
target[0, :128] = target[0, 64 * 128 : 64 * 128 + 4] = 0
|
||||
|
||||
_clear_token_0(buf)
|
||||
_clear_token_0(buf_cloned)
|
||||
|
||||
assert torch.all(
|
||||
buf == buf_cloned
|
||||
), f"{buf=} {buf_cloned=} {kwargs['loc'].to_list()=}"
|
||||
return
|
||||
|
||||
cls.triton(*args, **kwargs, buf=buf)
|
||||
|
||||
@classmethod
|
||||
def vanilla(cls, pool, buf, loc, index_k, index_k_scale):
|
||||
SetK.execute(pool=pool, buf=buf, loc=loc, index_k=index_k)
|
||||
SetS.execute(pool=pool, buf=buf, loc=loc, index_k_scale=index_k_scale)
|
||||
|
||||
@classmethod
|
||||
def triton(cls, pool, buf, loc, index_k, index_k_scale):
|
||||
loc = loc.to(torch.int64)
|
||||
|
||||
_set_k_and_s_triton(
|
||||
buf=buf,
|
||||
loc=loc,
|
||||
index_k=index_k,
|
||||
index_k_scale=index_k_scale,
|
||||
page_size=pool.page_size,
|
||||
)
|
||||
|
||||
|
||||
def _set_k_and_s_triton(
|
||||
buf: torch.Tensor,
|
||||
loc: torch.Tensor,
|
||||
index_k: torch.Tensor,
|
||||
index_k_scale: torch.Tensor,
|
||||
page_size: int,
|
||||
):
|
||||
"""
|
||||
:param buf: (num_pages, page_size 64 * (128B data + 4B scale)), uint8
|
||||
:param loc: (num_tokens_to_write,), int, element := the token index to write to
|
||||
:param index_k: (num_tokens_to_write, 128 elem), fp8
|
||||
:param index_k_scale: (num_tokens_to_write, 1 elem), fp32
|
||||
:return:
|
||||
"""
|
||||
num_pages, buf_numel_per_page = buf.shape
|
||||
(num_tokens_to_write,) = loc.shape
|
||||
num_tokens_to_write_, index_head_dim = index_k.shape
|
||||
|
||||
# Handle both 1D (num_tokens,) and 2D (num_tokens, 1) shapes for index_k_scale
|
||||
if index_k_scale.ndim == 1:
|
||||
num_tokens_to_write__ = index_k_scale.shape[0]
|
||||
scale_dim = 1
|
||||
elif index_k_scale.ndim == 2:
|
||||
num_tokens_to_write__, scale_dim = index_k_scale.shape
|
||||
else:
|
||||
raise ValueError(
|
||||
f"index_k_scale must be 1D or 2D, got shape {index_k_scale.shape}"
|
||||
)
|
||||
assert buf_numel_per_page == page_size * (128 + 4)
|
||||
assert num_tokens_to_write == num_tokens_to_write_ == num_tokens_to_write__
|
||||
assert index_head_dim == 128
|
||||
assert scale_dim == 1
|
||||
if _is_hip:
|
||||
if _use_aiter_preshuffle:
|
||||
assert (
|
||||
page_size % 16 == 0
|
||||
), f"HIP preshuffle requires page_size to be a multiple of 16, got {page_size}"
|
||||
else:
|
||||
assert page_size == 64
|
||||
|
||||
assert buf.dtype == torch.uint8
|
||||
assert loc.dtype == torch.int64, f"{loc.dtype=}" # can be int32
|
||||
if _is_fp8_fnuz:
|
||||
assert index_k.dtype == torch.float8_e4m3fnuz
|
||||
else:
|
||||
assert index_k.dtype == torch.float8_e4m3fn
|
||||
assert index_k_scale.dtype == torch.float32
|
||||
|
||||
assert buf.is_contiguous()
|
||||
assert loc.is_contiguous()
|
||||
assert index_k.is_contiguous()
|
||||
assert index_k_scale.is_contiguous()
|
||||
|
||||
if _is_fp8_fnuz:
|
||||
buf_fp8 = buf.view(torch.float8_e4m3fnuz)
|
||||
else:
|
||||
buf_fp8 = buf.view(torch.float8_e4m3fn)
|
||||
buf_fp32 = buf.view(torch.float32)
|
||||
|
||||
_set_k_and_s_triton_kernel[(num_tokens_to_write,)](
|
||||
buf_fp8,
|
||||
buf_fp32,
|
||||
loc,
|
||||
index_k,
|
||||
index_k_scale,
|
||||
index_k.stride(0),
|
||||
PAGE_SIZE=page_size,
|
||||
BUF_NUMEL_PER_PAGE=buf_numel_per_page,
|
||||
NUM_K_ELEMS_PER_TOKEN=index_head_dim,
|
||||
S_OFFSET_NBYTES_IN_PAGE=page_size * index_head_dim,
|
||||
)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _set_k_and_s_triton_kernel(
|
||||
buf_fp8_ptr,
|
||||
buf_fp32_ptr,
|
||||
loc_ptr,
|
||||
index_k_ptr,
|
||||
index_k_scale_ptr,
|
||||
index_k_ptr_stride_0,
|
||||
PAGE_SIZE: tl.constexpr,
|
||||
BUF_NUMEL_PER_PAGE: tl.constexpr,
|
||||
NUM_K_ELEMS_PER_TOKEN: tl.constexpr,
|
||||
S_OFFSET_NBYTES_IN_PAGE: tl.constexpr,
|
||||
):
|
||||
token_id = tl.program_id(0)
|
||||
|
||||
loc = tl.load(loc_ptr + token_id)
|
||||
|
||||
in_k_offsets = token_id * index_k_ptr_stride_0 + tl.arange(0, NUM_K_ELEMS_PER_TOKEN)
|
||||
|
||||
# no need for `mask`, since we read 128B for k and 4B for scale, both pow of 2
|
||||
k = tl.load(index_k_ptr + in_k_offsets)
|
||||
k_scale = tl.load(index_k_scale_ptr + token_id)
|
||||
|
||||
loc_page_index = loc // PAGE_SIZE
|
||||
loc_token_offset_in_page = loc % PAGE_SIZE
|
||||
|
||||
out_k_offsets = (
|
||||
loc_page_index * BUF_NUMEL_PER_PAGE
|
||||
+ loc_token_offset_in_page * NUM_K_ELEMS_PER_TOKEN
|
||||
+ tl.arange(0, NUM_K_ELEMS_PER_TOKEN)
|
||||
)
|
||||
|
||||
# "//4" b/c it is fp32 instead of uint8
|
||||
out_s_offset = (
|
||||
loc_page_index * BUF_NUMEL_PER_PAGE // 4
|
||||
+ S_OFFSET_NBYTES_IN_PAGE // 4
|
||||
+ loc_token_offset_in_page
|
||||
)
|
||||
|
||||
tl.store(buf_fp8_ptr + out_k_offsets, k)
|
||||
tl.store(buf_fp32_ptr + out_s_offset, k_scale)
|
||||
|
||||
|
||||
def _get_k_triton(
|
||||
buf: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_len: int,
|
||||
page_size: int,
|
||||
index_head_dim: int,
|
||||
):
|
||||
"""
|
||||
Gather K (key) data from paged buffer using Triton.
|
||||
|
||||
:param buf: (num_pages, page_size * 128 + page_size * 4), uint8
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:param seq_len: int, number of tokens to gather
|
||||
:param page_size: int, typically 64
|
||||
:param index_head_dim: int, typically 128
|
||||
:return: (seq_len, index_head_dim), uint8
|
||||
"""
|
||||
num_pages, buf_numel_per_page = buf.shape
|
||||
|
||||
# Allocate output
|
||||
out = torch.empty((seq_len, index_head_dim), dtype=torch.uint8, device=buf.device)
|
||||
|
||||
# Launch kernel with one thread per token
|
||||
grid = (seq_len,)
|
||||
_get_k_triton_kernel[grid](
|
||||
buf,
|
||||
page_indices,
|
||||
out,
|
||||
seq_len,
|
||||
page_size,
|
||||
buf_numel_per_page,
|
||||
index_head_dim,
|
||||
BLOCK_SIZE=128,
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _get_k_triton_kernel(
|
||||
buf_ptr,
|
||||
page_indices_ptr,
|
||||
out_ptr,
|
||||
seq_len: tl.constexpr,
|
||||
page_size: tl.constexpr,
|
||||
buf_numel_per_page: tl.constexpr,
|
||||
index_head_dim: tl.constexpr,
|
||||
BLOCK_SIZE: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Each program handles one token (seq_len tokens total).
|
||||
Loads 128 bytes from the appropriate page.
|
||||
"""
|
||||
token_id = tl.program_id(0)
|
||||
|
||||
# Calculate which page and offset within page
|
||||
page_idx = token_id // page_size
|
||||
token_offset_in_page = token_id % page_size
|
||||
|
||||
# Load the page index from page_indices
|
||||
page_index = tl.load(page_indices_ptr + page_idx)
|
||||
|
||||
# Calculate source offset in buf
|
||||
# buf[page_index, token_offset_in_page * index_head_dim : ...]
|
||||
src_base_offset = (
|
||||
page_index * buf_numel_per_page + token_offset_in_page * index_head_dim
|
||||
)
|
||||
|
||||
# Load 128 bytes (index_head_dim elements)
|
||||
offsets = tl.arange(0, BLOCK_SIZE)
|
||||
mask = offsets < index_head_dim
|
||||
data = tl.load(buf_ptr + src_base_offset + offsets, mask=mask)
|
||||
|
||||
# Store to output
|
||||
dst_offset = token_id * index_head_dim
|
||||
tl.store(out_ptr + dst_offset + offsets, data, mask=mask)
|
||||
|
||||
|
||||
def _get_s_triton(
|
||||
buf: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_len: int,
|
||||
page_size: int,
|
||||
index_head_dim: int,
|
||||
):
|
||||
"""
|
||||
Gather S (scale) data from paged buffer using Triton.
|
||||
|
||||
:param buf: (num_pages, page_size * 128 + page_size * 4), uint8
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:param seq_len: int, number of tokens to gather
|
||||
:param page_size: int, typically 64
|
||||
:param index_head_dim: int, typically 128
|
||||
:return: (seq_len, 4), uint8 (representing fp32 scale)
|
||||
"""
|
||||
num_pages, buf_numel_per_page = buf.shape
|
||||
s_offset_in_page = page_size * index_head_dim # Scales start after K data
|
||||
|
||||
# Allocate output
|
||||
out = torch.empty((seq_len, 4), dtype=torch.uint8, device=buf.device)
|
||||
|
||||
# Launch kernel with one thread per token
|
||||
grid = (seq_len,)
|
||||
_get_s_triton_kernel[grid](
|
||||
buf,
|
||||
page_indices,
|
||||
out,
|
||||
seq_len,
|
||||
page_size,
|
||||
buf_numel_per_page,
|
||||
s_offset_in_page,
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _get_s_triton_kernel(
|
||||
buf_ptr,
|
||||
page_indices_ptr,
|
||||
out_ptr,
|
||||
seq_len: tl.constexpr,
|
||||
page_size: tl.constexpr,
|
||||
buf_numel_per_page: tl.constexpr,
|
||||
s_offset_in_page: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Each program handles one token (seq_len tokens total).
|
||||
Loads 4 bytes (fp32 scale) from the appropriate page.
|
||||
"""
|
||||
token_id = tl.program_id(0)
|
||||
|
||||
# Calculate which page and offset within page
|
||||
page_idx = token_id // page_size
|
||||
token_offset_in_page = token_id % page_size
|
||||
|
||||
# Load the page index from page_indices
|
||||
page_index = tl.load(page_indices_ptr + page_idx)
|
||||
|
||||
# Calculate source offset in buf
|
||||
# Scales are stored after K data: page_size * index_head_dim offset
|
||||
# buf[page_index, s_offset_in_page + token_offset_in_page * 4 : ...]
|
||||
src_base_offset = (
|
||||
page_index * buf_numel_per_page + s_offset_in_page + token_offset_in_page * 4
|
||||
)
|
||||
|
||||
# Load 4 bytes (fp32 scale)
|
||||
offsets = tl.arange(0, 4)
|
||||
data = tl.load(buf_ptr + src_base_offset + offsets)
|
||||
|
||||
# Store to output
|
||||
dst_offset = token_id * 4
|
||||
tl.store(out_ptr + dst_offset + offsets, data)
|
||||
|
||||
|
||||
def _get_k_and_s_triton(
|
||||
buf: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
seq_len_sum: int,
|
||||
max_seq_len: int,
|
||||
page_size: int,
|
||||
index_head_dim: int,
|
||||
):
|
||||
"""
|
||||
Fused gather of both K (key) and S (scale) data from paged buffer using Triton.
|
||||
This is more efficient than calling GetK and GetS separately.
|
||||
|
||||
:param buf: (num_pages, page_size * 128 + page_size * 4), uint8
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:param seq_lens: tensor of sequence lens, int64
|
||||
:param seq_len_sum: sum of all sequence len, int32
|
||||
:param max_seq_len: max of sequence len, int32
|
||||
:param page_size: int, typically 64
|
||||
:param index_head_dim: int, typically 128
|
||||
:return: tuple of (k_out, s_out) where
|
||||
k_out: (seq_len, index_head_dim), uint8
|
||||
s_out: (seq_len, 4), uint8
|
||||
"""
|
||||
# Allocate outputs
|
||||
k_out = torch.empty(
|
||||
(seq_len_sum, index_head_dim), dtype=torch.uint8, device=buf.device
|
||||
)
|
||||
s_out = torch.empty((seq_len_sum, 4), dtype=torch.uint8, device=buf.device)
|
||||
|
||||
_, buf_numel_per_page = buf.shape
|
||||
_, page_indice_batch_offset = page_indices.shape
|
||||
s_offset_in_page = page_size * index_head_dim
|
||||
|
||||
# Launch kernel with one thread per token
|
||||
BLOCK_SIZE = 256
|
||||
BLOCK_SIZE_K = 128
|
||||
|
||||
num_token_blocks = (max_seq_len + BLOCK_SIZE - 1) // BLOCK_SIZE
|
||||
num_k_threads = (index_head_dim + BLOCK_SIZE_K - 1) // BLOCK_SIZE_K
|
||||
|
||||
seq_num = seq_lens.shape[0]
|
||||
grid = (seq_num, num_token_blocks, num_k_threads)
|
||||
seq_num_pow2 = 1
|
||||
while seq_num_pow2 < seq_num:
|
||||
seq_num_pow2 *= 2
|
||||
|
||||
_get_k_and_s_triton_kernel[grid](
|
||||
buf_ptr=buf,
|
||||
page_indices_ptr=page_indices,
|
||||
k_out_ptr=k_out,
|
||||
s_out_ptr=s_out,
|
||||
seq_len_ptr=seq_lens,
|
||||
seq_len_num_pow=seq_num_pow2,
|
||||
page_size=page_size,
|
||||
buf_numel_per_page=buf_numel_per_page,
|
||||
index_head_dim=index_head_dim,
|
||||
s_offset_in_page=s_offset_in_page,
|
||||
page_indice_batch_offset=page_indice_batch_offset,
|
||||
BLOCK_SIZE=BLOCK_SIZE,
|
||||
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
||||
)
|
||||
|
||||
return k_out, s_out
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _get_k_and_s_triton_kernel(
|
||||
buf_ptr,
|
||||
page_indices_ptr,
|
||||
k_out_ptr,
|
||||
s_out_ptr,
|
||||
seq_len_ptr,
|
||||
seq_len_num_pow: tl.constexpr,
|
||||
page_size: tl.constexpr,
|
||||
buf_numel_per_page: tl.constexpr,
|
||||
index_head_dim: tl.constexpr,
|
||||
s_offset_in_page: tl.constexpr,
|
||||
page_indice_batch_offset,
|
||||
BLOCK_SIZE: tl.constexpr,
|
||||
BLOCK_SIZE_K: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Fused kernel that gathers both K and S data in a single pass.
|
||||
Each program handles one token (seq_len tokens total).
|
||||
Loads 128 bytes (K) + 4 bytes (S) from the appropriate page.
|
||||
"""
|
||||
batch_id = tl.program_id(0)
|
||||
block_token_start = tl.program_id(1) * BLOCK_SIZE
|
||||
thread_idx = tl.program_id(2)
|
||||
|
||||
# Define the token range within the block and the K dimension range handled by the thread.
|
||||
token_ids_in_block = tl.arange(0, BLOCK_SIZE)
|
||||
token_ids = block_token_start + token_ids_in_block
|
||||
k_offsets = thread_idx * BLOCK_SIZE_K + tl.arange(0, BLOCK_SIZE_K)
|
||||
|
||||
seq_len = tl.load(seq_len_ptr + batch_id)
|
||||
token_valid_mask = token_ids < seq_len
|
||||
|
||||
pre_batch_idx = tl.arange(0, seq_len_num_pow)
|
||||
mask_pre_batch_idx = pre_batch_idx < batch_id
|
||||
prev_seq_lens = tl.load(seq_len_ptr + pre_batch_idx, mask=mask_pre_batch_idx)
|
||||
batch_token_offset = tl.sum(prev_seq_lens)
|
||||
|
||||
# Batch calculate the page index and in-page offset of each token.
|
||||
page_idx = token_ids // page_size
|
||||
token_offset_in_page = token_ids % page_size
|
||||
page_indices_base = batch_id * page_indice_batch_offset
|
||||
page_idx_valid_mask = page_idx < page_indice_batch_offset
|
||||
page_index = tl.load(
|
||||
page_indices_ptr + page_idx + page_indices_base,
|
||||
mask=token_valid_mask & page_idx_valid_mask,
|
||||
)
|
||||
|
||||
# ===== Load K data =====
|
||||
# The address calculation logic for K: page_index * total number of elements in a single page + K offset of the token within the page.
|
||||
k_src_token_offset = token_offset_in_page * index_head_dim
|
||||
k_src_base_offset = page_index * buf_numel_per_page + k_src_token_offset
|
||||
|
||||
k_load_addr = buf_ptr + k_src_base_offset[:, None] + k_offsets[None, :]
|
||||
k_dim_mask = k_offsets[None, :] < index_head_dim
|
||||
k_mask = token_valid_mask[:, None] & k_dim_mask
|
||||
|
||||
k_data = tl.load(k_load_addr, mask=k_mask, other=0)
|
||||
|
||||
# Store K to output
|
||||
k_dst_token_offset = batch_token_offset + token_ids
|
||||
k_dst_base_offset = k_dst_token_offset * index_head_dim
|
||||
k_store_addr = k_out_ptr + k_dst_base_offset[:, None] + k_offsets[None, :]
|
||||
tl.store(k_store_addr, k_data, mask=k_mask)
|
||||
|
||||
# ===== Load S data =====
|
||||
# The address calculation logic for S: page_index * total number of elements in a single page + starting offset of S within the page + offset of token within S in the page
|
||||
s_src_token_offset = s_offset_in_page + token_offset_in_page * 4
|
||||
s_src_base_offset = page_index * buf_numel_per_page + s_src_token_offset
|
||||
|
||||
s_offsets = tl.arange(0, 4)
|
||||
s_load_addr = buf_ptr + s_src_base_offset[:, None] + s_offsets[None, :]
|
||||
s_mask = token_valid_mask[:, None] & (s_offsets[None, :] < 4)
|
||||
s_data = tl.load(s_load_addr, mask=s_mask, other=0)
|
||||
|
||||
# Store S to output
|
||||
s_dst_token_offset = batch_token_offset + token_ids
|
||||
s_dst_base_offset = s_dst_token_offset * 4
|
||||
s_store_addr = s_out_ptr + s_dst_base_offset[:, None] + s_offsets[None, :]
|
||||
tl.store(s_store_addr, s_data, mask=s_mask)
|
||||
@@ -0,0 +1,449 @@
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
|
||||
def quantize_k_cache(cache_k):
|
||||
return _quantize_k_cache_fast_wrapped(cache_k)
|
||||
|
||||
|
||||
def quantize_k_cache_separate(
|
||||
k_nope: torch.Tensor,
|
||||
k_rope: torch.Tensor,
|
||||
tile_size: int = 128,
|
||||
):
|
||||
"""
|
||||
Quantize k_nope and k_rope separately without concat, returns two tensors.
|
||||
|
||||
This avoids the concat operation and enables direct reuse of set_mla_kv_buffer_triton
|
||||
by returning two separate byte tensors for the nope and rope parts.
|
||||
|
||||
Args:
|
||||
k_nope: (num_tokens, dim_nope) or (num_tokens, 1, dim_nope)
|
||||
Must have dim_nope=512 for FP8 MLA quantization
|
||||
k_rope: (num_tokens, dim_rope) or (num_tokens, 1, dim_rope)
|
||||
Must have dim_rope=64 for FP8 MLA quantization
|
||||
tile_size: quantization tile size (default 128)
|
||||
|
||||
Returns:
|
||||
Tuple of (nope_part, rope_part) where:
|
||||
- nope_part: (num_tokens, 1, 528) as uint8 view, contains [nope_fp8(512) | scales(16)]
|
||||
- rope_part: (num_tokens, 1, 128) as uint8 view, contains [rope_bf16_bytes(128)]
|
||||
|
||||
These two tensors can be directly passed to set_mla_kv_buffer_triton(kv_buffer, loc, nope_part, rope_part)
|
||||
"""
|
||||
# Squeeze middle dimension if present
|
||||
k_nope_2d = k_nope.squeeze(1) if k_nope.ndim == 3 else k_nope
|
||||
k_rope_2d = k_rope.squeeze(1) if k_rope.ndim == 3 else k_rope
|
||||
|
||||
num_tokens = k_nope_2d.shape[0]
|
||||
dim_nope = k_nope_2d.shape[1]
|
||||
dim_rope = k_rope_2d.shape[1]
|
||||
|
||||
# Validate dimensions for FP8 MLA
|
||||
if dim_nope != 512:
|
||||
raise ValueError(f"Expected dim_nope=512 for FP8 MLA, got {dim_nope}")
|
||||
if dim_rope != 64:
|
||||
raise ValueError(f"Expected dim_rope=64 for FP8 MLA, got {dim_rope}")
|
||||
if k_rope_2d.shape[0] != num_tokens:
|
||||
raise ValueError(
|
||||
f"k_nope and k_rope must have same num_tokens, got {num_tokens} vs {k_rope_2d.shape[0]}"
|
||||
)
|
||||
|
||||
return _quantize_k_cache_fast_separate(
|
||||
k_nope=k_nope_2d, k_rope=k_rope_2d, group_size=tile_size
|
||||
)
|
||||
|
||||
|
||||
# Copied from original
|
||||
def _quantize_k_cache_ref(
|
||||
input_k_cache: torch.Tensor, # (num_blocks, block_size, h_k, d)
|
||||
dv: int = 512,
|
||||
tile_size: int = 128,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Quantize the k-cache
|
||||
Return a tensor with shape (num_blocks, block_size, h_k, dv + 4(dv/tile_size) + t(d-dv)) of dtype uint8_t, where t = input_k_cache.element_size()
|
||||
For more detail about the layout of K/V, please refer to comments in flash_mla_interface.py or README.md
|
||||
"""
|
||||
assert dv % tile_size == 0
|
||||
num_tiles = dv // tile_size
|
||||
num_blocks, block_size, h_k, d = input_k_cache.shape
|
||||
assert h_k == 1
|
||||
input_k_cache = input_k_cache.squeeze(2) # [num_blocks, block_size, d]
|
||||
input_elem_size = input_k_cache.element_size()
|
||||
|
||||
result = torch.empty(
|
||||
(num_blocks, block_size, dv + num_tiles * 4 + input_elem_size * (d - dv)),
|
||||
dtype=torch.float8_e4m3fn,
|
||||
device=input_k_cache.device,
|
||||
)
|
||||
result_k_nope_part = result[..., :dv]
|
||||
result_k_scale_factor = result[..., dv : dv + num_tiles * 4].view(torch.float32)
|
||||
result_k_rope_part = result[..., dv + num_tiles * 4 :].view(input_k_cache.dtype)
|
||||
result_k_rope_part[:] = input_k_cache[..., dv:]
|
||||
|
||||
for tile_idx in range(0, num_tiles):
|
||||
cur_scale_factors_inv = (
|
||||
torch.abs(
|
||||
input_k_cache[..., tile_idx * tile_size : (tile_idx + 1) * tile_size]
|
||||
)
|
||||
.max(dim=-1)
|
||||
.values
|
||||
/ 448.0
|
||||
) # [num_blocks, block_size]
|
||||
result_k_scale_factor[:, :, tile_idx] = cur_scale_factors_inv
|
||||
|
||||
cur_scale_factors_inv.unsqueeze_(-1) # [num_blocks, block_size, 1]
|
||||
cur_quantized_nope = (
|
||||
input_k_cache[
|
||||
..., tile_idx * tile_size : (tile_idx + 1) * tile_size
|
||||
].float()
|
||||
/ cur_scale_factors_inv.float()
|
||||
).to(torch.float8_e4m3fn)
|
||||
result_k_nope_part[..., tile_idx * tile_size : (tile_idx + 1) * tile_size] = (
|
||||
cur_quantized_nope
|
||||
)
|
||||
|
||||
result = result.view(num_blocks, block_size, 1, -1)
|
||||
return result
|
||||
|
||||
|
||||
def _quantize_k_cache_fast_wrapped(
|
||||
input_k_cache: torch.Tensor,
|
||||
dv: int = 512,
|
||||
tile_size: int = 128,
|
||||
) -> torch.Tensor:
|
||||
# TODO the final API may be 2D instead of 4D, thus we convert them here
|
||||
num_blocks, block_size, _, dim_nope_and_rope = input_k_cache.shape
|
||||
assert dv == 512
|
||||
assert dim_nope_and_rope == 512 + 64
|
||||
assert tile_size == 128
|
||||
input_k_cache = input_k_cache.view((-1, dim_nope_and_rope))
|
||||
|
||||
# TODO deliberately split into two tensors, then upstream can provide the two tensors instead of concat into one
|
||||
k_nope = input_k_cache[:, :dv]
|
||||
k_rope = input_k_cache[:, dv:]
|
||||
|
||||
output = _quantize_k_cache_fast(k_nope=k_nope, k_rope=k_rope)
|
||||
|
||||
return output.view(num_blocks, block_size, 1, -1)
|
||||
|
||||
|
||||
def _quantize_k_cache_fast(k_nope, k_rope, group_size: int = 128):
|
||||
"""
|
||||
:param k_nope: (num_tokens, dim_nope 512)
|
||||
:param k_rope: (num_tokens, dim_rope 64)
|
||||
"""
|
||||
|
||||
assert k_nope.dtype == torch.bfloat16
|
||||
assert k_rope.dtype == torch.bfloat16
|
||||
|
||||
num_tokens, dim_nope = k_nope.shape
|
||||
num_tokens_, dim_rope = k_rope.shape
|
||||
assert num_tokens == num_tokens_
|
||||
assert dim_nope == 512
|
||||
assert dim_rope == 64
|
||||
assert k_nope.dtype == k_rope.dtype
|
||||
num_tiles = dim_nope // group_size
|
||||
|
||||
assert k_nope.stride(1) == 1
|
||||
assert k_rope.stride(1) == 1
|
||||
|
||||
output = torch.empty(
|
||||
(num_tokens, dim_nope + num_tiles * 4 + k_rope.element_size() * dim_rope),
|
||||
dtype=torch.float8_e4m3fn,
|
||||
device=k_nope.device,
|
||||
)
|
||||
output_nope_q = output[..., :dim_nope]
|
||||
output_nope_s = output[..., dim_nope : dim_nope + num_tiles * 4].view(torch.float32)
|
||||
output_rope = output[..., dim_nope + num_tiles * 4 :].view(torch.bfloat16)
|
||||
|
||||
num_blocks_per_token = triton.cdiv(dim_nope + dim_rope, group_size)
|
||||
assert num_blocks_per_token == 5
|
||||
|
||||
assert dim_nope % group_size == 0
|
||||
NUM_NOPE_BLOCKS = dim_nope // group_size
|
||||
|
||||
_quantize_k_cache_fast_kernel[(num_tokens, num_blocks_per_token)](
|
||||
output_nope_q,
|
||||
output_nope_s,
|
||||
output_rope,
|
||||
k_nope,
|
||||
k_rope,
|
||||
output_nope_q.stride(0),
|
||||
output_nope_s.stride(0),
|
||||
output_rope.stride(0),
|
||||
k_nope.stride(0),
|
||||
k_rope.stride(0),
|
||||
NUM_NOPE_BLOCKS=NUM_NOPE_BLOCKS,
|
||||
GROUP_SIZE=group_size,
|
||||
DIM_NOPE=dim_nope,
|
||||
DIM_ROPE=dim_rope,
|
||||
FP8_MIN=torch.finfo(torch.float8_e4m3fn).min,
|
||||
FP8_MAX=torch.finfo(torch.float8_e4m3fn).max,
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def _quantize_k_cache_fast_separate(k_nope, k_rope, group_size: int = 128):
|
||||
"""
|
||||
Quantize k_nope and k_rope in a single Triton kernel, directly outputting two separate tensors.
|
||||
|
||||
This avoids packing/unpacking and enables direct use with set_mla_kv_buffer_triton.
|
||||
|
||||
:param k_nope: (num_tokens, dim_nope 512) bfloat16
|
||||
:param k_rope: (num_tokens, dim_rope 64) bfloat16
|
||||
:param group_size: quantization tile size (default 128, kernel is tuned for this value)
|
||||
:return: Tuple of (nope_part_u8, rope_part_u8)
|
||||
- nope_part_u8: (num_tokens, 1, nope_part_bytes) uint8, layout [nope_fp8(dim_nope) | scales(num_tiles*4)]
|
||||
- rope_part_u8: (num_tokens, 1, rope_part_bytes) uint8, layout [rope_bf16_bytes(dim_rope*2)]
|
||||
"""
|
||||
num_tokens, dim_nope = k_nope.shape
|
||||
num_tokens_, dim_rope = k_rope.shape
|
||||
|
||||
assert num_tokens == num_tokens_, f"k_nope and k_rope must have same num_tokens"
|
||||
|
||||
# Ensure contiguous tensors for kernel
|
||||
k_nope = k_nope.contiguous()
|
||||
k_rope = k_rope.contiguous()
|
||||
|
||||
num_tiles = dim_nope // group_size
|
||||
|
||||
# Calculate byte sizes based on validated dimensions
|
||||
# nope_part: [FP8 quantized data (dim_nope bytes)] + [FP32 scales (num_tiles * 4 bytes)]
|
||||
# rope_part: [BF16 raw data (dim_rope * 2 bytes)]
|
||||
nope_part_bytes = (
|
||||
dim_nope + num_tiles * 4
|
||||
) # e.g., 512 + 4*4 = 528 for dim_nope=512, group_size=128
|
||||
rope_part_bytes = (
|
||||
dim_rope * k_rope.element_size()
|
||||
) # e.g., 64 * 2 = 128 for dim_rope=64, BF16
|
||||
|
||||
# Allocate two separate output buffers (as uint8 for direct byte-level access)
|
||||
nope_part_u8 = torch.empty(
|
||||
(num_tokens, nope_part_bytes), dtype=torch.uint8, device=k_nope.device
|
||||
)
|
||||
rope_part_u8 = torch.empty(
|
||||
(num_tokens, rope_part_bytes), dtype=torch.uint8, device=k_rope.device
|
||||
)
|
||||
|
||||
# Create typed views for the kernel to write into
|
||||
# Fixed byte layout for nope_part: [nope_fp8 (dim_nope bytes) | scales_fp32 (num_tiles*4 bytes)]
|
||||
# Fixed byte layout for rope_part: [rope_bf16 (dim_rope*2 bytes)]
|
||||
nope_q_view = nope_part_u8[:, :dim_nope].view(torch.float8_e4m3fn)
|
||||
nope_s_view = nope_part_u8[:, dim_nope:].view(torch.float32)
|
||||
rope_view = rope_part_u8.view(torch.bfloat16)
|
||||
|
||||
# Kernel launch parameters
|
||||
num_blocks_per_token = triton.cdiv(dim_nope + dim_rope, group_size)
|
||||
NUM_NOPE_BLOCKS = dim_nope // group_size
|
||||
|
||||
# Use the same kernel as _quantize_k_cache_fast (reuse existing implementation)
|
||||
_quantize_k_cache_fast_kernel[(num_tokens, num_blocks_per_token)](
|
||||
nope_q_view,
|
||||
nope_s_view,
|
||||
rope_view,
|
||||
k_nope,
|
||||
k_rope,
|
||||
nope_q_view.stride(0),
|
||||
nope_s_view.stride(0),
|
||||
rope_view.stride(0),
|
||||
k_nope.stride(0),
|
||||
k_rope.stride(0),
|
||||
NUM_NOPE_BLOCKS=NUM_NOPE_BLOCKS,
|
||||
GROUP_SIZE=group_size,
|
||||
DIM_NOPE=dim_nope,
|
||||
DIM_ROPE=dim_rope,
|
||||
FP8_MIN=torch.finfo(torch.float8_e4m3fn).min,
|
||||
FP8_MAX=torch.finfo(torch.float8_e4m3fn).max,
|
||||
)
|
||||
|
||||
# Add middle dimension for compatibility with set_mla_kv_buffer_triton
|
||||
return nope_part_u8.unsqueeze(1), rope_part_u8.unsqueeze(1)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _quantize_k_cache_fast_kernel(
|
||||
output_nope_q_ptr,
|
||||
output_nope_s_ptr,
|
||||
output_rope_ptr,
|
||||
k_nope_ptr,
|
||||
k_rope_ptr,
|
||||
output_nope_q_stride_0: int,
|
||||
output_nope_s_stride_0: int,
|
||||
output_rope_stride_0: int,
|
||||
k_nope_stride_0: int,
|
||||
k_rope_stride_0: int,
|
||||
NUM_NOPE_BLOCKS: tl.constexpr,
|
||||
GROUP_SIZE: tl.constexpr,
|
||||
DIM_NOPE: tl.constexpr,
|
||||
DIM_ROPE: tl.constexpr,
|
||||
FP8_MIN: tl.constexpr,
|
||||
FP8_MAX: tl.constexpr,
|
||||
):
|
||||
token_id = tl.program_id(0)
|
||||
raw_block_id = tl.program_id(1)
|
||||
|
||||
if raw_block_id < NUM_NOPE_BLOCKS:
|
||||
# a. quant nope
|
||||
effective_block_id = raw_block_id
|
||||
|
||||
offs = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs < DIM_NOPE
|
||||
ptr = k_nope_ptr + token_id * k_nope_stride_0 + offs
|
||||
|
||||
y = tl.load(ptr, mask=mask, other=0.0).to(tl.float32)
|
||||
|
||||
# the ref impl do not have a `tl.maximum(... eps)`, so we remove it here
|
||||
y_s = tl.max(tl.abs(y)) / FP8_MAX
|
||||
y_s_inv = 1.0 / y_s
|
||||
y_q = tl.clamp(y * y_s_inv, FP8_MIN, FP8_MAX).to(
|
||||
output_nope_q_ptr.dtype.element_ty
|
||||
)
|
||||
|
||||
dst_q_ptr = output_nope_q_ptr + token_id * output_nope_q_stride_0 + offs
|
||||
dst_s_ptr = (
|
||||
output_nope_s_ptr + token_id * output_nope_s_stride_0 + effective_block_id
|
||||
)
|
||||
|
||||
tl.store(dst_q_ptr, y_q, mask=mask)
|
||||
tl.store(dst_s_ptr, y_s)
|
||||
else:
|
||||
# b. copy rope
|
||||
effective_block_id = raw_block_id - NUM_NOPE_BLOCKS
|
||||
|
||||
offs = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs < DIM_ROPE
|
||||
|
||||
src_ptr = k_rope_ptr + token_id * k_rope_stride_0 + offs
|
||||
dst_ptr = output_rope_ptr + token_id * output_rope_stride_0 + offs
|
||||
|
||||
data = tl.load(src_ptr, mask=mask)
|
||||
tl.store(dst_ptr, data, mask=mask)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import dequant_k_cache
|
||||
|
||||
for num_blocks, block_size in [
|
||||
(1, 1),
|
||||
(10, 64),
|
||||
]:
|
||||
dim_nope_and_rope = 512 + 64
|
||||
|
||||
input_k_cache = torch.randn(
|
||||
(num_blocks, block_size, 1, dim_nope_and_rope),
|
||||
dtype=torch.bfloat16,
|
||||
device="cuda",
|
||||
)
|
||||
|
||||
ref_quant = _quantize_k_cache_ref(input_k_cache)
|
||||
actual_quant = _quantize_k_cache_fast_wrapped(input_k_cache)
|
||||
|
||||
ref_ref_dequant = dequant_k_cache._dequantize_k_cache_slow(ref_quant)
|
||||
ref_actual_dequant = dequant_k_cache._dequantize_k_cache_fast_wrapped(ref_quant)
|
||||
actual_actual_dequant = dequant_k_cache._dequantize_k_cache_fast_wrapped(
|
||||
actual_quant
|
||||
)
|
||||
|
||||
print(f"{ref_ref_dequant=}")
|
||||
print(f"{actual_actual_dequant=}")
|
||||
print(f"{actual_actual_dequant - ref_ref_dequant=}")
|
||||
print(f"{torch.mean(ref_ref_dequant - actual_actual_dequant)=}")
|
||||
|
||||
# TODO too different?
|
||||
torch.testing.assert_close(
|
||||
ref_ref_dequant, ref_actual_dequant, atol=0.2, rtol=0.2
|
||||
)
|
||||
torch.testing.assert_close(
|
||||
ref_ref_dequant, actual_actual_dequant, atol=0.2, rtol=0.2
|
||||
)
|
||||
|
||||
# test dequant_k_cache_paged
|
||||
page_table_1 = torch.arange(
|
||||
num_blocks * block_size, dtype=torch.int32, device="cuda"
|
||||
)
|
||||
actual_dequant_paged = dequant_k_cache.dequantize_k_cache_paged(
|
||||
actual_quant, page_table_1
|
||||
).reshape(actual_actual_dequant.shape)
|
||||
print(f"{torch.mean(actual_actual_dequant - actual_dequant_paged)=}")
|
||||
torch.testing.assert_close(
|
||||
ref_ref_dequant, actual_dequant_paged, atol=0.2, rtol=0.2
|
||||
)
|
||||
|
||||
print("Passed")
|
||||
|
||||
# Test quantize_k_cache_separate: verify output matches concat path
|
||||
print("\nTesting quantize_k_cache_separate...")
|
||||
for num_tokens in [64, 100]:
|
||||
dim_nope = 512
|
||||
dim_rope = 64
|
||||
|
||||
k_nope = torch.randn(
|
||||
num_tokens, 1, dim_nope, dtype=torch.bfloat16, device="cuda"
|
||||
)
|
||||
k_rope = torch.randn(
|
||||
num_tokens, 1, dim_rope, dtype=torch.bfloat16, device="cuda"
|
||||
)
|
||||
|
||||
# Old path: concat then quantize
|
||||
k_concat = torch.cat([k_nope, k_rope], dim=-1).squeeze(1) # (num_tokens, 576)
|
||||
old_output = quantize_k_cache(k_concat.unsqueeze(1).unsqueeze(1)) # 4D input
|
||||
old_output = old_output.squeeze(1).squeeze(1) # Back to (num_tokens, 656)
|
||||
|
||||
# New path: quantize separately
|
||||
nope_part, rope_part = quantize_k_cache_separate(k_nope, k_rope)
|
||||
new_bytes = torch.cat([nope_part.squeeze(1), rope_part.squeeze(1)], dim=-1)
|
||||
|
||||
# Compare byte-level equality
|
||||
old_bytes = old_output.view(torch.uint8)
|
||||
|
||||
if old_bytes.shape != new_bytes.shape:
|
||||
raise RuntimeError(
|
||||
f"Shape mismatch: {old_bytes.shape} vs {new_bytes.shape}"
|
||||
)
|
||||
|
||||
diff_bytes = (old_bytes != new_bytes).sum().item()
|
||||
if diff_bytes > 0:
|
||||
max_diff = (old_bytes.float() - new_bytes.float()).abs().max().item()
|
||||
raise RuntimeError(
|
||||
f"quantize_k_cache_separate output doesn't match concat path: "
|
||||
f"{diff_bytes} differing bytes, max_diff={max_diff}"
|
||||
)
|
||||
|
||||
print(f" num_tokens={num_tokens}: PASSED (outputs match byte-wise)")
|
||||
|
||||
print("quantize_k_cache_separate tests passed!")
|
||||
|
||||
print("\nDo benchmark...")
|
||||
|
||||
for num_blocks, block_size in [
|
||||
(1, 64),
|
||||
(64, 64),
|
||||
(128, 64),
|
||||
(256, 64),
|
||||
(512, 64),
|
||||
(1024, 64),
|
||||
(2048, 64),
|
||||
]:
|
||||
dim_nope_and_rope = 512 + 64
|
||||
|
||||
input_k_cache = torch.randn(
|
||||
(num_blocks, block_size, 1, dim_nope_and_rope),
|
||||
dtype=torch.bfloat16,
|
||||
device="cuda",
|
||||
)
|
||||
|
||||
actual_quant = _quantize_k_cache_fast_wrapped(input_k_cache)
|
||||
|
||||
page_table_1 = torch.arange(
|
||||
num_blocks * block_size, dtype=torch.int32, device="cuda"
|
||||
)
|
||||
|
||||
def run_ans():
|
||||
return dequant_k_cache.dequantize_k_cache_paged(actual_quant, page_table_1)
|
||||
|
||||
ans_time: float = triton.testing.do_bench(run_ans, warmup=10, rep=20) / 1000 # type: ignore
|
||||
print(f"seq_kv: {num_blocks * block_size}, time: {ans_time * 1e6: 4.0f} us")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,144 @@
|
||||
from typing import List, Optional
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
|
||||
def transform_index_page_table_prefill(**kwargs):
|
||||
return transform_index_page_table_prefill_ref(**kwargs)
|
||||
|
||||
|
||||
def transform_index_page_table_decode(**kwargs):
|
||||
return transform_index_page_table_decode_ref(**kwargs)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def transform_index_page_table_decode_kernel(
|
||||
page_table_ptr: torch.Tensor,
|
||||
topk_indices_ptr: torch.Tensor,
|
||||
result_ptr: torch.Tensor,
|
||||
page_size: tl.constexpr,
|
||||
max_seqlen_k: tl.constexpr,
|
||||
):
|
||||
TOPK: tl.constexpr = 2048
|
||||
req_id = tl.program_id(0)
|
||||
page_table_ptr = page_table_ptr + req_id * max_seqlen_k
|
||||
topk_indices_ptr = topk_indices_ptr + req_id * TOPK
|
||||
result_ptr = result_ptr + req_id * TOPK
|
||||
|
||||
offset = tl.arange(0, TOPK) # topk should be 2048
|
||||
loaded_topk_indices = tl.load(topk_indices_ptr + offset)
|
||||
mask = loaded_topk_indices >= 0
|
||||
loaded_kv_indices = tl.load(page_table_ptr + loaded_topk_indices, mask=mask)
|
||||
tl.store(result_ptr + offset, loaded_kv_indices, mask=mask)
|
||||
tl.store(result_ptr + offset, -1, mask=~mask)
|
||||
|
||||
|
||||
def transform_index_page_table_decode_fast(
|
||||
page_table: torch.Tensor,
|
||||
topk_indices: torch.Tensor,
|
||||
result: Optional[torch.Tensor] = None,
|
||||
page_size: int = 1,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Transform the page table according to topk indices for sparse topk attention.
|
||||
Args:
|
||||
page_table: [qo_len, max_seqlen_k], the original page table
|
||||
topk_indices: [qo_len, topk], the topk indices for each query position
|
||||
Returns:
|
||||
transformed_page_table: [qo_len, topk], the transformed page table
|
||||
For out-of-bound indices in topk_indices, this should be filled with -1.
|
||||
"""
|
||||
assert page_size == 1
|
||||
assert page_table.shape[0] == topk_indices.shape[0]
|
||||
assert topk_indices.shape[1] == 2048
|
||||
qo_len = topk_indices.shape[0]
|
||||
max_seqlen_k = page_table.shape[1]
|
||||
if result is None:
|
||||
result = torch.empty_like(topk_indices, dtype=torch.int32)
|
||||
# Launch triton kernel
|
||||
grid = (qo_len,)
|
||||
transform_index_page_table_decode_kernel[grid](
|
||||
page_table,
|
||||
topk_indices,
|
||||
result,
|
||||
page_size,
|
||||
max_seqlen_k=max_seqlen_k,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def transform_index_page_table_prefill_fast(
|
||||
page_table: torch.Tensor,
|
||||
topk_indices: torch.Tensor,
|
||||
extend_lens_cpu: List[int],
|
||||
page_size: int = 1,
|
||||
) -> torch.Tensor:
|
||||
# TODO(baizhou): can be implemented with another triton kernel
|
||||
assert page_size == 1
|
||||
result = torch.empty_like(topk_indices, dtype=torch.int32)
|
||||
assert len(extend_lens_cpu) == page_table.shape[0]
|
||||
offset = 0
|
||||
for i, l in enumerate(extend_lens_cpu):
|
||||
transform_index_page_table_decode_fast(
|
||||
page_table[i].unsqueeze(0).expand(l, -1),
|
||||
topk_indices[offset : offset + l],
|
||||
result=result[offset : offset + l],
|
||||
)
|
||||
offset += l
|
||||
assert offset == topk_indices.shape[0]
|
||||
return result
|
||||
|
||||
|
||||
def transform_index_page_table_decode_ref(
|
||||
page_table: torch.Tensor,
|
||||
topk_indices: torch.Tensor,
|
||||
result: Optional[torch.Tensor] = None,
|
||||
page_size: int = 1,
|
||||
) -> torch.Tensor:
|
||||
assert page_size == 1
|
||||
assert page_table.shape[0] == topk_indices.shape[0]
|
||||
if result is None:
|
||||
result = torch.empty_like(topk_indices, dtype=torch.int32)
|
||||
assert result.shape == topk_indices.shape
|
||||
torch.gather(
|
||||
page_table.to(result.dtype),
|
||||
dim=1,
|
||||
index=topk_indices.clamp(min=0),
|
||||
out=result,
|
||||
)
|
||||
result[topk_indices < 0] = -1
|
||||
return result
|
||||
|
||||
|
||||
def transform_index_page_table_prefill_ref(
|
||||
page_table: torch.Tensor,
|
||||
topk_indices: torch.Tensor,
|
||||
extend_lens_cpu: List[int],
|
||||
page_size: int = 1,
|
||||
) -> torch.Tensor:
|
||||
assert page_size == 1
|
||||
result = torch.empty_like(topk_indices, dtype=torch.int32)
|
||||
assert len(extend_lens_cpu) == page_table.shape[0]
|
||||
offset = 0
|
||||
for i, l in enumerate(extend_lens_cpu):
|
||||
transform_index_page_table_decode_ref(
|
||||
page_table[i].unsqueeze(0).expand(l, -1),
|
||||
topk_indices[offset : offset + l],
|
||||
result=result[offset : offset + l],
|
||||
)
|
||||
offset += l
|
||||
assert offset == topk_indices.shape[0]
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bs, topk, max_seqlen = 10, 2048, 3000
|
||||
page_table = torch.randint(0, 100, (bs, max_seqlen), device="cuda")
|
||||
topk_indices = torch.full((bs, topk), -1, device="cuda")
|
||||
topk_indices[:, :1600] = torch.arange(1600).unsqueeze(0).repeat(bs, 1)
|
||||
ref_result = transform_index_page_table_decode_ref(page_table, topk_indices)
|
||||
result = transform_index_page_table_decode_fast(page_table, topk_indices)
|
||||
assert torch.all(result == ref_result)
|
||||
print("Passed")
|
||||
@@ -0,0 +1,196 @@
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
|
||||
# Triton implementation
|
||||
@triton.jit
|
||||
def _act_quant_kernel(
|
||||
X_ptr,
|
||||
Y_ptr,
|
||||
S_ptr,
|
||||
M,
|
||||
N,
|
||||
group_size: tl.constexpr,
|
||||
round_scale: tl.constexpr,
|
||||
BLOCK_M: tl.constexpr,
|
||||
BLOCK_N: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Triton kernel for activation quantization.
|
||||
|
||||
Each block processes BLOCK_M rows and group_size columns.
|
||||
"""
|
||||
# Get block IDs
|
||||
pid_m = tl.program_id(0)
|
||||
pid_n = tl.program_id(1)
|
||||
|
||||
# FP8 constants
|
||||
fp8_min = -448.0
|
||||
fp8_max = 448.0
|
||||
fp8_max_inv = 1.0 / fp8_max
|
||||
|
||||
# Calculate row and column offsets
|
||||
row_start = pid_m * BLOCK_M
|
||||
col_start = pid_n * group_size
|
||||
|
||||
# Create offset arrays
|
||||
rows = row_start + tl.arange(0, BLOCK_M)
|
||||
cols = col_start + tl.arange(0, BLOCK_N)
|
||||
|
||||
# Mask for valid rows and columns
|
||||
row_mask = rows < M
|
||||
col_mask = cols < N
|
||||
mask = row_mask[:, None] & col_mask[None, :]
|
||||
|
||||
# Load input data
|
||||
x_ptrs = X_ptr + rows[:, None] * N + cols[None, :]
|
||||
x = tl.load(x_ptrs, mask=mask, other=0.0).to(tl.float32)
|
||||
|
||||
# Compute absolute max along columns (group_size dimension) for each row
|
||||
x_abs = tl.abs(x)
|
||||
amax = tl.max(x_abs, axis=1) # Shape: (BLOCK_M,)
|
||||
|
||||
# Clamp amax to avoid division by zero
|
||||
amax = tl.maximum(amax, 1e-4)
|
||||
|
||||
# Compute scale
|
||||
if round_scale:
|
||||
# Fast round scale using bit manipulation approximation
|
||||
# This is a simplified version - the exact bit manipulation is harder in Triton
|
||||
# Using log2 + ceil + pow2 as approximation
|
||||
log_val = tl.log2(amax * fp8_max_inv)
|
||||
log_ceil = tl.ceil(log_val)
|
||||
scale = tl.exp2(log_ceil)
|
||||
else:
|
||||
scale = amax * fp8_max_inv
|
||||
|
||||
# Quantize: y = clamp(x / scale, fp8_min, fp8_max)
|
||||
scale_broadcast = scale[:, None]
|
||||
y = x / scale_broadcast
|
||||
y = tl.minimum(tl.maximum(y, fp8_min), fp8_max)
|
||||
|
||||
# Store quantized output
|
||||
y_ptrs = Y_ptr + rows[:, None] * N + cols[None, :]
|
||||
tl.store(y_ptrs, y, mask=mask)
|
||||
|
||||
# Store scales
|
||||
s_cols = pid_n
|
||||
s_ptrs = S_ptr + rows * (N // group_size) + s_cols
|
||||
s_mask = row_mask
|
||||
tl.store(s_ptrs, scale, mask=s_mask)
|
||||
|
||||
|
||||
def act_quant(
|
||||
x: torch.Tensor, block_size: int = 128, scale_fmt: Optional[str] = None
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Quantizes the input tensor `x` using block-wise quantization with Triton.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor): The input tensor to be quantized. Must be contiguous and its last dimension size must be divisible by `block_size`.
|
||||
block_size (int, optional): The size of the blocks to be used for quantization. Default is 128.
|
||||
scale_fmt (Optional[str], optional): The format of the scale. Default is None.
|
||||
Returns:
|
||||
Tuple[torch.Tensor, torch.Tensor]: A tuple containing:
|
||||
- The quantized tensor with dtype `torch.float8_e4m3fn`.
|
||||
- A tensor of scaling factors with dtype `torch.float32`.
|
||||
"""
|
||||
assert x.is_contiguous(), "Input tensor must be contiguous"
|
||||
assert (
|
||||
x.size(-1) % block_size == 0
|
||||
), f"Last dimension size must be divisible by block_size (block_size={block_size})"
|
||||
|
||||
# Flatten all dims except last
|
||||
N = x.size(-1)
|
||||
x_flat = x.view(-1, N)
|
||||
M = x_flat.size(0)
|
||||
|
||||
# Allocate output tensors
|
||||
y = torch.empty_like(x, dtype=torch.float8_e4m3fn)
|
||||
y_flat = y.view(-1, N)
|
||||
s = x.new_empty(*x.size()[:-1], N // block_size, dtype=torch.float32)
|
||||
s_flat = s.view(-1, N // block_size)
|
||||
|
||||
# Launch kernel
|
||||
BLOCK_M = 32
|
||||
BLOCK_N = block_size
|
||||
grid = (triton.cdiv(M, BLOCK_M), triton.cdiv(N, block_size))
|
||||
round_scale = scale_fmt is not None
|
||||
|
||||
_act_quant_kernel[grid](
|
||||
x_flat,
|
||||
y_flat,
|
||||
s_flat,
|
||||
M,
|
||||
N,
|
||||
group_size=block_size,
|
||||
round_scale=round_scale,
|
||||
BLOCK_M=BLOCK_M,
|
||||
BLOCK_N=BLOCK_N,
|
||||
num_stages=0 if round_scale else 2,
|
||||
)
|
||||
|
||||
return y, s
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _get_valid_kv_indices_kernel(
|
||||
page_table_ptr, # [bs, topk]
|
||||
kv_indptr_ptr, # [bs + 1]
|
||||
kv_indices_ptr, # [bs * topk] output buffer
|
||||
bs: tl.constexpr,
|
||||
topk: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Extract valid indices (non -1) from page_table into kv_indices.
|
||||
Each program handles one batch.
|
||||
"""
|
||||
batch_id = tl.program_id(0)
|
||||
|
||||
# Get the start position for this batch in kv_indices
|
||||
dst_start = tl.load(kv_indptr_ptr + batch_id)
|
||||
|
||||
# Load all topk indices for this batch
|
||||
src_offset = batch_id * topk
|
||||
offsets = tl.arange(0, topk)
|
||||
indices = tl.load(page_table_ptr + src_offset + offsets)
|
||||
|
||||
# Count valid indices and compact them
|
||||
mask = indices != -1
|
||||
|
||||
# Use prefix sum to compute destination positions for valid elements
|
||||
# For each position, count how many valid elements are before it
|
||||
prefix_sum = tl.cumsum(mask.to(tl.int32), axis=0) - 1
|
||||
|
||||
# Store valid indices to their compacted positions
|
||||
dst_positions = dst_start + prefix_sum
|
||||
tl.store(kv_indices_ptr + dst_positions, indices, mask=mask)
|
||||
|
||||
|
||||
def get_valid_kv_indices(
|
||||
page_table_1: torch.Tensor,
|
||||
kv_indptr: torch.Tensor,
|
||||
kv_indices: torch.Tensor,
|
||||
bs: int,
|
||||
):
|
||||
"""
|
||||
Extract valid indices from page_table_1 into kv_indices buffer.
|
||||
|
||||
Args:
|
||||
page_table_1: [bs, topk] page table with -1 as invalid
|
||||
kv_indptr: [bs + 1] cumulative count of valid indices per batch
|
||||
kv_indices: [bs * topk] pre-allocated output buffer
|
||||
bs: batch size
|
||||
"""
|
||||
topk = page_table_1.shape[1]
|
||||
grid = (bs,)
|
||||
_get_valid_kv_indices_kernel[grid](
|
||||
page_table_1,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
bs,
|
||||
topk,
|
||||
)
|
||||
@@ -0,0 +1,271 @@
|
||||
from functools import lru_cache
|
||||
from typing import TYPE_CHECKING, List, Tuple, Union
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.dp_attention import (
|
||||
DpPaddingMode,
|
||||
get_attention_cp_rank,
|
||||
get_attention_cp_size,
|
||||
get_attention_dp_rank,
|
||||
)
|
||||
from sglang.srt.server_args import get_global_server_args
|
||||
from sglang.srt.utils import get_bool_env_var, is_hip
|
||||
from sglang.srt.utils.common import ceil_align, ceil_div
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def aiter_can_use_preshuffle_paged_mqa() -> bool:
|
||||
"""Whether aiter's preshuffle paged MQA / cache kernels can be used on this runtime.
|
||||
|
||||
aiter's ``deepgemm_fp8_paged_mqa_logits`` only supports ``KVBlockSize > 1`` and
|
||||
``Preshuffle=True`` on its gluon kernel path. The gluon path is enabled when
|
||||
Triton >= 3.5.0, OR when ``AITER_ENABLE_AOT_GLUON_PA_MQA_LOGITS=1`` is set
|
||||
(which additionally requires that the AOT gluon kernel artifacts ship inside
|
||||
the aiter wheel/image). Otherwise aiter asserts ``KVBlockSize == 1`` and
|
||||
refuses ``Preshuffle=True``.
|
||||
|
||||
sglang's DSA indexer uses this single decision to pick:
|
||||
* ``page_size``: 64 (preshuffle) vs 1 (legacy) on ROCm
|
||||
* ``Preshuffle`` / ``preshuffle`` flags on the aiter MQA + cache kernels
|
||||
* ``get_page_table_64`` vs ``get_page_table_1`` on the metadata
|
||||
* whether ``GetKAndS.execute`` uses the aiter or the triton implementation
|
||||
|
||||
The result is cached so the cost is paid once per process.
|
||||
|
||||
Set ``SGLANG_DSA_HIP_DISABLE_PRESHUFFLE=1`` to force the legacy path even when
|
||||
the gluon kernel would otherwise be available (useful for CI bisection).
|
||||
``SGLANG_NSA_HIP_DISABLE_PRESHUFFLE`` is a deprecated alias.
|
||||
"""
|
||||
if not is_hip():
|
||||
return False
|
||||
if not get_bool_env_var("SGLANG_USE_AITER"):
|
||||
return False
|
||||
if envs.SGLANG_DSA_HIP_DISABLE_PRESHUFFLE.get():
|
||||
return False
|
||||
if get_bool_env_var("AITER_ENABLE_AOT_GLUON_PA_MQA_LOGITS"):
|
||||
return True
|
||||
try:
|
||||
from packaging.version import Version
|
||||
|
||||
return Version(Version(triton.__version__).base_version) >= Version("3.5.0")
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
|
||||
|
||||
def compute_dsa_seqlens(original_seq_lens, dsa_index_topk: int):
|
||||
return original_seq_lens.clamp(max=dsa_index_topk)
|
||||
|
||||
|
||||
def is_dsa_enable_prefill_cp():
|
||||
return get_global_server_args().enable_dsa_prefill_context_parallel
|
||||
|
||||
|
||||
def is_dsa_prefill_cp_in_seq_split():
|
||||
return (
|
||||
is_dsa_enable_prefill_cp()
|
||||
and get_global_server_args().dsa_prefill_cp_mode == "in-seq-split"
|
||||
)
|
||||
|
||||
|
||||
def is_dsa_prefill_cp_round_robin_split():
|
||||
return (
|
||||
is_dsa_enable_prefill_cp()
|
||||
and get_global_server_args().dsa_prefill_cp_mode == "round-robin-split"
|
||||
)
|
||||
|
||||
|
||||
def can_dsa_prefill_cp_round_robin_split(forward_batch: "ForwardBatch"):
|
||||
if not forward_batch.forward_mode.is_context_parallel_extend():
|
||||
return False
|
||||
cp_size = get_attention_cp_size()
|
||||
seq_len = sum(forward_batch.extend_seq_lens_cpu)
|
||||
return (
|
||||
is_dsa_prefill_cp_round_robin_split()
|
||||
and seq_len > 0
|
||||
and seq_len >= cp_size
|
||||
and cp_size > 1
|
||||
)
|
||||
|
||||
|
||||
def dsa_cp_round_robin_split_data(input_: Union[torch.Tensor, List]):
|
||||
"""
|
||||
# for round-robin-split, split the tokens evenly according to the rule of token_idx % cp_size.
|
||||
| +-----------before split------------+|
|
||||
| token0, token1, token2, token3, token4, token5, token6, token7, ...
|
||||
|
|
||||
| +--------------result-------------------+
|
||||
| dp_atten_tp0: token0, token4, token8, token12, token16, ... |
|
||||
| dp_atten_tp1: token1, token5, token9, token13, token17, ... |
|
||||
| dp_atten_tp2: token2, token6, token10, token14, token18, ... |
|
||||
| dp_atten_tp3: token3, token7, token11, token15, token19, ... |
|
||||
| +-------------------------+
|
||||
"""
|
||||
cp_size = get_attention_cp_size()
|
||||
cp_rank = get_attention_cp_rank()
|
||||
if isinstance(input_, (tuple, list)):
|
||||
indices = range(cp_rank, len(input_), cp_size)
|
||||
return input_[indices]
|
||||
|
||||
tokens = len(input_)
|
||||
if tokens % cp_size != 0:
|
||||
cur_len = tokens // cp_size + (tokens % cp_size > cp_rank)
|
||||
if cur_len == 0:
|
||||
return input_.new_empty(0, *input_.shape[1:])
|
||||
indices = torch.arange(cp_rank, tokens, cp_size, device=input_.device)
|
||||
return input_[indices]
|
||||
|
||||
# for torch device tensor
|
||||
return input_.view(-1, cp_size, *input_.shape[1:])[:, cp_rank].contiguous()
|
||||
|
||||
|
||||
def cal_padded_tokens(forward_batch: "ForwardBatch"):
|
||||
# Consistent with the padding calculation logic in ForwardBatch.prepare_mlp_sync_batch,
|
||||
# calculate the actual token length after padding when attn_tp_size > 1 or in the MAX_LEN padding mode.
|
||||
global_num_tokens = forward_batch.global_num_tokens_cpu.copy()
|
||||
sync_group_size = len(global_num_tokens)
|
||||
attn_cp_size = get_attention_cp_size()
|
||||
for i in range(sync_group_size):
|
||||
global_num_tokens[i] = ceil_align(global_num_tokens[i], attn_cp_size)
|
||||
dp_padding_mode = DpPaddingMode.get_dp_padding_mode(
|
||||
forward_batch.is_extend_in_batch, global_num_tokens
|
||||
)
|
||||
if dp_padding_mode.is_max_len():
|
||||
tokens = max(global_num_tokens)
|
||||
elif len(global_num_tokens) > 1:
|
||||
tokens = global_num_tokens[get_attention_dp_rank()]
|
||||
else:
|
||||
tokens = global_num_tokens[0]
|
||||
if can_dsa_prefill_cp_round_robin_split(forward_batch):
|
||||
tokens = ceil_div(tokens, attn_cp_size)
|
||||
return tokens
|
||||
|
||||
|
||||
def pad_dsa_cache_seqlens(forward_batch: "ForwardBatch", dsa_cache_seqlens):
|
||||
attn_cp_size = get_attention_cp_size()
|
||||
needs_cp_pad = attn_cp_size > 1 and can_dsa_prefill_cp_round_robin_split(
|
||||
forward_batch
|
||||
)
|
||||
needs_dp_pad = forward_batch.global_num_tokens_cpu is not None
|
||||
if not needs_cp_pad and not needs_dp_pad:
|
||||
return dsa_cache_seqlens
|
||||
tokens = cal_padded_tokens(forward_batch)
|
||||
pad_len = tokens - dsa_cache_seqlens.shape[0]
|
||||
if pad_len > 0:
|
||||
dsa_cache_seqlens = torch.cat(
|
||||
[
|
||||
dsa_cache_seqlens,
|
||||
dsa_cache_seqlens.new_zeros(pad_len, *dsa_cache_seqlens.shape[1:]),
|
||||
]
|
||||
)
|
||||
return dsa_cache_seqlens
|
||||
|
||||
|
||||
def can_dsa_cp_split(seq_len: int, cp_size: int, use_dsa: bool, forward_batch):
|
||||
if is_dsa_prefill_cp_round_robin_split():
|
||||
cur_cp_seq_len = seq_len // cp_size
|
||||
assert (
|
||||
seq_len % cp_size == 0
|
||||
), f"seq_len {seq_len} is not divisible by cp_size {cp_size} when dsa_prefill_cp_mode is round-robin-split"
|
||||
else:
|
||||
# TODO current just support prefill batch=1 and len(input_ids) > self.cp_size * 2
|
||||
# Note: (self.cp_size * 2) To achieve load balancing for seq computation,
|
||||
# the seq data needs to be divided and recombined at twice the size of cp_size.
|
||||
cur_cp_seq_len = seq_len // (cp_size * 2)
|
||||
if (
|
||||
cur_cp_seq_len != 0
|
||||
and cp_size > 1
|
||||
and use_dsa
|
||||
and forward_batch.forward_mode.is_context_parallel_extend()
|
||||
and is_dsa_enable_prefill_cp()
|
||||
and sum(forward_batch.extend_seq_lens_cpu) >= cp_size
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
@triton.jit
|
||||
def dsa_cp_round_robin_split_q_seqs_kernel(
|
||||
in_seqs_ptr,
|
||||
out_seqs_ptr,
|
||||
bs_idx_ptr,
|
||||
tokens: tl.constexpr,
|
||||
cp_size: tl.constexpr,
|
||||
cp_rank: tl.constexpr,
|
||||
):
|
||||
extra_seq = 0
|
||||
bs_idx = 0
|
||||
for bs in range(tokens):
|
||||
cur_len = tl.load(in_seqs_ptr + bs)
|
||||
cur_len += extra_seq
|
||||
cur_seq = cur_len // cp_size + (cur_len % cp_size > cp_rank)
|
||||
if cur_seq > 0:
|
||||
tl.store(bs_idx_ptr + bs_idx, bs)
|
||||
tl.store(out_seqs_ptr + bs_idx, cur_seq)
|
||||
bs_idx += 1
|
||||
extra_seq = cur_len - cur_seq * cp_size
|
||||
|
||||
|
||||
def dsa_cp_round_robin_split_q_seqs_cpu(extend_seqs):
|
||||
cp_size = get_attention_cp_size()
|
||||
cp_rank = get_attention_cp_rank()
|
||||
extra_seq = 0
|
||||
q_seqs = []
|
||||
for bs, cur_len in enumerate(extend_seqs):
|
||||
cur_len += extra_seq
|
||||
cur_seq = cur_len // cp_size + int(cur_len % cp_size > cp_rank)
|
||||
q_seqs.append(cur_seq)
|
||||
extra_seq = cur_len - cur_seq * cp_size
|
||||
bs_idx = list([i for i, x in enumerate(q_seqs) if x > 0])
|
||||
q_seqs = [q_len for q_len in q_seqs if q_len > 0]
|
||||
return q_seqs, bs_idx
|
||||
|
||||
|
||||
def dsa_cp_round_robin_split_q_seqs(
|
||||
extend_seqs_cpu, extend_seqs
|
||||
) -> Tuple[List, torch.Tensor, List, torch.Tensor]:
|
||||
"""
|
||||
round-robin-split distributes tokens across ranks based on token_idx % cp_size.
|
||||
|
||||
Return:
|
||||
ret_q_lens_cpu(List) and ret_q_lens(torch.Tensor): the partitioned length (excluding zeros) on the current cp rank
|
||||
for each sequence after distribution across cp ranks.
|
||||
bs_idx_cpu(List) and bs_idx(torch.Tensor): marks which sequences are ultimately selected,
|
||||
i.e., those with a partitioned length greater than zero.
|
||||
"""
|
||||
cp_size = get_attention_cp_size()
|
||||
cp_rank = get_attention_cp_rank()
|
||||
# len(ret_q_lens_cpu) == len(bs_idx_cpu)
|
||||
ret_q_lens_cpu, bs_idx_cpu = dsa_cp_round_robin_split_q_seqs_cpu(extend_seqs_cpu)
|
||||
ret_q_lens = torch.empty(
|
||||
(len(bs_idx_cpu),), device=extend_seqs.device, dtype=extend_seqs.dtype
|
||||
)
|
||||
bs_idx = torch.empty(
|
||||
(len(bs_idx_cpu),), device=extend_seqs.device, dtype=torch.int32
|
||||
)
|
||||
grid = (1,)
|
||||
dsa_cp_round_robin_split_q_seqs_kernel[grid](
|
||||
extend_seqs, ret_q_lens, bs_idx, len(extend_seqs), cp_size, cp_rank
|
||||
)
|
||||
return ret_q_lens_cpu, ret_q_lens, bs_idx_cpu, bs_idx
|
||||
|
||||
|
||||
def dsa_use_prefill_cp(forward_batch, dsa_enable_prefill_cp=None):
|
||||
if dsa_enable_prefill_cp is None:
|
||||
dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if (
|
||||
forward_batch.attn_cp_metadata is not None
|
||||
and dsa_enable_prefill_cp
|
||||
and forward_batch.forward_mode.is_context_parallel_extend()
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,8 +10,8 @@ import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.attention.dsa.dsa_indexer import rotate_activation
|
||||
from sglang.srt.layers.attention.dsv4.compressor import Compressor as _CompressorBase
|
||||
from sglang.srt.layers.attention.nsa.nsa_indexer import rotate_activation
|
||||
from sglang.srt.layers.deepseek_v4_rope import (
|
||||
apply_rotary_emb_triton,
|
||||
fused_norm_rope_inplace_triton,
|
||||
|
||||
@@ -15,11 +15,11 @@ from sglang.jit_kernel.deepseek_v4 import (
|
||||
)
|
||||
from sglang.srt.configs.deepseek_v4 import DeepSeekV4Config
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.attention.dsa.triton_kernel import act_quant
|
||||
from sglang.srt.layers.attention.dsa.utils import dsa_use_prefill_cp
|
||||
from sglang.srt.layers.attention.dsv4.quant_k_cache import (
|
||||
quant_to_nope_fp8_rope_bf16_pack_triton,
|
||||
)
|
||||
from sglang.srt.layers.attention.nsa.triton_kernel import act_quant
|
||||
from sglang.srt.layers.attention.nsa.utils import nsa_use_prefill_cp
|
||||
from sglang.srt.layers.dp_attention import get_attention_cp_size
|
||||
from sglang.srt.layers.layernorm import RMSNorm
|
||||
from sglang.srt.layers.linear import ReplicatedLinear
|
||||
@@ -71,7 +71,7 @@ class CompressorBackendMixin:
|
||||
compress_ratio: int,
|
||||
is_paged: bool = False,
|
||||
) -> torch.Tensor:
|
||||
from sglang.srt.layers.attention.nsa.nsa_indexer import rotate_activation
|
||||
from sglang.srt.layers.attention.dsa.dsa_indexer import rotate_activation
|
||||
|
||||
assert compress_ratio in (
|
||||
4,
|
||||
@@ -358,7 +358,7 @@ class Compressor(nn.Module):
|
||||
kv_score = linear_bf16_fp32(x, self.wkv_gate.weight)
|
||||
|
||||
# CUDA path: delegate to backend
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
kv_score = cp_all_gather_rerange_output(
|
||||
kv_score,
|
||||
get_attention_cp_size(),
|
||||
|
||||
@@ -368,7 +368,7 @@ class C4IndexerBackendMixin:
|
||||
assert len(weights.shape) == 3
|
||||
weights = weights.squeeze(2)
|
||||
if envs.SGLANG_OPT_USE_TILELANG_INDEXER.get():
|
||||
from sglang.srt.layers.attention.nsa.tilelang_kernel import (
|
||||
from sglang.srt.layers.attention.dsa.tilelang_kernel import (
|
||||
tilelang_fp8_paged_mqa_logits as fn,
|
||||
)
|
||||
elif envs.SGLANG_FP8_PAGED_MQA_LOGITS_TORCH.get():
|
||||
|
||||
@@ -12,7 +12,7 @@ def flash_mla_with_kvcache_entrypoint(backend: str, **kwargs):
|
||||
if is_hip():
|
||||
import os
|
||||
|
||||
from sglang.srt.layers.attention.nsa.tilelang_kernel import (
|
||||
from sglang.srt.layers.attention.dsa.tilelang_kernel import (
|
||||
dpsk_v4_fp8_attention_fwd,
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Optional
|
||||
import torch
|
||||
|
||||
from sglang.srt.layers.attention.base_attn_backend import AttentionBackend
|
||||
from sglang.srt.layers.attention.nsa.nsa_indexer import BaseIndexerMetadata
|
||||
from sglang.srt.layers.attention.dsa.dsa_indexer import BaseIndexerMetadata
|
||||
from sglang.srt.layers.radix_attention import RadixAttention
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch, ForwardMode
|
||||
from sglang.srt.model_executor.model_runner import ModelRunner
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# [Deprecated] attention/nsa/ is a thin re-export shim for backward compatibility.
|
||||
# Use attention/dsa/ instead. This directory will be removed in a future release.
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"sglang.srt.layers.attention.nsa is deprecated; "
|
||||
"use sglang.srt.layers.attention.dsa instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from sglang.srt.layers.attention.dsa import * # noqa: F401, F403
|
||||
@@ -1,289 +1,10 @@
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
# [Deprecated] Re-export shim for backward compatibility. Use dsa.dequant_k_cache instead.
|
||||
import warnings
|
||||
|
||||
|
||||
def dequantize_k_cache(quant_k_cache):
|
||||
return _dequantize_k_cache_fast_wrapped(quant_k_cache)
|
||||
|
||||
|
||||
def _dequantize_k_cache_ref(
|
||||
quant_k_cache: torch.Tensor, # (num_blocks, block_size, 1, bytes_per_token)
|
||||
dv: int = 512,
|
||||
tile_size: int = 128,
|
||||
d: int = 576,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
De-quantize the k-cache
|
||||
"""
|
||||
assert dv % tile_size == 0
|
||||
original_ndim = quant_k_cache.ndim
|
||||
if original_ndim == 3:
|
||||
# set block_size = 1
|
||||
quant_k_cache = quant_k_cache.unsqueeze(1)
|
||||
num_tiles = dv // tile_size
|
||||
num_blocks, block_size, h_k, _ = quant_k_cache.shape
|
||||
assert h_k == 1
|
||||
result = torch.empty(
|
||||
(num_blocks, block_size, d), dtype=torch.bfloat16, device=quant_k_cache.device
|
||||
)
|
||||
|
||||
quant_k_cache = quant_k_cache.view(num_blocks, block_size, -1)
|
||||
|
||||
input_nope = quant_k_cache[..., :dv]
|
||||
input_scale = quant_k_cache[..., dv : dv + num_tiles * 4].view(torch.float32)
|
||||
input_rope = quant_k_cache[..., dv + num_tiles * 4 :].view(torch.bfloat16)
|
||||
result[..., dv:] = input_rope
|
||||
|
||||
for tile_idx in range(0, num_tiles):
|
||||
cur_nope = input_nope[
|
||||
..., tile_idx * tile_size : (tile_idx + 1) * tile_size
|
||||
].to(torch.float32)
|
||||
cur_scales = input_scale[..., tile_idx].unsqueeze(-1)
|
||||
result[..., tile_idx * tile_size : (tile_idx + 1) * tile_size] = (
|
||||
cur_nope * cur_scales
|
||||
)
|
||||
|
||||
if original_ndim == 3:
|
||||
return result.view(num_blocks, 1, -1)
|
||||
else:
|
||||
return result.view(num_blocks, block_size, 1, -1)
|
||||
|
||||
|
||||
def _dequantize_k_cache_fast_wrapped(
|
||||
quant_k_cache: torch.Tensor,
|
||||
dv: int = 512,
|
||||
tile_size: int = 128,
|
||||
) -> torch.Tensor:
|
||||
original_ndim = quant_k_cache.ndim
|
||||
if original_ndim == 3:
|
||||
# set block_size = 1
|
||||
quant_k_cache = quant_k_cache.unsqueeze(1)
|
||||
num_blocks, block_size, _, dim_quant = quant_k_cache.shape
|
||||
assert dv == 512
|
||||
assert dim_quant == 656
|
||||
assert tile_size == 128
|
||||
quant_k_cache = quant_k_cache.view((-1, dim_quant))
|
||||
|
||||
output = _dequantize_k_cache_fast(quant_k_cache)
|
||||
|
||||
if original_ndim == 3:
|
||||
return output.view(num_blocks, 1, -1)
|
||||
else:
|
||||
return output.view(num_blocks, block_size, 1, -1)
|
||||
|
||||
|
||||
def _dequantize_k_cache_fast(quant_k_cache, group_size: int = 128):
|
||||
num_tokens, dim_quant = quant_k_cache.shape
|
||||
|
||||
assert quant_k_cache.dtype == torch.float8_e4m3fn
|
||||
dim_nope = 512
|
||||
dim_rope = 64
|
||||
num_tiles = dim_nope // group_size
|
||||
assert dim_quant == 656
|
||||
|
||||
output = torch.empty(
|
||||
(num_tokens, dim_nope + dim_rope),
|
||||
dtype=torch.bfloat16,
|
||||
device=quant_k_cache.device,
|
||||
)
|
||||
|
||||
num_blocks_per_token = triton.cdiv(dim_nope + dim_rope, group_size)
|
||||
assert num_blocks_per_token == 5
|
||||
|
||||
assert dim_nope % group_size == 0
|
||||
|
||||
input_nope_q = quant_k_cache[:, :dim_nope]
|
||||
input_nope_s = quant_k_cache[:, dim_nope : dim_nope + num_tiles * 4].view(
|
||||
torch.float32
|
||||
)
|
||||
input_rope = quant_k_cache[:, dim_nope + num_tiles * 4 :].view(torch.bfloat16)
|
||||
|
||||
_dequantize_k_cache_fast_kernel[(num_tokens, num_blocks_per_token)](
|
||||
output,
|
||||
input_nope_q,
|
||||
input_nope_s,
|
||||
input_rope,
|
||||
output.stride(0),
|
||||
input_nope_q.stride(0),
|
||||
input_nope_s.stride(0),
|
||||
input_rope.stride(0),
|
||||
NUM_NOPE_BLOCKS=num_tiles,
|
||||
GROUP_SIZE=group_size,
|
||||
DIM_NOPE=dim_nope,
|
||||
DIM_ROPE=dim_rope,
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _dequantize_k_cache_fast_kernel(
|
||||
output_ptr,
|
||||
input_nope_q_ptr,
|
||||
input_nope_s_ptr,
|
||||
input_rope_ptr,
|
||||
output_stride_0: int,
|
||||
input_nope_q_stride_0: int,
|
||||
input_nope_s_stride_0: int,
|
||||
input_rope_stride_0: int,
|
||||
NUM_NOPE_BLOCKS: tl.constexpr,
|
||||
GROUP_SIZE: tl.constexpr,
|
||||
DIM_NOPE: tl.constexpr,
|
||||
DIM_ROPE: tl.constexpr,
|
||||
):
|
||||
token_id = tl.program_id(0)
|
||||
raw_block_id = tl.program_id(1)
|
||||
|
||||
if raw_block_id < NUM_NOPE_BLOCKS:
|
||||
# a. dequant nope
|
||||
effective_block_id = raw_block_id
|
||||
|
||||
offs_q = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs_q < DIM_NOPE
|
||||
ptr_q = input_nope_q_ptr + token_id * input_nope_q_stride_0 + offs_q
|
||||
ptr_s = input_nope_s_ptr + token_id * input_nope_s_stride_0 + effective_block_id
|
||||
|
||||
y_q = tl.load(ptr_q, mask=mask, other=0.0).to(tl.float32)
|
||||
y_s = tl.load(ptr_s)
|
||||
|
||||
y = (y_q * y_s).to(output_ptr.dtype.element_ty)
|
||||
|
||||
dst_ptr = output_ptr + token_id * output_stride_0 + offs_q
|
||||
tl.store(dst_ptr, y, mask=mask)
|
||||
else:
|
||||
# b. copy rope
|
||||
effective_block_id = raw_block_id - NUM_NOPE_BLOCKS
|
||||
|
||||
offs = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs < DIM_ROPE
|
||||
|
||||
src_ptr = input_rope_ptr + token_id * input_rope_stride_0 + offs
|
||||
dst_ptr = output_ptr + token_id * output_stride_0 + DIM_NOPE + offs
|
||||
|
||||
data = tl.load(src_ptr, mask=mask).to(tl.bfloat16)
|
||||
tl.store(dst_ptr, data, mask=mask)
|
||||
|
||||
|
||||
def dequantize_k_cache_paged(
|
||||
quant_k_cache: torch.Tensor,
|
||||
page_table_1_flattened: torch.Tensor,
|
||||
group_size: int = 128,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
De-quantize the k-cache with paged layout
|
||||
Args:
|
||||
quant_k_cache: [total_num_tokens, 1, dim_quant] or [num_blocks, block_size, 1, dim_quant], the quantized k-cache in paged layout
|
||||
page_table_1_flattened: [num_tokens], the flattened page_table_1 with the page indices in each requests concatenated together
|
||||
Returns:
|
||||
output: [num_tokens, 1, dim_nope + dim_rope], the de-quantized k-cache
|
||||
"""
|
||||
dim_quant = quant_k_cache.shape[-1]
|
||||
assert (
|
||||
dim_quant == 656
|
||||
), f"dim_quant: {dim_quant} != 656 detected in dequantize_k_cache_paged"
|
||||
quant_k_cache = quant_k_cache.view((-1, dim_quant))
|
||||
|
||||
# num_tokens can exceed kv_cache_size due to prefix sharing (multiple seqs share same KV slots)
|
||||
# Index bounds validated in nsa_backend.init_forward_metadata
|
||||
num_tokens = page_table_1_flattened.shape[0]
|
||||
assert quant_k_cache.dtype == torch.float8_e4m3fn
|
||||
dim_nope = 512
|
||||
dim_rope = 64
|
||||
num_tiles = dim_nope // group_size # 512 // 128 = 4
|
||||
|
||||
output = torch.empty(
|
||||
(num_tokens, 1, dim_nope + dim_rope),
|
||||
dtype=torch.bfloat16,
|
||||
device=quant_k_cache.device,
|
||||
)
|
||||
|
||||
# cdiv(512 + 64, 128) = 5
|
||||
num_blocks_per_token = triton.cdiv(dim_nope + dim_rope, group_size)
|
||||
assert num_blocks_per_token == 5
|
||||
|
||||
assert dim_nope % group_size == 0
|
||||
|
||||
input_nope_q = quant_k_cache[:, :dim_nope]
|
||||
# [:, 512:512+4*4] = [:, 512:528]
|
||||
input_nope_s = quant_k_cache[:, dim_nope : dim_nope + num_tiles * 4].view(
|
||||
torch.float32
|
||||
)
|
||||
# [:, 528:]
|
||||
input_rope = quant_k_cache[:, dim_nope + num_tiles * 4 :].view(torch.bfloat16)
|
||||
|
||||
_dequantize_k_cache_paged_kernel[(num_tokens, num_blocks_per_token)](
|
||||
output,
|
||||
input_nope_q,
|
||||
input_nope_s,
|
||||
input_rope,
|
||||
page_table_1_flattened,
|
||||
output.stride(0),
|
||||
input_nope_q.stride(0),
|
||||
input_nope_s.stride(0),
|
||||
input_rope.stride(0),
|
||||
NUM_NOPE_BLOCKS=num_tiles,
|
||||
GROUP_SIZE=group_size,
|
||||
DIM_NOPE=dim_nope,
|
||||
DIM_ROPE=dim_rope,
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _dequantize_k_cache_paged_kernel(
|
||||
output_ptr,
|
||||
input_nope_q_ptr,
|
||||
input_nope_s_ptr,
|
||||
input_rope_ptr,
|
||||
page_table_1_ptr,
|
||||
output_stride_0: int,
|
||||
input_nope_q_stride_0: int,
|
||||
input_nope_s_stride_0: int,
|
||||
input_rope_stride_0: int,
|
||||
NUM_NOPE_BLOCKS: tl.constexpr,
|
||||
GROUP_SIZE: tl.constexpr,
|
||||
DIM_NOPE: tl.constexpr,
|
||||
DIM_ROPE: tl.constexpr,
|
||||
):
|
||||
token_id = tl.program_id(0)
|
||||
token_id_paged = tl.load(page_table_1_ptr + token_id).to(tl.int32)
|
||||
raw_block_id = tl.program_id(1)
|
||||
|
||||
if raw_block_id < NUM_NOPE_BLOCKS:
|
||||
# a. dequant nope
|
||||
effective_block_id = raw_block_id
|
||||
|
||||
offs_q = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs_q < DIM_NOPE
|
||||
ptr_q = input_nope_q_ptr + token_id_paged * input_nope_q_stride_0 + offs_q
|
||||
ptr_s = (
|
||||
input_nope_s_ptr
|
||||
+ token_id_paged * input_nope_s_stride_0
|
||||
+ effective_block_id
|
||||
)
|
||||
|
||||
y_q = tl.load(ptr_q, mask=mask, other=0.0).to(tl.float32)
|
||||
y_s = tl.load(ptr_s)
|
||||
|
||||
y = (y_q * y_s).to(output_ptr.dtype.element_ty)
|
||||
|
||||
dst_ptr = output_ptr + token_id * output_stride_0 + offs_q
|
||||
tl.store(dst_ptr, y, mask=mask)
|
||||
else:
|
||||
# b. copy rope
|
||||
effective_block_id = raw_block_id - NUM_NOPE_BLOCKS
|
||||
|
||||
offs = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs < DIM_ROPE
|
||||
|
||||
src_ptr = input_rope_ptr + token_id_paged * input_rope_stride_0 + offs
|
||||
dst_ptr = output_ptr + token_id * output_stride_0 + DIM_NOPE + offs
|
||||
|
||||
data = tl.load(src_ptr, mask=mask).to(tl.bfloat16)
|
||||
tl.store(dst_ptr, data, mask=mask)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise Exception("UT is in quant_k_cache.py")
|
||||
warnings.warn(
|
||||
"sglang.srt.layers.attention.nsa.dequant_k_cache is deprecated; "
|
||||
"use sglang.srt.layers.attention.dsa.dequant_k_cache instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from sglang.srt.layers.attention.dsa.dequant_k_cache import * # noqa: F401, F403
|
||||
|
||||
@@ -1,814 +1,10 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.srt.layers.attention.nsa.utils import aiter_can_use_preshuffle_paged_mqa
|
||||
from sglang.srt.layers.quantization.fp8_kernel import is_fp8_fnuz
|
||||
from sglang.srt.utils import get_bool_env_var, is_hip
|
||||
|
||||
_is_hip = is_hip()
|
||||
_is_fp8_fnuz = is_fp8_fnuz()
|
||||
_use_aiter = get_bool_env_var("SGLANG_USE_AITER") and _is_hip
|
||||
# aiter cp_gather kernel with preshuffle=True is only valid when the indexer
|
||||
# uses the page_size=64 preshuffle layout (i.e. when the matching MQA gluon path
|
||||
# is also enabled).
|
||||
_use_aiter_preshuffle = aiter_can_use_preshuffle_paged_mqa()
|
||||
|
||||
if _use_aiter_preshuffle:
|
||||
from aiter.ops.cache import cp_gather_indexer_k_quant_cache
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.mem_cache.memory_pool import NSATokenToKVPool
|
||||
|
||||
"""
|
||||
k: data, 128 item per token, fp8
|
||||
s: scale, 1 item per token, fp32
|
||||
"""
|
||||
|
||||
|
||||
class GetK:
|
||||
@classmethod
|
||||
def execute(cls, *args, **kwargs):
|
||||
return cls.triton(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def slow(
|
||||
cls, pool: "NSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
num_pages = (seq_len + pool.page_size - 1) // pool.page_size
|
||||
seq_len_ = num_pages * pool.page_size
|
||||
index_k_fp8 = torch.empty(
|
||||
(seq_len_, pool.index_head_dim),
|
||||
dtype=torch.uint8,
|
||||
device=pool.device,
|
||||
)
|
||||
for i in range(num_pages):
|
||||
page_index = page_indices[i]
|
||||
index_k_fp8[i * pool.page_size : (i + 1) * pool.page_size] = buf[
|
||||
page_index
|
||||
][: pool.page_size * pool.index_head_dim].view(-1, pool.index_head_dim)
|
||||
|
||||
return index_k_fp8[:seq_len]
|
||||
|
||||
@classmethod
|
||||
def torch_fast(
|
||||
cls, pool: "NSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
"""
|
||||
:param page_indices: (num_pages,), int32
|
||||
:return: (seq_len, index_head_dim), uint8
|
||||
"""
|
||||
|
||||
# can handle per 128B instead of per element
|
||||
|
||||
# page_indices: (num_pages,), element := a page index
|
||||
buf_numel_per_page = buf.shape[1]
|
||||
|
||||
num_k_bytes_per_page = pool.page_size * pool.index_head_dim
|
||||
num_k_bytes_per_token = pool.index_head_dim
|
||||
|
||||
# buf: (num_pages, page_size 64 * head_dim 128 + page_size 64 * fp32_nbytes 4), uint8
|
||||
# flat_buf: (whatever,), uint8
|
||||
flat_buf = buf.flatten()
|
||||
|
||||
# flat_indices: (num_pages, num_k_bytes_per_page), int32, element := an index into flat_buf that we want to access
|
||||
flat_indices = (page_indices * buf_numel_per_page)[:, None] + torch.arange(
|
||||
num_k_bytes_per_page, dtype=torch.int32, device="cuda"
|
||||
)[None, :]
|
||||
flat_indices = flat_indices.flatten()[: seq_len * num_k_bytes_per_token]
|
||||
|
||||
out = flat_buf[flat_indices]
|
||||
return out.view(-1, 128)
|
||||
|
||||
@classmethod
|
||||
def triton(
|
||||
cls, pool: "NSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
"""
|
||||
Triton implementation for gathering K data from paged buffer.
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:return: (seq_len, index_head_dim), uint8
|
||||
"""
|
||||
return _get_k_triton(
|
||||
buf=buf,
|
||||
page_indices=page_indices,
|
||||
seq_len=seq_len,
|
||||
page_size=pool.page_size,
|
||||
index_head_dim=pool.index_head_dim,
|
||||
)
|
||||
|
||||
|
||||
class GetS:
|
||||
@classmethod
|
||||
def execute(cls, *args, **kwargs):
|
||||
return cls.triton(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def slow(
|
||||
cls, pool: "NSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
num_pages = (seq_len + pool.page_size - 1) // pool.page_size
|
||||
seq_len_ = num_pages * pool.page_size
|
||||
assert pool.index_head_dim // pool.quant_block_size == 1
|
||||
index_k_scale_fp8 = torch.empty(
|
||||
(seq_len_, 4),
|
||||
dtype=torch.uint8,
|
||||
device=pool.device,
|
||||
)
|
||||
for i in range(num_pages):
|
||||
page_index = page_indices[i]
|
||||
index_k_scale_fp8[i * pool.page_size : (i + 1) * pool.page_size] = buf[
|
||||
page_index
|
||||
][pool.page_size * pool.index_head_dim :].view(-1, 4)
|
||||
return index_k_scale_fp8[:seq_len]
|
||||
|
||||
@classmethod
|
||||
def torch_fast(
|
||||
cls, pool: "NSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
"""
|
||||
:param page_indices: (num_pages,), int32
|
||||
:return: (seq_len, index_head_dim // quant_block_size), uint8
|
||||
"""
|
||||
buf_numel_per_page = buf.shape[1]
|
||||
|
||||
num_s_bytes_per_page = buf.shape[1] - pool.page_size * pool.index_head_dim
|
||||
num_s_bytes_per_token = pool.index_head_dim // pool.quant_block_size * 4
|
||||
s_offset_in_page = pool.page_size * pool.index_head_dim
|
||||
|
||||
flat_buf = buf.flatten()
|
||||
flat_indices = (
|
||||
(page_indices * buf_numel_per_page)[:, None]
|
||||
+ torch.arange(num_s_bytes_per_page, dtype=torch.int32, device="cuda")[
|
||||
None, :
|
||||
]
|
||||
+ s_offset_in_page
|
||||
)
|
||||
flat_indices = flat_indices.flatten()[: seq_len * num_s_bytes_per_token]
|
||||
|
||||
out = flat_buf[flat_indices]
|
||||
return out.view(-1, 4)
|
||||
|
||||
@classmethod
|
||||
def triton(
|
||||
cls, pool: "NSATokenToKVPool", buf, seq_len: int, page_indices: torch.Tensor
|
||||
):
|
||||
"""
|
||||
Triton implementation for gathering S (scale) data from paged buffer.
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:return: (seq_len, 4), uint8
|
||||
"""
|
||||
return _get_s_triton(
|
||||
buf=buf,
|
||||
page_indices=page_indices,
|
||||
seq_len=seq_len,
|
||||
page_size=pool.page_size,
|
||||
index_head_dim=pool.index_head_dim,
|
||||
)
|
||||
|
||||
|
||||
class GetKAndS:
|
||||
@classmethod
|
||||
def execute(cls, *args, **kwargs):
|
||||
# The aiter path uses cp_gather_indexer_k_quant_cache(preshuffle=True),
|
||||
# which only matches the layout produced when the rest of the indexer
|
||||
# is on the page_size=64 preshuffle path. Otherwise fall back to the
|
||||
# triton implementation (which works on the page_size=1 legacy layout).
|
||||
if _use_aiter_preshuffle:
|
||||
return cls.aiter(*args, **kwargs)
|
||||
return cls.triton(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def aiter(
|
||||
cls,
|
||||
pool: "NSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_len_tensor: torch.Tensor,
|
||||
seq_len_sum: int,
|
||||
max_seq_len: int,
|
||||
):
|
||||
from sglang.srt.layers.quantization.fp8_kernel import fp8_dtype
|
||||
|
||||
page_size = pool.page_size
|
||||
index_head_dim = pool.index_head_dim
|
||||
quant_block_size = pool.quant_block_size
|
||||
scale_elems = index_head_dim // quant_block_size
|
||||
|
||||
kv_cache = buf.view(-1, page_size, index_head_dim + scale_elems * 4).view(
|
||||
fp8_dtype
|
||||
)
|
||||
dst_k = torch.empty(
|
||||
(seq_len_sum, index_head_dim), dtype=torch.uint8, device=buf.device
|
||||
)
|
||||
dst_scale = torch.empty(
|
||||
(seq_len_sum, scale_elems * 4), dtype=torch.uint8, device=buf.device
|
||||
)
|
||||
|
||||
cu_seq_lens = torch.zeros(
|
||||
seq_len_tensor.shape[0] + 1, dtype=torch.int32, device=buf.device
|
||||
)
|
||||
torch.cumsum(seq_len_tensor.to(torch.int32), dim=0, out=cu_seq_lens[1:])
|
||||
|
||||
cp_gather_indexer_k_quant_cache(
|
||||
kv_cache,
|
||||
dst_k.view(fp8_dtype),
|
||||
dst_scale,
|
||||
page_indices.to(torch.int32),
|
||||
cu_seq_lens,
|
||||
preshuffle=True,
|
||||
)
|
||||
return dst_k, dst_scale
|
||||
|
||||
@classmethod
|
||||
def triton(
|
||||
cls,
|
||||
pool: "NSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_len_tensor: torch.Tensor,
|
||||
seq_len_sum: int,
|
||||
max_seq_len: int,
|
||||
):
|
||||
"""
|
||||
Triton implementation for gathering both K and S data from paged buffer in a single call.
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:param seq_len_tensor: (num_pages,), int32/int64
|
||||
:param seq_len_sum: sum of all sequence len, int32
|
||||
:param max_seq_len: max of all sequence len, int32
|
||||
:return: tuple of (k_fp8, k_scale) where
|
||||
k_fp8: (seq_len, index_head_dim), uint8
|
||||
k_scale: (seq_len, 4), uint8
|
||||
"""
|
||||
return _get_k_and_s_triton(
|
||||
buf=buf,
|
||||
page_indices=page_indices,
|
||||
seq_lens=seq_len_tensor,
|
||||
seq_len_sum=seq_len_sum,
|
||||
max_seq_len=max_seq_len,
|
||||
page_size=pool.page_size,
|
||||
index_head_dim=pool.index_head_dim,
|
||||
)
|
||||
|
||||
|
||||
class SetK:
|
||||
@classmethod
|
||||
def execute(cls, *args, buf, **kwargs):
|
||||
return cls.torch_fast(*args, **kwargs, buf=buf)
|
||||
|
||||
@classmethod
|
||||
def slow(
|
||||
cls,
|
||||
pool: "NSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
loc: torch.Tensor,
|
||||
index_k: torch.Tensor,
|
||||
):
|
||||
for i in range(len(loc)):
|
||||
page_index = loc[i] // pool.page_size
|
||||
offset = loc[i] % pool.page_size
|
||||
buf[
|
||||
page_index,
|
||||
offset * pool.index_head_dim : (offset + 1) * pool.index_head_dim,
|
||||
] = index_k[i].view(torch.uint8)
|
||||
|
||||
@classmethod
|
||||
def torch_fast(
|
||||
cls,
|
||||
pool: "NSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
loc: torch.Tensor,
|
||||
index_k: torch.Tensor,
|
||||
):
|
||||
(num_tokens_to_write,) = loc.shape
|
||||
buf_numel_per_page = buf.shape[1]
|
||||
num_k_bytes_per_token = pool.index_head_dim
|
||||
|
||||
# loc: (num_tokens_to_write,), int32, element := the token index to write to
|
||||
loc_page_index = loc // pool.page_size
|
||||
loc_token_offset_in_page = loc % pool.page_size
|
||||
|
||||
flat_buf = buf.flatten()
|
||||
flat_indices = (
|
||||
(loc_page_index * buf_numel_per_page)[:, None]
|
||||
+ (loc_token_offset_in_page * num_k_bytes_per_token)[:, None]
|
||||
+ torch.arange(num_k_bytes_per_token, dtype=torch.int32, device="cuda")[
|
||||
None, :
|
||||
]
|
||||
)
|
||||
num_k_bytes_total = num_tokens_to_write * num_k_bytes_per_token
|
||||
flat_indices = flat_indices.flatten()[:num_k_bytes_total]
|
||||
flat_buf[flat_indices] = index_k.view(torch.uint8).flatten()
|
||||
|
||||
|
||||
class SetS:
|
||||
@classmethod
|
||||
def execute(cls, *args, buf, **kwargs):
|
||||
return cls.torch_fast(*args, **kwargs, buf=buf)
|
||||
|
||||
@classmethod
|
||||
def slow(
|
||||
cls,
|
||||
pool: "NSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
loc: torch.Tensor,
|
||||
index_k_scale: torch.Tensor,
|
||||
):
|
||||
for i in range(len(loc)):
|
||||
page_index = loc[i] // pool.page_size
|
||||
offset = loc[i] % pool.page_size
|
||||
start = pool.page_size * pool.index_head_dim
|
||||
buf[page_index, start + offset * 4 : start + (offset + 1) * 4] = (
|
||||
index_k_scale[i].view(torch.uint8)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def torch_fast(
|
||||
cls,
|
||||
pool: "NSATokenToKVPool",
|
||||
buf: torch.Tensor,
|
||||
loc: torch.Tensor,
|
||||
index_k_scale: torch.Tensor,
|
||||
):
|
||||
(num_tokens_to_write,) = loc.shape
|
||||
buf_numel_per_page = buf.shape[1]
|
||||
num_s_bytes_per_token = 4
|
||||
s_offset_in_page = pool.page_size * pool.index_head_dim
|
||||
|
||||
# loc: (num_tokens_to_write,), int32, element := the token index to write to
|
||||
loc_page_index = loc // pool.page_size
|
||||
loc_token_offset_in_page = loc % pool.page_size
|
||||
|
||||
flat_buf = buf.flatten()
|
||||
flat_indices = (
|
||||
(loc_page_index * buf_numel_per_page)[:, None]
|
||||
+ s_offset_in_page
|
||||
+ (loc_token_offset_in_page * num_s_bytes_per_token)[:, None]
|
||||
+ torch.arange(num_s_bytes_per_token, dtype=torch.int32, device="cuda")[
|
||||
None, :
|
||||
]
|
||||
)
|
||||
number_s_bytes_total = num_tokens_to_write * num_s_bytes_per_token
|
||||
flat_indices = flat_indices.flatten()[:number_s_bytes_total]
|
||||
flat_buf[flat_indices] = index_k_scale.view(torch.uint8).flatten()
|
||||
|
||||
|
||||
class SetKAndS:
|
||||
@classmethod
|
||||
def execute(cls, *args, buf, **kwargs):
|
||||
if 0:
|
||||
# print("SetK, SetS comparison test")
|
||||
buf_cloned = buf.clone()
|
||||
cls.vanilla(*args, **kwargs, buf=buf)
|
||||
cls.triton(*args, **kwargs, buf=buf_cloned)
|
||||
|
||||
def _clear_token_0(target):
|
||||
target[0, :128] = target[0, 64 * 128 : 64 * 128 + 4] = 0
|
||||
|
||||
_clear_token_0(buf)
|
||||
_clear_token_0(buf_cloned)
|
||||
|
||||
assert torch.all(
|
||||
buf == buf_cloned
|
||||
), f"{buf=} {buf_cloned=} {kwargs['loc'].to_list()=}"
|
||||
return
|
||||
|
||||
cls.triton(*args, **kwargs, buf=buf)
|
||||
|
||||
@classmethod
|
||||
def vanilla(cls, pool, buf, loc, index_k, index_k_scale):
|
||||
SetK.execute(pool=pool, buf=buf, loc=loc, index_k=index_k)
|
||||
SetS.execute(pool=pool, buf=buf, loc=loc, index_k_scale=index_k_scale)
|
||||
|
||||
@classmethod
|
||||
def triton(cls, pool, buf, loc, index_k, index_k_scale):
|
||||
loc = loc.to(torch.int64)
|
||||
|
||||
_set_k_and_s_triton(
|
||||
buf=buf,
|
||||
loc=loc,
|
||||
index_k=index_k,
|
||||
index_k_scale=index_k_scale,
|
||||
page_size=pool.page_size,
|
||||
)
|
||||
|
||||
|
||||
def _set_k_and_s_triton(
|
||||
buf: torch.Tensor,
|
||||
loc: torch.Tensor,
|
||||
index_k: torch.Tensor,
|
||||
index_k_scale: torch.Tensor,
|
||||
page_size: int,
|
||||
):
|
||||
"""
|
||||
:param buf: (num_pages, page_size 64 * (128B data + 4B scale)), uint8
|
||||
:param loc: (num_tokens_to_write,), int, element := the token index to write to
|
||||
:param index_k: (num_tokens_to_write, 128 elem), fp8
|
||||
:param index_k_scale: (num_tokens_to_write, 1 elem), fp32
|
||||
:return:
|
||||
"""
|
||||
num_pages, buf_numel_per_page = buf.shape
|
||||
(num_tokens_to_write,) = loc.shape
|
||||
num_tokens_to_write_, index_head_dim = index_k.shape
|
||||
|
||||
# Handle both 1D (num_tokens,) and 2D (num_tokens, 1) shapes for index_k_scale
|
||||
if index_k_scale.ndim == 1:
|
||||
num_tokens_to_write__ = index_k_scale.shape[0]
|
||||
scale_dim = 1
|
||||
elif index_k_scale.ndim == 2:
|
||||
num_tokens_to_write__, scale_dim = index_k_scale.shape
|
||||
else:
|
||||
raise ValueError(
|
||||
f"index_k_scale must be 1D or 2D, got shape {index_k_scale.shape}"
|
||||
)
|
||||
assert buf_numel_per_page == page_size * (128 + 4)
|
||||
assert num_tokens_to_write == num_tokens_to_write_ == num_tokens_to_write__
|
||||
assert index_head_dim == 128
|
||||
assert scale_dim == 1
|
||||
if _is_hip:
|
||||
if _use_aiter_preshuffle:
|
||||
assert (
|
||||
page_size % 16 == 0
|
||||
), f"HIP preshuffle requires page_size to be a multiple of 16, got {page_size}"
|
||||
else:
|
||||
assert page_size == 64
|
||||
|
||||
assert buf.dtype == torch.uint8
|
||||
assert loc.dtype == torch.int64, f"{loc.dtype=}" # can be int32
|
||||
if _is_fp8_fnuz:
|
||||
assert index_k.dtype == torch.float8_e4m3fnuz
|
||||
else:
|
||||
assert index_k.dtype == torch.float8_e4m3fn
|
||||
assert index_k_scale.dtype == torch.float32
|
||||
|
||||
assert buf.is_contiguous()
|
||||
assert loc.is_contiguous()
|
||||
assert index_k.is_contiguous()
|
||||
assert index_k_scale.is_contiguous()
|
||||
|
||||
if _is_fp8_fnuz:
|
||||
buf_fp8 = buf.view(torch.float8_e4m3fnuz)
|
||||
else:
|
||||
buf_fp8 = buf.view(torch.float8_e4m3fn)
|
||||
buf_fp32 = buf.view(torch.float32)
|
||||
|
||||
_set_k_and_s_triton_kernel[(num_tokens_to_write,)](
|
||||
buf_fp8,
|
||||
buf_fp32,
|
||||
loc,
|
||||
index_k,
|
||||
index_k_scale,
|
||||
index_k.stride(0),
|
||||
PAGE_SIZE=page_size,
|
||||
BUF_NUMEL_PER_PAGE=buf_numel_per_page,
|
||||
NUM_K_ELEMS_PER_TOKEN=index_head_dim,
|
||||
S_OFFSET_NBYTES_IN_PAGE=page_size * index_head_dim,
|
||||
)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _set_k_and_s_triton_kernel(
|
||||
buf_fp8_ptr,
|
||||
buf_fp32_ptr,
|
||||
loc_ptr,
|
||||
index_k_ptr,
|
||||
index_k_scale_ptr,
|
||||
index_k_ptr_stride_0,
|
||||
PAGE_SIZE: tl.constexpr,
|
||||
BUF_NUMEL_PER_PAGE: tl.constexpr,
|
||||
NUM_K_ELEMS_PER_TOKEN: tl.constexpr,
|
||||
S_OFFSET_NBYTES_IN_PAGE: tl.constexpr,
|
||||
):
|
||||
token_id = tl.program_id(0)
|
||||
|
||||
loc = tl.load(loc_ptr + token_id)
|
||||
|
||||
in_k_offsets = token_id * index_k_ptr_stride_0 + tl.arange(0, NUM_K_ELEMS_PER_TOKEN)
|
||||
|
||||
# no need for `mask`, since we read 128B for k and 4B for scale, both pow of 2
|
||||
k = tl.load(index_k_ptr + in_k_offsets)
|
||||
k_scale = tl.load(index_k_scale_ptr + token_id)
|
||||
|
||||
loc_page_index = loc // PAGE_SIZE
|
||||
loc_token_offset_in_page = loc % PAGE_SIZE
|
||||
|
||||
out_k_offsets = (
|
||||
loc_page_index * BUF_NUMEL_PER_PAGE
|
||||
+ loc_token_offset_in_page * NUM_K_ELEMS_PER_TOKEN
|
||||
+ tl.arange(0, NUM_K_ELEMS_PER_TOKEN)
|
||||
)
|
||||
|
||||
# "//4" b/c it is fp32 instead of uint8
|
||||
out_s_offset = (
|
||||
loc_page_index * BUF_NUMEL_PER_PAGE // 4
|
||||
+ S_OFFSET_NBYTES_IN_PAGE // 4
|
||||
+ loc_token_offset_in_page
|
||||
)
|
||||
|
||||
tl.store(buf_fp8_ptr + out_k_offsets, k)
|
||||
tl.store(buf_fp32_ptr + out_s_offset, k_scale)
|
||||
|
||||
|
||||
def _get_k_triton(
|
||||
buf: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_len: int,
|
||||
page_size: int,
|
||||
index_head_dim: int,
|
||||
):
|
||||
"""
|
||||
Gather K (key) data from paged buffer using Triton.
|
||||
|
||||
:param buf: (num_pages, page_size * 128 + page_size * 4), uint8
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:param seq_len: int, number of tokens to gather
|
||||
:param page_size: int, typically 64
|
||||
:param index_head_dim: int, typically 128
|
||||
:return: (seq_len, index_head_dim), uint8
|
||||
"""
|
||||
num_pages, buf_numel_per_page = buf.shape
|
||||
|
||||
# Allocate output
|
||||
out = torch.empty((seq_len, index_head_dim), dtype=torch.uint8, device=buf.device)
|
||||
|
||||
# Launch kernel with one thread per token
|
||||
grid = (seq_len,)
|
||||
_get_k_triton_kernel[grid](
|
||||
buf,
|
||||
page_indices,
|
||||
out,
|
||||
seq_len,
|
||||
page_size,
|
||||
buf_numel_per_page,
|
||||
index_head_dim,
|
||||
BLOCK_SIZE=128,
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _get_k_triton_kernel(
|
||||
buf_ptr,
|
||||
page_indices_ptr,
|
||||
out_ptr,
|
||||
seq_len: tl.constexpr,
|
||||
page_size: tl.constexpr,
|
||||
buf_numel_per_page: tl.constexpr,
|
||||
index_head_dim: tl.constexpr,
|
||||
BLOCK_SIZE: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Each program handles one token (seq_len tokens total).
|
||||
Loads 128 bytes from the appropriate page.
|
||||
"""
|
||||
token_id = tl.program_id(0)
|
||||
|
||||
# Calculate which page and offset within page
|
||||
page_idx = token_id // page_size
|
||||
token_offset_in_page = token_id % page_size
|
||||
|
||||
# Load the page index from page_indices
|
||||
page_index = tl.load(page_indices_ptr + page_idx)
|
||||
|
||||
# Calculate source offset in buf
|
||||
# buf[page_index, token_offset_in_page * index_head_dim : ...]
|
||||
src_base_offset = (
|
||||
page_index * buf_numel_per_page + token_offset_in_page * index_head_dim
|
||||
)
|
||||
|
||||
# Load 128 bytes (index_head_dim elements)
|
||||
offsets = tl.arange(0, BLOCK_SIZE)
|
||||
mask = offsets < index_head_dim
|
||||
data = tl.load(buf_ptr + src_base_offset + offsets, mask=mask)
|
||||
|
||||
# Store to output
|
||||
dst_offset = token_id * index_head_dim
|
||||
tl.store(out_ptr + dst_offset + offsets, data, mask=mask)
|
||||
|
||||
|
||||
def _get_s_triton(
|
||||
buf: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_len: int,
|
||||
page_size: int,
|
||||
index_head_dim: int,
|
||||
):
|
||||
"""
|
||||
Gather S (scale) data from paged buffer using Triton.
|
||||
|
||||
:param buf: (num_pages, page_size * 128 + page_size * 4), uint8
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:param seq_len: int, number of tokens to gather
|
||||
:param page_size: int, typically 64
|
||||
:param index_head_dim: int, typically 128
|
||||
:return: (seq_len, 4), uint8 (representing fp32 scale)
|
||||
"""
|
||||
num_pages, buf_numel_per_page = buf.shape
|
||||
s_offset_in_page = page_size * index_head_dim # Scales start after K data
|
||||
|
||||
# Allocate output
|
||||
out = torch.empty((seq_len, 4), dtype=torch.uint8, device=buf.device)
|
||||
|
||||
# Launch kernel with one thread per token
|
||||
grid = (seq_len,)
|
||||
_get_s_triton_kernel[grid](
|
||||
buf,
|
||||
page_indices,
|
||||
out,
|
||||
seq_len,
|
||||
page_size,
|
||||
buf_numel_per_page,
|
||||
s_offset_in_page,
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _get_s_triton_kernel(
|
||||
buf_ptr,
|
||||
page_indices_ptr,
|
||||
out_ptr,
|
||||
seq_len: tl.constexpr,
|
||||
page_size: tl.constexpr,
|
||||
buf_numel_per_page: tl.constexpr,
|
||||
s_offset_in_page: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Each program handles one token (seq_len tokens total).
|
||||
Loads 4 bytes (fp32 scale) from the appropriate page.
|
||||
"""
|
||||
token_id = tl.program_id(0)
|
||||
|
||||
# Calculate which page and offset within page
|
||||
page_idx = token_id // page_size
|
||||
token_offset_in_page = token_id % page_size
|
||||
|
||||
# Load the page index from page_indices
|
||||
page_index = tl.load(page_indices_ptr + page_idx)
|
||||
|
||||
# Calculate source offset in buf
|
||||
# Scales are stored after K data: page_size * index_head_dim offset
|
||||
# buf[page_index, s_offset_in_page + token_offset_in_page * 4 : ...]
|
||||
src_base_offset = (
|
||||
page_index * buf_numel_per_page + s_offset_in_page + token_offset_in_page * 4
|
||||
)
|
||||
|
||||
# Load 4 bytes (fp32 scale)
|
||||
offsets = tl.arange(0, 4)
|
||||
data = tl.load(buf_ptr + src_base_offset + offsets)
|
||||
|
||||
# Store to output
|
||||
dst_offset = token_id * 4
|
||||
tl.store(out_ptr + dst_offset + offsets, data)
|
||||
|
||||
|
||||
def _get_k_and_s_triton(
|
||||
buf: torch.Tensor,
|
||||
page_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
seq_len_sum: int,
|
||||
max_seq_len: int,
|
||||
page_size: int,
|
||||
index_head_dim: int,
|
||||
):
|
||||
"""
|
||||
Fused gather of both K (key) and S (scale) data from paged buffer using Triton.
|
||||
This is more efficient than calling GetK and GetS separately.
|
||||
|
||||
:param buf: (num_pages, page_size * 128 + page_size * 4), uint8
|
||||
:param page_indices: (num_pages,), int32/int64
|
||||
:param seq_lens: tensor of sequence lens, int64
|
||||
:param seq_len_sum: sum of all sequence len, int32
|
||||
:param max_seq_len: max of sequence len, int32
|
||||
:param page_size: int, typically 64
|
||||
:param index_head_dim: int, typically 128
|
||||
:return: tuple of (k_out, s_out) where
|
||||
k_out: (seq_len, index_head_dim), uint8
|
||||
s_out: (seq_len, 4), uint8
|
||||
"""
|
||||
# Allocate outputs
|
||||
k_out = torch.empty(
|
||||
(seq_len_sum, index_head_dim), dtype=torch.uint8, device=buf.device
|
||||
)
|
||||
s_out = torch.empty((seq_len_sum, 4), dtype=torch.uint8, device=buf.device)
|
||||
|
||||
_, buf_numel_per_page = buf.shape
|
||||
_, page_indice_batch_offset = page_indices.shape
|
||||
s_offset_in_page = page_size * index_head_dim
|
||||
|
||||
# Launch kernel with one thread per token
|
||||
BLOCK_SIZE = 256
|
||||
BLOCK_SIZE_K = 128
|
||||
|
||||
num_token_blocks = (max_seq_len + BLOCK_SIZE - 1) // BLOCK_SIZE
|
||||
num_k_threads = (index_head_dim + BLOCK_SIZE_K - 1) // BLOCK_SIZE_K
|
||||
|
||||
seq_num = seq_lens.shape[0]
|
||||
grid = (seq_num, num_token_blocks, num_k_threads)
|
||||
seq_num_pow2 = 1
|
||||
while seq_num_pow2 < seq_num:
|
||||
seq_num_pow2 *= 2
|
||||
|
||||
_get_k_and_s_triton_kernel[grid](
|
||||
buf_ptr=buf,
|
||||
page_indices_ptr=page_indices,
|
||||
k_out_ptr=k_out,
|
||||
s_out_ptr=s_out,
|
||||
seq_len_ptr=seq_lens,
|
||||
seq_len_num_pow=seq_num_pow2,
|
||||
page_size=page_size,
|
||||
buf_numel_per_page=buf_numel_per_page,
|
||||
index_head_dim=index_head_dim,
|
||||
s_offset_in_page=s_offset_in_page,
|
||||
page_indice_batch_offset=page_indice_batch_offset,
|
||||
BLOCK_SIZE=BLOCK_SIZE,
|
||||
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
||||
)
|
||||
|
||||
return k_out, s_out
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _get_k_and_s_triton_kernel(
|
||||
buf_ptr,
|
||||
page_indices_ptr,
|
||||
k_out_ptr,
|
||||
s_out_ptr,
|
||||
seq_len_ptr,
|
||||
seq_len_num_pow: tl.constexpr,
|
||||
page_size: tl.constexpr,
|
||||
buf_numel_per_page: tl.constexpr,
|
||||
index_head_dim: tl.constexpr,
|
||||
s_offset_in_page: tl.constexpr,
|
||||
page_indice_batch_offset,
|
||||
BLOCK_SIZE: tl.constexpr,
|
||||
BLOCK_SIZE_K: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Fused kernel that gathers both K and S data in a single pass.
|
||||
Each program handles one token (seq_len tokens total).
|
||||
Loads 128 bytes (K) + 4 bytes (S) from the appropriate page.
|
||||
"""
|
||||
batch_id = tl.program_id(0)
|
||||
block_token_start = tl.program_id(1) * BLOCK_SIZE
|
||||
thread_idx = tl.program_id(2)
|
||||
|
||||
# Define the token range within the block and the K dimension range handled by the thread.
|
||||
token_ids_in_block = tl.arange(0, BLOCK_SIZE)
|
||||
token_ids = block_token_start + token_ids_in_block
|
||||
k_offsets = thread_idx * BLOCK_SIZE_K + tl.arange(0, BLOCK_SIZE_K)
|
||||
|
||||
seq_len = tl.load(seq_len_ptr + batch_id)
|
||||
token_valid_mask = token_ids < seq_len
|
||||
|
||||
pre_batch_idx = tl.arange(0, seq_len_num_pow)
|
||||
mask_pre_batch_idx = pre_batch_idx < batch_id
|
||||
prev_seq_lens = tl.load(seq_len_ptr + pre_batch_idx, mask=mask_pre_batch_idx)
|
||||
batch_token_offset = tl.sum(prev_seq_lens)
|
||||
|
||||
# Batch calculate the page index and in-page offset of each token.
|
||||
page_idx = token_ids // page_size
|
||||
token_offset_in_page = token_ids % page_size
|
||||
page_indices_base = batch_id * page_indice_batch_offset
|
||||
page_idx_valid_mask = page_idx < page_indice_batch_offset
|
||||
page_index = tl.load(
|
||||
page_indices_ptr + page_idx + page_indices_base,
|
||||
mask=token_valid_mask & page_idx_valid_mask,
|
||||
)
|
||||
|
||||
# ===== Load K data =====
|
||||
# The address calculation logic for K: page_index * total number of elements in a single page + K offset of the token within the page.
|
||||
k_src_token_offset = token_offset_in_page * index_head_dim
|
||||
k_src_base_offset = page_index * buf_numel_per_page + k_src_token_offset
|
||||
|
||||
k_load_addr = buf_ptr + k_src_base_offset[:, None] + k_offsets[None, :]
|
||||
k_dim_mask = k_offsets[None, :] < index_head_dim
|
||||
k_mask = token_valid_mask[:, None] & k_dim_mask
|
||||
|
||||
k_data = tl.load(k_load_addr, mask=k_mask, other=0)
|
||||
|
||||
# Store K to output
|
||||
k_dst_token_offset = batch_token_offset + token_ids
|
||||
k_dst_base_offset = k_dst_token_offset * index_head_dim
|
||||
k_store_addr = k_out_ptr + k_dst_base_offset[:, None] + k_offsets[None, :]
|
||||
tl.store(k_store_addr, k_data, mask=k_mask)
|
||||
|
||||
# ===== Load S data =====
|
||||
# The address calculation logic for S: page_index * total number of elements in a single page + starting offset of S within the page + offset of token within S in the page
|
||||
s_src_token_offset = s_offset_in_page + token_offset_in_page * 4
|
||||
s_src_base_offset = page_index * buf_numel_per_page + s_src_token_offset
|
||||
|
||||
s_offsets = tl.arange(0, 4)
|
||||
s_load_addr = buf_ptr + s_src_base_offset[:, None] + s_offsets[None, :]
|
||||
s_mask = token_valid_mask[:, None] & (s_offsets[None, :] < 4)
|
||||
s_data = tl.load(s_load_addr, mask=s_mask, other=0)
|
||||
|
||||
# Store S to output
|
||||
s_dst_token_offset = batch_token_offset + token_ids
|
||||
s_dst_base_offset = s_dst_token_offset * 4
|
||||
s_store_addr = s_out_ptr + s_dst_base_offset[:, None] + s_offsets[None, :]
|
||||
tl.store(s_store_addr, s_data, mask=s_mask)
|
||||
# [Deprecated] Re-export shim for backward compatibility. Use dsa.index_buf_accessor instead.
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"sglang.srt.layers.attention.nsa.index_buf_accessor is deprecated; "
|
||||
"use sglang.srt.layers.attention.dsa.index_buf_accessor instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from sglang.srt.layers.attention.dsa.index_buf_accessor import * # noqa: F401, F403
|
||||
|
||||
@@ -1,325 +1,10 @@
|
||||
"""Multi-step precompute utilities for Native Sparse Attention backend.
|
||||
# [Deprecated] Re-export shim for backward compatibility. Use dsa.dsa_backend_mtp_precompute instead.
|
||||
import warnings
|
||||
|
||||
This module provides optimization utilities for multi-step speculative decoding
|
||||
by precomputing shared metadata once and copying it to multiple backend instances.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.layers.attention.nsa.utils import compute_nsa_seqlens
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardMode
|
||||
from sglang.srt.speculative.spec_info import SpecInput
|
||||
|
||||
|
||||
@dataclass
|
||||
class PrecomputedMetadata:
|
||||
"""Precomputed metadata shared across multiple backend instances.
|
||||
|
||||
Used for multi-step speculative decoding where multiple backends
|
||||
need identical metadata. Precomputing once and copying N times
|
||||
is much faster than computing N times.
|
||||
|
||||
"""
|
||||
|
||||
# Basic seqlens
|
||||
cache_seqlens: torch.Tensor # int32, [bs]
|
||||
cu_seqlens_k: torch.Tensor # int32, [bs+1]
|
||||
|
||||
# Page table
|
||||
page_indices: torch.Tensor # int32, [bs, max_len] or [expanded_bs, max_len]
|
||||
real_page_table: Optional[torch.Tensor] # int32, transformed version
|
||||
|
||||
# NSA seqlens
|
||||
seqlens_expanded: torch.Tensor # int32, [expanded_size]
|
||||
nsa_cache_seqlens: torch.Tensor # int32, [expanded_size]
|
||||
nsa_cu_seqlens_k: torch.Tensor # int32, [expanded_size+1]
|
||||
seqlens_expanded_size: int
|
||||
|
||||
# Dimensions
|
||||
max_len: int # for decode/draft_extend
|
||||
max_seqlen_k: int # for target_verify
|
||||
|
||||
# FlashMLA (optional)
|
||||
flashmla_metadata: Optional[torch.Tensor] = None
|
||||
|
||||
|
||||
def compute_cu_seqlens(seqlens: torch.Tensor) -> torch.Tensor:
|
||||
"""Compute cumulative sequence lengths with padding."""
|
||||
assert seqlens.dtype == torch.int32
|
||||
return torch.nn.functional.pad(
|
||||
torch.cumsum(seqlens, dim=0, dtype=torch.int32), (1, 0)
|
||||
)
|
||||
|
||||
|
||||
class NativeSparseAttnBackendMTPPrecomputeMixin:
|
||||
"""Mixin class providing metadata precomputation for multi-step speculative decoding.
|
||||
|
||||
This mixin provides the _precompute_replay_metadata method and its helpers,
|
||||
which are used to optimize CUDA graph replay in multi-step scenarios.
|
||||
"""
|
||||
|
||||
def _precompute_replay_metadata(
|
||||
self,
|
||||
bs: int,
|
||||
req_pool_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
seq_lens_cpu: torch.Tensor,
|
||||
forward_mode: "ForwardMode",
|
||||
spec_info: Optional["SpecInput"],
|
||||
) -> PrecomputedMetadata:
|
||||
"""Precompute all shared metadata for multi-step backends.
|
||||
|
||||
This function extracts and computes all operations that are
|
||||
identical across different backend instances in multi-step
|
||||
speculative decoding.
|
||||
|
||||
Args:
|
||||
bs: Batch size
|
||||
req_pool_indices: Request pool indices [bs]
|
||||
seq_lens: Sequence lengths [bs]
|
||||
seq_lens_cpu: Sequence lengths on CPU [bs]
|
||||
forward_mode: Forward mode (decode/target_verify/draft_extend)
|
||||
spec_info: Speculative decoding info (for draft_extend mode)
|
||||
|
||||
Returns:
|
||||
PrecomputedMetadata containing all shared intermediate results
|
||||
"""
|
||||
# Slice inputs to batch size
|
||||
seq_lens = seq_lens[:bs]
|
||||
seq_lens_cpu = seq_lens_cpu[:bs]
|
||||
req_pool_indices = req_pool_indices[:bs]
|
||||
|
||||
# Dispatch to mode-specific precomputation
|
||||
if forward_mode.is_decode_or_idle():
|
||||
return self._precompute_decode_mode(
|
||||
bs, req_pool_indices, seq_lens, seq_lens_cpu
|
||||
)
|
||||
elif forward_mode.is_target_verify():
|
||||
return self._precompute_target_verify_mode(
|
||||
bs, req_pool_indices, seq_lens, seq_lens_cpu
|
||||
)
|
||||
elif forward_mode.is_draft_extend():
|
||||
return self._precompute_draft_extend_mode(
|
||||
bs, req_pool_indices, seq_lens, seq_lens_cpu, spec_info
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported forward mode: {forward_mode}")
|
||||
|
||||
def _precompute_decode_mode(
|
||||
self,
|
||||
bs: int,
|
||||
req_pool_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
seq_lens_cpu: torch.Tensor,
|
||||
) -> PrecomputedMetadata:
|
||||
"""Precompute metadata for normal decode mode."""
|
||||
max_len = int(seq_lens_cpu.max().item())
|
||||
|
||||
# Convert to int32 and compute cumsum
|
||||
cache_seqlens = seq_lens.to(torch.int32)
|
||||
cu_seqlens_k = compute_cu_seqlens(cache_seqlens)
|
||||
|
||||
# Get page indices from cache
|
||||
page_indices = self.req_to_token[req_pool_indices, :max_len].contiguous()
|
||||
|
||||
# Compute NSA seqlens
|
||||
nsa_cache_seqlens = compute_nsa_seqlens(
|
||||
cache_seqlens, nsa_index_topk=self.nsa_index_topk
|
||||
)
|
||||
seqlens_expanded = cache_seqlens
|
||||
seqlens_expanded_size = seqlens_expanded.shape[0]
|
||||
|
||||
# Compute NSA cumsum
|
||||
nsa_cu_seqlens_k = compute_cu_seqlens(nsa_cache_seqlens)
|
||||
|
||||
# Transform page table if needed
|
||||
if self.real_page_size > 1:
|
||||
real_page_table = self._transform_table_1_to_real(page_indices)
|
||||
else:
|
||||
real_page_table = None # Will use page_indices directly
|
||||
|
||||
# Compute FlashMLA metadata if needed
|
||||
flashmla_metadata = None
|
||||
if self.nsa_decode_impl == "flashmla_kv":
|
||||
flashmla_metadata = self._compute_flashmla_metadata(
|
||||
cache_seqlens=nsa_cache_seqlens,
|
||||
seq_len_q=1,
|
||||
)
|
||||
|
||||
return PrecomputedMetadata(
|
||||
cache_seqlens=cache_seqlens,
|
||||
cu_seqlens_k=cu_seqlens_k,
|
||||
page_indices=page_indices,
|
||||
real_page_table=real_page_table,
|
||||
seqlens_expanded=seqlens_expanded,
|
||||
nsa_cache_seqlens=nsa_cache_seqlens,
|
||||
nsa_cu_seqlens_k=nsa_cu_seqlens_k,
|
||||
seqlens_expanded_size=seqlens_expanded_size,
|
||||
max_len=max_len,
|
||||
max_seqlen_k=max_len,
|
||||
flashmla_metadata=flashmla_metadata,
|
||||
)
|
||||
|
||||
def _precompute_target_verify_mode(
|
||||
self,
|
||||
bs: int,
|
||||
req_pool_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
seq_lens_cpu: torch.Tensor,
|
||||
) -> PrecomputedMetadata:
|
||||
"""Precompute metadata for target verify mode."""
|
||||
max_seqlen_k = int(
|
||||
seq_lens_cpu.max().item() + self.speculative_num_draft_tokens
|
||||
)
|
||||
|
||||
# Cache seqlens with draft tokens
|
||||
cache_seqlens = (seq_lens + self.speculative_num_draft_tokens).to(torch.int32)
|
||||
cu_seqlens_k = compute_cu_seqlens(cache_seqlens)
|
||||
|
||||
# Page indices (repeated for each draft token)
|
||||
page_indices = self.req_to_token[req_pool_indices, :max_seqlen_k]
|
||||
page_indices = torch.repeat_interleave(
|
||||
page_indices, repeats=self.speculative_num_draft_tokens, dim=0
|
||||
).contiguous()
|
||||
|
||||
# Generate expanded seqlens
|
||||
extend_seq_lens_cpu = [self.speculative_num_draft_tokens] * bs
|
||||
seqlens_int32_cpu = [
|
||||
self.speculative_num_draft_tokens + kv_len
|
||||
for kv_len in seq_lens_cpu.tolist()
|
||||
]
|
||||
seqlens_expanded = torch.cat(
|
||||
[
|
||||
torch.arange(
|
||||
kv_len - qo_len + 1,
|
||||
kv_len + 1,
|
||||
dtype=torch.int32,
|
||||
device=self.device,
|
||||
)
|
||||
for qo_len, kv_len in zip(
|
||||
extend_seq_lens_cpu,
|
||||
seqlens_int32_cpu,
|
||||
strict=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Compute NSA seqlens
|
||||
nsa_cache_seqlens = compute_nsa_seqlens(seqlens_expanded, self.nsa_index_topk)
|
||||
seqlens_expanded_size = seqlens_expanded.shape[0]
|
||||
|
||||
# NSA cumsum
|
||||
nsa_cu_seqlens_k = compute_cu_seqlens(nsa_cache_seqlens)
|
||||
|
||||
# Transform page table
|
||||
if self.real_page_size > 1:
|
||||
real_page_table = self._transform_table_1_to_real(page_indices)
|
||||
else:
|
||||
real_page_table = None
|
||||
|
||||
# FlashMLA metadata
|
||||
flashmla_metadata = None
|
||||
if self.nsa_decode_impl == "flashmla_kv":
|
||||
flashmla_metadata = self._compute_flashmla_metadata(
|
||||
cache_seqlens=nsa_cache_seqlens,
|
||||
seq_len_q=1,
|
||||
)
|
||||
|
||||
return PrecomputedMetadata(
|
||||
cache_seqlens=cache_seqlens,
|
||||
cu_seqlens_k=cu_seqlens_k,
|
||||
page_indices=page_indices,
|
||||
real_page_table=real_page_table,
|
||||
seqlens_expanded=seqlens_expanded,
|
||||
nsa_cache_seqlens=nsa_cache_seqlens,
|
||||
nsa_cu_seqlens_k=nsa_cu_seqlens_k,
|
||||
seqlens_expanded_size=seqlens_expanded_size,
|
||||
max_len=-1, # Not used in this mode
|
||||
max_seqlen_k=max_seqlen_k,
|
||||
flashmla_metadata=flashmla_metadata,
|
||||
)
|
||||
|
||||
def _precompute_draft_extend_mode(
|
||||
self,
|
||||
bs: int,
|
||||
req_pool_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
seq_lens_cpu: torch.Tensor,
|
||||
spec_info: "SpecInput",
|
||||
) -> PrecomputedMetadata:
|
||||
"""Precompute metadata for draft extend mode."""
|
||||
max_seqlen_k = int(seq_lens_cpu.max().item())
|
||||
|
||||
# Cache seqlens
|
||||
cache_seqlens = seq_lens.to(torch.int32)
|
||||
cu_seqlens_k = compute_cu_seqlens(cache_seqlens)
|
||||
|
||||
# Extend seqlens from spec_info: num_accept_tokens already includes
|
||||
# the bonus token (drafts + 1).
|
||||
extend_seq_lens = spec_info.num_accept_tokens[:bs]
|
||||
extend_seq_lens_cpu = extend_seq_lens.tolist()
|
||||
|
||||
# Page indices (repeated per accept length)
|
||||
page_indices = self.req_to_token[req_pool_indices, :max_seqlen_k]
|
||||
page_indices = torch.repeat_interleave(
|
||||
page_indices, repeats=extend_seq_lens, dim=0
|
||||
).contiguous()
|
||||
|
||||
# Generate expanded seqlens
|
||||
seqlens_expanded = torch.cat(
|
||||
[
|
||||
torch.arange(
|
||||
kv_len - qo_len + 1,
|
||||
kv_len + 1,
|
||||
dtype=torch.int32,
|
||||
device=self.device,
|
||||
)
|
||||
for qo_len, kv_len in zip(
|
||||
extend_seq_lens_cpu,
|
||||
seq_lens_cpu.tolist(),
|
||||
strict=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Compute NSA seqlens
|
||||
nsa_cache_seqlens = compute_nsa_seqlens(seqlens_expanded, self.nsa_index_topk)
|
||||
seqlens_expanded_size = seqlens_expanded.shape[0]
|
||||
|
||||
# NSA cumsum
|
||||
nsa_cu_seqlens_k = compute_cu_seqlens(nsa_cache_seqlens)
|
||||
|
||||
# Transform page table
|
||||
if self.real_page_size > 1:
|
||||
real_page_table = self._transform_table_1_to_real(page_indices)
|
||||
else:
|
||||
real_page_table = None
|
||||
|
||||
# FlashMLA metadata
|
||||
flashmla_metadata = None
|
||||
if self.nsa_decode_impl == "flashmla_kv":
|
||||
flashmla_metadata = self._compute_flashmla_metadata(
|
||||
cache_seqlens=nsa_cache_seqlens,
|
||||
seq_len_q=1,
|
||||
)
|
||||
|
||||
return PrecomputedMetadata(
|
||||
cache_seqlens=cache_seqlens,
|
||||
cu_seqlens_k=cu_seqlens_k,
|
||||
page_indices=page_indices,
|
||||
real_page_table=real_page_table,
|
||||
seqlens_expanded=seqlens_expanded,
|
||||
nsa_cache_seqlens=nsa_cache_seqlens,
|
||||
nsa_cu_seqlens_k=nsa_cu_seqlens_k,
|
||||
seqlens_expanded_size=seqlens_expanded_size,
|
||||
max_len=max_seqlen_k,
|
||||
max_seqlen_k=max_seqlen_k,
|
||||
flashmla_metadata=flashmla_metadata,
|
||||
)
|
||||
warnings.warn(
|
||||
"sglang.srt.layers.attention.nsa.nsa_backend_mtp_precompute is deprecated; "
|
||||
"use sglang.srt.layers.attention.dsa.dsa_backend_mtp_precompute instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from sglang.srt.layers.attention.dsa.dsa_backend_mtp_precompute import * # noqa: F401, F403
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,407 +1,10 @@
|
||||
"""
|
||||
Verification utilities for NSA backend fused metadata copy operations.
|
||||
# [Deprecated] Re-export shim for backward compatibility. Use dsa.dsa_mtp_verification instead.
|
||||
import warnings
|
||||
|
||||
This module contains verification code to ensure that fused metadata copy kernels
|
||||
produce the same results as individual copy operations.
|
||||
"""
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
def verify_single_backend_fused_metadata_copy(
|
||||
metadata,
|
||||
precomputed,
|
||||
forward_mode,
|
||||
bs,
|
||||
flashmla_num_splits_src=None,
|
||||
flashmla_metadata_src=None,
|
||||
flashmla_num_splits_dst=None,
|
||||
flashmla_metadata_dst=None,
|
||||
):
|
||||
"""
|
||||
Verify that the fused metadata copy kernel produces the same results as individual copies.
|
||||
|
||||
Args:
|
||||
metadata: The NSA metadata object containing destination tensors
|
||||
precomputed: The precomputed metadata containing source tensors
|
||||
forward_mode: The forward mode (decode, target_verify, or draft_extend)
|
||||
bs: Batch size
|
||||
flashmla_num_splits_src: Source FlashMLA num_splits tensor (optional)
|
||||
flashmla_metadata_src: Source FlashMLA metadata tensor (optional)
|
||||
flashmla_num_splits_dst: Destination FlashMLA num_splits tensor (optional)
|
||||
flashmla_metadata_dst: Destination FlashMLA metadata tensor (optional)
|
||||
|
||||
Raises:
|
||||
RuntimeError: If verification fails (tensors don't match)
|
||||
"""
|
||||
# Clone destination tensors to preserve fused kernel results
|
||||
fused_cache_seqlens = metadata.cache_seqlens_int32.clone()
|
||||
fused_cu_seqlens_k = metadata.cu_seqlens_k.clone()
|
||||
fused_page_table_1 = metadata.page_table_1.clone()
|
||||
fused_nsa_cache_seqlens = metadata.nsa_cache_seqlens_int32.clone()
|
||||
fused_nsa_seqlens_expanded = metadata.nsa_seqlens_expanded.clone()
|
||||
fused_nsa_cu_seqlens_k = metadata.nsa_cu_seqlens_k.clone()
|
||||
fused_real_page_table = (
|
||||
metadata.real_page_table.clone()
|
||||
if precomputed.real_page_table is not None
|
||||
else None
|
||||
)
|
||||
fused_flashmla_num_splits = None
|
||||
fused_flashmla_metadata = None
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
fused_flashmla_num_splits = flashmla_num_splits_dst.clone()
|
||||
fused_flashmla_metadata = flashmla_metadata_dst.clone()
|
||||
|
||||
# Create reference tensors (zeroed out)
|
||||
ref_cache_seqlens = torch.zeros_like(metadata.cache_seqlens_int32)
|
||||
ref_cu_seqlens_k = torch.zeros_like(metadata.cu_seqlens_k)
|
||||
ref_page_table_1 = torch.zeros_like(metadata.page_table_1)
|
||||
ref_nsa_cache_seqlens = torch.zeros_like(metadata.nsa_cache_seqlens_int32)
|
||||
ref_nsa_seqlens_expanded = torch.zeros_like(metadata.nsa_seqlens_expanded)
|
||||
ref_nsa_cu_seqlens_k = torch.zeros_like(metadata.nsa_cu_seqlens_k)
|
||||
ref_real_page_table = (
|
||||
torch.zeros_like(metadata.real_page_table)
|
||||
if precomputed.real_page_table is not None
|
||||
else None
|
||||
)
|
||||
ref_flashmla_num_splits = None
|
||||
ref_flashmla_metadata = None
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
ref_flashmla_num_splits = torch.zeros_like(flashmla_num_splits_dst)
|
||||
ref_flashmla_metadata = torch.zeros_like(flashmla_metadata_dst)
|
||||
|
||||
# Run individual copy operations (reference implementation)
|
||||
ref_cache_seqlens.copy_(precomputed.cache_seqlens)
|
||||
ref_cu_seqlens_k[1:].copy_(precomputed.cu_seqlens_k[1:])
|
||||
|
||||
if forward_mode.is_decode_or_idle():
|
||||
# Decode mode
|
||||
ref_page_table_1[:, : precomputed.max_len].copy_(precomputed.page_indices)
|
||||
ref_nsa_cache_seqlens.copy_(precomputed.nsa_cache_seqlens)
|
||||
elif forward_mode.is_target_verify():
|
||||
# Target verify mode
|
||||
ref_page_table_1[:, : precomputed.max_seqlen_k].copy_(precomputed.page_indices)
|
||||
ref_nsa_seqlens_expanded.copy_(precomputed.seqlens_expanded)
|
||||
ref_nsa_cache_seqlens.copy_(precomputed.nsa_cache_seqlens)
|
||||
elif forward_mode.is_draft_extend():
|
||||
# Draft extend mode
|
||||
rows = precomputed.page_indices.shape[0]
|
||||
cols = precomputed.max_seqlen_k
|
||||
ref_page_table_1[:rows, :cols].copy_(precomputed.page_indices)
|
||||
size = precomputed.seqlens_expanded_size
|
||||
ref_nsa_seqlens_expanded[:size].copy_(precomputed.seqlens_expanded)
|
||||
ref_nsa_cache_seqlens[:size].copy_(precomputed.nsa_cache_seqlens)
|
||||
|
||||
# Copy NSA cu_seqlens
|
||||
size = precomputed.seqlens_expanded_size
|
||||
ref_nsa_cu_seqlens_k[1 : 1 + size].copy_(precomputed.nsa_cu_seqlens_k[1 : 1 + size])
|
||||
|
||||
# Copy real page table
|
||||
if precomputed.real_page_table is not None:
|
||||
rows, cols = precomputed.real_page_table.shape
|
||||
ref_real_page_table[:rows, :cols].copy_(precomputed.real_page_table)
|
||||
|
||||
# Copy FlashMLA metadata
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
size = precomputed.seqlens_expanded_size
|
||||
ref_flashmla_num_splits[: size + 1].copy_(flashmla_num_splits_src[: size + 1])
|
||||
ref_flashmla_metadata.copy_(flashmla_metadata_src)
|
||||
|
||||
# Compare results and crash if inconsistent
|
||||
def check_tensor_equal(name, fused, ref):
|
||||
if not torch.equal(fused, ref):
|
||||
max_diff = (fused.float() - ref.float()).abs().max().item()
|
||||
mismatched_elements = (fused != ref).sum().item()
|
||||
total_elements = fused.numel()
|
||||
raise RuntimeError(
|
||||
f"FUSED METADATA COPY VERIFICATION FAILED!\n"
|
||||
f"Tensor: {name}\n"
|
||||
f"Max difference: {max_diff}\n"
|
||||
f"Mismatched elements: {mismatched_elements}/{total_elements}\n"
|
||||
f"Fused shape: {fused.shape}, Ref shape: {ref.shape}\n"
|
||||
f"Forward mode: {forward_mode}, bs={bs}\n"
|
||||
f"The fused kernel produces different results than individual copies.\n"
|
||||
f"This indicates a bug in the fused metadata copy kernel."
|
||||
)
|
||||
|
||||
# Verify all tensors (only compare the slices that were actually updated)
|
||||
check_tensor_equal("cache_seqlens", fused_cache_seqlens, ref_cache_seqlens)
|
||||
check_tensor_equal("cu_seqlens_k", fused_cu_seqlens_k, ref_cu_seqlens_k)
|
||||
|
||||
# Compare page_table_1 only for the region that was updated
|
||||
if forward_mode.is_decode_or_idle():
|
||||
check_tensor_equal(
|
||||
"page_table_1",
|
||||
fused_page_table_1[:, : precomputed.max_len],
|
||||
ref_page_table_1[:, : precomputed.max_len],
|
||||
)
|
||||
elif forward_mode.is_target_verify():
|
||||
check_tensor_equal(
|
||||
"page_table_1",
|
||||
fused_page_table_1[:, : precomputed.max_seqlen_k],
|
||||
ref_page_table_1[:, : precomputed.max_seqlen_k],
|
||||
)
|
||||
elif forward_mode.is_draft_extend():
|
||||
rows = precomputed.page_indices.shape[0]
|
||||
cols = precomputed.max_seqlen_k
|
||||
check_tensor_equal(
|
||||
"page_table_1",
|
||||
fused_page_table_1[:rows, :cols],
|
||||
ref_page_table_1[:rows, :cols],
|
||||
)
|
||||
|
||||
# Compare nsa_cache_seqlens only for the region that was updated
|
||||
if forward_mode.is_decode_or_idle():
|
||||
check_tensor_equal(
|
||||
"nsa_cache_seqlens",
|
||||
fused_nsa_cache_seqlens,
|
||||
ref_nsa_cache_seqlens,
|
||||
)
|
||||
else: # TARGET_VERIFY or DRAFT_EXTEND
|
||||
size = precomputed.seqlens_expanded_size
|
||||
check_tensor_equal(
|
||||
"nsa_cache_seqlens",
|
||||
fused_nsa_cache_seqlens[:size],
|
||||
ref_nsa_cache_seqlens[:size],
|
||||
)
|
||||
|
||||
# Compare nsa_seqlens_expanded only for TARGET_VERIFY and DRAFT_EXTEND
|
||||
if forward_mode.is_target_verify() or forward_mode.is_draft_extend():
|
||||
size = precomputed.seqlens_expanded_size
|
||||
check_tensor_equal(
|
||||
"nsa_seqlens_expanded",
|
||||
fused_nsa_seqlens_expanded[:size],
|
||||
ref_nsa_seqlens_expanded[:size],
|
||||
)
|
||||
|
||||
# Compare nsa_cu_seqlens_k only for the region that was updated
|
||||
size = precomputed.seqlens_expanded_size
|
||||
check_tensor_equal(
|
||||
"nsa_cu_seqlens_k",
|
||||
fused_nsa_cu_seqlens_k[: 1 + size],
|
||||
ref_nsa_cu_seqlens_k[: 1 + size],
|
||||
)
|
||||
|
||||
if precomputed.real_page_table is not None:
|
||||
rows, cols = precomputed.real_page_table.shape
|
||||
check_tensor_equal(
|
||||
"real_page_table",
|
||||
fused_real_page_table[:rows, :cols],
|
||||
ref_real_page_table[:rows, :cols],
|
||||
)
|
||||
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
size = precomputed.seqlens_expanded_size
|
||||
check_tensor_equal(
|
||||
"flashmla_num_splits",
|
||||
fused_flashmla_num_splits[: size + 1],
|
||||
ref_flashmla_num_splits[: size + 1],
|
||||
)
|
||||
check_tensor_equal(
|
||||
"flashmla_metadata",
|
||||
fused_flashmla_metadata,
|
||||
ref_flashmla_metadata,
|
||||
)
|
||||
|
||||
|
||||
def verify_multi_backend_fused_metadata_copy(
|
||||
metadata0,
|
||||
metadata1,
|
||||
metadata2,
|
||||
precomputed,
|
||||
bs,
|
||||
flashmla_num_splits_src=None,
|
||||
flashmla_metadata_src=None,
|
||||
):
|
||||
"""
|
||||
Verify that the multi-backend fused metadata copy kernel produces the same results
|
||||
as individual copies for all three backends.
|
||||
|
||||
Args:
|
||||
metadata0: The NSA metadata object for backend 0
|
||||
metadata1: The NSA metadata object for backend 1
|
||||
metadata2: The NSA metadata object for backend 2
|
||||
precomputed: The precomputed metadata containing source tensors
|
||||
bs: Batch size
|
||||
flashmla_num_splits_src: Source FlashMLA num_splits tensor (optional)
|
||||
flashmla_metadata_src: Source FlashMLA metadata tensor (optional)
|
||||
|
||||
Raises:
|
||||
RuntimeError: If verification fails (tensors don't match)
|
||||
"""
|
||||
# Clone destination tensors to preserve fused kernel results
|
||||
fused_results = []
|
||||
for idx, metadata in enumerate([metadata0, metadata1, metadata2]):
|
||||
fused_cache_seqlens = metadata.cache_seqlens_int32.clone()
|
||||
fused_cu_seqlens_k = metadata.cu_seqlens_k.clone()
|
||||
fused_page_table_1 = metadata.page_table_1.clone()
|
||||
fused_nsa_cache_seqlens = metadata.nsa_cache_seqlens_int32.clone()
|
||||
fused_nsa_cu_seqlens_k = metadata.nsa_cu_seqlens_k.clone()
|
||||
fused_real_page_table = (
|
||||
metadata.real_page_table.clone()
|
||||
if precomputed.real_page_table is not None
|
||||
else None
|
||||
)
|
||||
fused_flashmla_num_splits = None
|
||||
fused_flashmla_metadata = None
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
fused_flashmla_num_splits = metadata.flashmla_metadata.num_splits.clone()
|
||||
fused_flashmla_metadata = (
|
||||
metadata.flashmla_metadata.flashmla_metadata.clone()
|
||||
)
|
||||
|
||||
fused_results.append(
|
||||
{
|
||||
"cache_seqlens": fused_cache_seqlens,
|
||||
"cu_seqlens_k": fused_cu_seqlens_k,
|
||||
"page_table_1": fused_page_table_1,
|
||||
"nsa_cache_seqlens": fused_nsa_cache_seqlens,
|
||||
"nsa_cu_seqlens_k": fused_nsa_cu_seqlens_k,
|
||||
"real_page_table": fused_real_page_table,
|
||||
"flashmla_num_splits": fused_flashmla_num_splits,
|
||||
"flashmla_metadata": fused_flashmla_metadata,
|
||||
}
|
||||
)
|
||||
|
||||
# Run individual copy operations for each backend (reference implementation)
|
||||
ref_results = []
|
||||
for idx in range(3):
|
||||
metadata = [metadata0, metadata1, metadata2][idx]
|
||||
|
||||
# Create reference tensors (zeroed out)
|
||||
ref_cache_seqlens = torch.zeros_like(metadata.cache_seqlens_int32)
|
||||
ref_cu_seqlens_k = torch.zeros_like(metadata.cu_seqlens_k)
|
||||
ref_page_table_1 = torch.zeros_like(metadata.page_table_1)
|
||||
ref_nsa_cache_seqlens = torch.zeros_like(metadata.nsa_cache_seqlens_int32)
|
||||
ref_nsa_cu_seqlens_k = torch.zeros_like(metadata.nsa_cu_seqlens_k)
|
||||
ref_real_page_table = (
|
||||
torch.zeros_like(metadata.real_page_table)
|
||||
if precomputed.real_page_table is not None
|
||||
else None
|
||||
)
|
||||
ref_flashmla_num_splits = None
|
||||
ref_flashmla_metadata = None
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
ref_flashmla_num_splits = torch.zeros_like(
|
||||
metadata.flashmla_metadata.num_splits
|
||||
)
|
||||
ref_flashmla_metadata = torch.zeros_like(
|
||||
metadata.flashmla_metadata.flashmla_metadata
|
||||
)
|
||||
|
||||
# Copy operations (decode mode)
|
||||
ref_cache_seqlens.copy_(precomputed.cache_seqlens)
|
||||
ref_cu_seqlens_k[1:].copy_(precomputed.cu_seqlens_k[1:])
|
||||
ref_page_table_1[:, : precomputed.max_len].copy_(precomputed.page_indices)
|
||||
ref_nsa_cache_seqlens.copy_(precomputed.nsa_cache_seqlens)
|
||||
|
||||
# Copy NSA cu_seqlens
|
||||
size = precomputed.seqlens_expanded_size
|
||||
ref_nsa_cu_seqlens_k[1 : 1 + size].copy_(
|
||||
precomputed.nsa_cu_seqlens_k[1 : 1 + size]
|
||||
)
|
||||
|
||||
# Copy real page table
|
||||
if precomputed.real_page_table is not None:
|
||||
rows, cols = precomputed.real_page_table.shape
|
||||
ref_real_page_table[:rows, :cols].copy_(precomputed.real_page_table)
|
||||
|
||||
# Copy FlashMLA metadata
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
ref_flashmla_num_splits[: size + 1].copy_(
|
||||
flashmla_num_splits_src[: size + 1]
|
||||
)
|
||||
ref_flashmla_metadata.copy_(flashmla_metadata_src)
|
||||
|
||||
ref_results.append(
|
||||
{
|
||||
"cache_seqlens": ref_cache_seqlens,
|
||||
"cu_seqlens_k": ref_cu_seqlens_k,
|
||||
"page_table_1": ref_page_table_1,
|
||||
"nsa_cache_seqlens": ref_nsa_cache_seqlens,
|
||||
"nsa_cu_seqlens_k": ref_nsa_cu_seqlens_k,
|
||||
"real_page_table": ref_real_page_table,
|
||||
"flashmla_num_splits": ref_flashmla_num_splits,
|
||||
"flashmla_metadata": ref_flashmla_metadata,
|
||||
}
|
||||
)
|
||||
|
||||
# Compare results for all 3 backends
|
||||
def check_tensor_equal(backend_idx, name, fused, ref):
|
||||
if not torch.equal(fused, ref):
|
||||
max_diff = (fused.float() - ref.float()).abs().max().item()
|
||||
mismatched_elements = (fused != ref).sum().item()
|
||||
total_elements = fused.numel()
|
||||
raise RuntimeError(
|
||||
f"MULTI-BACKEND FUSED METADATA COPY VERIFICATION FAILED!\n"
|
||||
f"Backend: {backend_idx}\n"
|
||||
f"Tensor: {name}\n"
|
||||
f"Max difference: {max_diff}\n"
|
||||
f"Mismatched elements: {mismatched_elements}/{total_elements}\n"
|
||||
f"Fused shape: {fused.shape}, Ref shape: {ref.shape}\n"
|
||||
f"Batch size: {bs}\n"
|
||||
f"The multi-backend fused kernel produces different results than individual copies.\n"
|
||||
f"This indicates a bug in the fused metadata copy kernel."
|
||||
)
|
||||
|
||||
# Verify all tensors for all 3 backends (multi-backend is DECODE mode only)
|
||||
for idx in range(3):
|
||||
fused = fused_results[idx]
|
||||
ref = ref_results[idx]
|
||||
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"cache_seqlens",
|
||||
fused["cache_seqlens"],
|
||||
ref["cache_seqlens"],
|
||||
)
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"cu_seqlens_k",
|
||||
fused["cu_seqlens_k"],
|
||||
ref["cu_seqlens_k"],
|
||||
)
|
||||
# Multi-backend is DECODE mode only, so compare only [:, :max_len]
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"page_table_1",
|
||||
fused["page_table_1"][:, : precomputed.max_len],
|
||||
ref["page_table_1"][:, : precomputed.max_len],
|
||||
)
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"nsa_cache_seqlens",
|
||||
fused["nsa_cache_seqlens"],
|
||||
ref["nsa_cache_seqlens"],
|
||||
)
|
||||
# DECODE mode uses bs for nsa_cu_seqlens_k size
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"nsa_cu_seqlens_k",
|
||||
fused["nsa_cu_seqlens_k"][: bs + 1],
|
||||
ref["nsa_cu_seqlens_k"][: bs + 1],
|
||||
)
|
||||
|
||||
if precomputed.real_page_table is not None:
|
||||
rows, cols = precomputed.real_page_table.shape
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"real_page_table",
|
||||
fused["real_page_table"][:rows, :cols],
|
||||
ref["real_page_table"][:rows, :cols],
|
||||
)
|
||||
|
||||
if precomputed.flashmla_metadata is not None:
|
||||
# DECODE mode uses bs + 1 for flashmla_num_splits
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"flashmla_num_splits",
|
||||
fused["flashmla_num_splits"][: bs + 1],
|
||||
ref["flashmla_num_splits"][: bs + 1],
|
||||
)
|
||||
check_tensor_equal(
|
||||
idx,
|
||||
"flashmla_metadata",
|
||||
fused["flashmla_metadata"],
|
||||
ref["flashmla_metadata"],
|
||||
)
|
||||
warnings.warn(
|
||||
"sglang.srt.layers.attention.nsa.nsa_mtp_verification is deprecated; "
|
||||
"use sglang.srt.layers.attention.dsa.dsa_mtp_verification instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from sglang.srt.layers.attention.dsa.dsa_mtp_verification import * # noqa: F401, F403
|
||||
|
||||
@@ -1,449 +1,10 @@
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
|
||||
def quantize_k_cache(cache_k):
|
||||
return _quantize_k_cache_fast_wrapped(cache_k)
|
||||
|
||||
|
||||
def quantize_k_cache_separate(
|
||||
k_nope: torch.Tensor,
|
||||
k_rope: torch.Tensor,
|
||||
tile_size: int = 128,
|
||||
):
|
||||
"""
|
||||
Quantize k_nope and k_rope separately without concat, returns two tensors.
|
||||
|
||||
This avoids the concat operation and enables direct reuse of set_mla_kv_buffer_triton
|
||||
by returning two separate byte tensors for the nope and rope parts.
|
||||
|
||||
Args:
|
||||
k_nope: (num_tokens, dim_nope) or (num_tokens, 1, dim_nope)
|
||||
Must have dim_nope=512 for FP8 MLA quantization
|
||||
k_rope: (num_tokens, dim_rope) or (num_tokens, 1, dim_rope)
|
||||
Must have dim_rope=64 for FP8 MLA quantization
|
||||
tile_size: quantization tile size (default 128)
|
||||
|
||||
Returns:
|
||||
Tuple of (nope_part, rope_part) where:
|
||||
- nope_part: (num_tokens, 1, 528) as uint8 view, contains [nope_fp8(512) | scales(16)]
|
||||
- rope_part: (num_tokens, 1, 128) as uint8 view, contains [rope_bf16_bytes(128)]
|
||||
|
||||
These two tensors can be directly passed to set_mla_kv_buffer_triton(kv_buffer, loc, nope_part, rope_part)
|
||||
"""
|
||||
# Squeeze middle dimension if present
|
||||
k_nope_2d = k_nope.squeeze(1) if k_nope.ndim == 3 else k_nope
|
||||
k_rope_2d = k_rope.squeeze(1) if k_rope.ndim == 3 else k_rope
|
||||
|
||||
num_tokens = k_nope_2d.shape[0]
|
||||
dim_nope = k_nope_2d.shape[1]
|
||||
dim_rope = k_rope_2d.shape[1]
|
||||
|
||||
# Validate dimensions for FP8 MLA
|
||||
if dim_nope != 512:
|
||||
raise ValueError(f"Expected dim_nope=512 for FP8 MLA, got {dim_nope}")
|
||||
if dim_rope != 64:
|
||||
raise ValueError(f"Expected dim_rope=64 for FP8 MLA, got {dim_rope}")
|
||||
if k_rope_2d.shape[0] != num_tokens:
|
||||
raise ValueError(
|
||||
f"k_nope and k_rope must have same num_tokens, got {num_tokens} vs {k_rope_2d.shape[0]}"
|
||||
)
|
||||
|
||||
return _quantize_k_cache_fast_separate(
|
||||
k_nope=k_nope_2d, k_rope=k_rope_2d, group_size=tile_size
|
||||
)
|
||||
|
||||
|
||||
# Copied from original
|
||||
def _quantize_k_cache_ref(
|
||||
input_k_cache: torch.Tensor, # (num_blocks, block_size, h_k, d)
|
||||
dv: int = 512,
|
||||
tile_size: int = 128,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Quantize the k-cache
|
||||
Return a tensor with shape (num_blocks, block_size, h_k, dv + 4(dv/tile_size) + t(d-dv)) of dtype uint8_t, where t = input_k_cache.element_size()
|
||||
For more detail about the layout of K/V, please refer to comments in flash_mla_interface.py or README.md
|
||||
"""
|
||||
assert dv % tile_size == 0
|
||||
num_tiles = dv // tile_size
|
||||
num_blocks, block_size, h_k, d = input_k_cache.shape
|
||||
assert h_k == 1
|
||||
input_k_cache = input_k_cache.squeeze(2) # [num_blocks, block_size, d]
|
||||
input_elem_size = input_k_cache.element_size()
|
||||
|
||||
result = torch.empty(
|
||||
(num_blocks, block_size, dv + num_tiles * 4 + input_elem_size * (d - dv)),
|
||||
dtype=torch.float8_e4m3fn,
|
||||
device=input_k_cache.device,
|
||||
)
|
||||
result_k_nope_part = result[..., :dv]
|
||||
result_k_scale_factor = result[..., dv : dv + num_tiles * 4].view(torch.float32)
|
||||
result_k_rope_part = result[..., dv + num_tiles * 4 :].view(input_k_cache.dtype)
|
||||
result_k_rope_part[:] = input_k_cache[..., dv:]
|
||||
|
||||
for tile_idx in range(0, num_tiles):
|
||||
cur_scale_factors_inv = (
|
||||
torch.abs(
|
||||
input_k_cache[..., tile_idx * tile_size : (tile_idx + 1) * tile_size]
|
||||
)
|
||||
.max(dim=-1)
|
||||
.values
|
||||
/ 448.0
|
||||
) # [num_blocks, block_size]
|
||||
result_k_scale_factor[:, :, tile_idx] = cur_scale_factors_inv
|
||||
|
||||
cur_scale_factors_inv.unsqueeze_(-1) # [num_blocks, block_size, 1]
|
||||
cur_quantized_nope = (
|
||||
input_k_cache[
|
||||
..., tile_idx * tile_size : (tile_idx + 1) * tile_size
|
||||
].float()
|
||||
/ cur_scale_factors_inv.float()
|
||||
).to(torch.float8_e4m3fn)
|
||||
result_k_nope_part[..., tile_idx * tile_size : (tile_idx + 1) * tile_size] = (
|
||||
cur_quantized_nope
|
||||
)
|
||||
|
||||
result = result.view(num_blocks, block_size, 1, -1)
|
||||
return result
|
||||
|
||||
|
||||
def _quantize_k_cache_fast_wrapped(
|
||||
input_k_cache: torch.Tensor,
|
||||
dv: int = 512,
|
||||
tile_size: int = 128,
|
||||
) -> torch.Tensor:
|
||||
# TODO the final API may be 2D instead of 4D, thus we convert them here
|
||||
num_blocks, block_size, _, dim_nope_and_rope = input_k_cache.shape
|
||||
assert dv == 512
|
||||
assert dim_nope_and_rope == 512 + 64
|
||||
assert tile_size == 128
|
||||
input_k_cache = input_k_cache.view((-1, dim_nope_and_rope))
|
||||
|
||||
# TODO deliberately split into two tensors, then upstream can provide the two tensors instead of concat into one
|
||||
k_nope = input_k_cache[:, :dv]
|
||||
k_rope = input_k_cache[:, dv:]
|
||||
|
||||
output = _quantize_k_cache_fast(k_nope=k_nope, k_rope=k_rope)
|
||||
|
||||
return output.view(num_blocks, block_size, 1, -1)
|
||||
|
||||
|
||||
def _quantize_k_cache_fast(k_nope, k_rope, group_size: int = 128):
|
||||
"""
|
||||
:param k_nope: (num_tokens, dim_nope 512)
|
||||
:param k_rope: (num_tokens, dim_rope 64)
|
||||
"""
|
||||
|
||||
assert k_nope.dtype == torch.bfloat16
|
||||
assert k_rope.dtype == torch.bfloat16
|
||||
|
||||
num_tokens, dim_nope = k_nope.shape
|
||||
num_tokens_, dim_rope = k_rope.shape
|
||||
assert num_tokens == num_tokens_
|
||||
assert dim_nope == 512
|
||||
assert dim_rope == 64
|
||||
assert k_nope.dtype == k_rope.dtype
|
||||
num_tiles = dim_nope // group_size
|
||||
|
||||
assert k_nope.stride(1) == 1
|
||||
assert k_rope.stride(1) == 1
|
||||
|
||||
output = torch.empty(
|
||||
(num_tokens, dim_nope + num_tiles * 4 + k_rope.element_size() * dim_rope),
|
||||
dtype=torch.float8_e4m3fn,
|
||||
device=k_nope.device,
|
||||
)
|
||||
output_nope_q = output[..., :dim_nope]
|
||||
output_nope_s = output[..., dim_nope : dim_nope + num_tiles * 4].view(torch.float32)
|
||||
output_rope = output[..., dim_nope + num_tiles * 4 :].view(torch.bfloat16)
|
||||
|
||||
num_blocks_per_token = triton.cdiv(dim_nope + dim_rope, group_size)
|
||||
assert num_blocks_per_token == 5
|
||||
|
||||
assert dim_nope % group_size == 0
|
||||
NUM_NOPE_BLOCKS = dim_nope // group_size
|
||||
|
||||
_quantize_k_cache_fast_kernel[(num_tokens, num_blocks_per_token)](
|
||||
output_nope_q,
|
||||
output_nope_s,
|
||||
output_rope,
|
||||
k_nope,
|
||||
k_rope,
|
||||
output_nope_q.stride(0),
|
||||
output_nope_s.stride(0),
|
||||
output_rope.stride(0),
|
||||
k_nope.stride(0),
|
||||
k_rope.stride(0),
|
||||
NUM_NOPE_BLOCKS=NUM_NOPE_BLOCKS,
|
||||
GROUP_SIZE=group_size,
|
||||
DIM_NOPE=dim_nope,
|
||||
DIM_ROPE=dim_rope,
|
||||
FP8_MIN=torch.finfo(torch.float8_e4m3fn).min,
|
||||
FP8_MAX=torch.finfo(torch.float8_e4m3fn).max,
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def _quantize_k_cache_fast_separate(k_nope, k_rope, group_size: int = 128):
|
||||
"""
|
||||
Quantize k_nope and k_rope in a single Triton kernel, directly outputting two separate tensors.
|
||||
|
||||
This avoids packing/unpacking and enables direct use with set_mla_kv_buffer_triton.
|
||||
|
||||
:param k_nope: (num_tokens, dim_nope 512) bfloat16
|
||||
:param k_rope: (num_tokens, dim_rope 64) bfloat16
|
||||
:param group_size: quantization tile size (default 128, kernel is tuned for this value)
|
||||
:return: Tuple of (nope_part_u8, rope_part_u8)
|
||||
- nope_part_u8: (num_tokens, 1, nope_part_bytes) uint8, layout [nope_fp8(dim_nope) | scales(num_tiles*4)]
|
||||
- rope_part_u8: (num_tokens, 1, rope_part_bytes) uint8, layout [rope_bf16_bytes(dim_rope*2)]
|
||||
"""
|
||||
num_tokens, dim_nope = k_nope.shape
|
||||
num_tokens_, dim_rope = k_rope.shape
|
||||
|
||||
assert num_tokens == num_tokens_, f"k_nope and k_rope must have same num_tokens"
|
||||
|
||||
# Ensure contiguous tensors for kernel
|
||||
k_nope = k_nope.contiguous()
|
||||
k_rope = k_rope.contiguous()
|
||||
|
||||
num_tiles = dim_nope // group_size
|
||||
|
||||
# Calculate byte sizes based on validated dimensions
|
||||
# nope_part: [FP8 quantized data (dim_nope bytes)] + [FP32 scales (num_tiles * 4 bytes)]
|
||||
# rope_part: [BF16 raw data (dim_rope * 2 bytes)]
|
||||
nope_part_bytes = (
|
||||
dim_nope + num_tiles * 4
|
||||
) # e.g., 512 + 4*4 = 528 for dim_nope=512, group_size=128
|
||||
rope_part_bytes = (
|
||||
dim_rope * k_rope.element_size()
|
||||
) # e.g., 64 * 2 = 128 for dim_rope=64, BF16
|
||||
|
||||
# Allocate two separate output buffers (as uint8 for direct byte-level access)
|
||||
nope_part_u8 = torch.empty(
|
||||
(num_tokens, nope_part_bytes), dtype=torch.uint8, device=k_nope.device
|
||||
)
|
||||
rope_part_u8 = torch.empty(
|
||||
(num_tokens, rope_part_bytes), dtype=torch.uint8, device=k_rope.device
|
||||
)
|
||||
|
||||
# Create typed views for the kernel to write into
|
||||
# Fixed byte layout for nope_part: [nope_fp8 (dim_nope bytes) | scales_fp32 (num_tiles*4 bytes)]
|
||||
# Fixed byte layout for rope_part: [rope_bf16 (dim_rope*2 bytes)]
|
||||
nope_q_view = nope_part_u8[:, :dim_nope].view(torch.float8_e4m3fn)
|
||||
nope_s_view = nope_part_u8[:, dim_nope:].view(torch.float32)
|
||||
rope_view = rope_part_u8.view(torch.bfloat16)
|
||||
|
||||
# Kernel launch parameters
|
||||
num_blocks_per_token = triton.cdiv(dim_nope + dim_rope, group_size)
|
||||
NUM_NOPE_BLOCKS = dim_nope // group_size
|
||||
|
||||
# Use the same kernel as _quantize_k_cache_fast (reuse existing implementation)
|
||||
_quantize_k_cache_fast_kernel[(num_tokens, num_blocks_per_token)](
|
||||
nope_q_view,
|
||||
nope_s_view,
|
||||
rope_view,
|
||||
k_nope,
|
||||
k_rope,
|
||||
nope_q_view.stride(0),
|
||||
nope_s_view.stride(0),
|
||||
rope_view.stride(0),
|
||||
k_nope.stride(0),
|
||||
k_rope.stride(0),
|
||||
NUM_NOPE_BLOCKS=NUM_NOPE_BLOCKS,
|
||||
GROUP_SIZE=group_size,
|
||||
DIM_NOPE=dim_nope,
|
||||
DIM_ROPE=dim_rope,
|
||||
FP8_MIN=torch.finfo(torch.float8_e4m3fn).min,
|
||||
FP8_MAX=torch.finfo(torch.float8_e4m3fn).max,
|
||||
)
|
||||
|
||||
# Add middle dimension for compatibility with set_mla_kv_buffer_triton
|
||||
return nope_part_u8.unsqueeze(1), rope_part_u8.unsqueeze(1)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _quantize_k_cache_fast_kernel(
|
||||
output_nope_q_ptr,
|
||||
output_nope_s_ptr,
|
||||
output_rope_ptr,
|
||||
k_nope_ptr,
|
||||
k_rope_ptr,
|
||||
output_nope_q_stride_0: int,
|
||||
output_nope_s_stride_0: int,
|
||||
output_rope_stride_0: int,
|
||||
k_nope_stride_0: int,
|
||||
k_rope_stride_0: int,
|
||||
NUM_NOPE_BLOCKS: tl.constexpr,
|
||||
GROUP_SIZE: tl.constexpr,
|
||||
DIM_NOPE: tl.constexpr,
|
||||
DIM_ROPE: tl.constexpr,
|
||||
FP8_MIN: tl.constexpr,
|
||||
FP8_MAX: tl.constexpr,
|
||||
):
|
||||
token_id = tl.program_id(0)
|
||||
raw_block_id = tl.program_id(1)
|
||||
|
||||
if raw_block_id < NUM_NOPE_BLOCKS:
|
||||
# a. quant nope
|
||||
effective_block_id = raw_block_id
|
||||
|
||||
offs = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs < DIM_NOPE
|
||||
ptr = k_nope_ptr + token_id * k_nope_stride_0 + offs
|
||||
|
||||
y = tl.load(ptr, mask=mask, other=0.0).to(tl.float32)
|
||||
|
||||
# the ref impl do not have a `tl.maximum(... eps)`, so we remove it here
|
||||
y_s = tl.max(tl.abs(y)) / FP8_MAX
|
||||
y_s_inv = 1.0 / y_s
|
||||
y_q = tl.clamp(y * y_s_inv, FP8_MIN, FP8_MAX).to(
|
||||
output_nope_q_ptr.dtype.element_ty
|
||||
)
|
||||
|
||||
dst_q_ptr = output_nope_q_ptr + token_id * output_nope_q_stride_0 + offs
|
||||
dst_s_ptr = (
|
||||
output_nope_s_ptr + token_id * output_nope_s_stride_0 + effective_block_id
|
||||
)
|
||||
|
||||
tl.store(dst_q_ptr, y_q, mask=mask)
|
||||
tl.store(dst_s_ptr, y_s)
|
||||
else:
|
||||
# b. copy rope
|
||||
effective_block_id = raw_block_id - NUM_NOPE_BLOCKS
|
||||
|
||||
offs = effective_block_id * GROUP_SIZE + tl.arange(0, GROUP_SIZE)
|
||||
mask = offs < DIM_ROPE
|
||||
|
||||
src_ptr = k_rope_ptr + token_id * k_rope_stride_0 + offs
|
||||
dst_ptr = output_rope_ptr + token_id * output_rope_stride_0 + offs
|
||||
|
||||
data = tl.load(src_ptr, mask=mask)
|
||||
tl.store(dst_ptr, data, mask=mask)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import dequant_k_cache
|
||||
|
||||
for num_blocks, block_size in [
|
||||
(1, 1),
|
||||
(10, 64),
|
||||
]:
|
||||
dim_nope_and_rope = 512 + 64
|
||||
|
||||
input_k_cache = torch.randn(
|
||||
(num_blocks, block_size, 1, dim_nope_and_rope),
|
||||
dtype=torch.bfloat16,
|
||||
device="cuda",
|
||||
)
|
||||
|
||||
ref_quant = _quantize_k_cache_ref(input_k_cache)
|
||||
actual_quant = _quantize_k_cache_fast_wrapped(input_k_cache)
|
||||
|
||||
ref_ref_dequant = dequant_k_cache._dequantize_k_cache_slow(ref_quant)
|
||||
ref_actual_dequant = dequant_k_cache._dequantize_k_cache_fast_wrapped(ref_quant)
|
||||
actual_actual_dequant = dequant_k_cache._dequantize_k_cache_fast_wrapped(
|
||||
actual_quant
|
||||
)
|
||||
|
||||
print(f"{ref_ref_dequant=}")
|
||||
print(f"{actual_actual_dequant=}")
|
||||
print(f"{actual_actual_dequant - ref_ref_dequant=}")
|
||||
print(f"{torch.mean(ref_ref_dequant - actual_actual_dequant)=}")
|
||||
|
||||
# TODO too different?
|
||||
torch.testing.assert_close(
|
||||
ref_ref_dequant, ref_actual_dequant, atol=0.2, rtol=0.2
|
||||
)
|
||||
torch.testing.assert_close(
|
||||
ref_ref_dequant, actual_actual_dequant, atol=0.2, rtol=0.2
|
||||
)
|
||||
|
||||
# test dequant_k_cache_paged
|
||||
page_table_1 = torch.arange(
|
||||
num_blocks * block_size, dtype=torch.int32, device="cuda"
|
||||
)
|
||||
actual_dequant_paged = dequant_k_cache.dequantize_k_cache_paged(
|
||||
actual_quant, page_table_1
|
||||
).reshape(actual_actual_dequant.shape)
|
||||
print(f"{torch.mean(actual_actual_dequant - actual_dequant_paged)=}")
|
||||
torch.testing.assert_close(
|
||||
ref_ref_dequant, actual_dequant_paged, atol=0.2, rtol=0.2
|
||||
)
|
||||
|
||||
print("Passed")
|
||||
|
||||
# Test quantize_k_cache_separate: verify output matches concat path
|
||||
print("\nTesting quantize_k_cache_separate...")
|
||||
for num_tokens in [64, 100]:
|
||||
dim_nope = 512
|
||||
dim_rope = 64
|
||||
|
||||
k_nope = torch.randn(
|
||||
num_tokens, 1, dim_nope, dtype=torch.bfloat16, device="cuda"
|
||||
)
|
||||
k_rope = torch.randn(
|
||||
num_tokens, 1, dim_rope, dtype=torch.bfloat16, device="cuda"
|
||||
)
|
||||
|
||||
# Old path: concat then quantize
|
||||
k_concat = torch.cat([k_nope, k_rope], dim=-1).squeeze(1) # (num_tokens, 576)
|
||||
old_output = quantize_k_cache(k_concat.unsqueeze(1).unsqueeze(1)) # 4D input
|
||||
old_output = old_output.squeeze(1).squeeze(1) # Back to (num_tokens, 656)
|
||||
|
||||
# New path: quantize separately
|
||||
nope_part, rope_part = quantize_k_cache_separate(k_nope, k_rope)
|
||||
new_bytes = torch.cat([nope_part.squeeze(1), rope_part.squeeze(1)], dim=-1)
|
||||
|
||||
# Compare byte-level equality
|
||||
old_bytes = old_output.view(torch.uint8)
|
||||
|
||||
if old_bytes.shape != new_bytes.shape:
|
||||
raise RuntimeError(
|
||||
f"Shape mismatch: {old_bytes.shape} vs {new_bytes.shape}"
|
||||
)
|
||||
|
||||
diff_bytes = (old_bytes != new_bytes).sum().item()
|
||||
if diff_bytes > 0:
|
||||
max_diff = (old_bytes.float() - new_bytes.float()).abs().max().item()
|
||||
raise RuntimeError(
|
||||
f"quantize_k_cache_separate output doesn't match concat path: "
|
||||
f"{diff_bytes} differing bytes, max_diff={max_diff}"
|
||||
)
|
||||
|
||||
print(f" num_tokens={num_tokens}: PASSED (outputs match byte-wise)")
|
||||
|
||||
print("quantize_k_cache_separate tests passed!")
|
||||
|
||||
print("\nDo benchmark...")
|
||||
|
||||
for num_blocks, block_size in [
|
||||
(1, 64),
|
||||
(64, 64),
|
||||
(128, 64),
|
||||
(256, 64),
|
||||
(512, 64),
|
||||
(1024, 64),
|
||||
(2048, 64),
|
||||
]:
|
||||
dim_nope_and_rope = 512 + 64
|
||||
|
||||
input_k_cache = torch.randn(
|
||||
(num_blocks, block_size, 1, dim_nope_and_rope),
|
||||
dtype=torch.bfloat16,
|
||||
device="cuda",
|
||||
)
|
||||
|
||||
actual_quant = _quantize_k_cache_fast_wrapped(input_k_cache)
|
||||
|
||||
page_table_1 = torch.arange(
|
||||
num_blocks * block_size, dtype=torch.int32, device="cuda"
|
||||
)
|
||||
|
||||
def run_ans():
|
||||
return dequant_k_cache.dequantize_k_cache_paged(actual_quant, page_table_1)
|
||||
|
||||
ans_time: float = triton.testing.do_bench(run_ans, warmup=10, rep=20) / 1000 # type: ignore
|
||||
print(f"seq_kv: {num_blocks * block_size}, time: {ans_time * 1e6: 4.0f} us")
|
||||
# [Deprecated] Re-export shim for backward compatibility. Use dsa.quant_k_cache instead.
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"sglang.srt.layers.attention.nsa.quant_k_cache is deprecated; "
|
||||
"use sglang.srt.layers.attention.dsa.quant_k_cache instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from sglang.srt.layers.attention.dsa.quant_k_cache import * # noqa: F401, F403
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,144 +1,10 @@
|
||||
from typing import List, Optional
|
||||
# [Deprecated] Re-export shim for backward compatibility. Use dsa.transform_index instead.
|
||||
import warnings
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
|
||||
def transform_index_page_table_prefill(**kwargs):
|
||||
return transform_index_page_table_prefill_ref(**kwargs)
|
||||
|
||||
|
||||
def transform_index_page_table_decode(**kwargs):
|
||||
return transform_index_page_table_decode_ref(**kwargs)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def transform_index_page_table_decode_kernel(
|
||||
page_table_ptr: torch.Tensor,
|
||||
topk_indices_ptr: torch.Tensor,
|
||||
result_ptr: torch.Tensor,
|
||||
page_size: tl.constexpr,
|
||||
max_seqlen_k: tl.constexpr,
|
||||
):
|
||||
TOPK: tl.constexpr = 2048
|
||||
req_id = tl.program_id(0)
|
||||
page_table_ptr = page_table_ptr + req_id * max_seqlen_k
|
||||
topk_indices_ptr = topk_indices_ptr + req_id * TOPK
|
||||
result_ptr = result_ptr + req_id * TOPK
|
||||
|
||||
offset = tl.arange(0, TOPK) # topk should be 2048
|
||||
loaded_topk_indices = tl.load(topk_indices_ptr + offset)
|
||||
mask = loaded_topk_indices >= 0
|
||||
loaded_kv_indices = tl.load(page_table_ptr + loaded_topk_indices, mask=mask)
|
||||
tl.store(result_ptr + offset, loaded_kv_indices, mask=mask)
|
||||
tl.store(result_ptr + offset, -1, mask=~mask)
|
||||
|
||||
|
||||
def transform_index_page_table_decode_fast(
|
||||
page_table: torch.Tensor,
|
||||
topk_indices: torch.Tensor,
|
||||
result: Optional[torch.Tensor] = None,
|
||||
page_size: int = 1,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Transform the page table according to topk indices for sparse topk attention.
|
||||
Args:
|
||||
page_table: [qo_len, max_seqlen_k], the original page table
|
||||
topk_indices: [qo_len, topk], the topk indices for each query position
|
||||
Returns:
|
||||
transformed_page_table: [qo_len, topk], the transformed page table
|
||||
For out-of-bound indices in topk_indices, this should be filled with -1.
|
||||
"""
|
||||
assert page_size == 1
|
||||
assert page_table.shape[0] == topk_indices.shape[0]
|
||||
assert topk_indices.shape[1] == 2048
|
||||
qo_len = topk_indices.shape[0]
|
||||
max_seqlen_k = page_table.shape[1]
|
||||
if result is None:
|
||||
result = torch.empty_like(topk_indices, dtype=torch.int32)
|
||||
# Launch triton kernel
|
||||
grid = (qo_len,)
|
||||
transform_index_page_table_decode_kernel[grid](
|
||||
page_table,
|
||||
topk_indices,
|
||||
result,
|
||||
page_size,
|
||||
max_seqlen_k=max_seqlen_k,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def transform_index_page_table_prefill_fast(
|
||||
page_table: torch.Tensor,
|
||||
topk_indices: torch.Tensor,
|
||||
extend_lens_cpu: List[int],
|
||||
page_size: int = 1,
|
||||
) -> torch.Tensor:
|
||||
# TODO(baizhou): can be implemented with another triton kernel
|
||||
assert page_size == 1
|
||||
result = torch.empty_like(topk_indices, dtype=torch.int32)
|
||||
assert len(extend_lens_cpu) == page_table.shape[0]
|
||||
offset = 0
|
||||
for i, l in enumerate(extend_lens_cpu):
|
||||
transform_index_page_table_decode_fast(
|
||||
page_table[i].unsqueeze(0).expand(l, -1),
|
||||
topk_indices[offset : offset + l],
|
||||
result=result[offset : offset + l],
|
||||
)
|
||||
offset += l
|
||||
assert offset == topk_indices.shape[0]
|
||||
return result
|
||||
|
||||
|
||||
def transform_index_page_table_decode_ref(
|
||||
page_table: torch.Tensor,
|
||||
topk_indices: torch.Tensor,
|
||||
result: Optional[torch.Tensor] = None,
|
||||
page_size: int = 1,
|
||||
) -> torch.Tensor:
|
||||
assert page_size == 1
|
||||
assert page_table.shape[0] == topk_indices.shape[0]
|
||||
if result is None:
|
||||
result = torch.empty_like(topk_indices, dtype=torch.int32)
|
||||
assert result.shape == topk_indices.shape
|
||||
torch.gather(
|
||||
page_table.to(result.dtype),
|
||||
dim=1,
|
||||
index=topk_indices.clamp(min=0),
|
||||
out=result,
|
||||
)
|
||||
result[topk_indices < 0] = -1
|
||||
return result
|
||||
|
||||
|
||||
def transform_index_page_table_prefill_ref(
|
||||
page_table: torch.Tensor,
|
||||
topk_indices: torch.Tensor,
|
||||
extend_lens_cpu: List[int],
|
||||
page_size: int = 1,
|
||||
) -> torch.Tensor:
|
||||
assert page_size == 1
|
||||
result = torch.empty_like(topk_indices, dtype=torch.int32)
|
||||
assert len(extend_lens_cpu) == page_table.shape[0]
|
||||
offset = 0
|
||||
for i, l in enumerate(extend_lens_cpu):
|
||||
transform_index_page_table_decode_ref(
|
||||
page_table[i].unsqueeze(0).expand(l, -1),
|
||||
topk_indices[offset : offset + l],
|
||||
result=result[offset : offset + l],
|
||||
)
|
||||
offset += l
|
||||
assert offset == topk_indices.shape[0]
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bs, topk, max_seqlen = 10, 2048, 3000
|
||||
page_table = torch.randint(0, 100, (bs, max_seqlen), device="cuda")
|
||||
topk_indices = torch.full((bs, topk), -1, device="cuda")
|
||||
topk_indices[:, :1600] = torch.arange(1600).unsqueeze(0).repeat(bs, 1)
|
||||
ref_result = transform_index_page_table_decode_ref(page_table, topk_indices)
|
||||
result = transform_index_page_table_decode_fast(page_table, topk_indices)
|
||||
assert torch.all(result == ref_result)
|
||||
print("Passed")
|
||||
warnings.warn(
|
||||
"sglang.srt.layers.attention.nsa.transform_index is deprecated; "
|
||||
"use sglang.srt.layers.attention.dsa.transform_index instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from sglang.srt.layers.attention.dsa.transform_index import * # noqa: F401, F403
|
||||
|
||||
@@ -1,196 +1,10 @@
|
||||
from typing import Optional, Tuple
|
||||
# [Deprecated] Re-export shim for backward compatibility. Use dsa.triton_kernel instead.
|
||||
import warnings
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
|
||||
# Triton implementation
|
||||
@triton.jit
|
||||
def _act_quant_kernel(
|
||||
X_ptr,
|
||||
Y_ptr,
|
||||
S_ptr,
|
||||
M,
|
||||
N,
|
||||
group_size: tl.constexpr,
|
||||
round_scale: tl.constexpr,
|
||||
BLOCK_M: tl.constexpr,
|
||||
BLOCK_N: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Triton kernel for activation quantization.
|
||||
|
||||
Each block processes BLOCK_M rows and group_size columns.
|
||||
"""
|
||||
# Get block IDs
|
||||
pid_m = tl.program_id(0)
|
||||
pid_n = tl.program_id(1)
|
||||
|
||||
# FP8 constants
|
||||
fp8_min = -448.0
|
||||
fp8_max = 448.0
|
||||
fp8_max_inv = 1.0 / fp8_max
|
||||
|
||||
# Calculate row and column offsets
|
||||
row_start = pid_m * BLOCK_M
|
||||
col_start = pid_n * group_size
|
||||
|
||||
# Create offset arrays
|
||||
rows = row_start + tl.arange(0, BLOCK_M)
|
||||
cols = col_start + tl.arange(0, BLOCK_N)
|
||||
|
||||
# Mask for valid rows and columns
|
||||
row_mask = rows < M
|
||||
col_mask = cols < N
|
||||
mask = row_mask[:, None] & col_mask[None, :]
|
||||
|
||||
# Load input data
|
||||
x_ptrs = X_ptr + rows[:, None] * N + cols[None, :]
|
||||
x = tl.load(x_ptrs, mask=mask, other=0.0).to(tl.float32)
|
||||
|
||||
# Compute absolute max along columns (group_size dimension) for each row
|
||||
x_abs = tl.abs(x)
|
||||
amax = tl.max(x_abs, axis=1) # Shape: (BLOCK_M,)
|
||||
|
||||
# Clamp amax to avoid division by zero
|
||||
amax = tl.maximum(amax, 1e-4)
|
||||
|
||||
# Compute scale
|
||||
if round_scale:
|
||||
# Fast round scale using bit manipulation approximation
|
||||
# This is a simplified version - the exact bit manipulation is harder in Triton
|
||||
# Using log2 + ceil + pow2 as approximation
|
||||
log_val = tl.log2(amax * fp8_max_inv)
|
||||
log_ceil = tl.ceil(log_val)
|
||||
scale = tl.exp2(log_ceil)
|
||||
else:
|
||||
scale = amax * fp8_max_inv
|
||||
|
||||
# Quantize: y = clamp(x / scale, fp8_min, fp8_max)
|
||||
scale_broadcast = scale[:, None]
|
||||
y = x / scale_broadcast
|
||||
y = tl.minimum(tl.maximum(y, fp8_min), fp8_max)
|
||||
|
||||
# Store quantized output
|
||||
y_ptrs = Y_ptr + rows[:, None] * N + cols[None, :]
|
||||
tl.store(y_ptrs, y, mask=mask)
|
||||
|
||||
# Store scales
|
||||
s_cols = pid_n
|
||||
s_ptrs = S_ptr + rows * (N // group_size) + s_cols
|
||||
s_mask = row_mask
|
||||
tl.store(s_ptrs, scale, mask=s_mask)
|
||||
|
||||
|
||||
def act_quant(
|
||||
x: torch.Tensor, block_size: int = 128, scale_fmt: Optional[str] = None
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Quantizes the input tensor `x` using block-wise quantization with Triton.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor): The input tensor to be quantized. Must be contiguous and its last dimension size must be divisible by `block_size`.
|
||||
block_size (int, optional): The size of the blocks to be used for quantization. Default is 128.
|
||||
scale_fmt (Optional[str], optional): The format of the scale. Default is None.
|
||||
Returns:
|
||||
Tuple[torch.Tensor, torch.Tensor]: A tuple containing:
|
||||
- The quantized tensor with dtype `torch.float8_e4m3fn`.
|
||||
- A tensor of scaling factors with dtype `torch.float32`.
|
||||
"""
|
||||
assert x.is_contiguous(), "Input tensor must be contiguous"
|
||||
assert (
|
||||
x.size(-1) % block_size == 0
|
||||
), f"Last dimension size must be divisible by block_size (block_size={block_size})"
|
||||
|
||||
# Flatten all dims except last
|
||||
N = x.size(-1)
|
||||
x_flat = x.view(-1, N)
|
||||
M = x_flat.size(0)
|
||||
|
||||
# Allocate output tensors
|
||||
y = torch.empty_like(x, dtype=torch.float8_e4m3fn)
|
||||
y_flat = y.view(-1, N)
|
||||
s = x.new_empty(*x.size()[:-1], N // block_size, dtype=torch.float32)
|
||||
s_flat = s.view(-1, N // block_size)
|
||||
|
||||
# Launch kernel
|
||||
BLOCK_M = 32
|
||||
BLOCK_N = block_size
|
||||
grid = (triton.cdiv(M, BLOCK_M), triton.cdiv(N, block_size))
|
||||
round_scale = scale_fmt is not None
|
||||
|
||||
_act_quant_kernel[grid](
|
||||
x_flat,
|
||||
y_flat,
|
||||
s_flat,
|
||||
M,
|
||||
N,
|
||||
group_size=block_size,
|
||||
round_scale=round_scale,
|
||||
BLOCK_M=BLOCK_M,
|
||||
BLOCK_N=BLOCK_N,
|
||||
num_stages=0 if round_scale else 2,
|
||||
)
|
||||
|
||||
return y, s
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _get_valid_kv_indices_kernel(
|
||||
page_table_ptr, # [bs, topk]
|
||||
kv_indptr_ptr, # [bs + 1]
|
||||
kv_indices_ptr, # [bs * topk] output buffer
|
||||
bs: tl.constexpr,
|
||||
topk: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Extract valid indices (non -1) from page_table into kv_indices.
|
||||
Each program handles one batch.
|
||||
"""
|
||||
batch_id = tl.program_id(0)
|
||||
|
||||
# Get the start position for this batch in kv_indices
|
||||
dst_start = tl.load(kv_indptr_ptr + batch_id)
|
||||
|
||||
# Load all topk indices for this batch
|
||||
src_offset = batch_id * topk
|
||||
offsets = tl.arange(0, topk)
|
||||
indices = tl.load(page_table_ptr + src_offset + offsets)
|
||||
|
||||
# Count valid indices and compact them
|
||||
mask = indices != -1
|
||||
|
||||
# Use prefix sum to compute destination positions for valid elements
|
||||
# For each position, count how many valid elements are before it
|
||||
prefix_sum = tl.cumsum(mask.to(tl.int32), axis=0) - 1
|
||||
|
||||
# Store valid indices to their compacted positions
|
||||
dst_positions = dst_start + prefix_sum
|
||||
tl.store(kv_indices_ptr + dst_positions, indices, mask=mask)
|
||||
|
||||
|
||||
def get_valid_kv_indices(
|
||||
page_table_1: torch.Tensor,
|
||||
kv_indptr: torch.Tensor,
|
||||
kv_indices: torch.Tensor,
|
||||
bs: int,
|
||||
):
|
||||
"""
|
||||
Extract valid indices from page_table_1 into kv_indices buffer.
|
||||
|
||||
Args:
|
||||
page_table_1: [bs, topk] page table with -1 as invalid
|
||||
kv_indptr: [bs + 1] cumulative count of valid indices per batch
|
||||
kv_indices: [bs * topk] pre-allocated output buffer
|
||||
bs: batch size
|
||||
"""
|
||||
topk = page_table_1.shape[1]
|
||||
grid = (bs,)
|
||||
_get_valid_kv_indices_kernel[grid](
|
||||
page_table_1,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
bs,
|
||||
topk,
|
||||
)
|
||||
warnings.warn(
|
||||
"sglang.srt.layers.attention.nsa.triton_kernel is deprecated; "
|
||||
"use sglang.srt.layers.attention.dsa.triton_kernel instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from sglang.srt.layers.attention.dsa.triton_kernel import * # noqa: F401, F403
|
||||
|
||||
@@ -1,269 +1,10 @@
|
||||
from functools import lru_cache
|
||||
from typing import TYPE_CHECKING, List, Tuple, Union
|
||||
# [Deprecated] Re-export shim for backward compatibility. Use dsa.utils instead.
|
||||
import warnings
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.srt.layers.dp_attention import (
|
||||
DpPaddingMode,
|
||||
get_attention_cp_rank,
|
||||
get_attention_cp_size,
|
||||
get_attention_dp_rank,
|
||||
warnings.warn(
|
||||
"sglang.srt.layers.attention.nsa.utils is deprecated; "
|
||||
"use sglang.srt.layers.attention.dsa.utils instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
from sglang.srt.server_args import get_global_server_args
|
||||
from sglang.srt.utils import get_bool_env_var, is_hip
|
||||
from sglang.srt.utils.common import ceil_align, ceil_div
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def aiter_can_use_preshuffle_paged_mqa() -> bool:
|
||||
"""Whether aiter's preshuffle paged MQA / cache kernels can be used on this runtime.
|
||||
|
||||
aiter's ``deepgemm_fp8_paged_mqa_logits`` only supports ``KVBlockSize > 1`` and
|
||||
``Preshuffle=True`` on its gluon kernel path. The gluon path is enabled when
|
||||
Triton >= 3.5.0, OR when ``AITER_ENABLE_AOT_GLUON_PA_MQA_LOGITS=1`` is set
|
||||
(which additionally requires that the AOT gluon kernel artifacts ship inside
|
||||
the aiter wheel/image). Otherwise aiter asserts ``KVBlockSize == 1`` and
|
||||
refuses ``Preshuffle=True``.
|
||||
|
||||
sglang's NSA indexer uses this single decision to pick:
|
||||
* ``page_size``: 64 (preshuffle) vs 1 (legacy) on ROCm
|
||||
* ``Preshuffle`` / ``preshuffle`` flags on the aiter MQA + cache kernels
|
||||
* ``get_page_table_64`` vs ``get_page_table_1`` on the metadata
|
||||
* whether ``GetKAndS.execute`` uses the aiter or the triton implementation
|
||||
|
||||
The result is cached so the cost is paid once per process.
|
||||
|
||||
Set ``SGLANG_NSA_HIP_DISABLE_PRESHUFFLE=1`` to force the legacy path even when
|
||||
the gluon kernel would otherwise be available (useful for CI bisection).
|
||||
"""
|
||||
if not is_hip():
|
||||
return False
|
||||
if not get_bool_env_var("SGLANG_USE_AITER"):
|
||||
return False
|
||||
if get_bool_env_var("SGLANG_NSA_HIP_DISABLE_PRESHUFFLE"):
|
||||
return False
|
||||
if get_bool_env_var("AITER_ENABLE_AOT_GLUON_PA_MQA_LOGITS"):
|
||||
return True
|
||||
try:
|
||||
from packaging.version import Version
|
||||
|
||||
return Version(Version(triton.__version__).base_version) >= Version("3.5.0")
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
|
||||
|
||||
def compute_nsa_seqlens(original_seq_lens, nsa_index_topk: int):
|
||||
return original_seq_lens.clamp(max=nsa_index_topk)
|
||||
|
||||
|
||||
def is_nsa_enable_prefill_cp():
|
||||
return get_global_server_args().enable_nsa_prefill_context_parallel
|
||||
|
||||
|
||||
def is_nsa_prefill_cp_in_seq_split():
|
||||
return (
|
||||
is_nsa_enable_prefill_cp()
|
||||
and get_global_server_args().nsa_prefill_cp_mode == "in-seq-split"
|
||||
)
|
||||
|
||||
|
||||
def is_nsa_prefill_cp_round_robin_split():
|
||||
return (
|
||||
is_nsa_enable_prefill_cp()
|
||||
and get_global_server_args().nsa_prefill_cp_mode == "round-robin-split"
|
||||
)
|
||||
|
||||
|
||||
def can_nsa_prefill_cp_round_robin_split(forward_batch: "ForwardBatch"):
|
||||
if not forward_batch.forward_mode.is_context_parallel_extend():
|
||||
return False
|
||||
cp_size = get_attention_cp_size()
|
||||
seq_len = sum(forward_batch.extend_seq_lens_cpu)
|
||||
return (
|
||||
is_nsa_prefill_cp_round_robin_split()
|
||||
and seq_len > 0
|
||||
and seq_len >= cp_size
|
||||
and cp_size > 1
|
||||
)
|
||||
|
||||
|
||||
def nsa_cp_round_robin_split_data(input_: Union[torch.Tensor, List]):
|
||||
"""
|
||||
# for round-robin-split, split the tokens evenly according to the rule of token_idx % cp_size.
|
||||
| +-----------before split------------+|
|
||||
| token0, token1, token2, token3, token4, token5, token6, token7, ...
|
||||
|
|
||||
| +--------------result-------------------+
|
||||
| dp_atten_tp0: token0, token4, token8, token12, token16, ... |
|
||||
| dp_atten_tp1: token1, token5, token9, token13, token17, ... |
|
||||
| dp_atten_tp2: token2, token6, token10, token14, token18, ... |
|
||||
| dp_atten_tp3: token3, token7, token11, token15, token19, ... |
|
||||
| +-------------------------+
|
||||
"""
|
||||
cp_size = get_attention_cp_size()
|
||||
cp_rank = get_attention_cp_rank()
|
||||
if isinstance(input_, (tuple, list)):
|
||||
indices = range(cp_rank, len(input_), cp_size)
|
||||
return input_[indices]
|
||||
|
||||
tokens = len(input_)
|
||||
if tokens % cp_size != 0:
|
||||
cur_len = tokens // cp_size + (tokens % cp_size > cp_rank)
|
||||
if cur_len == 0:
|
||||
return input_.new_empty(0, *input_.shape[1:])
|
||||
indices = torch.arange(cp_rank, tokens, cp_size, device=input_.device)
|
||||
return input_[indices]
|
||||
|
||||
# for torch device tensor
|
||||
return input_.view(-1, cp_size, *input_.shape[1:])[:, cp_rank].contiguous()
|
||||
|
||||
|
||||
def cal_padded_tokens(forward_batch: "ForwardBatch"):
|
||||
# Consistent with the padding calculation logic in ForwardBatch.prepare_mlp_sync_batch,
|
||||
# calculate the actual token length after padding when attn_tp_size > 1 or in the MAX_LEN padding mode.
|
||||
global_num_tokens = forward_batch.global_num_tokens_cpu.copy()
|
||||
sync_group_size = len(global_num_tokens)
|
||||
attn_cp_size = get_attention_cp_size()
|
||||
for i in range(sync_group_size):
|
||||
global_num_tokens[i] = ceil_align(global_num_tokens[i], attn_cp_size)
|
||||
dp_padding_mode = DpPaddingMode.get_dp_padding_mode(
|
||||
forward_batch.is_extend_in_batch, global_num_tokens
|
||||
)
|
||||
if dp_padding_mode.is_max_len():
|
||||
tokens = max(global_num_tokens)
|
||||
elif len(global_num_tokens) > 1:
|
||||
tokens = global_num_tokens[get_attention_dp_rank()]
|
||||
else:
|
||||
tokens = global_num_tokens[0]
|
||||
if can_nsa_prefill_cp_round_robin_split(forward_batch):
|
||||
tokens = ceil_div(tokens, attn_cp_size)
|
||||
return tokens
|
||||
|
||||
|
||||
def pad_nsa_cache_seqlens(forward_batch: "ForwardBatch", nsa_cache_seqlens):
|
||||
attn_cp_size = get_attention_cp_size()
|
||||
needs_cp_pad = attn_cp_size > 1 and can_nsa_prefill_cp_round_robin_split(
|
||||
forward_batch
|
||||
)
|
||||
needs_dp_pad = forward_batch.global_num_tokens_cpu is not None
|
||||
if not needs_cp_pad and not needs_dp_pad:
|
||||
return nsa_cache_seqlens
|
||||
tokens = cal_padded_tokens(forward_batch)
|
||||
pad_len = tokens - nsa_cache_seqlens.shape[0]
|
||||
if pad_len > 0:
|
||||
nsa_cache_seqlens = torch.cat(
|
||||
[
|
||||
nsa_cache_seqlens,
|
||||
nsa_cache_seqlens.new_zeros(pad_len, *nsa_cache_seqlens.shape[1:]),
|
||||
]
|
||||
)
|
||||
return nsa_cache_seqlens
|
||||
|
||||
|
||||
def can_nsa_cp_split(seq_len: int, cp_size: int, use_nsa: bool, forward_batch):
|
||||
if is_nsa_prefill_cp_round_robin_split():
|
||||
cur_cp_seq_len = seq_len // cp_size
|
||||
assert (
|
||||
seq_len % cp_size == 0
|
||||
), f"seq_len {seq_len} is not divisible by cp_size {cp_size} when nsa_prefill_cp_mode is round-robin-split"
|
||||
else:
|
||||
# TODO current just support prefill batch=1 and len(input_ids) > self.cp_size * 2
|
||||
# Note: (self.cp_size * 2) To achieve load balancing for seq computation,
|
||||
# the seq data needs to be divided and recombined at twice the size of cp_size.
|
||||
cur_cp_seq_len = seq_len // (cp_size * 2)
|
||||
if (
|
||||
cur_cp_seq_len != 0
|
||||
and cp_size > 1
|
||||
and use_nsa
|
||||
and forward_batch.forward_mode.is_context_parallel_extend()
|
||||
and is_nsa_enable_prefill_cp()
|
||||
and sum(forward_batch.extend_seq_lens_cpu) >= cp_size
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
@triton.jit
|
||||
def nsa_cp_round_robin_split_q_seqs_kernel(
|
||||
in_seqs_ptr,
|
||||
out_seqs_ptr,
|
||||
bs_idx_ptr,
|
||||
tokens: tl.constexpr,
|
||||
cp_size: tl.constexpr,
|
||||
cp_rank: tl.constexpr,
|
||||
):
|
||||
extra_seq = 0
|
||||
bs_idx = 0
|
||||
for bs in range(tokens):
|
||||
cur_len = tl.load(in_seqs_ptr + bs)
|
||||
cur_len += extra_seq
|
||||
cur_seq = cur_len // cp_size + (cur_len % cp_size > cp_rank)
|
||||
if cur_seq > 0:
|
||||
tl.store(bs_idx_ptr + bs_idx, bs)
|
||||
tl.store(out_seqs_ptr + bs_idx, cur_seq)
|
||||
bs_idx += 1
|
||||
extra_seq = cur_len - cur_seq * cp_size
|
||||
|
||||
|
||||
def nsa_cp_round_robin_split_q_seqs_cpu(extend_seqs):
|
||||
cp_size = get_attention_cp_size()
|
||||
cp_rank = get_attention_cp_rank()
|
||||
extra_seq = 0
|
||||
q_seqs = []
|
||||
for bs, cur_len in enumerate(extend_seqs):
|
||||
cur_len += extra_seq
|
||||
cur_seq = cur_len // cp_size + int(cur_len % cp_size > cp_rank)
|
||||
q_seqs.append(cur_seq)
|
||||
extra_seq = cur_len - cur_seq * cp_size
|
||||
bs_idx = list([i for i, x in enumerate(q_seqs) if x > 0])
|
||||
q_seqs = [q_len for q_len in q_seqs if q_len > 0]
|
||||
return q_seqs, bs_idx
|
||||
|
||||
|
||||
def nsa_cp_round_robin_split_q_seqs(
|
||||
extend_seqs_cpu, extend_seqs
|
||||
) -> Tuple[List, torch.Tensor, List, torch.Tensor]:
|
||||
"""
|
||||
round-robin-split distributes tokens across ranks based on token_idx % cp_size.
|
||||
|
||||
Return:
|
||||
ret_q_lens_cpu(List) and ret_q_lens(torch.Tensor): the partitioned length (excluding zeros) on the current cp rank
|
||||
for each sequence after distribution across cp ranks.
|
||||
bs_idx_cpu(List) and bs_idx(torch.Tensor): marks which sequences are ultimately selected,
|
||||
i.e., those with a partitioned length greater than zero.
|
||||
"""
|
||||
cp_size = get_attention_cp_size()
|
||||
cp_rank = get_attention_cp_rank()
|
||||
# len(ret_q_lens_cpu) == len(bs_idx_cpu)
|
||||
ret_q_lens_cpu, bs_idx_cpu = nsa_cp_round_robin_split_q_seqs_cpu(extend_seqs_cpu)
|
||||
ret_q_lens = torch.empty(
|
||||
(len(bs_idx_cpu),), device=extend_seqs.device, dtype=extend_seqs.dtype
|
||||
)
|
||||
bs_idx = torch.empty(
|
||||
(len(bs_idx_cpu),), device=extend_seqs.device, dtype=torch.int32
|
||||
)
|
||||
grid = (1,)
|
||||
nsa_cp_round_robin_split_q_seqs_kernel[grid](
|
||||
extend_seqs, ret_q_lens, bs_idx, len(extend_seqs), cp_size, cp_rank
|
||||
)
|
||||
return ret_q_lens_cpu, ret_q_lens, bs_idx_cpu, bs_idx
|
||||
|
||||
|
||||
def nsa_use_prefill_cp(forward_batch, nsa_enable_prefill_cp=None):
|
||||
if nsa_enable_prefill_cp is None:
|
||||
nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if (
|
||||
forward_batch.attn_cp_metadata is not None
|
||||
and nsa_enable_prefill_cp
|
||||
and forward_batch.forward_mode.is_context_parallel_extend()
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
from sglang.srt.layers.attention.dsa.utils import * # noqa: F401, F403
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,9 +33,9 @@ from sglang.srt.distributed.device_communicators.pynccl_allocator import (
|
||||
use_symmetric_memory,
|
||||
)
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
is_nsa_enable_prefill_cp,
|
||||
nsa_use_prefill_cp,
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
dsa_use_prefill_cp,
|
||||
is_dsa_enable_prefill_cp,
|
||||
)
|
||||
from sglang.srt.layers.dp_attention import (
|
||||
attn_tp_all_gather_into_tensor,
|
||||
@@ -202,7 +202,7 @@ class ScatterMode(Enum):
|
||||
@staticmethod
|
||||
def model_input_output():
|
||||
"""The scatter mode for model forward pass input and output data"""
|
||||
if is_nsa_enable_prefill_cp():
|
||||
if is_dsa_enable_prefill_cp():
|
||||
return ScatterMode.SCATTERED
|
||||
|
||||
return ScatterMode.TP_ATTN_FULL
|
||||
@@ -256,15 +256,15 @@ class AttnTpContext:
|
||||
self.allow_input_scattered = False
|
||||
self.input_scattered_ = False
|
||||
self.attn_inputs_: Optional[AttentionInputs] = None
|
||||
self.is_nsa = False
|
||||
self.is_dsa = False
|
||||
|
||||
def init_context(self, q_lora_rank, is_nsa):
|
||||
self.is_nsa = is_nsa
|
||||
def init_context(self, q_lora_rank, is_dsa):
|
||||
self.is_dsa = is_dsa
|
||||
self.allow_input_scattered = (
|
||||
get_global_server_args().enable_attn_tp_input_scattered
|
||||
and (_is_cuda or _is_npu)
|
||||
and q_lora_rank is not None
|
||||
and not is_nsa
|
||||
and not is_dsa
|
||||
and get_tensor_model_parallel_world_size() > 1
|
||||
and not is_dp_attention_enabled()
|
||||
and get_moe_a2a_backend().is_none()
|
||||
@@ -379,8 +379,8 @@ class LayerScatterModes:
|
||||
or should_use_flashinfer_cutlass_moe_fp4_allgather()
|
||||
):
|
||||
return ScatterMode.SCATTERED
|
||||
# NSA CP doesn't support MOE_FULL yet; fall back to FULL
|
||||
if is_enable_moe_cp_allgather() and not is_nsa_enable_prefill_cp():
|
||||
# DSA CP doesn't support MOE_FULL yet; fall back to FULL
|
||||
if is_enable_moe_cp_allgather() and not is_dsa_enable_prefill_cp():
|
||||
return ScatterMode.MOE_FULL
|
||||
return ScatterMode.FULL
|
||||
else:
|
||||
@@ -551,10 +551,10 @@ class LayerCommunicator:
|
||||
)
|
||||
elif _use_aiter and _is_gfx95_supported and (quant_format == "fp8"):
|
||||
# aiter (ROCm gfx95) fused RMSNorm + FP8 group quant.
|
||||
# When NSA is active, also preserve the unquantized bf16
|
||||
# output as a 3-tuple (fp8, scale, bf16) so the NSA
|
||||
# When DSA is active, also preserve the unquantized bf16
|
||||
# output as a 3-tuple (fp8, scale, bf16) so the DSA
|
||||
# indexer can skip redundant FP8 dequantization.
|
||||
_nsa_needs_bf16 = get_attn_tp_context().is_nsa
|
||||
_dsa_needs_bf16 = get_attn_tp_context().is_dsa
|
||||
hidden_states, _unq_bf16, _, _res = fused_rms_fp8_group_quant(
|
||||
hidden_states,
|
||||
self.input_layernorm.weight,
|
||||
@@ -565,9 +565,9 @@ class LayerCommunicator:
|
||||
group_size=128,
|
||||
dtype_quant=torch.float8_e4m3fn,
|
||||
res1=None,
|
||||
output_unquantized_inp1=_nsa_needs_bf16,
|
||||
output_unquantized_inp1=_dsa_needs_bf16,
|
||||
)
|
||||
if _nsa_needs_bf16:
|
||||
if _dsa_needs_bf16:
|
||||
hidden_states = (
|
||||
hidden_states[0],
|
||||
hidden_states[1],
|
||||
@@ -596,9 +596,9 @@ class LayerCommunicator:
|
||||
)
|
||||
elif _use_aiter and _is_gfx95_supported and (quant_format == "fp8"):
|
||||
# aiter (ROCm gfx95) fused RMSNorm + FP8 group quant
|
||||
# with residual addition. When NSA is active, pack
|
||||
# with residual addition. When DSA is active, pack
|
||||
# the unquantized bf16 as a 3-tuple (fp8, scale, bf16).
|
||||
_nsa_needs_bf16 = get_attn_tp_context().is_nsa
|
||||
_dsa_needs_bf16 = get_attn_tp_context().is_dsa
|
||||
hidden_states, _unq_bf16, _, residual = (
|
||||
fused_rms_fp8_group_quant(
|
||||
hidden_states,
|
||||
@@ -610,10 +610,10 @@ class LayerCommunicator:
|
||||
group_size=128,
|
||||
dtype_quant=torch.float8_e4m3fn,
|
||||
res1=residual,
|
||||
output_unquantized_inp1=_nsa_needs_bf16,
|
||||
output_unquantized_inp1=_dsa_needs_bf16,
|
||||
)
|
||||
)
|
||||
if _nsa_needs_bf16:
|
||||
if _dsa_needs_bf16:
|
||||
hidden_states = (
|
||||
hidden_states[0],
|
||||
hidden_states[1],
|
||||
@@ -709,7 +709,7 @@ class LayerCommunicator:
|
||||
return True
|
||||
if forward_batch.dp_padding_mode.is_max_len():
|
||||
return True
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
return True
|
||||
if get_attn_tp_context().input_scattered and not self.is_last_layer:
|
||||
return True
|
||||
|
||||
+19
-19
@@ -18,9 +18,9 @@ from typing import Callable, Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
is_nsa_enable_prefill_cp,
|
||||
nsa_use_prefill_cp,
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
dsa_use_prefill_cp,
|
||||
is_dsa_enable_prefill_cp,
|
||||
)
|
||||
from sglang.srt.layers.communicator import (
|
||||
CommunicateContext,
|
||||
@@ -40,14 +40,14 @@ from sglang.srt.layers.dp_attention import (
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
|
||||
|
||||
def nsa_enable_prefill_cp():
|
||||
def dsa_enable_prefill_cp():
|
||||
# After using cp, the communication mode of this part changes.
|
||||
# The three parts of prepare_attn, prepare_mlp, and postprocess_layer
|
||||
# no longer require additional communication for reduce, scatter, etc.
|
||||
return is_nsa_enable_prefill_cp()
|
||||
return is_dsa_enable_prefill_cp()
|
||||
|
||||
|
||||
class NSACPLayerCommunicator(LayerCommunicator):
|
||||
class DSACPLayerCommunicator(LayerCommunicator):
|
||||
def __init__(
|
||||
self,
|
||||
layer_scatter_modes: LayerScatterModes,
|
||||
@@ -73,19 +73,19 @@ class NSACPLayerCommunicator(LayerCommunicator):
|
||||
assert (
|
||||
self._context.attn_dp_size == 1
|
||||
), f"dp_size should be 1 when moe_runner_backend is none"
|
||||
self._communicate_simple_fn = NSACPCommunicateSimpleFn.get_fn(
|
||||
self._communicate_simple_fn = DSACPCommunicateSimpleFn.get_fn(
|
||||
input_mode=ScatterMode.SCATTERED,
|
||||
output_mode=ScatterMode.SCATTERED,
|
||||
context=self._context,
|
||||
)
|
||||
self._communicate_with_all_reduce_and_layer_norm_fn = NSACPCommunicateWithAllReduceAndLayerNormFn.get_fn(
|
||||
self._communicate_with_all_reduce_and_layer_norm_fn = DSACPCommunicateWithAllReduceAndLayerNormFn.get_fn(
|
||||
hidden_states_input_mode=ScatterMode.SCATTERED,
|
||||
residual_input_mode=ScatterMode.SCATTERED,
|
||||
hidden_states_output_mode=self.layer_scatter_modes.mlp_mode, # SCATTERED, FULL
|
||||
residual_output_mode=ScatterMode.SCATTERED,
|
||||
context=self._context,
|
||||
)
|
||||
self._communicate_summable_tensor_pair_fn = NSACPCommunicateSummableTensorPairFn.get_fn(
|
||||
self._communicate_summable_tensor_pair_fn = DSACPCommunicateSummableTensorPairFn.get_fn(
|
||||
hidden_states_input_mode=self.layer_scatter_modes.mlp_mode, # SCATTERED, FULL
|
||||
residual_input_mode=ScatterMode.SCATTERED,
|
||||
output_mode=ScatterMode.SCATTERED,
|
||||
@@ -93,7 +93,7 @@ class NSACPLayerCommunicator(LayerCommunicator):
|
||||
)
|
||||
|
||||
|
||||
class NSACPCommunicateSimpleFn(CommunicateSimpleFn):
|
||||
class DSACPCommunicateSimpleFn(CommunicateSimpleFn):
|
||||
@staticmethod
|
||||
def get_fn(
|
||||
input_mode: ScatterMode,
|
||||
@@ -101,12 +101,12 @@ class NSACPCommunicateSimpleFn(CommunicateSimpleFn):
|
||||
context: CommunicateContext,
|
||||
):
|
||||
if context.is_same_group_size(input_mode, output_mode):
|
||||
return NSACPCommunicateSimpleFn._trivial
|
||||
return DSACPCommunicateSimpleFn._trivial
|
||||
|
||||
raise NotImplementedError(f"{input_mode=} {output_mode=}")
|
||||
|
||||
|
||||
class NSACPCommunicateWithAllReduceAndLayerNormFn(
|
||||
class DSACPCommunicateWithAllReduceAndLayerNormFn(
|
||||
CommunicateWithAllReduceAndLayerNormFn
|
||||
):
|
||||
"""Besides communication, needs to
|
||||
@@ -126,11 +126,11 @@ class NSACPCommunicateWithAllReduceAndLayerNormFn(
|
||||
assert residual_input_mode == ScatterMode.SCATTERED
|
||||
assert residual_output_mode == ScatterMode.SCATTERED
|
||||
if hidden_states_output_mode == ScatterMode.SCATTERED:
|
||||
return NSACPCommunicateWithAllReduceAndLayerNormFn._simple
|
||||
return DSACPCommunicateWithAllReduceAndLayerNormFn._simple
|
||||
|
||||
if hidden_states_output_mode == ScatterMode.FULL:
|
||||
return partial(
|
||||
NSACPCommunicateWithAllReduceAndLayerNormFn._gather_hidden_states_and_residual,
|
||||
DSACPCommunicateWithAllReduceAndLayerNormFn._gather_hidden_states_and_residual,
|
||||
residual_input_mode=residual_input_mode,
|
||||
)
|
||||
|
||||
@@ -152,7 +152,7 @@ class NSACPCommunicateWithAllReduceAndLayerNormFn(
|
||||
hidden_states, residual = layernorm(hidden_states, residual)
|
||||
# for prefill: attn tp scattered -> full
|
||||
# for decode: attn tp full -> full
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
assert context.attn_dp_size == 1
|
||||
hidden_states, local_hidden_states = (
|
||||
get_local_dp_buffer(get_attention_cp_group()),
|
||||
@@ -165,7 +165,7 @@ class NSACPCommunicateWithAllReduceAndLayerNormFn(
|
||||
return hidden_states, residual
|
||||
|
||||
|
||||
class NSACPCommunicateSummableTensorPairFn(CommunicateSummableTensorPairFn):
|
||||
class DSACPCommunicateSummableTensorPairFn(CommunicateSummableTensorPairFn):
|
||||
"""It is allowed to make (hidden_states, residual) := (hidden_states + residual, None) if needed."""
|
||||
|
||||
@staticmethod
|
||||
@@ -184,12 +184,12 @@ class NSACPCommunicateSummableTensorPairFn(CommunicateSummableTensorPairFn):
|
||||
and (residual_input_mode == ScatterMode.SCATTERED)
|
||||
and (output_mode == ScatterMode.SCATTERED)
|
||||
):
|
||||
return NSACPCommunicateSummableTensorPairFn._scatter_hidden_states
|
||||
return DSACPCommunicateSummableTensorPairFn._scatter_hidden_states
|
||||
|
||||
if context.is_same_group_size(
|
||||
hidden_states_input_mode, output_mode
|
||||
) and context.is_same_group_size(residual_input_mode, output_mode):
|
||||
return NSACPCommunicateSummableTensorPairFn._trivial
|
||||
return DSACPCommunicateSummableTensorPairFn._trivial
|
||||
|
||||
raise NotImplementedError(
|
||||
f"{hidden_states_input_mode=} {residual_input_mode=} {output_mode=}"
|
||||
@@ -205,7 +205,7 @@ class NSACPCommunicateSummableTensorPairFn(CommunicateSummableTensorPairFn):
|
||||
):
|
||||
# for prefill: full -> attn tp scattered
|
||||
# for decode: full -> attn tp full
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
assert context.attn_dp_size == 1
|
||||
input_hidden_states = hidden_states
|
||||
hidden_states = hidden_states.tensor_split(context.attn_cp_size)[
|
||||
@@ -72,7 +72,7 @@ class DpPaddingMode(IntEnum):
|
||||
# When is_extend_in_batch and dp_size > 1, use SUM_LEN to avoid padding
|
||||
# overhead from uneven token distribution.
|
||||
# For dp_size=1, max_len equals sum_len, so prefer MAX_LEN mode
|
||||
# to enable symmetric memory optimization (needed for NSA CP, etc.).
|
||||
# to enable symmetric memory optimization (needed for DSA CP, etc.).
|
||||
if is_extend_in_batch and dp_size > 1:
|
||||
return DpPaddingMode.SUM_LEN
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import torch
|
||||
|
||||
from sglang.jit_kernel.utils import is_arch_support_pdl
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.attention.nsa.utils import is_nsa_prefill_cp_round_robin_split
|
||||
from sglang.srt.layers.attention.dsa.utils import is_dsa_prefill_cp_round_robin_split
|
||||
from sglang.srt.layers.utils.common import strict_contiguous
|
||||
|
||||
tilelang.set_log_level("WARNING")
|
||||
@@ -880,7 +880,7 @@ def mhc_post(
|
||||
post_layer_mix: torch.Tensor,
|
||||
comb_res_mix: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
if is_nsa_prefill_cp_round_robin_split():
|
||||
if is_dsa_prefill_cp_round_robin_split():
|
||||
x = strict_contiguous(x)
|
||||
residual = strict_contiguous(residual)
|
||||
post_layer_mix = strict_contiguous(post_layer_mix)
|
||||
|
||||
@@ -66,17 +66,17 @@ def can_cp_split(seq_len: int, cp_size: int, forward_batch):
|
||||
|
||||
|
||||
def cp_split_and_rebuild_data(forward_batch, input_: torch.Tensor):
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
is_nsa_prefill_cp_round_robin_split,
|
||||
nsa_cp_round_robin_split_data,
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
dsa_cp_round_robin_split_data,
|
||||
is_dsa_prefill_cp_round_robin_split,
|
||||
)
|
||||
|
||||
if is_nsa_prefill_cp_round_robin_split():
|
||||
if is_dsa_prefill_cp_round_robin_split():
|
||||
cp_size = get_attention_cp_size()
|
||||
assert (
|
||||
input_.shape[0] % cp_size == 0
|
||||
), f"Expect input shape 0 can divided by cp size, but got input shape {input_.shape}, cp size {cp_size}"
|
||||
return nsa_cp_round_robin_split_data(input_)
|
||||
return dsa_cp_round_robin_split_data(input_)
|
||||
|
||||
input_list = list(
|
||||
torch.split(input_, forward_batch.attn_cp_metadata.split_list, dim=0)
|
||||
@@ -88,18 +88,18 @@ def cp_split_and_rebuild_data(forward_batch, input_: torch.Tensor):
|
||||
|
||||
|
||||
def cp_split_and_rebuild_position(forward_batch, positions: torch.Tensor):
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
is_nsa_prefill_cp_round_robin_split,
|
||||
nsa_cp_round_robin_split_data,
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
dsa_cp_round_robin_split_data,
|
||||
is_dsa_prefill_cp_round_robin_split,
|
||||
)
|
||||
|
||||
if is_nsa_prefill_cp_round_robin_split():
|
||||
if is_dsa_prefill_cp_round_robin_split():
|
||||
cp_size = get_attention_cp_size()
|
||||
assert positions.shape[0] % cp_size == 0, (
|
||||
f"Expect positions shape 0 can divided by cp size, but got positions shape {positions.shape}, "
|
||||
f"cp size {cp_size}"
|
||||
)
|
||||
return nsa_cp_round_robin_split_data(positions)
|
||||
return dsa_cp_round_robin_split_data(positions)
|
||||
|
||||
position_id_list = list(
|
||||
torch.split(positions, forward_batch.attn_cp_metadata.split_list, dim=-1)
|
||||
@@ -238,11 +238,11 @@ def cp_all_gather_rerange_output(input_tensor, cp_size, forward_batch, stream):
|
||||
| token0, token1, token2, token3, token4, token5, token6, token7, ...
|
||||
| +-------------------------+
|
||||
"""
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
is_nsa_prefill_cp_round_robin_split,
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
is_dsa_prefill_cp_round_robin_split,
|
||||
)
|
||||
|
||||
if is_nsa_prefill_cp_round_robin_split():
|
||||
if is_dsa_prefill_cp_round_robin_split():
|
||||
with use_symmetric_memory(
|
||||
get_attention_cp_group(), disabled=not is_allocation_symmetric()
|
||||
):
|
||||
@@ -395,11 +395,11 @@ def prepare_context_parallel_metadata(
|
||||
cp_size,
|
||||
seqs_len,
|
||||
):
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
is_nsa_prefill_cp_round_robin_split,
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
is_dsa_prefill_cp_round_robin_split,
|
||||
)
|
||||
|
||||
if is_nsa_prefill_cp_round_robin_split():
|
||||
if is_dsa_prefill_cp_round_robin_split():
|
||||
return ContextParallelMetadata()
|
||||
|
||||
"""prepare_input_dp_with_cp_dsa-zigzag index
|
||||
@@ -505,16 +505,16 @@ def prepare_context_parallel_metadata(
|
||||
|
||||
# TODO Support multi-batch-cp-split, multi-batch-cp support has accuracy issues
|
||||
# Prefix offset is critical when radix cache hits (prefix_len > 0).
|
||||
# For non-NSA CP (e.g. qwen3-moe), consumers use these values directly as
|
||||
# For non-DSA CP (e.g. qwen3-moe), consumers use these values directly as
|
||||
# FlashAttention cache_seqlens, so the prefix must be baked in here.
|
||||
# For NSA CP, `_get_topk_ragged_with_cp` re-adds the cached-prefix offset
|
||||
# For DSA CP, `_get_topk_ragged_with_cp` re-adds the cached-prefix offset
|
||||
# from (seq_lens_cpu - extend_seq_lens_cpu); baking prefix_len in here
|
||||
# would silently drop it whenever the scheduler packs multiple requests
|
||||
# into a single CP extend (len(seqs_len) != 1 -> prefix_len falls back
|
||||
# to 0), corrupting the indexer's ke_offset on prefix-cache hits.
|
||||
from sglang.srt.layers.attention.nsa.utils import is_nsa_enable_prefill_cp
|
||||
from sglang.srt.layers.attention.dsa.utils import is_dsa_enable_prefill_cp
|
||||
|
||||
if is_nsa_enable_prefill_cp():
|
||||
if is_dsa_enable_prefill_cp():
|
||||
kv_len_prev = prefix_sum_list[cp_rank]
|
||||
kv_len_next = prefix_sum_list[cp_size * 2 - cp_rank - 1]
|
||||
else:
|
||||
|
||||
@@ -9,7 +9,7 @@ from sglang.srt.managers.schedule_batch import Req
|
||||
from sglang.srt.mem_cache.hisparse_memory_pool import (
|
||||
DeepSeekV4HiSparseTokenToKVPoolAllocator,
|
||||
DeepSeekV4SingleKVPoolHost,
|
||||
HiSparseNSATokenToKVPool,
|
||||
HiSparseDSATokenToKVPool,
|
||||
HiSparseTokenToKVPoolAllocator,
|
||||
)
|
||||
from sglang.srt.mem_cache.memory_pool_host import MLATokenToKVPoolHost
|
||||
@@ -79,7 +79,7 @@ class HiSparseCoordinator:
|
||||
assert isinstance(
|
||||
self.token_to_kv_pool_allocator, HiSparseTokenToKVPoolAllocator
|
||||
)
|
||||
self.mem_pool_device: HiSparseNSATokenToKVPool = (
|
||||
self.mem_pool_device: HiSparseDSATokenToKVPool = (
|
||||
self.token_to_kv_pool_allocator.get_kvcache()
|
||||
)
|
||||
self.mem_pool_host = MLATokenToKVPoolHost(
|
||||
|
||||
@@ -34,7 +34,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union
|
||||
import torch
|
||||
|
||||
from sglang.srt.dllm.config import DllmConfig
|
||||
from sglang.srt.layers.attention.nsa.utils import is_nsa_prefill_cp_in_seq_split
|
||||
from sglang.srt.layers.attention.dsa.utils import is_dsa_prefill_cp_in_seq_split
|
||||
from sglang.srt.layers.utils.cp_utils import is_prefill_context_parallel_enabled
|
||||
from sglang.srt.managers.schedule_batch import Req, ScheduleBatch
|
||||
from sglang.srt.mem_cache.base_prefix_cache import (
|
||||
@@ -467,7 +467,7 @@ class PrefillAdder:
|
||||
self.priority_scheduling_preemption_threshold = (
|
||||
priority_scheduling_preemption_threshold
|
||||
)
|
||||
self.nsa_prefill_cp_in_seq_split = is_nsa_prefill_cp_in_seq_split()
|
||||
self.dsa_prefill_cp_in_seq_split = is_dsa_prefill_cp_in_seq_split()
|
||||
self.max_running_requests = max_running_requests
|
||||
self.prefill_context_parallel_enabled = is_prefill_context_parallel_enabled()
|
||||
self.prefill_max_requests = prefill_max_requests
|
||||
@@ -826,7 +826,7 @@ class PrefillAdder:
|
||||
# Enabling context parallelism currently presents precision issues;
|
||||
# therefore, the prefill-batch setting is temporarily set to 1.
|
||||
if (
|
||||
self.nsa_prefill_cp_in_seq_split or self.prefill_context_parallel_enabled
|
||||
self.dsa_prefill_cp_in_seq_split or self.prefill_context_parallel_enabled
|
||||
) and len(self.can_run_list) >= 1:
|
||||
return AddReqResult.OTHER
|
||||
|
||||
|
||||
@@ -523,7 +523,7 @@ class SchedulerPPMixin:
|
||||
self.pp_loop_size: int = self.ps.pp_size + self.server_args.pp_async_batch_depth
|
||||
# In CP mode, attention weights are duplicated, eliminating the need for the attention TP all-gather operation.
|
||||
self.require_attn_tp_allgather = (
|
||||
not self.server_args.enable_nsa_prefill_context_parallel
|
||||
not self.server_args.enable_dsa_prefill_context_parallel
|
||||
)
|
||||
self.mbs = [None] * self.pp_loop_size
|
||||
self.last_mbs = [None] * self.pp_loop_size
|
||||
|
||||
@@ -9,11 +9,11 @@ import torch
|
||||
from sglang.jit_kernel.deepseek_v4 import fused_k_norm_rope_flashmla, fused_store_cache
|
||||
from sglang.srt.constants import GPU_MEMORY_TYPE_KV_CACHE
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.attention.dsa import index_buf_accessor
|
||||
from sglang.srt.layers.attention.dsv4 import (
|
||||
index_buf_accessor as dsv4_index_buf_accessor,
|
||||
)
|
||||
from sglang.srt.layers.attention.dsv4.index_buf_accessor import NopeFp8RopeBf16Pack
|
||||
from sglang.srt.layers.attention.nsa import index_buf_accessor
|
||||
from sglang.srt.mem_cache.base_swa_memory_pool import BaseSWAKVPool
|
||||
from sglang.srt.mem_cache.deepseek_v4_compress_state import CompressStatePool
|
||||
from sglang.srt.mem_cache.memory_pool import KVCache
|
||||
|
||||
@@ -36,12 +36,12 @@ from sglang.srt.mem_cache.hybrid_cache.hybrid_cache_controller import (
|
||||
HybridCacheController,
|
||||
)
|
||||
from sglang.srt.mem_cache.hybrid_cache.hybrid_pool_assembler import (
|
||||
attach_hybrid_nsa_pool_to_hiradix_cache,
|
||||
attach_hybrid_dsa_pool_to_hiradix_cache,
|
||||
)
|
||||
from sglang.srt.mem_cache.memory_pool import (
|
||||
DSATokenToKVPool,
|
||||
MHATokenToKVPool,
|
||||
MLATokenToKVPool,
|
||||
NSATokenToKVPool,
|
||||
)
|
||||
from sglang.srt.mem_cache.memory_pool_host import (
|
||||
MHATokenToKVPoolHost,
|
||||
@@ -82,8 +82,8 @@ class HiRadixCache(RadixCache):
|
||||
server_args.hicache_mem_layout,
|
||||
allocator_type=server_args.hicache_storage_backend,
|
||||
)
|
||||
elif isinstance(self.kv_cache, NSATokenToKVPool):
|
||||
# Filled by attach_hybrid_nsa_pool_to_hiradix_cache after storage extra_config is parsed.
|
||||
elif isinstance(self.kv_cache, DSATokenToKVPool):
|
||||
# Filled by attach_hybrid_dsa_pool_to_hiradix_cache after storage extra_config is parsed.
|
||||
self.token_to_kv_pool_host = None
|
||||
elif isinstance(self.kv_cache, MLATokenToKVPool):
|
||||
self.token_to_kv_pool_host = MLATokenToKVPoolHost(
|
||||
@@ -95,9 +95,7 @@ class HiRadixCache(RadixCache):
|
||||
allocator_type=server_args.hicache_storage_backend,
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
"HiRadixCache only supports MHA, MLA, and NSA (DSA) models"
|
||||
)
|
||||
raise ValueError("HiRadixCache only supports MHA, MLA, and DSA models")
|
||||
|
||||
self.tp_group = params.tp_cache_group
|
||||
self.attn_cp_group = params.attn_cp_cache_group
|
||||
@@ -122,8 +120,8 @@ class HiRadixCache(RadixCache):
|
||||
self.prefetch_stop_policy = server_args.hicache_storage_prefetch_policy
|
||||
|
||||
self.load_cache_event = threading.Event()
|
||||
if isinstance(self.kv_cache, NSATokenToKVPool):
|
||||
attach_hybrid_nsa_pool_to_hiradix_cache(
|
||||
if isinstance(self.kv_cache, DSATokenToKVPool):
|
||||
attach_hybrid_dsa_pool_to_hiradix_cache(
|
||||
self,
|
||||
params,
|
||||
server_args,
|
||||
@@ -643,7 +641,7 @@ class HiRadixCache(RadixCache):
|
||||
def _get_extra_pools(self) -> dict:
|
||||
if not isinstance(self.cache_controller, HybridCacheController):
|
||||
return {}
|
||||
if isinstance(self.kv_cache, NSATokenToKVPool):
|
||||
if isinstance(self.kv_cache, DSATokenToKVPool):
|
||||
pool = PoolTransfer(
|
||||
name=PoolName.INDEXER,
|
||||
hit_policy=PoolHitPolicy.ALL_PAGES,
|
||||
|
||||
@@ -16,7 +16,7 @@ from sglang.srt.mem_cache.deepseek_v4_memory_pool import (
|
||||
DeepSeekV4TokenToKVPool,
|
||||
HiSparseC4DevicePool,
|
||||
)
|
||||
from sglang.srt.mem_cache.memory_pool import NSATokenToKVPool
|
||||
from sglang.srt.mem_cache.memory_pool import DSATokenToKVPool
|
||||
from sglang.srt.mem_cache.memory_pool_host import HiSparseHostPoolMixin
|
||||
from sglang.srt.utils import is_cuda, is_hip
|
||||
from sglang.srt.utils.common import get_num_new_pages
|
||||
@@ -37,7 +37,7 @@ else:
|
||||
)
|
||||
|
||||
|
||||
class HiSparseNSATokenToKVPool(NSATokenToKVPool):
|
||||
class HiSparseDSATokenToKVPool(DSATokenToKVPool):
|
||||
def __init__(
|
||||
self,
|
||||
size: int,
|
||||
@@ -143,7 +143,7 @@ class HiSparseTokenToKVPoolAllocator(BaseTokenToKVPoolAllocator):
|
||||
page_size: int,
|
||||
dtype: torch.dtype,
|
||||
device: torch.device,
|
||||
kvcache: HiSparseNSATokenToKVPool,
|
||||
kvcache: HiSparseDSATokenToKVPool,
|
||||
need_sort: bool,
|
||||
host_to_device_ratio: int = 2,
|
||||
):
|
||||
|
||||
@@ -10,12 +10,12 @@ from sglang.srt.mem_cache.hybrid_cache.hybrid_cache_controller import (
|
||||
from sglang.srt.mem_cache.memory_pool_host import (
|
||||
DeepSeekV4PagedHostPool,
|
||||
DeepSeekV4StateHostPool,
|
||||
DSAIndexerPoolHost,
|
||||
HostPoolGroup,
|
||||
LogicalHostPool,
|
||||
MambaPoolHost,
|
||||
MHATokenToKVPoolHost,
|
||||
MLATokenToKVPoolHost,
|
||||
NSAIndexerPoolHost,
|
||||
PoolEntry,
|
||||
)
|
||||
|
||||
@@ -656,9 +656,9 @@ def attach_hybrid_pool_to_unified_cache(
|
||||
from sglang.srt.mem_cache.base_prefix_cache import EvictParams
|
||||
from sglang.srt.mem_cache.deepseek_v4_memory_pool import DeepSeekV4TokenToKVPool
|
||||
from sglang.srt.mem_cache.memory_pool import (
|
||||
DSATokenToKVPool,
|
||||
HybridLinearKVPool,
|
||||
MLATokenToKVPool,
|
||||
NSATokenToKVPool,
|
||||
)
|
||||
from sglang.srt.mem_cache.swa_memory_pool import SWAKVPool
|
||||
from sglang.srt.mem_cache.unified_cache_components import ComponentType
|
||||
@@ -667,7 +667,7 @@ def attach_hybrid_pool_to_unified_cache(
|
||||
kvcache = params.token_to_kv_pool_allocator.get_kvcache()
|
||||
swa_stack = isinstance(kvcache, SWAKVPool)
|
||||
mamba_stack = isinstance(kvcache, HybridLinearKVPool)
|
||||
nsa_stack = isinstance(kvcache, NSATokenToKVPool)
|
||||
dsa_stack = isinstance(kvcache, DSATokenToKVPool)
|
||||
deepseek_v4_stack = isinstance(kvcache, DeepSeekV4TokenToKVPool)
|
||||
|
||||
if deepseek_v4_stack:
|
||||
@@ -820,7 +820,7 @@ def attach_hybrid_pool_to_unified_cache(
|
||||
cache.swa_kv_pool_host
|
||||
)
|
||||
transfer_layer_num = len(full_layer_mapping | swa_layer_mapping)
|
||||
elif nsa_stack:
|
||||
elif dsa_stack:
|
||||
full_layer_mapping = {
|
||||
layer_id: layer_id for layer_id in range(full_kv_pool.layer_num)
|
||||
}
|
||||
@@ -838,7 +838,7 @@ def attach_hybrid_pool_to_unified_cache(
|
||||
storage_backend=None,
|
||||
use_mla=use_mla,
|
||||
override_kv_cache_dim=full_kv_pool.kv_cache_dim,
|
||||
sidecar_host_pool_factory=lambda kv_host_pool: NSAIndexerPoolHost(
|
||||
sidecar_host_pool_factory=lambda kv_host_pool: DSAIndexerPoolHost(
|
||||
full_kv_pool,
|
||||
kv_host_pool,
|
||||
server_args.hicache_mem_layout,
|
||||
@@ -897,7 +897,7 @@ def attach_hybrid_pool_to_unified_cache(
|
||||
pools_desc = "KV + MAMBA"
|
||||
elif swa_stack:
|
||||
pools_desc = "KV + SWA"
|
||||
elif nsa_stack:
|
||||
elif dsa_stack:
|
||||
pools_desc = "KV + INDEXER"
|
||||
else:
|
||||
pools_desc = "KV"
|
||||
@@ -911,7 +911,7 @@ def attach_hybrid_pool_to_unified_cache(
|
||||
raise
|
||||
|
||||
|
||||
def attach_hybrid_nsa_pool_to_hiradix_cache(
|
||||
def attach_hybrid_dsa_pool_to_hiradix_cache(
|
||||
radix_cache: HiRadixCache,
|
||||
params: CacheInitParams,
|
||||
server_args: ServerArgs,
|
||||
@@ -925,7 +925,7 @@ def attach_hybrid_nsa_pool_to_hiradix_cache(
|
||||
) -> None:
|
||||
"""Attach HostPoolGroup (KV + indexer) + HybridCacheController for HiRadixCache.
|
||||
|
||||
This entrypoint is currently intended only for HiRadixCache's NSA path.
|
||||
This entrypoint is currently intended only for HiRadixCache's DSA path.
|
||||
"""
|
||||
try:
|
||||
kv = radix_cache.kv_cache
|
||||
@@ -945,7 +945,7 @@ def attach_hybrid_nsa_pool_to_hiradix_cache(
|
||||
use_mla=True,
|
||||
override_kv_cache_dim=kv.kv_cache_dim,
|
||||
prefetch_threshold=prefetch_threshold,
|
||||
sidecar_host_pool_factory=lambda kv_host_pool: NSAIndexerPoolHost(
|
||||
sidecar_host_pool_factory=lambda kv_host_pool: DSAIndexerPoolHost(
|
||||
kv,
|
||||
kv_host_pool,
|
||||
server_args.hicache_mem_layout,
|
||||
@@ -961,12 +961,12 @@ def attach_hybrid_nsa_pool_to_hiradix_cache(
|
||||
radix_cache.token_to_kv_pool_host = host_pool_group
|
||||
radix_cache.cache_controller = cache_controller
|
||||
logger.info(
|
||||
"Attached hybrid NSA pool stack to HiRadixCache: pools=KV + INDEXER, "
|
||||
"Attached hybrid DSA pool stack to HiRadixCache: pools=KV + INDEXER, "
|
||||
"transfer_layer_num=%s",
|
||||
len(layer_mapping),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("attach_hybrid_nsa_pool_to_hiradix_cache failed")
|
||||
logger.exception("attach_hybrid_dsa_pool_to_hiradix_cache failed")
|
||||
raise
|
||||
|
||||
|
||||
|
||||
@@ -40,12 +40,12 @@ from sglang.jit_kernel.kvcache import can_use_store_cache, store_cache
|
||||
from sglang.srt.configs.mamba_utils import BaseLinearStateParams
|
||||
from sglang.srt.constants import GPU_MEMORY_TYPE_KV_CACHE
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.attention.nsa import index_buf_accessor
|
||||
from sglang.srt.layers.attention.nsa.quant_k_cache import (
|
||||
from sglang.srt.layers.attention.dsa import index_buf_accessor
|
||||
from sglang.srt.layers.attention.dsa.quant_k_cache import (
|
||||
quantize_k_cache,
|
||||
quantize_k_cache_separate,
|
||||
)
|
||||
from sglang.srt.layers.attention.nsa.utils import aiter_can_use_preshuffle_paged_mqa
|
||||
from sglang.srt.layers.attention.dsa.utils import aiter_can_use_preshuffle_paged_mqa
|
||||
from sglang.srt.layers.quantization.fp8_kernel import fp8_dtype, is_fp8_fnuz
|
||||
from sglang.srt.layers.radix_attention import RadixAttention
|
||||
from sglang.srt.mem_cache.utils import (
|
||||
@@ -1618,7 +1618,7 @@ class MLATokenToKVPool(KVCache):
|
||||
enable_memory_saver: bool,
|
||||
start_layer: Optional[int] = None,
|
||||
end_layer: Optional[int] = None,
|
||||
use_nsa: bool = False,
|
||||
use_dsa: bool = False,
|
||||
override_kv_cache_dim: Optional[int] = None,
|
||||
):
|
||||
super().__init__(
|
||||
@@ -1634,17 +1634,17 @@ class MLATokenToKVPool(KVCache):
|
||||
|
||||
self.kv_lora_rank = kv_lora_rank
|
||||
self.qk_rope_head_dim = qk_rope_head_dim
|
||||
self.use_nsa = use_nsa
|
||||
self.nsa_kv_cache_store_fp8 = (
|
||||
use_nsa
|
||||
self.use_dsa = use_dsa
|
||||
self.dsa_kv_cache_store_fp8 = (
|
||||
use_dsa
|
||||
and dtype == torch.float8_e4m3fn
|
||||
and override_kv_cache_dim is not None
|
||||
)
|
||||
# When override_kv_cache_dim is provided with nsa model, we assume the
|
||||
# When override_kv_cache_dim is provided with dsa model, we assume the
|
||||
# override kv cache dim is correct and use it directly.
|
||||
self.kv_cache_dim = (
|
||||
override_kv_cache_dim
|
||||
if self.nsa_kv_cache_store_fp8
|
||||
if self.dsa_kv_cache_store_fp8
|
||||
else (kv_lora_rank + qk_rope_head_dim)
|
||||
)
|
||||
|
||||
@@ -1655,8 +1655,8 @@ class MLATokenToKVPool(KVCache):
|
||||
dtype=torch.uint64,
|
||||
device=self.device,
|
||||
)
|
||||
if not use_nsa:
|
||||
# NSA will allocate indexer KV cache later and then log the total size
|
||||
if not use_dsa:
|
||||
# DSA will allocate indexer KV cache later and then log the total size
|
||||
self._finalize_allocation_log(size)
|
||||
|
||||
def _create_buffers(self):
|
||||
@@ -1726,7 +1726,7 @@ class MLATokenToKVPool(KVCache):
|
||||
cache_v: torch.Tensor,
|
||||
):
|
||||
layer_id = layer.layer_id
|
||||
assert not self.nsa_kv_cache_store_fp8
|
||||
assert not self.dsa_kv_cache_store_fp8
|
||||
if cache_k.dtype != self.dtype:
|
||||
cache_k = cache_k.to(self.dtype)
|
||||
|
||||
@@ -1746,7 +1746,7 @@ class MLATokenToKVPool(KVCache):
|
||||
):
|
||||
layer_id = layer.layer_id
|
||||
|
||||
if _is_hip and self.use_nsa and self.dtype == fp8_dtype:
|
||||
if _is_hip and self.use_dsa and self.dtype == fp8_dtype:
|
||||
# HIP FP8 path uses raw MLA KV layout (nope + rope) without per-block scales.
|
||||
# Fuse BF16/FP16 -> FP8 cast with paged KV write.
|
||||
set_mla_kv_buffer_triton_fp8_quant(
|
||||
@@ -1756,7 +1756,7 @@ class MLATokenToKVPool(KVCache):
|
||||
cache_k_rope,
|
||||
fp8_dtype,
|
||||
)
|
||||
elif self.nsa_kv_cache_store_fp8:
|
||||
elif self.dsa_kv_cache_store_fp8:
|
||||
# OPTIMIZATION: Quantize k_nope and k_rope separately to avoid concat overhead
|
||||
# This also enables reuse of set_mla_kv_buffer_triton two-tensor write path
|
||||
# quantize_k_cache_separate returns (nope_part, rope_part) as uint8 bytes
|
||||
@@ -1905,7 +1905,7 @@ class MLATokenToKVPoolFP4(MLATokenToKVPool):
|
||||
cache_v: torch.Tensor,
|
||||
):
|
||||
layer_id = layer.layer_id
|
||||
assert not self.nsa_kv_cache_store_fp8
|
||||
assert not self.dsa_kv_cache_store_fp8
|
||||
if cache_k.dtype != self.dtype:
|
||||
from sglang.srt.layers.quantization.kvfp4_tensor import KVFP4QuantizeUtil
|
||||
|
||||
@@ -1930,7 +1930,7 @@ class MLATokenToKVPoolFP4(MLATokenToKVPool):
|
||||
):
|
||||
layer_id = layer.layer_id
|
||||
|
||||
if self.nsa_kv_cache_store_fp8:
|
||||
if self.dsa_kv_cache_store_fp8:
|
||||
# original cache_k: (num_tokens, num_heads 1, hidden 576); we unsqueeze the page_size=1 dim here
|
||||
# TODO no need to cat
|
||||
cache_k = torch.cat([cache_k_nope, cache_k_rope], dim=-1)
|
||||
@@ -1968,7 +1968,7 @@ class MLATokenToKVPoolFP4(MLATokenToKVPool):
|
||||
)
|
||||
|
||||
|
||||
class NSATokenToKVPool(MLATokenToKVPool):
|
||||
class DSATokenToKVPool(MLATokenToKVPool):
|
||||
quant_block_size = 128
|
||||
index_k_with_scale_buffer_dtype = torch.uint8
|
||||
rope_storage_dtype = torch.bfloat16 # rope is always stored in bf16
|
||||
@@ -2005,7 +2005,7 @@ class NSATokenToKVPool(MLATokenToKVPool):
|
||||
enable_memory_saver,
|
||||
start_layer,
|
||||
end_layer,
|
||||
use_nsa=True,
|
||||
use_dsa=True,
|
||||
override_kv_cache_dim=override_dim,
|
||||
)
|
||||
# self.index_k_dtype = torch.float8_e4m3fn
|
||||
@@ -2013,7 +2013,7 @@ class NSATokenToKVPool(MLATokenToKVPool):
|
||||
self.index_head_dim = index_head_dim
|
||||
if index_buf_size is None:
|
||||
index_buf_size = size
|
||||
# num head == 1 and head dim == 128 for index_k in NSA
|
||||
# num head == 1 and head dim == 128 for index_k in DSA
|
||||
assert index_head_dim == 128
|
||||
|
||||
if _is_hip:
|
||||
@@ -2024,7 +2024,7 @@ class NSATokenToKVPool(MLATokenToKVPool):
|
||||
else:
|
||||
assert (
|
||||
self.page_size == 1
|
||||
), f"HIP legacy NSA path requires page_size == 1, got {self.page_size}"
|
||||
), f"HIP legacy DSA path requires page_size == 1, got {self.page_size}"
|
||||
else:
|
||||
assert self.page_size == 64
|
||||
with (
|
||||
@@ -2133,11 +2133,11 @@ class NSATokenToKVPool(MLATokenToKVPool):
|
||||
)
|
||||
|
||||
def get_cpu_copy(self, indices):
|
||||
# NSA keeps a page-indexed index_k_with_scale_buffer alongside kv_buffer.
|
||||
# DSA keeps a page-indexed index_k_with_scale_buffer alongside kv_buffer.
|
||||
# Retract frees the slots/pages and they get reused by other reqs'
|
||||
# set_index_k_scale_buffer, so we must offload it here too -- otherwise
|
||||
# resume restores kv_buffer but leaves foreign index/scale in place and
|
||||
# NSA attention reads garbage at those token positions.
|
||||
# DSA attention reads garbage at those token positions.
|
||||
kv_cache_cpu = super().get_cpu_copy(indices)
|
||||
|
||||
page_indices = indices[:: self.page_size] // self.page_size
|
||||
|
||||
@@ -31,11 +31,11 @@ from sglang.jit_kernel.hicache import (
|
||||
transfer_hicache_one_layer_mla as jit_transfer_hicache_one_layer_mla,
|
||||
)
|
||||
from sglang.srt.mem_cache.memory_pool import (
|
||||
DSATokenToKVPool,
|
||||
KVCache,
|
||||
MambaPool,
|
||||
MHATokenToKVPool,
|
||||
MLATokenToKVPool,
|
||||
NSATokenToKVPool,
|
||||
)
|
||||
from sglang.srt.utils import is_cuda, is_hip, is_mps, is_npu, is_xpu
|
||||
|
||||
@@ -2608,14 +2608,14 @@ class HostPoolGroup:
|
||||
)
|
||||
|
||||
|
||||
class NSAIndexerPoolHost(HostKVCache):
|
||||
"""Host-side NSA index buffers only. Slot layout matches the anchor MLA host pool."""
|
||||
class DSAIndexerPoolHost(HostKVCache):
|
||||
"""Host-side DSA index buffers only. Slot layout matches the anchor MLA host pool."""
|
||||
|
||||
device_pool: NSATokenToKVPool
|
||||
device_pool: DSATokenToKVPool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device_pool: NSATokenToKVPool,
|
||||
device_pool: DSATokenToKVPool,
|
||||
anchor_host: MLATokenToKVPoolHost,
|
||||
layout: str,
|
||||
pin_memory: bool = True,
|
||||
@@ -2635,7 +2635,7 @@ class NSAIndexerPoolHost(HostKVCache):
|
||||
|
||||
self.index_head_dim = device_pool.index_head_dim
|
||||
self.indexer_quant_block_size = device_pool.quant_block_size
|
||||
self.indexer_dtype = NSATokenToKVPool.index_k_with_scale_buffer_dtype
|
||||
self.indexer_dtype = DSATokenToKVPool.index_k_with_scale_buffer_dtype
|
||||
self.indexer_size_per_token = (
|
||||
self.index_head_dim
|
||||
+ self.index_head_dim // self.indexer_quant_block_size * 4
|
||||
@@ -2658,12 +2658,12 @@ class NSAIndexerPoolHost(HostKVCache):
|
||||
available_bytes = host_mem.available - HICACHE_HOST_MEMORY_RESERVE_BYTES
|
||||
if requested_bytes > available_bytes:
|
||||
raise ValueError(
|
||||
f"Not enough host memory for NSA indexer hierarchical cache. "
|
||||
f"Not enough host memory for DSA indexer hierarchical cache. "
|
||||
f"Requesting {requested_bytes / 1e9:.2f} GB but only have "
|
||||
f"{available_bytes / 1e9:.2f} GB free."
|
||||
)
|
||||
logger.info(
|
||||
"Allocating %.2f GB host memory for NSA indexer (layout=%s).",
|
||||
"Allocating %.2f GB host memory for DSA indexer (layout=%s).",
|
||||
requested_bytes / 1e9,
|
||||
layout,
|
||||
)
|
||||
@@ -2726,7 +2726,7 @@ class NSAIndexerPoolHost(HostKVCache):
|
||||
return host_indices, device_indices
|
||||
if host_indices.numel() % self.page_size != 0:
|
||||
raise ValueError(
|
||||
"Index buffer transfer expects page-aligned indices for NSA."
|
||||
"Index buffer transfer expects page-aligned indices for DSA."
|
||||
)
|
||||
host_page_indices = (
|
||||
host_indices.reshape(-1, self.page_size)[:, 0] // self.page_size
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from sglang.srt.mem_cache.sparsity.algorithms import (
|
||||
BaseSparseAlgorithm,
|
||||
BaseSparseAlgorithmImpl,
|
||||
DeepSeekNSAAlgorithm,
|
||||
DeepSeekDSAAlgorithm,
|
||||
QuestAlgorithm,
|
||||
)
|
||||
from sglang.srt.mem_cache.sparsity.backend import BackendAdaptor, FlashAttentionAdaptor
|
||||
@@ -17,7 +17,7 @@ __all__ = [
|
||||
"BaseSparseAlgorithm",
|
||||
"BaseSparseAlgorithmImpl",
|
||||
"QuestAlgorithm",
|
||||
"DeepSeekNSAAlgorithm",
|
||||
"DeepSeekDSAAlgorithm",
|
||||
"BackendAdaptor",
|
||||
"FlashAttentionAdaptor",
|
||||
"SparseConfig",
|
||||
|
||||
@@ -2,12 +2,12 @@ from sglang.srt.mem_cache.sparsity.algorithms.base_algorithm import (
|
||||
BaseSparseAlgorithm,
|
||||
BaseSparseAlgorithmImpl,
|
||||
)
|
||||
from sglang.srt.mem_cache.sparsity.algorithms.deepseek_nsa import DeepSeekNSAAlgorithm
|
||||
from sglang.srt.mem_cache.sparsity.algorithms.deepseek_dsa import DeepSeekDSAAlgorithm
|
||||
from sglang.srt.mem_cache.sparsity.algorithms.quest_algorithm import QuestAlgorithm
|
||||
|
||||
__all__ = [
|
||||
"BaseSparseAlgorithm",
|
||||
"BaseSparseAlgorithmImpl",
|
||||
"DeepSeekNSAAlgorithm",
|
||||
"DeepSeekDSAAlgorithm",
|
||||
"QuestAlgorithm",
|
||||
]
|
||||
|
||||
+4
-4
@@ -7,12 +7,12 @@ from sglang.srt.mem_cache.sparsity.algorithms.base_algorithm import (
|
||||
)
|
||||
|
||||
|
||||
class DeepSeekNSAAlgorithm(BaseSparseAlgorithmImpl):
|
||||
class DeepSeekDSAAlgorithm(BaseSparseAlgorithmImpl):
|
||||
"""
|
||||
Sparse attention algorithm for DeepSeek NSA.
|
||||
Sparse attention algorithm for DeepSeek DSA.
|
||||
|
||||
This algorithm uses NSA's native indexer for TopK retrieval.
|
||||
Overrides all parent methods as NSA has its own specialized flow.
|
||||
This algorithm uses DSA's native indexer for TopK retrieval.
|
||||
Overrides all parent methods as DSA has its own specialized flow.
|
||||
"""
|
||||
|
||||
def __init__(self, config, device: torch.device, **kwargs):
|
||||
@@ -1,7 +1,7 @@
|
||||
from sglang.srt.mem_cache.sparsity.backend.backend_adaptor import (
|
||||
BackendAdaptor,
|
||||
DSABackendAdaptor,
|
||||
FlashAttentionAdaptor,
|
||||
NSABackendAdaptor,
|
||||
)
|
||||
|
||||
__all__ = ["BackendAdaptor", "FlashAttentionAdaptor", "NSABackendAdaptor"]
|
||||
__all__ = ["BackendAdaptor", "FlashAttentionAdaptor", "DSABackendAdaptor"]
|
||||
|
||||
@@ -46,8 +46,8 @@ class BackendAdaptor(ABC):
|
||||
pass
|
||||
|
||||
|
||||
class NSABackendAdaptor(BackendAdaptor):
|
||||
"""Adaptor for NSA (Native Sparse Attention) backend."""
|
||||
class DSABackendAdaptor(BackendAdaptor):
|
||||
"""Adaptor for DSA (DeepSeek Sparse Attention) backend."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -70,9 +70,9 @@ class NSABackendAdaptor(BackendAdaptor):
|
||||
**kwargs,
|
||||
) -> Optional[torch.Tensor]:
|
||||
"""
|
||||
Transform logical page indices to physical device indices for NSA backend.
|
||||
Transform logical page indices to physical device indices for DSA backend.
|
||||
"""
|
||||
# TODO: Implement NSA backend adaptor logic
|
||||
# TODO: Implement DSA backend adaptor logic
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ from typing import Optional
|
||||
import torch
|
||||
|
||||
from sglang.srt.mem_cache.sparsity.algorithms.base_algorithm import BaseSparseAlgorithm
|
||||
from sglang.srt.mem_cache.sparsity.algorithms.deepseek_nsa import DeepSeekNSAAlgorithm
|
||||
from sglang.srt.mem_cache.sparsity.algorithms.deepseek_dsa import DeepSeekDSAAlgorithm
|
||||
from sglang.srt.mem_cache.sparsity.algorithms.quest_algorithm import QuestAlgorithm
|
||||
from sglang.srt.mem_cache.sparsity.backend.backend_adaptor import (
|
||||
DSABackendAdaptor,
|
||||
FlashAttentionAdaptor,
|
||||
NSABackendAdaptor,
|
||||
)
|
||||
from sglang.srt.mem_cache.sparsity.core.sparse_coordinator import (
|
||||
SparseConfig,
|
||||
@@ -22,7 +22,7 @@ _global_sparse_coordinator: Optional[SparseCoordinator] = None
|
||||
|
||||
_ALGORITHM_REGISTRY = {
|
||||
"quest": lambda config, device, **kw: QuestAlgorithm(config, device, **kw),
|
||||
"deepseek_nsa": lambda config, device, **kw: DeepSeekNSAAlgorithm(
|
||||
"deepseek_dsa": lambda config, device, **kw: DeepSeekDSAAlgorithm(
|
||||
config, device, **kw
|
||||
),
|
||||
}
|
||||
@@ -49,8 +49,8 @@ def _create_backend_adaptor(
|
||||
req_to_token_pool,
|
||||
):
|
||||
"""Create backend adaptor."""
|
||||
if isinstance(sparse_algorithm, DeepSeekNSAAlgorithm):
|
||||
return NSABackendAdaptor(device, req_to_token_pool)
|
||||
if isinstance(sparse_algorithm, DeepSeekDSAAlgorithm):
|
||||
return DSABackendAdaptor(device, req_to_token_pool)
|
||||
|
||||
if backend in ["fa3", "flashattention"]:
|
||||
return FlashAttentionAdaptor(device)
|
||||
|
||||
@@ -43,7 +43,7 @@ from sglang.srt.distributed.parallel_state import (
|
||||
)
|
||||
from sglang.srt.dllm.config import DllmConfig
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.attention.nsa.utils import is_nsa_enable_prefill_cp
|
||||
from sglang.srt.layers.attention.dsa.utils import is_dsa_enable_prefill_cp
|
||||
from sglang.srt.layers.dp_attention import (
|
||||
DpPaddingMode,
|
||||
get_attention_cp_size,
|
||||
@@ -285,7 +285,7 @@ class DecodeInputBuffers(ForwardInputBuffers):
|
||||
seq_len_fill_value: int,
|
||||
require_gathered_buffer: bool,
|
||||
num_tokens_per_bs: int,
|
||||
nsa_enable_prefill_cp: bool,
|
||||
dsa_enable_prefill_cp: bool,
|
||||
enable_num_token_non_padded_flag: bool,
|
||||
pp_proxy_tensors: Optional[PPProxyTensors] = None,
|
||||
):
|
||||
@@ -354,7 +354,7 @@ class DecodeInputBuffers(ForwardInputBuffers):
|
||||
self.global_num_tokens_for_logprob_gpu.fill_(bs * num_tokens_per_bs)
|
||||
|
||||
if enable_num_token_non_padded_flag:
|
||||
if require_gathered_buffer and not nsa_enable_prefill_cp:
|
||||
if require_gathered_buffer and not dsa_enable_prefill_cp:
|
||||
num_tokens_per_dp = bs * num_tokens_per_bs
|
||||
local = compute_local_num_token_non_padded(
|
||||
global_num_token_non_padded=forward_batch.num_token_non_padded,
|
||||
@@ -588,7 +588,7 @@ class CudaGraphRunner:
|
||||
|
||||
self.attn_tp_size = get_attention_tp_size()
|
||||
self.attn_tp_rank = get_attention_tp_rank()
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
|
||||
self.deepep_adapter = DeepEPCudaGraphRunnerAdapter()
|
||||
|
||||
@@ -946,7 +946,7 @@ class CudaGraphRunner:
|
||||
if (
|
||||
enable_num_token_non_padded()
|
||||
and self.require_gathered_buffer
|
||||
and not self.nsa_enable_prefill_cp
|
||||
and not self.dsa_enable_prefill_cp
|
||||
):
|
||||
local = compute_local_num_token_non_padded(
|
||||
global_num_token_non_padded=buffers.num_token_non_padded,
|
||||
@@ -1211,7 +1211,7 @@ class CudaGraphRunner:
|
||||
seq_len_fill_value=self.seq_len_fill_value,
|
||||
require_gathered_buffer=self.require_gathered_buffer,
|
||||
num_tokens_per_bs=self.num_tokens_per_bs,
|
||||
nsa_enable_prefill_cp=self.nsa_enable_prefill_cp,
|
||||
dsa_enable_prefill_cp=self.dsa_enable_prefill_cp,
|
||||
enable_num_token_non_padded_flag=enable_num_token_non_padded(),
|
||||
pp_proxy_tensors=pp_proxy_tensors,
|
||||
)
|
||||
|
||||
@@ -110,7 +110,7 @@ from sglang.srt.layers.attention.attention_registry import (
|
||||
ATTENTION_BACKENDS,
|
||||
attn_backend_wrapper,
|
||||
)
|
||||
from sglang.srt.layers.attention.nsa.utils import is_nsa_enable_prefill_cp
|
||||
from sglang.srt.layers.attention.dsa.utils import is_dsa_enable_prefill_cp
|
||||
from sglang.srt.layers.attention.tbo_backend import TboAttnBackend
|
||||
from sglang.srt.layers.dp_attention import (
|
||||
DpPaddingMode,
|
||||
@@ -246,7 +246,8 @@ MLA_ATTENTION_BACKENDS = [
|
||||
"trtllm_mla",
|
||||
"tokenspeed_mla",
|
||||
"ascend",
|
||||
"nsa",
|
||||
"dsa",
|
||||
"nsa", # Deprecated alias for "dsa"
|
||||
"intel_xpu",
|
||||
]
|
||||
|
||||
@@ -3061,7 +3062,7 @@ class ModelRunner(ModelRunnerKVCacheMixin):
|
||||
) -> Union[LogitsProcessorOutput, PPProxyTensors]:
|
||||
# In DP Attention, IDLE batches are padded (batch_size > 0) for MLP sync.
|
||||
# in this case, we need to reinit the forward metadata, otherwise the stale
|
||||
# metadata causes batch_size mismatch in attention kernel(e.g. NSA Indexer).
|
||||
# metadata causes batch_size mismatch in attention kernel(e.g. DSA Indexer).
|
||||
if forward_batch.batch_size > 0:
|
||||
self.attn_backend.init_forward_metadata(forward_batch)
|
||||
|
||||
@@ -3237,7 +3238,7 @@ class ModelRunner(ModelRunnerKVCacheMixin):
|
||||
forward_batch.num_token_non_padded is not None
|
||||
and forward_batch.global_num_tokens_gpu is not None
|
||||
and require_gathered_buffer(self.server_args)
|
||||
and not is_nsa_enable_prefill_cp()
|
||||
and not is_dsa_enable_prefill_cp()
|
||||
):
|
||||
forward_batch.adjust_num_token_non_padded_for_attn_tp(
|
||||
server_args=self.server_args,
|
||||
|
||||
@@ -6,8 +6,8 @@ from typing import TYPE_CHECKING
|
||||
import torch
|
||||
|
||||
from sglang.srt.configs.model_config import (
|
||||
get_nsa_index_head_dim,
|
||||
is_deepseek_nsa,
|
||||
get_dsa_index_head_dim,
|
||||
is_deepseek_dsa,
|
||||
is_deepseek_v4,
|
||||
)
|
||||
from sglang.srt.distributed.parallel_state import get_world_group
|
||||
@@ -20,10 +20,11 @@ from sglang.srt.mem_cache.allocator import (
|
||||
from sglang.srt.mem_cache.deepseek_v4_memory_pool import DeepSeekV4TokenToKVPool
|
||||
from sglang.srt.mem_cache.hisparse_memory_pool import (
|
||||
DeepSeekV4HiSparseTokenToKVPoolAllocator,
|
||||
HiSparseNSATokenToKVPool,
|
||||
HiSparseDSATokenToKVPool,
|
||||
HiSparseTokenToKVPoolAllocator,
|
||||
)
|
||||
from sglang.srt.mem_cache.memory_pool import (
|
||||
DSATokenToKVPool,
|
||||
HybridLinearKVPool,
|
||||
HybridReqToTokenPool,
|
||||
MHATokenToKVPool,
|
||||
@@ -31,7 +32,6 @@ from sglang.srt.mem_cache.memory_pool import (
|
||||
MLATokenToKVPool,
|
||||
MLATokenToKVPoolFP4,
|
||||
NoOpMHATokenToKVPool,
|
||||
NSATokenToKVPool,
|
||||
ReqToTokenPool,
|
||||
)
|
||||
from sglang.srt.mem_cache.swa_memory_pool import SWAKVPool, SWATokenToKVPoolAllocator
|
||||
@@ -137,36 +137,36 @@ class ModelRunnerKVCacheMixin:
|
||||
return total_rest_memory - mamba_state_memory
|
||||
|
||||
def calculate_mla_kv_cache_dim(self: ModelRunner) -> int:
|
||||
is_nsa_model = is_deepseek_nsa(self.model_config.hf_config)
|
||||
is_dsa_model = is_deepseek_dsa(self.model_config.hf_config)
|
||||
kv_cache_dtype = self.kv_cache_dtype
|
||||
kv_lora_rank = self.model_config.kv_lora_rank
|
||||
qk_rope_head_dim = self.model_config.qk_rope_head_dim
|
||||
kv_cache_dim = kv_lora_rank + qk_rope_head_dim # default mla kv cache dim
|
||||
|
||||
# For non-NSA models, MLA kv cache dim is simply kv_lora_rank + qk_rope_head_dim
|
||||
if not is_nsa_model:
|
||||
# For non-DSA models, MLA kv cache dim is simply kv_lora_rank + qk_rope_head_dim
|
||||
if not is_dsa_model:
|
||||
return kv_cache_dim
|
||||
|
||||
# TRTLLM backend does not override kv_cache_dim for MLA kv cache
|
||||
# Assuming nsa prefill and decode backends are the same when using trtllm MLA backend,
|
||||
# Assuming dsa prefill and decode backends are the same when using trtllm MLA backend,
|
||||
# since it is not compatible for trtllm and other mla attn backend due to the different
|
||||
# kv cache layout.
|
||||
if (
|
||||
self.server_args.nsa_prefill_backend == "trtllm"
|
||||
or self.server_args.nsa_decode_backend == "trtllm"
|
||||
self.server_args.dsa_prefill_backend == "trtllm"
|
||||
or self.server_args.dsa_decode_backend == "trtllm"
|
||||
):
|
||||
return kv_cache_dim
|
||||
|
||||
# On HIP with TileLang backend, keep the default MLA KV cache dimension.
|
||||
# FP8 attention uses the nope(512 fp8) + rope(64 fp8) layout, without extra per-block scales.
|
||||
if _is_hip and (
|
||||
self.server_args.nsa_prefill_backend == "tilelang"
|
||||
or self.server_args.nsa_decode_backend == "tilelang"
|
||||
self.server_args.dsa_prefill_backend == "tilelang"
|
||||
or self.server_args.dsa_decode_backend == "tilelang"
|
||||
):
|
||||
return kv_cache_dim
|
||||
|
||||
quant_block_size = NSATokenToKVPool.quant_block_size
|
||||
rope_storage_dtype = NSATokenToKVPool.rope_storage_dtype
|
||||
quant_block_size = DSATokenToKVPool.quant_block_size
|
||||
rope_storage_dtype = DSATokenToKVPool.rope_storage_dtype
|
||||
# Calculate override_kv_cache_dim for FP8 storage in backends that use scaled KV layout (excluding TRTLLM and HIP+TileLang).
|
||||
# kv_lora_rank + scale storage (kv_lora_rank // quant_block_size * 4 bytes) + rope dimension storage
|
||||
# Note: rope dimension is stored in original dtype (bf16), not quantized to fp8
|
||||
@@ -199,7 +199,7 @@ class ModelRunnerKVCacheMixin:
|
||||
|
||||
def _validate_prefill_only_disable_kv_cache_pool_family(
|
||||
self: ModelRunner,
|
||||
is_nsa_model: bool,
|
||||
is_dsa_model: bool,
|
||||
is_dsv4_model: bool,
|
||||
current_platform,
|
||||
):
|
||||
@@ -215,8 +215,8 @@ class ModelRunnerKVCacheMixin:
|
||||
self.server_args.attention_backend == "ascend" and not self.mambaish_config
|
||||
):
|
||||
unsupported_pool_family = "NPU/Ascend KV pool"
|
||||
elif self.use_mla_backend and is_nsa_model:
|
||||
unsupported_pool_family = "NSA/MLA KV pool"
|
||||
elif self.use_mla_backend and is_dsa_model:
|
||||
unsupported_pool_family = "DSA/MLA KV pool"
|
||||
elif self.use_mla_backend and not self.mambaish_config:
|
||||
unsupported_pool_family = "MLA KV pool"
|
||||
elif self.is_hybrid_swa:
|
||||
@@ -328,14 +328,14 @@ class ModelRunnerKVCacheMixin:
|
||||
assert self.is_draft_worker
|
||||
|
||||
# Initialize token_to_kv_pool
|
||||
is_nsa_model = is_deepseek_nsa(self.model_config.hf_config)
|
||||
is_dsa_model = is_deepseek_dsa(self.model_config.hf_config)
|
||||
is_dsv4_model = is_deepseek_v4(self.model_config.hf_config)
|
||||
|
||||
# Out-of-tree platform plugin system — used by elif below
|
||||
from sglang.srt.platforms import current_platform
|
||||
|
||||
self._validate_prefill_only_disable_kv_cache_pool_family(
|
||||
is_nsa_model, is_dsv4_model, current_platform
|
||||
is_dsa_model, is_dsv4_model, current_platform
|
||||
)
|
||||
|
||||
if is_dsv4_model:
|
||||
@@ -375,8 +375,8 @@ class ModelRunnerKVCacheMixin:
|
||||
enable_hisparse=self.enable_hisparse,
|
||||
)
|
||||
elif current_platform.is_out_of_tree() and not self.mambaish_config:
|
||||
if self.use_mla_backend and is_nsa_model:
|
||||
PoolCls = current_platform.get_nsa_kv_pool_cls()
|
||||
if self.use_mla_backend and is_dsa_model:
|
||||
PoolCls = current_platform.get_dsa_kv_pool_cls()
|
||||
self.token_to_kv_pool = PoolCls(
|
||||
self.max_total_num_tokens,
|
||||
page_size=self.page_size,
|
||||
@@ -389,7 +389,7 @@ class ModelRunnerKVCacheMixin:
|
||||
enable_memory_saver=self.server_args.enable_memory_saver,
|
||||
start_layer=self.start_layer,
|
||||
end_layer=self.end_layer,
|
||||
index_head_dim=get_nsa_index_head_dim(self.model_config.hf_config),
|
||||
index_head_dim=get_dsa_index_head_dim(self.model_config.hf_config),
|
||||
)
|
||||
elif self.use_mla_backend:
|
||||
PoolCls = current_platform.get_mla_kv_pool_cls()
|
||||
@@ -400,7 +400,7 @@ class ModelRunnerKVCacheMixin:
|
||||
kv_lora_rank=self.model_config.kv_lora_rank,
|
||||
qk_rope_head_dim=self.model_config.qk_rope_head_dim,
|
||||
index_head_dim=(
|
||||
self.model_config.index_head_dim if is_nsa_model else None
|
||||
self.model_config.index_head_dim if is_dsa_model else None
|
||||
),
|
||||
layer_num=self.num_effective_layers,
|
||||
device=self.device,
|
||||
@@ -472,7 +472,7 @@ class ModelRunnerKVCacheMixin:
|
||||
kv_lora_rank=self.model_config.kv_lora_rank,
|
||||
qk_rope_head_dim=self.model_config.qk_rope_head_dim,
|
||||
index_head_dim=(
|
||||
self.model_config.index_head_dim if is_nsa_model else None
|
||||
self.model_config.index_head_dim if is_dsa_model else None
|
||||
),
|
||||
layer_num=self.num_effective_layers,
|
||||
device=self.device,
|
||||
@@ -499,9 +499,9 @@ class ModelRunnerKVCacheMixin:
|
||||
start_layer=self.start_layer,
|
||||
end_layer=self.end_layer,
|
||||
)
|
||||
elif self.use_mla_backend and is_nsa_model:
|
||||
elif self.use_mla_backend and is_dsa_model:
|
||||
PoolCls = (
|
||||
HiSparseNSATokenToKVPool if self.enable_hisparse else NSATokenToKVPool
|
||||
HiSparseDSATokenToKVPool if self.enable_hisparse else DSATokenToKVPool
|
||||
)
|
||||
pool_kwargs = {}
|
||||
if self.enable_hisparse:
|
||||
@@ -522,11 +522,11 @@ class ModelRunnerKVCacheMixin:
|
||||
enable_memory_saver=self.server_args.enable_memory_saver,
|
||||
start_layer=self.start_layer,
|
||||
end_layer=self.end_layer,
|
||||
index_head_dim=get_nsa_index_head_dim(self.model_config.hf_config),
|
||||
index_head_dim=get_dsa_index_head_dim(self.model_config.hf_config),
|
||||
**pool_kwargs,
|
||||
)
|
||||
elif self.use_mla_backend and not self.mambaish_config:
|
||||
assert not is_nsa_model
|
||||
assert not is_dsa_model
|
||||
if is_float4_e2m1fn_x2(self.kv_cache_dtype):
|
||||
self.token_to_kv_pool = MLATokenToKVPoolFP4(
|
||||
self.max_total_num_tokens,
|
||||
|
||||
@@ -20,14 +20,14 @@ from typing import TYPE_CHECKING, Optional
|
||||
import torch
|
||||
|
||||
from sglang.srt.configs.model_config import (
|
||||
get_nsa_index_head_dim,
|
||||
is_deepseek_nsa,
|
||||
get_dsa_index_head_dim,
|
||||
is_deepseek_dsa,
|
||||
is_deepseek_v4,
|
||||
)
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
from sglang.srt.mem_cache.deepseek_v4_memory_pool import get_compress_state_ring_size
|
||||
from sglang.srt.mem_cache.memory_pool import NSATokenToKVPool
|
||||
from sglang.srt.mem_cache.memory_pool import DSATokenToKVPool
|
||||
from sglang.srt.utils.common import is_float4_e2m1fn_x2
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class MemoryPoolConfigurator:
|
||||
|
||||
|
||||
class DefaultPoolConfigurator(MemoryPoolConfigurator):
|
||||
"""Configurator for standard models: MHA, MLA, NSA, FP4.
|
||||
"""Configurator for standard models: MHA, MLA, DSA, FP4.
|
||||
|
||||
coeff = cell_size (bytes per token across all layers)
|
||||
bias = 0
|
||||
@@ -149,15 +149,15 @@ class DefaultPoolConfigurator(MemoryPoolConfigurator):
|
||||
* kv_size
|
||||
)
|
||||
|
||||
# Add indexer KV cache overhead for NSA models (DeepSeek V3.2)
|
||||
if is_deepseek_nsa(model_config.hf_config):
|
||||
index_head_dim = get_nsa_index_head_dim(model_config.hf_config)
|
||||
# Add indexer KV cache overhead for DSA models (DeepSeek V3.2)
|
||||
if is_deepseek_dsa(model_config.hf_config):
|
||||
index_head_dim = get_dsa_index_head_dim(model_config.hf_config)
|
||||
indexer_size_per_token = (
|
||||
index_head_dim
|
||||
+ index_head_dim // NSATokenToKVPool.quant_block_size * 4
|
||||
+ index_head_dim // DSATokenToKVPool.quant_block_size * 4
|
||||
)
|
||||
element_size = torch._utils._element_size(
|
||||
NSATokenToKVPool.index_k_with_scale_buffer_dtype
|
||||
DSATokenToKVPool.index_k_with_scale_buffer_dtype
|
||||
)
|
||||
cell_size += indexer_size_per_token * num_layers * element_size
|
||||
else:
|
||||
|
||||
@@ -147,9 +147,9 @@ def handle_attention_aiter(attn, forward_batch):
|
||||
return AttnForwardMethod.MLA
|
||||
|
||||
|
||||
def handle_attention_nsa(attn, forward_batch):
|
||||
def handle_attention_dsa(attn, forward_batch):
|
||||
"""
|
||||
Dispatch logic is centralized in NativeSparseAttnBackend.set_nsa_prefill_impl and executed
|
||||
Dispatch logic is centralized in DeepseekSparseAttnBackend.set_dsa_prefill_impl and executed
|
||||
in init_forward_metadata. Read the decision from backend.use_mha.
|
||||
"""
|
||||
|
||||
@@ -191,6 +191,9 @@ AttentionBackendRegistry.register("fa4", handle_attention_fa4)
|
||||
AttentionBackendRegistry.register("trtllm_mla", handle_attention_trtllm_mla)
|
||||
AttentionBackendRegistry.register("tokenspeed_mla", handle_attention_tokenspeed_mla)
|
||||
AttentionBackendRegistry.register("aiter", handle_attention_aiter)
|
||||
AttentionBackendRegistry.register("nsa", handle_attention_nsa)
|
||||
AttentionBackendRegistry.register("dsa", handle_attention_dsa)
|
||||
AttentionBackendRegistry.register(
|
||||
"nsa", handle_attention_dsa
|
||||
) # Deprecated alias; use "dsa"
|
||||
AttentionBackendRegistry.register("triton", handle_attention_triton)
|
||||
AttentionBackendRegistry.register("intel_xpu", handle_attention_intel_xpu)
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
|
||||
import torch
|
||||
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.attention.nsa.dequant_k_cache import dequantize_k_cache_paged
|
||||
from sglang.srt.layers.attention.dsa.dequant_k_cache import dequantize_k_cache_paged
|
||||
from sglang.srt.layers.attention.tbo_backend import TboAttnBackend
|
||||
from sglang.srt.layers.attention.utils import concat_and_cast_mha_k_triton
|
||||
from sglang.srt.layers.communicator import get_attn_tp_context
|
||||
@@ -122,10 +122,10 @@ class DeepseekMHAForwardMixin:
|
||||
)
|
||||
)
|
||||
|
||||
# NSA Indexer: cache quantized keys, auto-skip topk for sequences <= nsa_index_topk
|
||||
# DSA Indexer: cache quantized keys, auto-skip topk for sequences <= dsa_index_topk
|
||||
|
||||
if self.use_nsa:
|
||||
# NSA requires unquantized q_lora for the indexer. When q_b_proj is FP8
|
||||
if self.use_dsa:
|
||||
# DSA requires unquantized q_lora for the indexer. When q_b_proj is FP8
|
||||
# on gfx95, we can still use fused RMSNorm+FP8 quant, but MUST request
|
||||
# the unquantized output for q_lora; otherwise q_lora becomes the (fp8,scale)
|
||||
# tuple.
|
||||
@@ -230,15 +230,15 @@ class DeepseekMHAForwardMixin:
|
||||
and sum(forward_batch.extend_prefix_lens_cpu) != 0
|
||||
):
|
||||
if (
|
||||
self.use_nsa
|
||||
self.use_dsa
|
||||
and self.kv_cache_dtype == "fp8_e4m3"
|
||||
and (
|
||||
not get_global_server_args().nsa_decode_backend == "trtllm"
|
||||
or not get_global_server_args().nsa_prefill_backend == "trtllm"
|
||||
not get_global_server_args().dsa_decode_backend == "trtllm"
|
||||
or not get_global_server_args().dsa_prefill_backend == "trtllm"
|
||||
)
|
||||
):
|
||||
# FP8 path: dequantize NSA-specific FP8 format to BF16
|
||||
kv_a, k_pe = self._get_mla_kv_buffer_from_fp8_for_nsa(forward_batch)
|
||||
# FP8 path: dequantize DSA-specific FP8 format to BF16
|
||||
kv_a, k_pe = self._get_mla_kv_buffer_from_fp8_for_dsa(forward_batch)
|
||||
else:
|
||||
# BF16/FP16 path: directly fetch from cache
|
||||
kv_a, k_pe = self._get_mla_kv_buffer(
|
||||
@@ -471,12 +471,12 @@ class DeepseekMHAForwardMixin:
|
||||
kv_a = kv_a.squeeze(1).contiguous()
|
||||
return kv_a, k_pe
|
||||
|
||||
def _get_mla_kv_buffer_from_fp8_for_nsa(
|
||||
def _get_mla_kv_buffer_from_fp8_for_dsa(
|
||||
self: DeepseekV2AttentionMLA,
|
||||
forward_batch: ForwardBatch,
|
||||
):
|
||||
"""
|
||||
Dequantize FP8 KV cache to BF16 for MLA attention (NSA-specific format).
|
||||
Dequantize FP8 KV cache to BF16 for MLA attention (DSA-specific format).
|
||||
|
||||
Returns: (kv_a, k_pe) both in BF16
|
||||
"""
|
||||
|
||||
@@ -6,7 +6,7 @@ import torch
|
||||
|
||||
from sglang.srt.compilation.piecewise_context_manager import is_in_piecewise_cuda_graph
|
||||
from sglang.srt.layers import deep_gemm_wrapper
|
||||
from sglang.srt.layers.attention.nsa.utils import nsa_use_prefill_cp
|
||||
from sglang.srt.layers.attention.dsa.utils import dsa_use_prefill_cp
|
||||
from sglang.srt.layers.communicator import get_attn_tp_context
|
||||
from sglang.srt.layers.quantization.fp8_kernel import (
|
||||
fp8_dtype,
|
||||
@@ -124,7 +124,6 @@ if _use_aiter_gfx95:
|
||||
|
||||
|
||||
class DeepseekMLAForwardMixin:
|
||||
|
||||
def init_mla_forward(self: DeepseekV2AttentionMLA):
|
||||
self.flashinfer_mla_disable_ragged = (
|
||||
get_global_server_args().flashinfer_mla_disable_ragged
|
||||
@@ -178,7 +177,7 @@ class DeepseekMLAForwardMixin:
|
||||
_use_aiter_gfx95
|
||||
and self.q_b_proj.weight.dtype == torch.float8_e4m3fn
|
||||
):
|
||||
if self.use_nsa:
|
||||
if self.use_dsa:
|
||||
q_quanted, q_lora, k_nope, _ = fused_rms_fp8_group_quant(
|
||||
q,
|
||||
self.q_a_layernorm.weight,
|
||||
@@ -220,7 +219,7 @@ class DeepseekMLAForwardMixin:
|
||||
k_nope = self.kv_a_layernorm(k_nope)
|
||||
|
||||
# q_lora needed by indexer
|
||||
if self.use_nsa:
|
||||
if self.use_dsa:
|
||||
if q_lora is None:
|
||||
q_lora = q
|
||||
|
||||
@@ -281,9 +280,13 @@ class DeepseekMLAForwardMixin:
|
||||
k_pe = latent_cache[..., self.kv_lora_rank :].unsqueeze(1)
|
||||
|
||||
if self.use_deep_gemm_bmm:
|
||||
q_nope_val, q_nope_scale, masked_m, expected_m, aligned_m = (
|
||||
per_token_group_quant_mla_deep_gemm_masked_fp8(q_nope.transpose(0, 1))
|
||||
)
|
||||
(
|
||||
q_nope_val,
|
||||
q_nope_scale,
|
||||
masked_m,
|
||||
expected_m,
|
||||
aligned_m,
|
||||
) = per_token_group_quant_mla_deep_gemm_masked_fp8(q_nope.transpose(0, 1))
|
||||
q_nope_out = q_nope.new_empty(
|
||||
(self.num_local_heads, aligned_m, self.kv_lora_rank)
|
||||
)
|
||||
@@ -362,18 +365,18 @@ class DeepseekMLAForwardMixin:
|
||||
if is_kv_b_lora_active(self):
|
||||
q_nope_out = apply_kv_b_lora_q_correction(self, q_nope, q_nope_out)
|
||||
|
||||
skip_rope_for_nsa_tilelang_fused = self._skip_rope_for_nsa_tilelang_fused()
|
||||
skip_rope_for_dsa_tilelang_fused = self._skip_rope_for_dsa_tilelang_fused()
|
||||
skip_rope_for_aiter_fused_mla = self._skip_rope_for_aiter_fused_mla()
|
||||
if (
|
||||
self.rotary_emb is not None
|
||||
and (not self._fuse_rope_for_trtllm_mla(forward_batch))
|
||||
and (not skip_rope_for_nsa_tilelang_fused)
|
||||
and (not skip_rope_for_dsa_tilelang_fused)
|
||||
and (not skip_rope_for_aiter_fused_mla)
|
||||
and (not _use_aiter or not _is_gfx95_supported or self.use_nsa)
|
||||
and (not _use_aiter or not _is_gfx95_supported or self.use_dsa)
|
||||
):
|
||||
q_pe, k_pe = self.rotary_emb(positions, q_pe, k_pe)
|
||||
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
# support allgather+rerrange
|
||||
k_nope, k_pe = self.rebuild_cp_kv_cache(
|
||||
latent_cache, forward_batch, k_nope, k_pe
|
||||
@@ -406,7 +409,7 @@ class DeepseekMLAForwardMixin:
|
||||
save_kv_cache = True
|
||||
|
||||
if self.current_attention_backend in FORWARD_ABSORB_CORE_ATTENTION_BACKENDS:
|
||||
if self._skip_rope_for_nsa_tilelang_fused() and self.rotary_emb is not None:
|
||||
if self._skip_rope_for_dsa_tilelang_fused() and self.rotary_emb is not None:
|
||||
cos = self.rotary_emb.cos_cache
|
||||
sin = self.rotary_emb.sin_cache
|
||||
kv_cache_dtype = (
|
||||
@@ -430,14 +433,14 @@ class DeepseekMLAForwardMixin:
|
||||
)
|
||||
save_kv_cache = False
|
||||
# On decode, pass q_cat directly to attn_mqa with q_rope=None so
|
||||
# nsa_backend.forward_decode reuses q_cat as a zero-copy view
|
||||
# dsa_backend.forward_decode reuses q_cat as a zero-copy view
|
||||
# (`q.contiguous().view(...)` fast-path) instead of running the
|
||||
# redundant `concat_mla_absorb_q_general(q_nope_fused, q_pe_fused)`
|
||||
# that would otherwise rebuild a tensor byte-identical to q_cat.
|
||||
# On ROCm tilelang decode, this eliminates the
|
||||
# `CatArrayBatchedCopy<OpaqueType<1u>, ...>` kernel that used to
|
||||
# fire once per layer per decode step (~2.6 us / layer saved).
|
||||
# Prefill keeps the split form because nsa_backend.forward_extend
|
||||
# Prefill keeps the split form because dsa_backend.forward_extend
|
||||
# asserts `q_rope is not None`.
|
||||
if forward_batch.forward_mode.is_decode_or_idle():
|
||||
if llama_4_scaling is not None:
|
||||
@@ -545,10 +548,14 @@ class DeepseekMLAForwardMixin:
|
||||
attn_output = attn_output.view(-1, self.num_local_heads, self.kv_lora_rank)
|
||||
|
||||
if self.use_deep_gemm_bmm:
|
||||
attn_output_val, attn_output_scale, masked_m, expected_m, aligned_m = (
|
||||
per_token_group_quant_mla_deep_gemm_masked_fp8(
|
||||
attn_output.transpose(0, 1)
|
||||
)
|
||||
(
|
||||
attn_output_val,
|
||||
attn_output_scale,
|
||||
masked_m,
|
||||
expected_m,
|
||||
aligned_m,
|
||||
) = per_token_group_quant_mla_deep_gemm_masked_fp8(
|
||||
attn_output.transpose(0, 1)
|
||||
)
|
||||
attn_bmm_output = attn_output.new_empty(
|
||||
(self.num_local_heads, aligned_m, self.v_head_dim)
|
||||
@@ -683,10 +690,10 @@ class DeepseekMLAForwardMixin:
|
||||
"""
|
||||
Check if we should skip rope and do fused rope+quantize for TRTLLM MLA decode in fp8_e4m3 path.
|
||||
"""
|
||||
if self.current_attention_backend == "nsa":
|
||||
if self.current_attention_backend in ("dsa", "nsa"):
|
||||
return (
|
||||
get_global_server_args().nsa_decode_backend == "trtllm"
|
||||
or get_global_server_args().nsa_prefill_backend == "trtllm"
|
||||
get_global_server_args().dsa_decode_backend == "trtllm"
|
||||
or get_global_server_args().dsa_prefill_backend == "trtllm"
|
||||
) and forward_batch.attn_backend.kv_cache_dtype == torch.float8_e4m3fn
|
||||
|
||||
return (
|
||||
@@ -698,17 +705,17 @@ class DeepseekMLAForwardMixin:
|
||||
and forward_batch.attn_backend.data_type == torch.float8_e4m3fn
|
||||
)
|
||||
|
||||
def _skip_rope_for_nsa_tilelang_fused(self: DeepseekV2AttentionMLA) -> bool:
|
||||
def _skip_rope_for_dsa_tilelang_fused(self: DeepseekV2AttentionMLA) -> bool:
|
||||
"""
|
||||
Check if we should skip rope and use fused rope+cache path for TileLang NSA on gfx95.
|
||||
Check if we should skip rope and use fused rope+cache path for TileLang DSA on gfx95.
|
||||
"""
|
||||
server_args = get_global_server_args()
|
||||
return (
|
||||
_use_aiter_gfx95
|
||||
and self.current_attention_backend == "nsa"
|
||||
and self.current_attention_backend in ("dsa", "nsa")
|
||||
and (
|
||||
server_args.nsa_decode_backend == "tilelang"
|
||||
or server_args.nsa_prefill_backend == "tilelang"
|
||||
server_args.dsa_decode_backend == "tilelang"
|
||||
or server_args.dsa_prefill_backend == "tilelang"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -57,7 +57,8 @@ NVFP4_CKPT_FP8_ATTN_QUANT_MODULES = ["q_b_proj"]
|
||||
|
||||
FORWARD_ABSORB_CORE_ATTENTION_BACKENDS = [
|
||||
"fa3",
|
||||
"nsa",
|
||||
"dsa",
|
||||
"nsa", # Deprecated alias for "dsa"
|
||||
"flashinfer",
|
||||
"cutlass_mla",
|
||||
"trtllm_mla",
|
||||
|
||||
@@ -23,14 +23,14 @@ from safetensors.torch import load_file
|
||||
from torch import nn
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
from sglang.srt.configs.model_config import is_deepseek_nsa
|
||||
from sglang.srt.configs.model_config import is_deepseek_dsa
|
||||
from sglang.srt.distributed import get_pp_group, get_tensor_model_parallel_world_size
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.eplb.expert_distribution import get_global_expert_distribution_recorder
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
can_nsa_cp_split,
|
||||
is_nsa_enable_prefill_cp,
|
||||
nsa_use_prefill_cp,
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
can_dsa_cp_split,
|
||||
dsa_use_prefill_cp,
|
||||
is_dsa_enable_prefill_cp,
|
||||
)
|
||||
from sglang.srt.layers.dp_attention import (
|
||||
get_attention_cp_rank,
|
||||
@@ -148,8 +148,8 @@ class DeepseekModelNextN(nn.Module):
|
||||
|
||||
self.shared_head = nn.Module()
|
||||
self.shared_head.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.cp_size = get_attention_cp_size()
|
||||
else:
|
||||
self.cp_size = None
|
||||
@@ -193,7 +193,7 @@ class DeepseekModelNextN(nn.Module):
|
||||
else:
|
||||
hidden_states = self.eh_proj(eh_input)
|
||||
|
||||
if nsa_use_prefill_cp(forward_batch, self.nsa_enable_prefill_cp):
|
||||
if dsa_use_prefill_cp(forward_batch, self.dsa_enable_prefill_cp):
|
||||
hidden_states = cp_split_and_rebuild_data(forward_batch, hidden_states)
|
||||
positions = cp_split_and_rebuild_position(forward_batch, positions)
|
||||
residual = None
|
||||
@@ -212,7 +212,7 @@ class DeepseekModelNextN(nn.Module):
|
||||
else:
|
||||
hidden_states = self.shared_head.norm(hidden_states)
|
||||
|
||||
if nsa_use_prefill_cp(forward_batch, self.nsa_enable_prefill_cp):
|
||||
if dsa_use_prefill_cp(forward_batch, self.dsa_enable_prefill_cp):
|
||||
# allgather + rerrange
|
||||
hidden_states = cp_all_gather_rerange_output(
|
||||
hidden_states,
|
||||
@@ -248,9 +248,9 @@ class DeepseekV3ForCausalLMNextN(DeepseekV3ForCausalLM):
|
||||
# if not set, model load will be broken in DeepseekV3ForCausalLM load_weights()
|
||||
self.pp_group = get_pp_group()
|
||||
self.determine_num_fused_shared_experts("DeepseekV3ForCausalLMNextN")
|
||||
self.use_nsa = is_deepseek_nsa(config)
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.use_dsa = is_deepseek_dsa(config)
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.cp_rank = get_attention_cp_rank()
|
||||
self.cp_size = get_attention_cp_size()
|
||||
else:
|
||||
@@ -289,9 +289,9 @@ class DeepseekV3ForCausalLMNextN(DeepseekV3ForCausalLM):
|
||||
forward_batch: ForwardBatch,
|
||||
) -> torch.Tensor:
|
||||
# TODO current just support prefill batch=1 and len(input_ids) > self.cp_size * 2
|
||||
if self.nsa_enable_prefill_cp:
|
||||
if can_nsa_cp_split(
|
||||
len(input_ids), self.cp_size, self.use_nsa, forward_batch
|
||||
if self.dsa_enable_prefill_cp:
|
||||
if can_dsa_cp_split(
|
||||
len(input_ids), self.cp_size, self.use_dsa, forward_batch
|
||||
):
|
||||
forward_batch.attn_cp_metadata = prepare_context_parallel_metadata(
|
||||
len(input_ids),
|
||||
|
||||
@@ -40,10 +40,10 @@ from sglang.srt.batch_overlap.two_batch_overlap import (
|
||||
)
|
||||
from sglang.srt.configs.model_config import (
|
||||
compute_mla_mscale_scaling,
|
||||
get_nsa_index_head_dim,
|
||||
get_nsa_index_n_heads,
|
||||
get_nsa_index_topk,
|
||||
is_deepseek_nsa,
|
||||
get_dsa_index_head_dim,
|
||||
get_dsa_index_n_heads,
|
||||
get_dsa_index_topk,
|
||||
is_deepseek_dsa,
|
||||
)
|
||||
from sglang.srt.distributed import (
|
||||
divide,
|
||||
@@ -59,11 +59,11 @@ from sglang.srt.eplb.expert_location_dispatch import ExpertLocationDispatchInfo
|
||||
from sglang.srt.layers import deep_gemm_wrapper
|
||||
from sglang.srt.layers.activation import SiluAndMul
|
||||
from sglang.srt.layers.amx_utils import PackWeightMethod
|
||||
from sglang.srt.layers.attention.nsa.nsa_indexer import Indexer
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
can_nsa_cp_split,
|
||||
is_nsa_enable_prefill_cp,
|
||||
nsa_use_prefill_cp,
|
||||
from sglang.srt.layers.attention.dsa.dsa_indexer import Indexer
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
can_dsa_cp_split,
|
||||
dsa_use_prefill_cp,
|
||||
is_dsa_enable_prefill_cp,
|
||||
)
|
||||
from sglang.srt.layers.communicator import (
|
||||
LayerCommunicator,
|
||||
@@ -71,7 +71,7 @@ from sglang.srt.layers.communicator import (
|
||||
enable_moe_dense_fully_dp,
|
||||
get_attn_tp_context,
|
||||
)
|
||||
from sglang.srt.layers.communicator_nsa_cp import NSACPLayerCommunicator
|
||||
from sglang.srt.layers.communicator_dsa_cp import DSACPLayerCommunicator
|
||||
from sglang.srt.layers.dp_attention import (
|
||||
get_attention_cp_rank,
|
||||
get_attention_cp_size,
|
||||
@@ -368,7 +368,7 @@ class MoEGate(nn.Module):
|
||||
self.e_score_correction_bias = None
|
||||
if _is_cpu and _is_cpu_amx_available:
|
||||
self.quant_method = PackWeightMethod(weight_names=["weight"])
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
|
||||
def forward(
|
||||
self,
|
||||
@@ -390,7 +390,7 @@ class MoEGate(nn.Module):
|
||||
if (
|
||||
not self.is_deepseek_v4
|
||||
and forward_batch is not None
|
||||
and nsa_use_prefill_cp(forward_batch)
|
||||
and dsa_use_prefill_cp(forward_batch)
|
||||
):
|
||||
logits = F.linear(hidden_states, self.weight, None)
|
||||
else:
|
||||
@@ -1352,12 +1352,12 @@ class DeepseekV2AttentionMLA(
|
||||
self.quant_config = quant_config
|
||||
attn_tp_rank = get_attention_tp_rank()
|
||||
attn_tp_size = get_attention_tp_size()
|
||||
self.use_nsa = is_deepseek_nsa(config)
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
assert self.use_nsa, "CP currently only supports deepseek v3.2 model"
|
||||
self.use_dsa = is_deepseek_dsa(config)
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
assert self.use_dsa, "CP currently only supports deepseek v3.2 model"
|
||||
# cp reuse the attn_tp comm group but need to duplicate the weights
|
||||
if self.nsa_enable_prefill_cp and self.use_nsa:
|
||||
if self.dsa_enable_prefill_cp and self.use_dsa:
|
||||
self.cp_size = get_attention_cp_size()
|
||||
self.num_heads = num_heads
|
||||
assert num_heads % attn_tp_size == 0
|
||||
@@ -1410,14 +1410,14 @@ class DeepseekV2AttentionMLA(
|
||||
|
||||
self.skip_topk = None
|
||||
self.next_skip_topk = None
|
||||
if self.use_nsa:
|
||||
if self.use_dsa:
|
||||
is_neox_style = not getattr(config, "indexer_rope_interleave", False)
|
||||
self.indexer = Indexer(
|
||||
hidden_size=hidden_size,
|
||||
index_n_heads=get_nsa_index_n_heads(config),
|
||||
index_head_dim=get_nsa_index_head_dim(config),
|
||||
index_n_heads=get_dsa_index_n_heads(config),
|
||||
index_head_dim=get_dsa_index_head_dim(config),
|
||||
rope_head_dim=qk_rope_head_dim,
|
||||
index_topk=get_nsa_index_topk(config),
|
||||
index_topk=get_dsa_index_topk(config),
|
||||
q_lora_rank=q_lora_rank,
|
||||
max_position_embeddings=max_position_embeddings,
|
||||
rope_theta=rope_theta,
|
||||
@@ -1582,7 +1582,7 @@ class DeepseekV2AttentionMLA(
|
||||
|
||||
def op_core(self, state):
|
||||
result = self.forward_core(state.pop("attn_intermediate_state"))
|
||||
# forward_core may return (hidden_states, topk_indices) for NSA models
|
||||
# forward_core may return (hidden_states, topk_indices) for DSA models
|
||||
# with index cache enabled. In the TBO path, topk_indices is not
|
||||
# propagated between layers, so we discard it here.
|
||||
if isinstance(result, tuple):
|
||||
@@ -1808,7 +1808,7 @@ class DeepseekV2DecoderLayer(nn.Module):
|
||||
self.speculative_algorithm = SpeculativeAlgorithm.from_string(
|
||||
get_global_server_args().speculative_algorithm
|
||||
)
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
self.layer_id = layer_id
|
||||
self.is_nextn = is_nextn
|
||||
self.self_attn = DeepseekV2AttentionMLA(
|
||||
@@ -1881,8 +1881,8 @@ class DeepseekV2DecoderLayer(nn.Module):
|
||||
|
||||
self._gfx95_quant_format = self._detect_gfx95_quant_format()
|
||||
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.layer_communicator = NSACPLayerCommunicator(
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.layer_communicator = DSACPLayerCommunicator(
|
||||
layer_scatter_modes=self.layer_scatter_modes,
|
||||
input_layernorm=self.input_layernorm,
|
||||
post_attention_layernorm=self.post_attention_layernorm,
|
||||
@@ -1996,7 +1996,7 @@ class DeepseekV2DecoderLayer(nn.Module):
|
||||
gemm_output_zero_allocator,
|
||||
)
|
||||
|
||||
if not self.nsa_enable_prefill_cp and should_allreduce_fusion:
|
||||
if not self.dsa_enable_prefill_cp and should_allreduce_fusion:
|
||||
hidden_states._sglang_needs_allreduce_fusion = True
|
||||
|
||||
if not should_allreduce_fusion:
|
||||
@@ -2093,8 +2093,8 @@ class DeepseekV2Model(nn.Module):
|
||||
self.vocab_size = config.vocab_size
|
||||
self.first_k_dense_replace = config.first_k_dense_replace
|
||||
self.pp_group = get_pp_group()
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.cp_size = get_attention_cp_size()
|
||||
else:
|
||||
self.cp_size = None
|
||||
@@ -2253,7 +2253,7 @@ class DeepseekV2Model(nn.Module):
|
||||
else None
|
||||
)
|
||||
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
if self.pp_group.is_first_rank:
|
||||
hidden_states = cp_split_and_rebuild_data(forward_batch, hidden_states)
|
||||
positions = cp_split_and_rebuild_position(forward_batch, positions)
|
||||
@@ -2338,7 +2338,7 @@ class DeepseekV2Model(nn.Module):
|
||||
else:
|
||||
hidden_states, _ = self.norm(hidden_states, residual)
|
||||
|
||||
if self.pp_group.is_last_rank and nsa_use_prefill_cp(forward_batch):
|
||||
if self.pp_group.is_last_rank and dsa_use_prefill_cp(forward_batch):
|
||||
# allgather + rerrange
|
||||
hidden_states = cp_all_gather_rerange_output(
|
||||
hidden_states,
|
||||
@@ -2385,7 +2385,7 @@ class DeepseekV2ForCausalLM(nn.Module, DeepseekV2WeightLoaderMixin):
|
||||
self.tp_size = get_tensor_model_parallel_world_size()
|
||||
self.quant_config = quant_config
|
||||
self.determine_num_fused_shared_experts()
|
||||
self.use_nsa = is_deepseek_nsa(config)
|
||||
self.use_dsa = is_deepseek_dsa(config)
|
||||
self.model = DeepseekV2Model(
|
||||
config, quant_config, prefix=add_prefix("model", prefix)
|
||||
)
|
||||
@@ -2415,15 +2415,15 @@ class DeepseekV2ForCausalLM(nn.Module, DeepseekV2WeightLoaderMixin):
|
||||
)
|
||||
self.capture_aux_hidden_states = False
|
||||
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.cp_rank = get_attention_cp_rank()
|
||||
self.cp_size = get_attention_cp_size()
|
||||
else:
|
||||
self.cp_rank = self.cp_size = None
|
||||
|
||||
q_lora_rank = config.q_lora_rank if hasattr(config, "q_lora_rank") else None
|
||||
get_attn_tp_context().init_context(q_lora_rank, is_deepseek_nsa(config))
|
||||
get_attn_tp_context().init_context(q_lora_rank, is_deepseek_dsa(config))
|
||||
|
||||
@property
|
||||
def routed_experts_weights_of_layer(self):
|
||||
@@ -2498,9 +2498,9 @@ class DeepseekV2ForCausalLM(nn.Module, DeepseekV2WeightLoaderMixin):
|
||||
input_embeds: torch.Tensor = None,
|
||||
pp_proxy_tensors: Optional[PPProxyTensors] = None,
|
||||
) -> torch.Tensor:
|
||||
if self.nsa_enable_prefill_cp:
|
||||
if can_nsa_cp_split(
|
||||
len(input_ids), self.cp_size, self.use_nsa, forward_batch
|
||||
if self.dsa_enable_prefill_cp:
|
||||
if can_dsa_cp_split(
|
||||
len(input_ids), self.cp_size, self.use_dsa, forward_batch
|
||||
):
|
||||
forward_batch.attn_cp_metadata = prepare_context_parallel_metadata(
|
||||
len(input_ids),
|
||||
|
||||
@@ -33,14 +33,14 @@ from sglang.srt.distributed import (
|
||||
)
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.eplb.expert_location import ModelConfigForExpertLocation
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
can_dsa_cp_split,
|
||||
dsa_use_prefill_cp,
|
||||
is_dsa_enable_prefill_cp,
|
||||
is_dsa_prefill_cp_round_robin_split,
|
||||
)
|
||||
from sglang.srt.layers.attention.dsv4.compressor import Compressor
|
||||
from sglang.srt.layers.attention.dsv4.indexer import C4Indexer
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
can_nsa_cp_split,
|
||||
is_nsa_enable_prefill_cp,
|
||||
is_nsa_prefill_cp_round_robin_split,
|
||||
nsa_use_prefill_cp,
|
||||
)
|
||||
from sglang.srt.layers.communicator import get_attn_tp_context
|
||||
from sglang.srt.layers.dp_attention import (
|
||||
_DpGatheredBufferWrapper,
|
||||
@@ -177,8 +177,8 @@ class MQALayer(nn.Module):
|
||||
super().__init__()
|
||||
self.tp_rank = attn_tp_rank = get_attention_tp_rank()
|
||||
self.tp_size = attn_tp_size = get_attention_tp_size()
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.cp_size = get_attention_cp_size()
|
||||
self.tp_rank = attn_tp_rank = 0
|
||||
self.tp_size = attn_tp_size = 1
|
||||
@@ -390,7 +390,7 @@ class MQALayer(nn.Module):
|
||||
) -> None:
|
||||
"""Fused: rmsnorm + RoPE + write directly to FlashMLA paged cache.
|
||||
|
||||
Replaces the bf16-kv-intermediate path. Used everywhere except the NSA
|
||||
Replaces the bf16-kv-intermediate path. Used everywhere except the DSA
|
||||
prefill-CP case (which needs bf16 kv for the cross-rank all-gather).
|
||||
"""
|
||||
if qkv_a is not None:
|
||||
@@ -416,7 +416,7 @@ class MQALayer(nn.Module):
|
||||
positions: torch.Tensor,
|
||||
qkv_a: Optional[torch.Tensor] = None,
|
||||
) -> torch.Tensor:
|
||||
"""Bf16-kv path used by the NSA prefill-CP case (needs all-gather)."""
|
||||
"""Bf16-kv path used by the DSA prefill-CP case (needs all-gather)."""
|
||||
if qkv_a is not None:
|
||||
kv = qkv_a[..., self.q_lora_rank :]
|
||||
else:
|
||||
@@ -508,10 +508,10 @@ class MQALayer(nn.Module):
|
||||
q_lora = self.q_norm(q_lora)
|
||||
q = self._compute_q_b(q_lora, positions, q_out)
|
||||
|
||||
use_cp = self.nsa_enable_prefill_cp and nsa_use_prefill_cp(forward_batch)
|
||||
use_cp = self.dsa_enable_prefill_cp and dsa_use_prefill_cp(forward_batch)
|
||||
kv: Optional[torch.Tensor]
|
||||
if use_cp:
|
||||
# NSA CP: keep bf16 kv around for the cross-rank all-gather, then
|
||||
# DSA CP: keep bf16 kv around for the cross-rank all-gather, then
|
||||
# write to the FlashMLA cache after gather.
|
||||
kv = self._compute_kv_bf16(x, positions, qkv_a=qkv_a)
|
||||
kv = cp_all_gather_rerange_output(
|
||||
@@ -567,7 +567,7 @@ class MQALayer(nn.Module):
|
||||
and self.alt_streams is not None
|
||||
and get_is_capture_mode()
|
||||
and x.shape[0] <= self._multi_stream_bs_limit
|
||||
and not (self.nsa_enable_prefill_cp and nsa_use_prefill_cp(forward_batch))
|
||||
and not (self.dsa_enable_prefill_cp and dsa_use_prefill_cp(forward_batch))
|
||||
)
|
||||
|
||||
tp_slice, q_padded, q_out = slice(None), None, None
|
||||
@@ -591,7 +591,7 @@ class MQALayer(nn.Module):
|
||||
|
||||
# The cache write is always fused / already done by _forward_prepare* --
|
||||
# tell the backend to skip its own store_cache. When `kv is None`
|
||||
# (no NSA-CP), pass `q` as a sentinel for the `k is v` assert; the
|
||||
# (no DSA-CP), pass `q` as a sentinel for the `k is v` assert; the
|
||||
# attention path doesn't read it once `save_kv_cache=False`.
|
||||
attn_k = kv if kv is not None else q
|
||||
o = attn_backend.forward(
|
||||
@@ -694,7 +694,7 @@ class DeepseekV4DecoderLayer(nn.Module):
|
||||
self.hc_attn_scale = nn.Parameter(torch.empty(3, dtype=torch.float32))
|
||||
self.hc_ffn_scale = nn.Parameter(torch.empty(3, dtype=torch.float32))
|
||||
self.rms_norm_eps = config.rms_norm_eps
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
|
||||
def hc_pre(
|
||||
self,
|
||||
@@ -869,7 +869,7 @@ class DeepseekV4DecoderLayer(nn.Module):
|
||||
if not norm_fused:
|
||||
hidden_states = self.post_attention_layernorm(hidden_states)
|
||||
|
||||
_use_cp = self.nsa_enable_prefill_cp and nsa_use_prefill_cp(forward_batch)
|
||||
_use_cp = self.dsa_enable_prefill_cp and dsa_use_prefill_cp(forward_batch)
|
||||
_use_tp_moe_gather = (
|
||||
not _use_cp
|
||||
and get_attention_dp_size() > 1
|
||||
@@ -979,8 +979,8 @@ class DeepseekV4Model(nn.Module):
|
||||
self.hc_head_base = nn.Parameter(torch.empty(hc_mult, dtype=torch.float32))
|
||||
self.hc_head_scale = nn.Parameter(torch.empty(1, dtype=torch.float32))
|
||||
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.cp_size = get_attention_cp_size()
|
||||
|
||||
def hc_head(
|
||||
@@ -1040,7 +1040,7 @@ class DeepseekV4Model(nn.Module):
|
||||
else:
|
||||
input_ids_global = input_ids
|
||||
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
if self.pp_group.is_first_rank:
|
||||
hidden_states = cp_split_and_rebuild_data(forward_batch, hidden_states)
|
||||
positions = cp_split_and_rebuild_position(forward_batch, positions)
|
||||
@@ -1060,7 +1060,7 @@ class DeepseekV4Model(nn.Module):
|
||||
)
|
||||
|
||||
# CP all-gather only on the last PP rank; PP IPC carries CP-split tensors.
|
||||
if self.pp_group.is_last_rank and nsa_use_prefill_cp(forward_batch):
|
||||
if self.pp_group.is_last_rank and dsa_use_prefill_cp(forward_batch):
|
||||
hidden_states = cp_all_gather_rerange_output(
|
||||
hidden_states,
|
||||
self.cp_size,
|
||||
@@ -1113,7 +1113,7 @@ class DeepseekV4ForCausalLM(nn.Module):
|
||||
self.lm_head = PPMissingLayer()
|
||||
self.logits_processor = LogitsProcessor(config)
|
||||
self.capture_aux_hidden_states = False
|
||||
get_attn_tp_context().init_context(config.q_lora_rank, is_nsa=True)
|
||||
get_attn_tp_context().init_context(config.q_lora_rank, is_dsa=True)
|
||||
|
||||
self._routed_experts_weights_of_layer = LazyValue(
|
||||
lambda: {
|
||||
@@ -1129,8 +1129,8 @@ class DeepseekV4ForCausalLM(nn.Module):
|
||||
self.start_layer = self.model.start_layer
|
||||
self.end_layer = self.model.end_layer
|
||||
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.cp_rank = get_attention_cp_rank()
|
||||
self.cp_size = get_attention_cp_size()
|
||||
|
||||
@@ -1159,15 +1159,15 @@ class DeepseekV4ForCausalLM(nn.Module):
|
||||
input_embeds: Optional[torch.Tensor] = None,
|
||||
pp_proxy_tensors: Optional[PPProxyTensors] = None,
|
||||
) -> torch.Tensor:
|
||||
if self.nsa_enable_prefill_cp:
|
||||
if can_nsa_cp_split(len(input_ids), self.cp_size, True, forward_batch):
|
||||
if self.dsa_enable_prefill_cp:
|
||||
if can_dsa_cp_split(len(input_ids), self.cp_size, True, forward_batch):
|
||||
forward_batch.attn_cp_metadata = prepare_context_parallel_metadata(
|
||||
len(input_ids),
|
||||
self.cp_rank,
|
||||
self.cp_size,
|
||||
forward_batch.seq_lens_cpu.tolist(),
|
||||
)
|
||||
if is_nsa_prefill_cp_round_robin_split():
|
||||
if is_dsa_prefill_cp_round_robin_split():
|
||||
metadata = forward_batch.attn_backend.forward_metadata
|
||||
core_meta = metadata.core_attn_metadata
|
||||
core_meta.apply_cp_reindex()
|
||||
|
||||
@@ -7,11 +7,11 @@ from torch import nn
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
from sglang.srt.distributed import get_pp_group, get_tensor_model_parallel_world_size
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
can_nsa_cp_split,
|
||||
is_nsa_enable_prefill_cp,
|
||||
is_nsa_prefill_cp_round_robin_split,
|
||||
nsa_use_prefill_cp,
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
can_dsa_cp_split,
|
||||
dsa_use_prefill_cp,
|
||||
is_dsa_enable_prefill_cp,
|
||||
is_dsa_prefill_cp_round_robin_split,
|
||||
)
|
||||
from sglang.srt.layers.dp_attention import (
|
||||
_DpGatheredBufferWrapper,
|
||||
@@ -104,8 +104,8 @@ class DeepseekV4ModelNextN(nn.Module):
|
||||
compress_ratio_override=COMPRESS_RATIO_NEXTN_LAYER,
|
||||
)
|
||||
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.cp_size = get_attention_cp_size()
|
||||
else:
|
||||
self.cp_size = None
|
||||
@@ -165,7 +165,7 @@ class DeepseekV4ModelNextN(nn.Module):
|
||||
else:
|
||||
input_ids_global = input_ids
|
||||
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
hidden_states = cp_split_and_rebuild_data(forward_batch, hidden_states)
|
||||
positions = cp_split_and_rebuild_position(forward_batch, positions)
|
||||
|
||||
@@ -177,7 +177,7 @@ class DeepseekV4ModelNextN(nn.Module):
|
||||
input_ids_global=input_ids_global,
|
||||
)
|
||||
|
||||
if nsa_use_prefill_cp(forward_batch):
|
||||
if dsa_use_prefill_cp(forward_batch):
|
||||
hidden_states = cp_all_gather_rerange_output(
|
||||
hidden_states,
|
||||
self.cp_size,
|
||||
@@ -209,8 +209,8 @@ class DeepseekV4ForCausalLMNextN(DeepseekV4ForCausalLM):
|
||||
self.pp_group = get_pp_group()
|
||||
self.quant_config = quant_config
|
||||
self.determine_num_fused_shared_experts()
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.cp_rank = get_attention_cp_rank()
|
||||
self.cp_size = get_attention_cp_size()
|
||||
else:
|
||||
@@ -236,15 +236,15 @@ class DeepseekV4ForCausalLMNextN(DeepseekV4ForCausalLM):
|
||||
positions: torch.Tensor,
|
||||
forward_batch: ForwardBatch,
|
||||
) -> torch.Tensor:
|
||||
if self.nsa_enable_prefill_cp:
|
||||
if can_nsa_cp_split(len(input_ids), self.cp_size, True, forward_batch):
|
||||
if self.dsa_enable_prefill_cp:
|
||||
if can_dsa_cp_split(len(input_ids), self.cp_size, True, forward_batch):
|
||||
forward_batch.attn_cp_metadata = prepare_context_parallel_metadata(
|
||||
len(input_ids),
|
||||
self.cp_rank,
|
||||
self.cp_size,
|
||||
forward_batch.seq_lens_cpu.tolist(),
|
||||
)
|
||||
if is_nsa_prefill_cp_round_robin_split():
|
||||
if is_dsa_prefill_cp_round_robin_split():
|
||||
metadata = forward_batch.attn_backend.forward_metadata
|
||||
core_meta = metadata.core_attn_metadata
|
||||
core_meta.apply_cp_reindex()
|
||||
|
||||
@@ -30,7 +30,7 @@ from sglang.srt.distributed import (
|
||||
get_tensor_model_parallel_world_size,
|
||||
)
|
||||
from sglang.srt.layers.activation import SiluAndMul
|
||||
from sglang.srt.layers.attention.nsa.utils import is_nsa_enable_prefill_cp
|
||||
from sglang.srt.layers.attention.dsa.utils import is_dsa_enable_prefill_cp
|
||||
from sglang.srt.layers.communicator import (
|
||||
LayerCommunicator,
|
||||
LayerScatterModes,
|
||||
@@ -341,7 +341,7 @@ class Glm4MoeLiteDecoderLayer(DeepseekV2DecoderLayer):
|
||||
nn.Module.__init__(self)
|
||||
self.hidden_size = config.hidden_size
|
||||
self.config = config
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
rope_theta, rope_scaling = get_rope_config(config)
|
||||
max_position_embeddings = getattr(config, "max_position_embeddings", 202752)
|
||||
self.layer_id = layer_id
|
||||
@@ -433,8 +433,8 @@ class Glm4MoeLiteModel(DeepseekV2Model):
|
||||
self.pp_group = get_pp_group()
|
||||
|
||||
# DeepseekV2Model.forward expects these attributes to exist.
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
self.cp_size = get_attention_tp_size() if self.nsa_enable_prefill_cp else None
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
self.cp_size = get_attention_tp_size() if self.dsa_enable_prefill_cp else None
|
||||
self.gemm_output_zero_allocator_size = 0
|
||||
self.llama_4_scaling_config = getattr(config, "llama_4_scaling", None)
|
||||
|
||||
@@ -503,8 +503,8 @@ class Glm4MoeLiteForCausalLM(DeepseekV2ForCausalLM):
|
||||
)
|
||||
self.capture_aux_hidden_states = False
|
||||
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
if self.nsa_enable_prefill_cp:
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
if self.dsa_enable_prefill_cp:
|
||||
self.cp_rank = get_attention_tp_rank()
|
||||
self.cp_size = get_attention_tp_size()
|
||||
else:
|
||||
|
||||
@@ -8,7 +8,7 @@ from torch import nn
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
from sglang.srt.distributed import get_pp_group
|
||||
from sglang.srt.layers.attention.nsa.utils import is_nsa_enable_prefill_cp
|
||||
from sglang.srt.layers.attention.dsa.utils import is_dsa_enable_prefill_cp
|
||||
from sglang.srt.layers.layernorm import RMSNorm
|
||||
from sglang.srt.layers.linear import RowParallelLinear
|
||||
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
||||
@@ -35,7 +35,7 @@ class MistralLarge3EagleModel(DeepseekV2Model):
|
||||
self.vocab_size = config.vocab_size
|
||||
assert get_pp_group().world_size == 1
|
||||
self.pp_group = get_pp_group()
|
||||
self.nsa_enable_prefill_cp = is_nsa_enable_prefill_cp()
|
||||
self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp()
|
||||
|
||||
self.embed_tokens = VocabParallelEmbedding(
|
||||
config.vocab_size,
|
||||
|
||||
@@ -104,7 +104,8 @@ class AttnForwardMethod(IntEnum):
|
||||
|
||||
|
||||
SEPARATE_ROPE_BACKENDS = frozenset(
|
||||
["fa3", "flashinfer", "nsa", "cutlass_mla", "trtllm_mla"]
|
||||
["fa3", "flashinfer", "dsa", "nsa", "cutlass_mla", "trtllm_mla"]
|
||||
# "nsa" is a deprecated alias for "dsa"
|
||||
)
|
||||
CONCAT_ROPE_BACKENDS = frozenset(["flashmla", "triton"])
|
||||
|
||||
@@ -667,7 +668,6 @@ class SarvamMoEMLAAttention(nn.Module):
|
||||
k_pe: torch.Tensor,
|
||||
forward_batch: ForwardBatch,
|
||||
) -> torch.Tensor:
|
||||
|
||||
q_pe, k_pe = self.rotary_emb(positions, q_pe, k_pe)
|
||||
q[..., self.qk_nope_head_dim :] = q_pe
|
||||
|
||||
@@ -989,7 +989,6 @@ class SarvamMoEMLAAttention(nn.Module):
|
||||
|
||||
|
||||
class SarvamMoEMLADecoderLayer(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: PretrainedConfig,
|
||||
@@ -1139,7 +1138,6 @@ class SarvamMoEMLADecoderLayer(nn.Module):
|
||||
|
||||
|
||||
class SarvamMLAModel(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: PretrainedConfig,
|
||||
@@ -1223,7 +1221,6 @@ class SarvamMLAModel(nn.Module):
|
||||
|
||||
|
||||
class SarvamMLAForCausalLM(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: PretrainedConfig,
|
||||
@@ -1475,7 +1472,6 @@ class SarvamMLAForCausalLM(nn.Module):
|
||||
|
||||
|
||||
class SarvamMoEForCausalLM(BailingMoEForCausalLM):
|
||||
|
||||
@torch.no_grad()
|
||||
def forward_split_prefill(
|
||||
self,
|
||||
|
||||
@@ -61,8 +61,8 @@ class SRTPlatform(DeviceMixin):
|
||||
"""Return the MLA KV pool class for this platform."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_nsa_kv_pool_cls(self) -> type:
|
||||
"""Return the NSA KV pool class for this platform (DeepSeek V3.2)."""
|
||||
def get_dsa_kv_pool_cls(self) -> type:
|
||||
"""Return the DSA KV pool class for this platform (DeepSeek V3.2)."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_paged_allocator_cls(self) -> type:
|
||||
|
||||
@@ -155,7 +155,8 @@ ATTENTION_BACKEND_CHOICES = [
|
||||
"triton",
|
||||
"torch_native",
|
||||
"flex_attention",
|
||||
"nsa",
|
||||
"dsa",
|
||||
"nsa", # Deprecated alias for "dsa"
|
||||
"dsv4",
|
||||
"compressed", # Deprecated alias for "dsv4"
|
||||
# NVIDIA specific
|
||||
@@ -246,13 +247,14 @@ LORA_BACKEND_CHOICES = ["triton", "csgmv", "ascend", "torch_native"]
|
||||
|
||||
ENCODER_TRANSFER_BACKEND_CHOICES = ["zmq_to_scheduler", "zmq_to_tokenizer", "mooncake"]
|
||||
|
||||
NSA_PREFILL_CP_SPLIT_CHOICES = ["in-seq-split", "round-robin-split"]
|
||||
DSA_PREFILL_CP_SPLIT_CHOICES = ["in-seq-split", "round-robin-split"]
|
||||
NSA_PREFILL_CP_SPLIT_CHOICES = DSA_PREFILL_CP_SPLIT_CHOICES # deprecated alias
|
||||
|
||||
PREFILL_CP_SPLIT_CHOICES = ["in-seq-split"]
|
||||
|
||||
DEFAULT_LORA_EVICTION_POLICY = "lru"
|
||||
|
||||
NSA_CHOICES = [
|
||||
DSA_CHOICES = [
|
||||
"flashmla_sparse",
|
||||
"flashmla_kv",
|
||||
"flashmla_auto",
|
||||
@@ -261,6 +263,7 @@ NSA_CHOICES = [
|
||||
"aiter",
|
||||
"trtllm",
|
||||
]
|
||||
NSA_CHOICES = DSA_CHOICES # deprecated alias
|
||||
|
||||
MAMBA_SCHEDULER_STRATEGY_CHOICES = ["auto", "no_buffer", "extra_buffer"]
|
||||
|
||||
@@ -535,10 +538,10 @@ class ServerArgs:
|
||||
mm_attention_backend: Optional[str] = None
|
||||
fp8_gemm_runner_backend: str = "auto"
|
||||
fp4_gemm_runner_backend: str = "auto"
|
||||
nsa_prefill_backend: Optional[str] = (
|
||||
dsa_prefill_backend: Optional[str] = (
|
||||
None # None = auto-detect based on hardware/kv_cache_dtype
|
||||
)
|
||||
nsa_decode_backend: Optional[str] = (
|
||||
dsa_decode_backend: Optional[str] = (
|
||||
None # auto-detect based on hardware/kv_cache_dtype
|
||||
)
|
||||
disable_flashinfer_autotune: bool = False
|
||||
@@ -741,8 +744,8 @@ class ServerArgs:
|
||||
enable_attn_tp_input_scattered: bool = False
|
||||
gc_threshold: Optional[List[int]] = None
|
||||
# Context parallelism used in the long sequence prefill phase of DeepSeek v3.2
|
||||
enable_nsa_prefill_context_parallel: bool = False
|
||||
nsa_prefill_cp_mode: str = "round-robin-split"
|
||||
enable_dsa_prefill_context_parallel: bool = False
|
||||
dsa_prefill_cp_mode: str = "round-robin-split"
|
||||
enable_fused_qk_norm_rope: bool = False
|
||||
enable_precise_embedding_interpolation: bool = False
|
||||
enable_fused_moe_sum_all_reduce: bool = False
|
||||
@@ -1647,15 +1650,15 @@ class ServerArgs:
|
||||
|
||||
return capture_sizes
|
||||
|
||||
def _set_default_nsa_kv_cache_dtype(self, major: int, quantization: str) -> str:
|
||||
user_set_prefill = self.nsa_prefill_backend is not None
|
||||
user_set_decode = self.nsa_decode_backend is not None
|
||||
def _set_default_dsa_kv_cache_dtype(self, major: int, quantization: str) -> str:
|
||||
user_set_prefill = self.dsa_prefill_backend is not None
|
||||
user_set_decode = self.dsa_decode_backend is not None
|
||||
|
||||
# If user specified a backend but didn't explicitly set kv_cache_dtype,
|
||||
# suggest them to be explicit about kv_cache_dtype to avoid surprises
|
||||
if (user_set_prefill or user_set_decode) and self.kv_cache_dtype == "auto":
|
||||
logger.warning(
|
||||
"When specifying --nsa-prefill-backend or --nsa-decode-backend, "
|
||||
"When specifying --dsa-prefill-backend or --dsa-decode-backend, "
|
||||
"you should also explicitly set --kv-cache-dtype (e.g., 'fp8_e4m3' or 'bfloat16'). "
|
||||
"DeepSeek V3.2 defaults to FP8 KV cache which may not be compatible with all backends."
|
||||
)
|
||||
@@ -1675,56 +1678,56 @@ class ServerArgs:
|
||||
"fp8_e4m3",
|
||||
], "DeepSeek DSA only supports bf16/bfloat16 or fp8_e4m3 kv_cache_dtype"
|
||||
|
||||
def _set_default_nsa_backends(self, kv_cache_dtype: str, major: int) -> str:
|
||||
def _set_default_dsa_backends(self, kv_cache_dtype: str, major: int) -> str:
|
||||
from sglang.srt.arg_groups.hisparse_hook import (
|
||||
apply_hisparse_nsa_backend_defaults,
|
||||
apply_hisparse_dsa_backend_defaults,
|
||||
)
|
||||
|
||||
user_set_prefill = self.nsa_prefill_backend is not None
|
||||
user_set_decode = self.nsa_decode_backend is not None
|
||||
user_set_prefill = self.dsa_prefill_backend is not None
|
||||
user_set_decode = self.dsa_decode_backend is not None
|
||||
|
||||
if apply_hisparse_nsa_backend_defaults(
|
||||
if apply_hisparse_dsa_backend_defaults(
|
||||
self, user_set_prefill, user_set_decode, kv_cache_dtype
|
||||
):
|
||||
return
|
||||
|
||||
if not user_set_prefill and not user_set_decode and is_hip():
|
||||
self.nsa_prefill_backend = "tilelang"
|
||||
self.nsa_decode_backend = "tilelang"
|
||||
self.dsa_prefill_backend = "tilelang"
|
||||
self.dsa_decode_backend = "tilelang"
|
||||
elif kv_cache_dtype == "fp8_e4m3":
|
||||
if major >= 10:
|
||||
if not user_set_prefill:
|
||||
self.nsa_prefill_backend = "trtllm"
|
||||
self.dsa_prefill_backend = "trtllm"
|
||||
if not user_set_decode:
|
||||
self.nsa_decode_backend = "trtllm"
|
||||
self.dsa_decode_backend = "trtllm"
|
||||
else:
|
||||
# Hopper FP8 defaults to flashmla_kv for both prefill and decode.
|
||||
if not user_set_prefill:
|
||||
self.nsa_prefill_backend = "flashmla_kv"
|
||||
self.dsa_prefill_backend = "flashmla_kv"
|
||||
if not user_set_decode:
|
||||
self.nsa_decode_backend = "flashmla_kv"
|
||||
self.dsa_decode_backend = "flashmla_kv"
|
||||
else:
|
||||
# set prefill/decode backends based on hardware architecture.
|
||||
if major >= 10:
|
||||
if not user_set_prefill:
|
||||
self.nsa_prefill_backend = "flashmla_sparse"
|
||||
self.dsa_prefill_backend = "flashmla_sparse"
|
||||
if not user_set_decode:
|
||||
self.nsa_decode_backend = "trtllm"
|
||||
self.dsa_decode_backend = "trtllm"
|
||||
else:
|
||||
# Hopper defaults for bfloat16
|
||||
if not user_set_prefill:
|
||||
self.nsa_prefill_backend = "flashmla_sparse"
|
||||
self.dsa_prefill_backend = "flashmla_sparse"
|
||||
if not user_set_decode:
|
||||
self.nsa_decode_backend = "fa3"
|
||||
self.dsa_decode_backend = "fa3"
|
||||
|
||||
logger.warning(
|
||||
f"Set NSA backends for {self.kv_cache_dtype} KV Cache: prefill={self.nsa_prefill_backend}, decode={self.nsa_decode_backend}."
|
||||
f"Set DSA backends for {self.kv_cache_dtype} KV Cache: prefill={self.dsa_prefill_backend}, decode={self.dsa_decode_backend}."
|
||||
)
|
||||
|
||||
def _handle_model_specific_adjustments(self):
|
||||
from sglang.srt.configs.model_config import (
|
||||
get_mimo_v2_fused_qkv_expected_tp_size,
|
||||
is_deepseek_nsa,
|
||||
is_deepseek_dsa,
|
||||
)
|
||||
|
||||
if parse_connector_type(self.model_path) == ConnectorType.INSTANCE:
|
||||
@@ -1765,37 +1768,37 @@ class ServerArgs:
|
||||
"GlmMoeDsaForCausalLM",
|
||||
]:
|
||||
# Set attention backend for DeepSeek
|
||||
if is_deepseek_nsa(hf_config): # DeepSeek 3.2/GLM 5
|
||||
if is_deepseek_dsa(hf_config): # DeepSeek 3.2/GLM 5
|
||||
if model_arch == "GlmMoeDsaForCausalLM" and is_blackwell_supported():
|
||||
envs.SGLANG_NSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.set(0)
|
||||
envs.SGLANG_DSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.set(0)
|
||||
logger.warning(
|
||||
"Force NSA prefill to use sparse MLA (i.e. disable MHA_ONE_SHOT) for GlmMoeDsaForCausalLM on Blackwell."
|
||||
"Force DSA prefill to use sparse MLA (i.e. disable MHA_ONE_SHOT) for GlmMoeDsaForCausalLM on Blackwell."
|
||||
)
|
||||
else:
|
||||
if envs.SGLANG_NSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.is_set():
|
||||
if envs.SGLANG_DSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.is_set():
|
||||
logger.warning(
|
||||
f"Dense attention kv len threshold is manually set to {envs.SGLANG_NSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.get()} for DSA. Caution: This may cause performance regression if the threshold is larger than the index topk of model."
|
||||
f"Dense attention kv len threshold is manually set to {envs.SGLANG_DSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.get()} for DSA. Caution: This may cause performance regression if the threshold is larger than the index topk of model."
|
||||
)
|
||||
else:
|
||||
# When threshold is not manually set, set it to the index topk of model
|
||||
from sglang.srt.configs.model_config import get_nsa_index_topk
|
||||
from sglang.srt.configs.model_config import get_dsa_index_topk
|
||||
|
||||
envs.SGLANG_NSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.set(
|
||||
get_nsa_index_topk(hf_config)
|
||||
envs.SGLANG_DSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.set(
|
||||
get_dsa_index_topk(hf_config)
|
||||
)
|
||||
logger.warning(
|
||||
f"Set dense attention kv len threshold to model index_topk={envs.SGLANG_NSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.get()} for DeepSeek with DSA."
|
||||
f"Set dense attention kv len threshold to model index_topk={envs.SGLANG_DSA_PREFILL_DENSE_ATTN_KV_LEN_THRESHOLD.get()} for DeepSeek with DSA."
|
||||
)
|
||||
if self.is_attention_backend_not_set():
|
||||
self.attention_backend = "nsa"
|
||||
logger.info("Use nsa attention backend for DeepSeek with DSA.")
|
||||
self.attention_backend = "dsa"
|
||||
logger.info("Use dsa attention backend for DeepSeek with DSA.")
|
||||
|
||||
if not is_npu() and not is_xpu(): # CUDA or ROCm GPU
|
||||
if self.enable_nsa_prefill_context_parallel:
|
||||
if self.enable_dsa_prefill_context_parallel:
|
||||
logger.warning(
|
||||
"Context parallel feature is still under experiment. It has only been verified on Hopper platform."
|
||||
)
|
||||
if self.nsa_prefill_cp_mode == "in-seq-split":
|
||||
if self.dsa_prefill_cp_mode == "in-seq-split":
|
||||
# TODO Supports moe_dense_tp_size != 1, kv cache dtype = "fp8",moe_a2a_backend non-deepep and cross-machine operation .
|
||||
self.enable_dp_attention = True
|
||||
self.moe_dense_tp_size = 1
|
||||
@@ -1819,7 +1822,7 @@ class ServerArgs:
|
||||
f"Enable Context Parallel opt for deeeseekv3.2-DSA, Setting dp_size == {self.dp_size} and moe_dense_tp_size == {self.moe_dense_tp_size}, ep_size == {self.ep_size}, tp_size == {self.tp_size}, kv_cache_dtype == {self.kv_cache_dtype}, moe_a2a_backend {self.moe_a2a_backend} "
|
||||
)
|
||||
else:
|
||||
# Pure TP and partial DP Attention mode is active for NSA, logging a warning
|
||||
# Pure TP and partial DP Attention mode is active for DSA, logging a warning
|
||||
if self.dp_size < self.tp_size:
|
||||
logger.warning(
|
||||
f"DSA with TP mode is active, dp_size={self.dp_size}, tp_size={self.tp_size}, "
|
||||
@@ -1827,15 +1830,15 @@ class ServerArgs:
|
||||
)
|
||||
|
||||
# Deferred import to avoid a circular import at module-load
|
||||
# time (nsa.utils imports get_global_server_args).
|
||||
from sglang.srt.layers.attention.nsa.utils import (
|
||||
# time (dsa.utils imports get_global_server_args).
|
||||
from sglang.srt.layers.attention.dsa.utils import (
|
||||
aiter_can_use_preshuffle_paged_mqa,
|
||||
)
|
||||
|
||||
if is_hip() and not aiter_can_use_preshuffle_paged_mqa():
|
||||
# Legacy ROCm NSA path: aiter's gluon paged-MQA kernel is
|
||||
# Legacy ROCm DSA path: aiter's gluon paged-MQA kernel is
|
||||
# unavailable (Triton<3.5 and AITER_ENABLE_AOT_GLUON_PA_MQA_LOGITS
|
||||
# not set, or SGLANG_NSA_HIP_DISABLE_PRESHUFFLE=1 / SGLANG_USE_AITER=0).
|
||||
# not set, or SGLANG_DSA_HIP_DISABLE_PRESHUFFLE=1 / SGLANG_USE_AITER=0).
|
||||
self.page_size = 1
|
||||
logger.warning(
|
||||
"Setting page size to 1 for DeepSeek DSA on ROCm "
|
||||
@@ -1849,13 +1852,13 @@ class ServerArgs:
|
||||
import torch
|
||||
|
||||
major, _ = torch.cuda.get_device_capability()
|
||||
self._set_default_nsa_kv_cache_dtype(major, self.quantization)
|
||||
self._set_default_nsa_backends(self.kv_cache_dtype, major)
|
||||
self._set_default_dsa_kv_cache_dtype(major, self.quantization)
|
||||
self._set_default_dsa_backends(self.kv_cache_dtype, major)
|
||||
|
||||
if self.enable_nsa_prefill_context_parallel:
|
||||
if self.enable_dsa_prefill_context_parallel:
|
||||
assert (
|
||||
self.disaggregation_mode != "decode"
|
||||
), "CP is only supported for prefill when PD disaggregation, please remove --enable-nsa-prefill-context-parallel."
|
||||
), "CP is only supported for prefill when PD disaggregation, please remove --enable-dsa-prefill-context-parallel."
|
||||
|
||||
else:
|
||||
# DeepSeek V3/R1/V3.1
|
||||
@@ -3408,7 +3411,7 @@ class ServerArgs:
|
||||
"the paged cache, which the no-op pool does not support."
|
||||
)
|
||||
|
||||
# HiSparse selects a different pool class (HiSparseNSATokenToKVPool /
|
||||
# HiSparse selects a different pool class (HiSparseDSATokenToKVPool /
|
||||
# HiSparseTokenToKVPoolAllocator) that is not the no-op pool.
|
||||
if self.enable_hisparse:
|
||||
raise ValueError(
|
||||
@@ -5349,18 +5352,40 @@ class ServerArgs:
|
||||
help="Set multimodal attention backend.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nsa-prefill-backend",
|
||||
default=ServerArgs.nsa_prefill_backend,
|
||||
"--dsa-prefill-backend",
|
||||
dest="dsa_prefill_backend",
|
||||
default=ServerArgs.dsa_prefill_backend,
|
||||
type=str,
|
||||
choices=NSA_CHOICES,
|
||||
help="NSA prefill backend. If not specified, auto-detects based on hardware and kv_cache_dtype.",
|
||||
choices=DSA_CHOICES,
|
||||
help="DSA (DeepSeek Sparse Attention) prefill backend. If not specified, auto-detects based on hardware and kv_cache_dtype.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nsa-prefill-backend",
|
||||
dest="dsa_prefill_backend",
|
||||
action=DeprecatedAliasStoreAction,
|
||||
new_flag="--dsa-prefill-backend",
|
||||
default=argparse.SUPPRESS,
|
||||
type=str,
|
||||
choices=DSA_CHOICES,
|
||||
help="[Deprecated] Use --dsa-prefill-backend instead.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dsa-decode-backend",
|
||||
dest="dsa_decode_backend",
|
||||
default=ServerArgs.dsa_decode_backend,
|
||||
type=str,
|
||||
choices=DSA_CHOICES,
|
||||
help="DSA (DeepSeek Sparse Attention) decode backend. If not specified, auto-detects based on hardware and kv_cache_dtype.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nsa-decode-backend",
|
||||
default=ServerArgs.nsa_decode_backend,
|
||||
dest="dsa_decode_backend",
|
||||
action=DeprecatedAliasStoreAction,
|
||||
new_flag="--dsa-decode-backend",
|
||||
default=argparse.SUPPRESS,
|
||||
type=str,
|
||||
choices=NSA_CHOICES,
|
||||
help="NSA decode backend. If not specified, auto-detects based on hardware and kv_cache_dtype.",
|
||||
choices=DSA_CHOICES,
|
||||
help="[Deprecated] Use --dsa-decode-backend instead.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fp8-gemm-backend",
|
||||
@@ -6396,15 +6421,34 @@ class ServerArgs:
|
||||
help="Allow input of attention to be scattered when only using tensor parallelism, to reduce the computational load of operations such as qkv latent.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--enable-nsa-prefill-context-parallel",
|
||||
"--enable-dsa-prefill-context-parallel",
|
||||
dest="enable_dsa_prefill_context_parallel",
|
||||
action="store_true",
|
||||
help="Enable context parallelism used in the long sequence prefill phase of DeepSeek v3.2.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nsa-prefill-cp-mode",
|
||||
"--enable-nsa-prefill-context-parallel",
|
||||
dest="enable_dsa_prefill_context_parallel",
|
||||
action=DeprecatedStoreTrueAction,
|
||||
new_flag="--enable-dsa-prefill-context-parallel",
|
||||
help="[Deprecated] Use --enable-dsa-prefill-context-parallel instead.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dsa-prefill-cp-mode",
|
||||
dest="dsa_prefill_cp_mode",
|
||||
type=str,
|
||||
default=ServerArgs.nsa_prefill_cp_mode,
|
||||
choices=NSA_PREFILL_CP_SPLIT_CHOICES,
|
||||
default=ServerArgs.dsa_prefill_cp_mode,
|
||||
choices=DSA_PREFILL_CP_SPLIT_CHOICES,
|
||||
help="Token splitting mode for the prefill phase of DeepSeek v3.2 under context parallelism.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nsa-prefill-cp-mode",
|
||||
dest="dsa_prefill_cp_mode",
|
||||
action=DeprecatedAliasStoreAction,
|
||||
new_flag="--dsa-prefill-cp-mode",
|
||||
default=argparse.SUPPRESS,
|
||||
type=str,
|
||||
choices=DSA_PREFILL_CP_SPLIT_CHOICES,
|
||||
help="Token splitting mode for the prefill phase of DeepSeek v3.2 under context parallelism. Optional values: 'round-robin-split'(default), 'in-seq-split' "
|
||||
"'round-robin-split' distributes tokens across ranks based on token_idx %% cp_size. It supports multi-batch prefill, fused MoE, and FP8 KV cache.",
|
||||
)
|
||||
|
||||
@@ -54,7 +54,8 @@ class DraftBackendFactory:
|
||||
"trtllm_mha": self._create_trtllm_mha_decode_backend,
|
||||
"trtllm_mla": self._create_trtllm_mla_decode_backend,
|
||||
"tokenspeed_mla": self._create_tokenspeed_mla_decode_backend,
|
||||
"nsa": self._create_nsa_decode_backend,
|
||||
"dsa": self._create_dsa_decode_backend,
|
||||
"nsa": self._create_dsa_decode_backend, # Deprecated alias for "dsa"
|
||||
"ascend": self._create_ascend_decode_backend,
|
||||
"fa4": self._create_fa4_decode_backend,
|
||||
"dsv4": self._create_dsv4_decode_backend,
|
||||
@@ -81,7 +82,8 @@ class DraftBackendFactory:
|
||||
"trtllm_mha": self._create_trtllm_mha_prefill_backend,
|
||||
"trtllm_mla": self._create_trtllm_mla_prefill_backend,
|
||||
"tokenspeed_mla": self._create_tokenspeed_mla_prefill_backend,
|
||||
"nsa": self._create_nsa_prefill_backend,
|
||||
"dsa": self._create_dsa_prefill_backend,
|
||||
"nsa": self._create_dsa_prefill_backend, # Deprecated alias for "dsa"
|
||||
"ascend": self._create_ascend_prefill_backend,
|
||||
"fa4": self._create_fa4_prefill_backend,
|
||||
"dsv4": self._create_dsv4_prefill_backend,
|
||||
@@ -97,19 +99,19 @@ class DraftBackendFactory:
|
||||
"EAGLE is not supported in attention backend {backend_type}",
|
||||
)
|
||||
|
||||
def _create_nsa_decode_backend(self):
|
||||
from sglang.srt.layers.attention.nsa_backend import (
|
||||
NativeSparseAttnMultiStepBackend,
|
||||
def _create_dsa_decode_backend(self):
|
||||
from sglang.srt.layers.attention.dsa_backend import (
|
||||
DeepseekSparseAttnMultiStepBackend,
|
||||
)
|
||||
|
||||
return NativeSparseAttnMultiStepBackend(
|
||||
return DeepseekSparseAttnMultiStepBackend(
|
||||
self.draft_model_runner, self.topk, self.speculative_num_steps
|
||||
)
|
||||
|
||||
def _create_nsa_prefill_backend(self):
|
||||
from sglang.srt.layers.attention.nsa_backend import NativeSparseAttnBackend
|
||||
def _create_dsa_prefill_backend(self):
|
||||
from sglang.srt.layers.attention.dsa_backend import DeepseekSparseAttnBackend
|
||||
|
||||
return NativeSparseAttnBackend(self.draft_model_runner, skip_prefill=False)
|
||||
return DeepseekSparseAttnBackend(self.draft_model_runner, skip_prefill=False)
|
||||
|
||||
def _create_flashinfer_decode_backend(self):
|
||||
if not get_global_server_args().use_mla_backend:
|
||||
|
||||
@@ -64,7 +64,7 @@ class NightlyBenchmarkRunner:
|
||||
|
||||
Args:
|
||||
model_path: Path to the model (e.g., "deepseek-ai/DeepSeek-V3.1")
|
||||
variant: Optional variant suffix (e.g., "basic", "mtp", "nsa")
|
||||
variant: Optional variant suffix (e.g., "basic", "mtp", "dsa")
|
||||
|
||||
Returns:
|
||||
Tuple of (profile_path_prefix, json_output_file)
|
||||
|
||||
Reference in New Issue
Block a user