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
450 lines
16 KiB
Python
450 lines
16 KiB
Python
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")
|