Files
sgl-project--sglang/test/manual/test_trtllm_fp8_kv_kernel.py
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

482 lines
14 KiB
Python

"""
Unit tests for TRTLLM FP8 KV cache fusion kernel.
"""
import unittest
import torch
from sglang.kernels.ops.kvcache.trtllm_fp8_kv_kernel import (
fused_fp8_set_kv_buffer,
)
from sglang.test.test_utils import CustomTestCase
class TestTRTLLMFP8KVKernel(CustomTestCase):
"""Test fused FP8 KV cache write kernel correctness."""
@classmethod
def setUpClass(cls):
if not torch.cuda.is_available():
raise unittest.SkipTest("CUDA not available")
if torch.cuda.get_device_capability()[0] < 9:
raise unittest.SkipTest("FP8 requires compute capability >= 9.0")
def _test_kernel_correctness(
self,
num_tokens,
num_kv_heads,
head_dim,
page_size,
use_scale,
input_ndim,
cache_ndim,
):
"""Compare Triton kernel output against naive implementation."""
device = torch.device("cuda")
dtype = torch.bfloat16
# Create input tensors
if input_ndim == 3:
k = torch.randn(
num_tokens, num_kv_heads, head_dim, device=device, dtype=dtype
)
v = torch.randn(
num_tokens, num_kv_heads, head_dim, device=device, dtype=dtype
)
else:
k = torch.randn(
num_tokens, num_kv_heads * head_dim, device=device, dtype=dtype
)
v = torch.randn(
num_tokens, num_kv_heads * head_dim, device=device, dtype=dtype
)
# Create cache tensors (use FP8 to match real runtime behavior)
num_pages = 128
total_slots = num_pages * page_size
cache_dtype = torch.float8_e4m3fn
if cache_ndim == 3:
k_cache_triton = torch.zeros(
total_slots, num_kv_heads, head_dim, device=device, dtype=cache_dtype
)
v_cache_triton = torch.zeros(
total_slots, num_kv_heads, head_dim, device=device, dtype=cache_dtype
)
k_cache_naive = torch.zeros(
total_slots, num_kv_heads, head_dim, device=device, dtype=cache_dtype
)
v_cache_naive = torch.zeros(
total_slots, num_kv_heads, head_dim, device=device, dtype=cache_dtype
)
else:
k_cache_triton = torch.zeros(
num_pages,
page_size,
num_kv_heads,
head_dim,
device=device,
dtype=cache_dtype,
)
v_cache_triton = torch.zeros(
num_pages,
page_size,
num_kv_heads,
head_dim,
device=device,
dtype=cache_dtype,
)
k_cache_naive = torch.zeros(
num_pages,
page_size,
num_kv_heads,
head_dim,
device=device,
dtype=cache_dtype,
)
v_cache_naive = torch.zeros(
num_pages,
page_size,
num_kv_heads,
head_dim,
device=device,
dtype=cache_dtype,
)
# Create cache locations (ensure unique indices to avoid race conditions)
cache_loc = torch.randperm(total_slots, device=device, dtype=torch.int32)[
:num_tokens
]
# Optional scales
k_scale = 0.5 if use_scale else None
v_scale = 0.75 if use_scale else None
# Run Triton kernel
fused_fp8_set_kv_buffer(
k.clone(),
v.clone(),
k_cache_triton,
v_cache_triton,
cache_loc,
k_scale,
v_scale,
page_size,
use_triton=True,
)
# Run naive fallback
fused_fp8_set_kv_buffer(
k.clone(),
v.clone(),
k_cache_naive,
v_cache_naive,
cache_loc,
k_scale,
v_scale,
page_size,
use_triton=False,
)
# Compare results (bit-exact match expected)
self.assertTrue(
torch.equal(k_cache_triton, k_cache_naive),
"K cache mismatch between Triton and naive",
)
self.assertTrue(
torch.equal(v_cache_triton, v_cache_naive),
"V cache mismatch between Triton and naive",
)
def test_basic_3d_input_3d_cache(self):
"""Test basic case: 3D input, 3D cache, no scale."""
self._test_kernel_correctness(
num_tokens=16,
num_kv_heads=8,
head_dim=128,
page_size=16,
use_scale=False,
input_ndim=3,
cache_ndim=3,
)
def test_basic_3d_input_4d_cache(self):
"""Test basic case: 3D input, 4D cache, no scale."""
self._test_kernel_correctness(
num_tokens=16,
num_kv_heads=8,
head_dim=128,
page_size=16,
use_scale=False,
input_ndim=3,
cache_ndim=4,
)
def test_with_scale_3d_cache(self):
"""Test with scale: 3D input, 3D cache."""
self._test_kernel_correctness(
num_tokens=16,
num_kv_heads=8,
head_dim=128,
page_size=16,
use_scale=True,
input_ndim=3,
cache_ndim=3,
)
def test_with_scale_4d_cache(self):
"""Test with scale: 3D input, 4D cache."""
self._test_kernel_correctness(
num_tokens=16,
num_kv_heads=8,
head_dim=128,
page_size=16,
use_scale=True,
input_ndim=3,
cache_ndim=4,
)
def test_2d_input_3d_cache(self):
"""Test 2D input (flattened): 2D input, 3D cache."""
self._test_kernel_correctness(
num_tokens=16,
num_kv_heads=8,
head_dim=128,
page_size=16,
use_scale=False,
input_ndim=2,
cache_ndim=3,
)
def test_2d_input_4d_cache(self):
"""Test 2D input (flattened): 2D input, 4D cache."""
self._test_kernel_correctness(
num_tokens=16,
num_kv_heads=8,
head_dim=128,
page_size=16,
use_scale=False,
input_ndim=2,
cache_ndim=4,
)
def test_single_token(self):
"""Test edge case: single token."""
self._test_kernel_correctness(
num_tokens=1,
num_kv_heads=8,
head_dim=128,
page_size=16,
use_scale=True,
input_ndim=3,
cache_ndim=3,
)
def test_large_batch(self):
"""Test larger batch size."""
self._test_kernel_correctness(
num_tokens=128,
num_kv_heads=16,
head_dim=64,
page_size=16,
use_scale=True,
input_ndim=3,
cache_ndim=4,
)
def test_different_head_dims(self):
"""Test different head dimensions."""
for head_dim in [64, 128]:
self._test_kernel_correctness(
num_tokens=16,
num_kv_heads=8,
head_dim=head_dim,
page_size=16,
use_scale=False,
input_ndim=3,
cache_ndim=3,
)
def test_empty_input(self):
"""Test edge case: empty input (0 tokens)."""
device = torch.device("cuda")
dtype = torch.bfloat16
num_kv_heads = 8
head_dim = 128
page_size = 16
num_tokens = 0
# Empty inputs
k = torch.randn(num_tokens, num_kv_heads, head_dim, device=device, dtype=dtype)
v = torch.randn(num_tokens, num_kv_heads, head_dim, device=device, dtype=dtype)
# Cache (use FP8 to match real runtime behavior)
total_slots = 128
k_cache = torch.zeros(
total_slots,
num_kv_heads,
head_dim,
device=device,
dtype=torch.float8_e4m3fn,
)
v_cache = torch.zeros(
total_slots,
num_kv_heads,
head_dim,
device=device,
dtype=torch.float8_e4m3fn,
)
# Empty cache locations
cache_loc = torch.empty(num_tokens, device=device, dtype=torch.int32)
# Should not crash
fused_fp8_set_kv_buffer(
k,
v,
k_cache,
v_cache,
cache_loc,
k_scale=None,
v_scale=None,
page_size=page_size,
)
def test_fp8_kv_kernel_accepts_tensor_scales(self):
"""
Regression test for B200 Triton compilation issue.
This test ensures that fused_fp8_set_kv_buffer correctly handles
k_scale/v_scale when they are 0-dimensional tensors (torch.nn.Parameter).
Previously, Triton would treat 0-D tensor arguments as pointers,
causing a type error when performing "1.0 / k_scale" inside the kernel.
The fix converts tensor scales to Python floats in the wrapper.
"""
device = torch.device("cuda")
num_tokens = 4
num_kv_heads = 2
head_dim = 64
page_size = 16
total_slots = page_size
k = torch.randn(
num_tokens, num_kv_heads, head_dim, device=device, dtype=torch.bfloat16
)
v = torch.randn_like(k)
k_cache = torch.empty(
total_slots,
num_kv_heads,
head_dim,
device=device,
dtype=torch.float8_e4m3fn,
)
v_cache = torch.empty_like(k_cache)
cache_loc = torch.arange(num_tokens, device=device, dtype=torch.int32)
# Use 0D tensor form of scale to reproduce the original bug scenario
k_scale = torch.tensor(1.0, device=device, dtype=torch.float32)
v_scale = torch.tensor(1.0, device=device, dtype=torch.float32)
# Old code would trigger Triton's IncompatibleTypeError here
# New code should handle this gracefully by converting to float
fused_fp8_set_kv_buffer(
k,
v,
k_cache,
v_cache,
cache_loc,
k_scale=k_scale,
v_scale=v_scale,
page_size=page_size,
use_triton=True,
)
# If we get here without exception, the regression is fixed
def test_fp8_kv_kernel_cuda_graph_compatible(self):
"""
Regression test for CUDA graph capture compatibility.
This test ensures that fused_fp8_set_kv_buffer works correctly within
CUDA graph capture, which is used in production for performance.
Previously, float(k_scale) caused GPU→CPU synchronization, triggering
cudaErrorStreamCaptureUnsupported during graph capture. The fix computes
inverse scales purely on GPU using tensor operations.
"""
device = torch.device("cuda")
num_tokens = 4
num_kv_heads = 2
head_dim = 64
page_size = 16
total_slots = page_size
k = torch.randn(
num_tokens, num_kv_heads, head_dim, device=device, dtype=torch.bfloat16
)
v = torch.randn_like(k)
k_cache = torch.empty(
total_slots,
num_kv_heads,
head_dim,
device=device,
dtype=torch.float8_e4m3fn,
)
v_cache = torch.empty_like(k_cache)
cache_loc = torch.arange(num_tokens, device=device, dtype=torch.int32)
# Use 0D tensor scales (like nn.Parameter) to reproduce production scenario
k_scale = torch.tensor(1.0, device=device, dtype=torch.float32)
v_scale = torch.tensor(1.0, device=device, dtype=torch.float32)
# Test that kernel works under CUDA graph capture
graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph):
# Old code would fail here with cudaErrorStreamCaptureUnsupported
# New code should succeed because all operations stay on GPU
fused_fp8_set_kv_buffer(
k,
v,
k_cache,
v_cache,
cache_loc,
k_scale=k_scale,
v_scale=v_scale,
page_size=page_size,
use_triton=True,
)
# Replay the graph to verify it works
graph.replay()
# If we get here without exception, CUDA graph compatibility is confirmed
def test_fp8_kv_kernel_cuda_graph_compatible_no_scale(self):
"""
Regression test for CUDA graph capture compatibility without scales.
This test ensures that fused_fp8_set_kv_buffer works correctly within
CUDA graph capture when k_scale/v_scale are None (use_provided_scale=False).
Previously, the code created new GPU tensors (torch.tensor(1.0, device=...))
during graph capture, triggering cudaErrorStreamCaptureUnsupported.
The fix passes dummy pointers when use_provided_scale=False, as the kernel
uses constant 1.0 and Triton optimizes away the pointer loads.
"""
device = torch.device("cuda")
num_tokens = 4
num_kv_heads = 2
head_dim = 64
page_size = 16
total_slots = page_size
k = torch.randn(
num_tokens, num_kv_heads, head_dim, device=device, dtype=torch.bfloat16
)
v = torch.randn_like(k)
k_cache = torch.empty(
total_slots,
num_kv_heads,
head_dim,
device=device,
dtype=torch.float8_e4m3fn,
)
v_cache = torch.empty_like(k_cache)
cache_loc = torch.arange(num_tokens, device=device, dtype=torch.int32)
# Test that kernel works under CUDA graph capture WITHOUT scales
graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph):
# No k_scale/v_scale provided - use_provided_scale=False branch
# Old code would fail here with cudaErrorStreamCaptureUnsupported
# New code should succeed by using dummy pointers
fused_fp8_set_kv_buffer(
k,
v,
k_cache,
v_cache,
cache_loc,
page_size=page_size,
use_triton=True,
)
# Replay the graph to verify it works
graph.replay()
# If we get here without exception, no-scale CUDA graph compatibility is confirmed
if __name__ == "__main__":
unittest.main()