94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
227 lines
7.8 KiB
Python
227 lines
7.8 KiB
Python
from typing import Optional
|
|
|
|
import torch
|
|
import triton
|
|
import triton.language as tl
|
|
|
|
from sglang.srt.layers.quantization.fp8_kernel import is_fp8_fnuz
|
|
|
|
fp8_dtype = torch.float8_e4m3fnuz if is_fp8_fnuz() else torch.float8_e4m3fn
|
|
|
|
# v4 KV cache layout (see dsv4.index_buf_accessor._set_k_and_s_triton_kernel):
|
|
# per-token: 448 fp8 nope + 64 bf16 rope (= 576 contiguous bytes) +
|
|
# 7 ue8m0 scales padded to 8 bytes.
|
|
# per-page: [token 0..P-1 nope+rope (P*576 bytes)] [token 0..P-1 scale (P*8 bytes)]
|
|
# padded up to a multiple of 576.
|
|
DIM_NOPE = 448
|
|
DIM_ROPE = 64
|
|
TILE_SIZE = 64 # one nope scale tile = 64 fp8 values
|
|
NUM_SCALE_TILES = DIM_NOPE // TILE_SIZE # 7
|
|
NOPE_ROPE_BYTES = DIM_NOPE + DIM_ROPE * 2 # 576
|
|
PADDED_SCALE_PER_TOKEN = NUM_SCALE_TILES + 1 # 8
|
|
|
|
|
|
def dequantize_k_cache_paged(
|
|
quant_k_cache: torch.Tensor,
|
|
page_table_1_flattened: torch.Tensor,
|
|
page_size: int,
|
|
out: Optional[torch.Tensor] = None,
|
|
) -> torch.Tensor:
|
|
"""Dequantize the DeepSeek v4 paged KV cache for a list of token IDs.
|
|
|
|
Args:
|
|
quant_k_cache: (num_pages, bytes_per_page_padded) uint8.
|
|
page_table_1_flattened: (num_tokens,) int — token IDs into the cache.
|
|
page_size: number of tokens per page.
|
|
out: optional (num_tokens, 1, DIM_NOPE + DIM_ROPE) bf16 destination.
|
|
May be a slice of a larger workspace; the kernel uses out.stride(0)
|
|
so contiguous-along-dim-0 slices work.
|
|
|
|
Returns:
|
|
(num_tokens, 1, DIM_NOPE + DIM_ROPE) bfloat16.
|
|
"""
|
|
assert quant_k_cache.is_contiguous()
|
|
assert page_table_1_flattened.dtype in (torch.int32, torch.int64)
|
|
|
|
# The buffer's dtype is whatever the pool exposes (often bf16); the
|
|
# underlying storage is uint8. Reinterpret to byte-space first.
|
|
quant_k_cache_u8 = quant_k_cache.view(torch.uint8)
|
|
num_tokens = page_table_1_flattened.shape[0]
|
|
bytes_per_page = quant_k_cache_u8.shape[-1]
|
|
s_offset_bytes = page_size * NOPE_ROPE_BYTES
|
|
|
|
# Three typed views over the same underlying bytes.
|
|
buf_fp8 = quant_k_cache_u8.view(fp8_dtype).reshape(-1)
|
|
buf_bf16 = quant_k_cache_u8.view(torch.bfloat16).reshape(-1)
|
|
buf_uint8 = quant_k_cache_u8.reshape(-1)
|
|
|
|
if out is None:
|
|
out = torch.empty(
|
|
(num_tokens, 1, DIM_NOPE + DIM_ROPE),
|
|
dtype=torch.bfloat16,
|
|
device=quant_k_cache.device,
|
|
)
|
|
else:
|
|
assert out.shape == (num_tokens, 1, DIM_NOPE + DIM_ROPE)
|
|
assert out.dtype == torch.bfloat16
|
|
|
|
_dequantize_k_cache_paged_kernel[(num_tokens,)](
|
|
out,
|
|
buf_fp8,
|
|
buf_bf16,
|
|
buf_uint8,
|
|
page_table_1_flattened,
|
|
out.stride(0),
|
|
BYTES_PER_PAGE=bytes_per_page,
|
|
PAGE_SIZE=page_size,
|
|
DIM_NOPE=DIM_NOPE,
|
|
DIM_ROPE=DIM_ROPE,
|
|
TILE_SIZE=TILE_SIZE,
|
|
NUM_SCALE_TILES=NUM_SCALE_TILES,
|
|
NOPE_ROPE_BYTES=NOPE_ROPE_BYTES,
|
|
PADDED_SCALE_PER_TOKEN=PADDED_SCALE_PER_TOKEN,
|
|
S_OFFSET_BYTES=s_offset_bytes,
|
|
)
|
|
return out
|
|
|
|
|
|
@triton.jit
|
|
def _dequantize_k_cache_paged_kernel(
|
|
output_ptr,
|
|
buf_fp8_ptr,
|
|
buf_bf16_ptr,
|
|
buf_uint8_ptr,
|
|
page_table_ptr,
|
|
output_stride_0,
|
|
BYTES_PER_PAGE: tl.constexpr,
|
|
PAGE_SIZE: tl.constexpr,
|
|
DIM_NOPE: tl.constexpr,
|
|
DIM_ROPE: tl.constexpr,
|
|
TILE_SIZE: tl.constexpr,
|
|
NUM_SCALE_TILES: tl.constexpr,
|
|
NOPE_ROPE_BYTES: tl.constexpr,
|
|
PADDED_SCALE_PER_TOKEN: tl.constexpr,
|
|
S_OFFSET_BYTES: tl.constexpr,
|
|
):
|
|
# One program per token: load page_table[token_id] once and emit all
|
|
# NUM_SCALE_TILES nope tiles + rope tail via tl.static_range.
|
|
token_id = tl.program_id(0)
|
|
loc = tl.load(page_table_ptr + token_id).to(tl.int64)
|
|
page_idx = loc // PAGE_SIZE
|
|
in_page = loc % PAGE_SIZE
|
|
page_byte_base = page_idx * BYTES_PER_PAGE
|
|
token_data_base = page_byte_base + in_page * NOPE_ROPE_BYTES
|
|
token_scale_base = (
|
|
page_byte_base + S_OFFSET_BYTES + in_page * PADDED_SCALE_PER_TOKEN
|
|
)
|
|
out_row_base = token_id * output_stride_0
|
|
|
|
nope_offs = tl.arange(0, TILE_SIZE)
|
|
for tile_id in tl.static_range(NUM_SCALE_TILES):
|
|
fp8_off = token_data_base + tile_id * TILE_SIZE + nope_offs
|
|
fp8_vals = tl.load(buf_fp8_ptr + fp8_off).to(tl.float32)
|
|
|
|
scale_u8 = tl.load(buf_uint8_ptr + token_scale_base + tile_id).to(tl.int32)
|
|
scale_pow2 = tl.exp2((scale_u8 - 127).to(tl.float32))
|
|
|
|
out_off = out_row_base + tile_id * TILE_SIZE + nope_offs
|
|
tl.store(
|
|
output_ptr + out_off,
|
|
(fp8_vals * scale_pow2).to(output_ptr.dtype.element_ty),
|
|
)
|
|
|
|
rope_offs = tl.arange(0, DIM_ROPE)
|
|
bf16_off = (token_data_base + DIM_NOPE) // 2 + rope_offs
|
|
rope_data = tl.load(buf_bf16_ptr + bf16_off)
|
|
tl.store(output_ptr + out_row_base + DIM_NOPE + rope_offs, rope_data)
|
|
|
|
|
|
def dequantize_k_cache_paged_ref(
|
|
quant_k_cache: torch.Tensor,
|
|
page_table_1_flattened: torch.Tensor,
|
|
page_size: int,
|
|
) -> torch.Tensor:
|
|
"""Pure-torch reference for :func:`dequantize_k_cache_paged`.
|
|
|
|
Decodes the same v4 paged layout with vectorized torch indexing instead of
|
|
a Triton kernel. Used to validate the kernel (see the ``__main__`` block
|
|
below); not on any hot path.
|
|
"""
|
|
assert page_table_1_flattened.dtype in (torch.int32, torch.int64)
|
|
u8 = quant_k_cache.view(torch.uint8)
|
|
bytes_per_page = u8.shape[-1]
|
|
s_offset_bytes = page_size * NOPE_ROPE_BYTES
|
|
|
|
flat_u8 = u8.reshape(-1)
|
|
flat_fp8 = u8.view(fp8_dtype).reshape(-1)
|
|
flat_bf16 = u8.view(torch.bfloat16).reshape(-1)
|
|
|
|
loc = page_table_1_flattened.to(torch.int64)
|
|
page_idx = loc // page_size
|
|
in_page = loc % page_size
|
|
page_byte_base = page_idx * bytes_per_page
|
|
token_data_base = page_byte_base + in_page * NOPE_ROPE_BYTES
|
|
token_scale_base = (
|
|
page_byte_base + s_offset_bytes + in_page * PADDED_SCALE_PER_TOKEN
|
|
)
|
|
|
|
device = quant_k_cache.device
|
|
nope_byte = (
|
|
token_data_base[:, None] + torch.arange(DIM_NOPE, device=device)[None, :]
|
|
)
|
|
nope_fp8 = flat_fp8[nope_byte].to(torch.float32)
|
|
scale_byte = (
|
|
token_scale_base[:, None]
|
|
+ torch.arange(NUM_SCALE_TILES, device=device)[None, :]
|
|
)
|
|
scale_u8 = flat_u8[scale_byte].to(torch.int32)
|
|
scale_pow2 = torch.exp2((scale_u8 - 127).to(torch.float32))
|
|
scale_pow2 = torch.where(
|
|
scale_pow2 < (2.0**-126), torch.zeros_like(scale_pow2), scale_pow2
|
|
)
|
|
scale_full = scale_pow2.repeat_interleave(TILE_SIZE, dim=1)
|
|
nope = nope_fp8 * scale_full
|
|
|
|
rope_bf16_base = (token_data_base + DIM_NOPE) // 2
|
|
rope_idx = rope_bf16_base[:, None] + torch.arange(DIM_ROPE, device=device)[None, :]
|
|
rope = flat_bf16[rope_idx]
|
|
|
|
out = torch.empty(
|
|
(loc.shape[0], 1, DIM_NOPE + DIM_ROPE),
|
|
dtype=torch.bfloat16,
|
|
device=device,
|
|
)
|
|
out[:, 0, :DIM_NOPE] = nope.to(torch.bfloat16)
|
|
out[:, 0, DIM_NOPE:] = rope
|
|
return out
|
|
|
|
|
|
if __name__ == "__main__":
|
|
assert torch.cuda.is_available(), "this self-test needs a CUDA device"
|
|
torch.manual_seed(0)
|
|
device = "cuda"
|
|
|
|
page_size = 64
|
|
num_pages = 8
|
|
num_tokens = 333
|
|
raw_bytes = page_size * (NOPE_ROPE_BYTES + PADDED_SCALE_PER_TOKEN)
|
|
bytes_per_page = (
|
|
(raw_bytes + NOPE_ROPE_BYTES - 1) // NOPE_ROPE_BYTES
|
|
) * NOPE_ROPE_BYTES
|
|
|
|
quant_k_cache = torch.randint(
|
|
0, 256, (num_pages, bytes_per_page), dtype=torch.uint8, device=device
|
|
)
|
|
page_table = torch.randint(
|
|
0, num_pages * page_size, (num_tokens,), dtype=torch.int32, device=device
|
|
)
|
|
|
|
out_kernel = dequantize_k_cache_paged(quant_k_cache, page_table, page_size)
|
|
out_ref = dequantize_k_cache_paged_ref(quant_k_cache, page_table, page_size)
|
|
|
|
torch.testing.assert_close(out_kernel, out_ref, atol=0, rtol=0, equal_nan=True)
|
|
print(
|
|
f"OK: kernel matches torch ref for {num_tokens} tokens "
|
|
f"(page_size={page_size}, bytes_per_page={bytes_per_page})"
|
|
)
|