[DeepseekV32] Enable flashmla_prefill kernel with fp8 kvcache (#11655)
Signed-off-by: Hao Lu <14827759+hlu1@users.noreply.github.com>
This commit is contained in:
@@ -22,6 +22,10 @@ def _dequantize_k_cache_slow(
|
|||||||
De-quantize the k-cache
|
De-quantize the k-cache
|
||||||
"""
|
"""
|
||||||
assert dv % tile_size == 0
|
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_tiles = dv // tile_size
|
||||||
num_blocks, block_size, h_k, _ = quant_k_cache.shape
|
num_blocks, block_size, h_k, _ = quant_k_cache.shape
|
||||||
assert h_k == 1
|
assert h_k == 1
|
||||||
@@ -45,8 +49,10 @@ def _dequantize_k_cache_slow(
|
|||||||
cur_nope * cur_scales
|
cur_nope * cur_scales
|
||||||
)
|
)
|
||||||
|
|
||||||
result = result.view(num_blocks, block_size, 1, d)
|
if original_ndim == 3:
|
||||||
return result
|
return result.view(num_blocks, 1, -1)
|
||||||
|
else:
|
||||||
|
return result.view(num_blocks, block_size, 1, -1)
|
||||||
|
|
||||||
|
|
||||||
def _dequantize_k_cache_fast_wrapped(
|
def _dequantize_k_cache_fast_wrapped(
|
||||||
@@ -54,7 +60,10 @@ def _dequantize_k_cache_fast_wrapped(
|
|||||||
dv: int = 512,
|
dv: int = 512,
|
||||||
tile_size: int = 128,
|
tile_size: int = 128,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
# TODO the final API may be 2D instead of 4D, thus we convert them here
|
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
|
num_blocks, block_size, _, dim_quant = quant_k_cache.shape
|
||||||
assert dv == 512
|
assert dv == 512
|
||||||
assert dim_quant == 656
|
assert dim_quant == 656
|
||||||
@@ -63,7 +72,10 @@ def _dequantize_k_cache_fast_wrapped(
|
|||||||
|
|
||||||
output = _dequantize_k_cache_fast(quant_k_cache)
|
output = _dequantize_k_cache_fast(quant_k_cache)
|
||||||
|
|
||||||
return output.view(num_blocks, block_size, 1, -1)
|
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):
|
def _dequantize_k_cache_fast(quant_k_cache, group_size: int = 128):
|
||||||
@@ -85,7 +97,6 @@ def _dequantize_k_cache_fast(quant_k_cache, group_size: int = 128):
|
|||||||
assert num_blocks_per_token == 5
|
assert num_blocks_per_token == 5
|
||||||
|
|
||||||
assert dim_nope % group_size == 0
|
assert dim_nope % group_size == 0
|
||||||
NUM_NOPE_BLOCKS = dim_nope // group_size
|
|
||||||
|
|
||||||
input_nope_q = quant_k_cache[:, :dim_nope]
|
input_nope_q = quant_k_cache[:, :dim_nope]
|
||||||
input_nope_s = quant_k_cache[:, dim_nope : dim_nope + num_tiles * 4].view(
|
input_nope_s = quant_k_cache[:, dim_nope : dim_nope + num_tiles * 4].view(
|
||||||
@@ -102,7 +113,7 @@ def _dequantize_k_cache_fast(quant_k_cache, group_size: int = 128):
|
|||||||
input_nope_q.stride(0),
|
input_nope_q.stride(0),
|
||||||
input_nope_s.stride(0),
|
input_nope_s.stride(0),
|
||||||
input_rope.stride(0),
|
input_rope.stride(0),
|
||||||
NUM_NOPE_BLOCKS=NUM_NOPE_BLOCKS,
|
NUM_NOPE_BLOCKS=num_tiles,
|
||||||
GROUP_SIZE=group_size,
|
GROUP_SIZE=group_size,
|
||||||
DIM_NOPE=dim_nope,
|
DIM_NOPE=dim_nope,
|
||||||
DIM_ROPE=dim_rope,
|
DIM_ROPE=dim_rope,
|
||||||
@@ -159,5 +170,126 @@ def _dequantize_k_cache_fast_kernel(
|
|||||||
tl.store(dst_ptr, data, mask=mask)
|
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))
|
||||||
|
|
||||||
|
total_num_tokens, _ = quant_k_cache.shape
|
||||||
|
num_tokens = page_table_1_flattened.shape[0]
|
||||||
|
assert num_tokens <= total_num_tokens
|
||||||
|
|
||||||
|
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__":
|
if __name__ == "__main__":
|
||||||
raise Exception("UT is in quant_k_cache.py")
|
raise Exception("UT is in quant_k_cache.py")
|
||||||
|
|||||||
@@ -206,6 +206,8 @@ def _quantize_k_cache_fast_kernel(
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
import dequant_k_cache
|
||||||
|
|
||||||
for num_blocks, block_size in [
|
for num_blocks, block_size in [
|
||||||
(1, 1),
|
(1, 1),
|
||||||
(10, 64),
|
(10, 64),
|
||||||
@@ -217,21 +219,9 @@ if __name__ == "__main__":
|
|||||||
dtype=torch.bfloat16,
|
dtype=torch.bfloat16,
|
||||||
device="cuda",
|
device="cuda",
|
||||||
)
|
)
|
||||||
# temp debug
|
|
||||||
# input_k_cache = (576 - torch.arange(num_blocks * block_size * 1 * dim_nope_and_rope, device="cuda")).to(torch.bfloat16).reshape(num_blocks, block_size, 1, dim_nope_and_rope)
|
|
||||||
|
|
||||||
ref_quant = _quantize_k_cache_slow(input_k_cache)
|
ref_quant = _quantize_k_cache_slow(input_k_cache)
|
||||||
actual_quant = _quantize_k_cache_fast_wrapped(input_k_cache)
|
actual_quant = _quantize_k_cache_fast_wrapped(input_k_cache)
|
||||||
# print(f"{input_k_cache=}")
|
|
||||||
# print(f"{ref_quant=}")
|
|
||||||
# print(f"{actual_quant=}")
|
|
||||||
# print(f"{ref_quant == actual_quant=}")
|
|
||||||
# print(f"{actual_quant.to(torch.float32) - ref_quant.to(torch.float32)=}")
|
|
||||||
# print(f"{ref_quant.view(torch.bfloat16)=}")
|
|
||||||
# print(f"{actual_quant.view(torch.bfloat16)=}")
|
|
||||||
# assert torch.all(ref_quant == actual_quant)
|
|
||||||
|
|
||||||
import dequant_k_cache
|
|
||||||
|
|
||||||
ref_ref_dequant = dequant_k_cache._dequantize_k_cache_slow(ref_quant)
|
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)
|
ref_actual_dequant = dequant_k_cache._dequantize_k_cache_fast_wrapped(ref_quant)
|
||||||
@@ -252,4 +242,46 @@ if __name__ == "__main__":
|
|||||||
ref_ref_dequant, actual_actual_dequant, atol=0.2, rtol=0.2
|
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")
|
print("Passed")
|
||||||
|
print("Do 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")
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from enum import IntEnum, auto
|
||||||
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, TypeAlias
|
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, TypeAlias
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from sglang.srt.configs.model_config import get_nsa_index_topk, is_deepseek_nsa
|
from sglang.srt.configs.model_config import get_nsa_index_topk, is_deepseek_nsa
|
||||||
from sglang.srt.layers.attention.base_attn_backend import AttentionBackend
|
from sglang.srt.layers.attention.base_attn_backend import AttentionBackend
|
||||||
|
from sglang.srt.layers.attention.nsa.dequant_k_cache import dequantize_k_cache_paged
|
||||||
from sglang.srt.layers.attention.nsa.nsa_indexer import BaseIndexerMetadata
|
from sglang.srt.layers.attention.nsa.nsa_indexer import BaseIndexerMetadata
|
||||||
from sglang.srt.layers.attention.nsa.quant_k_cache import quantize_k_cache
|
from sglang.srt.layers.attention.nsa.quant_k_cache import quantize_k_cache
|
||||||
from sglang.srt.layers.attention.nsa.transform_index import (
|
from sglang.srt.layers.attention.nsa.transform_index import (
|
||||||
@@ -98,11 +100,27 @@ class NSAMetadata:
|
|||||||
nsa_max_seqlen_q: Literal[1] = 1 # always 1 for decode, variable for extend
|
nsa_max_seqlen_q: Literal[1] = 1 # always 1 for decode, variable for extend
|
||||||
|
|
||||||
flashmla_metadata: Optional[NSAFlashMLAMetadata] = None
|
flashmla_metadata: Optional[NSAFlashMLAMetadata] = None
|
||||||
|
# The sum of sequence lengths for key, prefill only
|
||||||
|
seq_lens_sum: Optional[int] = None
|
||||||
|
# The flattened 1D page table with shape (seq_lens_sum,), prefill only
|
||||||
|
# this table is always with page_size = 1
|
||||||
|
page_table_1_flattened: Optional[torch.Tensor] = None
|
||||||
|
# The offset of topk indices in ragged kv, prefill only
|
||||||
|
# shape: (seq_lens_sum,)
|
||||||
|
topk_indices_offset: Optional[torch.Tensor] = None
|
||||||
|
|
||||||
|
|
||||||
|
class TopkTransformMethod(IntEnum):
|
||||||
|
# Transform topk indices to indices to the page table (page_size = 1)
|
||||||
|
PAGED = auto()
|
||||||
|
# Transform topk indices to indices to ragged kv (non-paged)
|
||||||
|
RAGGED = auto()
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class NSAIndexerMetadata(BaseIndexerMetadata):
|
class NSAIndexerMetadata(BaseIndexerMetadata):
|
||||||
attn_metadata: NSAMetadata
|
attn_metadata: NSAMetadata
|
||||||
|
topk_transform_method: TopkTransformMethod
|
||||||
|
|
||||||
def get_seqlens_int32(self) -> torch.Tensor:
|
def get_seqlens_int32(self) -> torch.Tensor:
|
||||||
return self.attn_metadata.cache_seqlens_int32
|
return self.attn_metadata.cache_seqlens_int32
|
||||||
@@ -118,23 +136,36 @@ class NSAIndexerMetadata(BaseIndexerMetadata):
|
|||||||
logits: torch.Tensor,
|
logits: torch.Tensor,
|
||||||
topk: int,
|
topk: int,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
from sgl_kernel import fast_topk_transform_fused, fast_topk_v2
|
from sgl_kernel import (
|
||||||
|
fast_topk_transform_fused,
|
||||||
|
fast_topk_transform_ragged_fused,
|
||||||
|
fast_topk_v2,
|
||||||
|
)
|
||||||
|
|
||||||
if not NSA_FUSE_TOPK:
|
if not NSA_FUSE_TOPK:
|
||||||
return fast_topk_v2(logits, self.get_seqlens_expanded(), topk)
|
return fast_topk_v2(logits, self.get_seqlens_expanded(), topk)
|
||||||
|
elif self.topk_transform_method == TopkTransformMethod.PAGED:
|
||||||
# NOTE(dark): if fused, we return a transformed page table directly
|
# NOTE(dark): if fused, we return a transformed page table directly
|
||||||
return fast_topk_transform_fused(
|
return fast_topk_transform_fused(
|
||||||
score=logits,
|
score=logits,
|
||||||
lengths=self.get_seqlens_expanded(),
|
lengths=self.get_seqlens_expanded(),
|
||||||
page_table_size_1=self.attn_metadata.page_table_1,
|
page_table_size_1=self.attn_metadata.page_table_1,
|
||||||
cu_seqlens_q=self.attn_metadata.cu_seqlens_q,
|
cu_seqlens_q=self.attn_metadata.cu_seqlens_q,
|
||||||
topk=topk,
|
topk=topk,
|
||||||
)
|
)
|
||||||
|
elif self.topk_transform_method == TopkTransformMethod.RAGGED:
|
||||||
|
return fast_topk_transform_ragged_fused(
|
||||||
|
score=logits,
|
||||||
|
lengths=self.get_seqlens_expanded(),
|
||||||
|
topk_indices_offset=self.attn_metadata.topk_indices_offset,
|
||||||
|
topk=topk,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
assert False, f"Unsupported {self.topk_transform_method = }"
|
||||||
|
|
||||||
|
|
||||||
def compute_cu_seqlens(seqlens: torch.Tensor) -> torch.Tensor:
|
def compute_cu_seqlens(seqlens: torch.Tensor) -> torch.Tensor:
|
||||||
assert seqlens.dtype == torch.int32 and seqlens.is_cuda
|
assert seqlens.dtype == torch.int32
|
||||||
return torch.nn.functional.pad(
|
return torch.nn.functional.pad(
|
||||||
torch.cumsum(seqlens, dim=0, dtype=torch.int32), (1, 0)
|
torch.cumsum(seqlens, dim=0, dtype=torch.int32), (1, 0)
|
||||||
)
|
)
|
||||||
@@ -181,6 +212,7 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
global NSA_PREFILL_IMPL, NSA_DECODE_IMPL
|
global NSA_PREFILL_IMPL, NSA_DECODE_IMPL
|
||||||
NSA_PREFILL_IMPL = model_runner.server_args.nsa_prefill_backend
|
NSA_PREFILL_IMPL = model_runner.server_args.nsa_prefill_backend
|
||||||
NSA_DECODE_IMPL = model_runner.server_args.nsa_decode_backend
|
NSA_DECODE_IMPL = model_runner.server_args.nsa_decode_backend
|
||||||
|
self.enable_auto_select_prefill_impl = NSA_PREFILL_IMPL == "flashmla_auto"
|
||||||
|
|
||||||
self._arange_buf = torch.arange(16384, device=self.device, dtype=torch.int32)
|
self._arange_buf = torch.arange(16384, device=self.device, dtype=torch.int32)
|
||||||
|
|
||||||
@@ -231,10 +263,16 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
cu_seqlens_k = compute_cu_seqlens(cache_seqlens_int32)
|
cu_seqlens_k = compute_cu_seqlens(cache_seqlens_int32)
|
||||||
assert forward_batch.seq_lens_cpu is not None
|
assert forward_batch.seq_lens_cpu is not None
|
||||||
max_seqlen_k = int(forward_batch.seq_lens_cpu.max().item() + draft_token_num)
|
max_seqlen_k = int(forward_batch.seq_lens_cpu.max().item() + draft_token_num)
|
||||||
|
# [b, max_seqlen_k]
|
||||||
page_table = forward_batch.req_to_token_pool.req_to_token[
|
page_table = forward_batch.req_to_token_pool.req_to_token[
|
||||||
forward_batch.req_pool_indices, :max_seqlen_k
|
forward_batch.req_pool_indices, :max_seqlen_k
|
||||||
]
|
]
|
||||||
|
|
||||||
|
page_table_1_flattened = None
|
||||||
|
topk_indices_offset = None
|
||||||
|
self.set_nsa_prefill_impl(forward_batch)
|
||||||
|
topk_transform_method = self.get_topk_transform_method()
|
||||||
|
|
||||||
if forward_batch.forward_mode.is_decode_or_idle():
|
if forward_batch.forward_mode.is_decode_or_idle():
|
||||||
extend_seq_lens_cpu = [1] * batch_size
|
extend_seq_lens_cpu = [1] * batch_size
|
||||||
max_seqlen_q = 1
|
max_seqlen_q = 1
|
||||||
@@ -295,6 +333,7 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
else:
|
else:
|
||||||
max_seqlen_q = max_seqlen_k
|
max_seqlen_q = max_seqlen_k
|
||||||
cu_seqlens_q = cu_seqlens_k
|
cu_seqlens_q = cu_seqlens_k
|
||||||
|
|
||||||
seqlens_expanded = torch.cat(
|
seqlens_expanded = torch.cat(
|
||||||
[
|
[
|
||||||
torch.arange(
|
torch.arange(
|
||||||
@@ -310,6 +349,24 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if topk_transform_method == TopkTransformMethod.RAGGED:
|
||||||
|
page_table_1_flattened = torch.cat(
|
||||||
|
[
|
||||||
|
page_table[i, :kv_len]
|
||||||
|
for i, kv_len in enumerate(
|
||||||
|
forward_batch.seq_lens_cpu.tolist(),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
page_table_1_flattened.shape[0] == forward_batch.seq_lens_sum
|
||||||
|
), f"{page_table_1_flattened.shape[0] = } must be the same as {forward_batch.seq_lens_sum = }"
|
||||||
|
|
||||||
|
topk_indices_offset = torch.repeat_interleave(
|
||||||
|
cu_seqlens_k[:-1],
|
||||||
|
forward_batch.extend_seq_lens,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert False, f"Unsupported {forward_batch.forward_mode = }"
|
assert False, f"Unsupported {forward_batch.forward_mode = }"
|
||||||
|
|
||||||
@@ -328,7 +385,9 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
max_seq_len_k=max_seqlen_k,
|
max_seq_len_k=max_seqlen_k,
|
||||||
cu_seqlens_q=cu_seqlens_q,
|
cu_seqlens_q=cu_seqlens_q,
|
||||||
cu_seqlens_k=cu_seqlens_k,
|
cu_seqlens_k=cu_seqlens_k,
|
||||||
|
seq_lens_sum=forward_batch.seq_lens_sum,
|
||||||
page_table_1=page_table,
|
page_table_1=page_table,
|
||||||
|
page_table_1_flattened=page_table_1_flattened,
|
||||||
flashmla_metadata=(
|
flashmla_metadata=(
|
||||||
self._compute_flashmla_metadata(
|
self._compute_flashmla_metadata(
|
||||||
cache_seqlens=nsa_cache_seqlens_int32,
|
cache_seqlens=nsa_cache_seqlens_int32,
|
||||||
@@ -344,6 +403,7 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
nsa_extend_seq_lens_list=extend_seq_lens_cpu,
|
nsa_extend_seq_lens_list=extend_seq_lens_cpu,
|
||||||
real_page_table=self._transform_table_1_to_real(page_table),
|
real_page_table=self._transform_table_1_to_real(page_table),
|
||||||
nsa_max_seqlen_q=1,
|
nsa_max_seqlen_q=1,
|
||||||
|
topk_indices_offset=topk_indices_offset,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.forward_metadata = metadata
|
self.forward_metadata = metadata
|
||||||
@@ -396,6 +456,8 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
forward_mode: ForwardMode,
|
forward_mode: ForwardMode,
|
||||||
spec_info: Optional[SpecInput],
|
spec_info: Optional[SpecInput],
|
||||||
):
|
):
|
||||||
|
self.set_nsa_prefill_impl(forward_batch=None)
|
||||||
|
|
||||||
"""Initialize forward metadata for capturing CUDA graph."""
|
"""Initialize forward metadata for capturing CUDA graph."""
|
||||||
if forward_mode.is_decode_or_idle():
|
if forward_mode.is_decode_or_idle():
|
||||||
# Normal Decode
|
# Normal Decode
|
||||||
@@ -586,6 +648,8 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
"""Initialize forward metadata for replaying CUDA graph."""
|
"""Initialize forward metadata for replaying CUDA graph."""
|
||||||
assert seq_lens_cpu is not None
|
assert seq_lens_cpu is not None
|
||||||
|
|
||||||
|
self.set_nsa_prefill_impl(forward_batch=None)
|
||||||
|
|
||||||
seq_lens = seq_lens[:bs]
|
seq_lens = seq_lens[:bs]
|
||||||
seq_lens_cpu = seq_lens_cpu[:bs]
|
seq_lens_cpu = seq_lens_cpu[:bs]
|
||||||
req_pool_indices = req_pool_indices[:bs]
|
req_pool_indices = req_pool_indices[:bs]
|
||||||
@@ -780,17 +844,31 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
q_rope = q_all[:, :, layer.v_head_dim :]
|
q_rope = q_all[:, :, layer.v_head_dim :]
|
||||||
|
|
||||||
# NOTE(dark): here, we use page size = 1
|
# NOTE(dark): here, we use page size = 1
|
||||||
|
topk_transform_method = self.get_topk_transform_method()
|
||||||
if NSA_FUSE_TOPK:
|
if NSA_FUSE_TOPK:
|
||||||
page_table_1 = topk_indices
|
page_table_1 = topk_indices
|
||||||
else:
|
else:
|
||||||
assert metadata.nsa_extend_seq_lens_list is not None
|
if topk_transform_method == TopkTransformMethod.RAGGED:
|
||||||
page_table_1 = transform_index_page_table_prefill(
|
topk_indices_offset = metadata.topk_indices_offset
|
||||||
page_table=metadata.page_table_1,
|
assert topk_indices_offset is not None
|
||||||
topk_indices=topk_indices,
|
mask = topk_indices != -1
|
||||||
extend_lens_cpu=metadata.nsa_extend_seq_lens_list,
|
topk_indices_offset = (
|
||||||
page_size=1,
|
topk_indices_offset.unsqueeze(1)
|
||||||
)
|
if topk_indices_offset.ndim == 1
|
||||||
|
else topk_indices_offset
|
||||||
|
)
|
||||||
|
topk_indices = torch.where(
|
||||||
|
mask, topk_indices + topk_indices_offset, topk_indices
|
||||||
|
)
|
||||||
|
elif topk_transform_method == TopkTransformMethod.PAGED:
|
||||||
|
assert metadata.nsa_extend_seq_lens_list is not None
|
||||||
|
page_table_1 = transform_index_page_table_prefill(
|
||||||
|
page_table=metadata.page_table_1,
|
||||||
|
topk_indices=topk_indices,
|
||||||
|
extend_lens_cpu=metadata.nsa_extend_seq_lens_list,
|
||||||
|
page_size=1,
|
||||||
|
)
|
||||||
|
|
||||||
if NSA_PREFILL_IMPL == "tilelang":
|
if NSA_PREFILL_IMPL == "tilelang":
|
||||||
if q_rope is not None:
|
if q_rope is not None:
|
||||||
q_all = torch.cat([q_nope, q_rope], dim=-1)
|
q_all = torch.cat([q_nope, q_rope], dim=-1)
|
||||||
@@ -804,6 +882,22 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
elif NSA_PREFILL_IMPL == "flashmla_sparse":
|
elif NSA_PREFILL_IMPL == "flashmla_sparse":
|
||||||
if q_rope is not None:
|
if q_rope is not None:
|
||||||
q_all = torch.cat([q_nope, q_rope], dim=-1)
|
q_all = torch.cat([q_nope, q_rope], dim=-1)
|
||||||
|
|
||||||
|
# NSA_FLASHMLA_BACKEND_DECODE_COMPUTE_FP8 has no effect here,
|
||||||
|
# because the flashmla_sparse kernel doesn't support fp8 compute
|
||||||
|
if topk_transform_method == TopkTransformMethod.RAGGED:
|
||||||
|
if any(forward_batch.extend_prefix_lens_cpu):
|
||||||
|
page_table_1_flattened = (
|
||||||
|
self.forward_metadata.page_table_1_flattened
|
||||||
|
)
|
||||||
|
assert page_table_1_flattened is not None
|
||||||
|
kv_cache = dequantize_k_cache_paged(
|
||||||
|
kv_cache, page_table_1_flattened
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
kv_cache = torch.cat([k, k_rope], dim=-1)
|
||||||
|
page_table_1 = topk_indices
|
||||||
|
|
||||||
return self._forward_flashmla_sparse(
|
return self._forward_flashmla_sparse(
|
||||||
q_all=q_all,
|
q_all=q_all,
|
||||||
kv_cache=kv_cache,
|
kv_cache=kv_cache,
|
||||||
@@ -1121,10 +1215,52 @@ class NativeSparseAttnBackend(AttentionBackend):
|
|||||||
"""Get the fill value for sequence length in CUDA graph."""
|
"""Get the fill value for sequence length in CUDA graph."""
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
def set_nsa_prefill_impl(self, forward_batch: Optional[ForwardBatch] = None) -> str:
|
||||||
|
from sglang.srt.utils import is_blackwell
|
||||||
|
|
||||||
|
global NSA_PREFILL_IMPL
|
||||||
|
if self.enable_auto_select_prefill_impl:
|
||||||
|
if self.nsa_kv_cache_store_fp8:
|
||||||
|
if (
|
||||||
|
# TODO(hlu1): enable MTP
|
||||||
|
is_blackwell()
|
||||||
|
and forward_batch is not None
|
||||||
|
and forward_batch.forward_mode.is_extend()
|
||||||
|
and forward_batch.spec_algorithm.is_none()
|
||||||
|
):
|
||||||
|
total_kv_tokens = forward_batch.seq_lens_sum
|
||||||
|
total_q_tokens = forward_batch.extend_num_tokens
|
||||||
|
# Heuristic based on benchmarking flashmla_kv vs flashmla_sparse + dequantize_k_cache_paged
|
||||||
|
if total_kv_tokens < total_q_tokens * 512:
|
||||||
|
NSA_PREFILL_IMPL = "flashmla_sparse"
|
||||||
|
return
|
||||||
|
NSA_PREFILL_IMPL = "flashmla_kv"
|
||||||
|
else:
|
||||||
|
# bf16 kv cache
|
||||||
|
NSA_PREFILL_IMPL = "flashmla_sparse"
|
||||||
|
|
||||||
|
def get_topk_transform_method(self) -> TopkTransformMethod:
|
||||||
|
"""
|
||||||
|
NSA_FUSE_TOPK controls whether to fuse the topk transform into the topk kernel.
|
||||||
|
This method is used to select the topk transform method which can be fused or unfused.
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
# disable for MTP
|
||||||
|
self.nsa_kv_cache_store_fp8
|
||||||
|
and NSA_PREFILL_IMPL == "flashmla_sparse"
|
||||||
|
):
|
||||||
|
topk_transform_method = TopkTransformMethod.RAGGED
|
||||||
|
else:
|
||||||
|
topk_transform_method = TopkTransformMethod.PAGED
|
||||||
|
return topk_transform_method
|
||||||
|
|
||||||
def get_indexer_metadata(
|
def get_indexer_metadata(
|
||||||
self, layer_id: int, forward_batch: ForwardBatch
|
self, layer_id: int, forward_batch: ForwardBatch
|
||||||
) -> NSAIndexerMetadata:
|
) -> NSAIndexerMetadata:
|
||||||
return NSAIndexerMetadata(attn_metadata=self.forward_metadata)
|
return NSAIndexerMetadata(
|
||||||
|
attn_metadata=self.forward_metadata,
|
||||||
|
topk_transform_method=self.get_topk_transform_method(),
|
||||||
|
)
|
||||||
|
|
||||||
def _compute_flashmla_metadata(self, cache_seqlens: torch.Tensor, seq_len_q: int):
|
def _compute_flashmla_metadata(self, cache_seqlens: torch.Tensor, seq_len_q: int):
|
||||||
from flash_mla import get_mla_metadata
|
from flash_mla import get_mla_metadata
|
||||||
|
|||||||
@@ -135,7 +135,16 @@ GRAMMAR_BACKEND_CHOICES = ["xgrammar", "outlines", "llguidance", "none"]
|
|||||||
|
|
||||||
DETERMINISTIC_ATTENTION_BACKEND_CHOICES = ["flashinfer", "fa3", "triton"]
|
DETERMINISTIC_ATTENTION_BACKEND_CHOICES = ["flashinfer", "fa3", "triton"]
|
||||||
|
|
||||||
NSA_CHOICES = ["flashmla_sparse", "flashmla_kv", "fa3", "tilelang", "aiter"]
|
DEFAULT_LORA_EVICTION_POLICY = "lru"
|
||||||
|
|
||||||
|
NSA_CHOICES = [
|
||||||
|
"flashmla_sparse",
|
||||||
|
"flashmla_kv",
|
||||||
|
"flashmla_auto",
|
||||||
|
"fa3",
|
||||||
|
"tilelang",
|
||||||
|
"aiter",
|
||||||
|
]
|
||||||
|
|
||||||
RADIX_EVICTION_POLICY_CHOICES = ["lru", "lfu"]
|
RADIX_EVICTION_POLICY_CHOICES = ["lru", "lfu"]
|
||||||
|
|
||||||
@@ -1022,16 +1031,30 @@ class ServerArgs:
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
major, _ = torch.cuda.get_device_capability()
|
major, _ = torch.cuda.get_device_capability()
|
||||||
if major >= 10:
|
if self.kv_cache_dtype == "auto":
|
||||||
self.kv_cache_dtype = "fp8_e4m3"
|
self.kv_cache_dtype = "fp8_e4m3" if major >= 10 else "bfloat16"
|
||||||
logger.warning("Setting KV cache dtype to fp8.")
|
logger.warning(
|
||||||
|
f"Setting KV cache dtype to {self.kv_cache_dtype} for DeepSeek NSA."
|
||||||
|
)
|
||||||
|
if self.kv_cache_dtype == "bf16":
|
||||||
|
self.kv_cache_dtype = "bfloat16"
|
||||||
|
assert self.kv_cache_dtype in [
|
||||||
|
"bfloat16",
|
||||||
|
"fp8_e4m3",
|
||||||
|
], "DeepSeek NSA only supports bf16/bfloat16 or fp8_e4m3 kv_cache_dtype"
|
||||||
|
|
||||||
if self.kv_cache_dtype == "fp8_e4m3":
|
if self.kv_cache_dtype == "fp8_e4m3":
|
||||||
self.nsa_prefill_backend = "flashmla_kv"
|
# flashmla_auto dispatches to flashmla_sparse/flashmla_kv based on hardware and heuristics
|
||||||
|
self.nsa_prefill_backend = "flashmla_auto"
|
||||||
self.nsa_decode_backend = "flashmla_kv"
|
self.nsa_decode_backend = "flashmla_kv"
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Setting NSA backend to flashmla_kv for FP8 KV Cache."
|
"Setting NSA backend to flashmla_auto for prefill and flashmla_kv for decode for FP8 KV Cache."
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
# set prefill/decode backends for Blackwell. The default settings are for Hopper.
|
||||||
|
if major >= 10:
|
||||||
|
self.nsa_prefill_backend = "flashmla_sparse"
|
||||||
|
self.nsa_decode_backend = "flashmla_sparse"
|
||||||
|
|
||||||
# Logging env vars for NSA
|
# Logging env vars for NSA
|
||||||
from sglang.srt.layers.attention.nsa.utils import (
|
from sglang.srt.layers.attention.nsa.utils import (
|
||||||
|
|||||||
Reference in New Issue
Block a user