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
330 lines
9.9 KiB
Python
330 lines
9.9 KiB
Python
"""JIT-compiled Q8KV8 sparse prefill attention kernel for SM90 (Hopper/H200).
|
|
|
|
Uses native FP8 GMMA instructions via CUTLASS/CUTE for MLA attention
|
|
with FP8 quantized Q and KV tensors.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Optional
|
|
|
|
import torch
|
|
|
|
from sglang.jit_kernel.utils import cache_once, load_jit, override_jit_cuda_arch
|
|
from sglang.kernel_api_logging import debug_kernel_api
|
|
from sglang.srt.utils.custom_op import register_custom_op
|
|
|
|
if TYPE_CHECKING:
|
|
from tvm_ffi.module import Module
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Build flags
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _q8kv8_cuda_flags() -> list[str]:
|
|
# Minimal flag set, verified by per-flag ablation on SM90/H200 (CUDA 12.9).
|
|
# The original list was lifted from DeepSeek FlashMLA's AOT setup.py; under
|
|
# this tvm_ffi JIT build only --use_fast_math has any measurable effect, so
|
|
# the rest are dropped.
|
|
#
|
|
# --use_fast_math maps the softmax exp2f to the ex2.approx.f32 MUFU op. Cost
|
|
# of removing it: ~+4.3% at short-context / large-topk (s_kv=8192,
|
|
# topk=2048), ~+1-2% mid, ~0% at long context -- with no accuracy change
|
|
# (its ~2^-22 relative error is far below the fp8-e4m3 quantization noise).
|
|
#
|
|
# Dropped, all confirmed to leave perf and accuracy bit-identical here:
|
|
# * -U__CUDA_NO_HALF*/__CUDA_NO_BFLOAT16_CONVERSIONS__: these only matter
|
|
# when the toolchain pre-defines the matching -D__CUDA_NO_* macros, as
|
|
# torch.utils.cpp_extension's AOT path does (COMMON_NVCC_FLAGS). The JIT
|
|
# toolchain never defines them, so undefining is a no-op.
|
|
# * --expt-relaxed-constexpr and -O3: already supplied by the JIT default
|
|
# target flags (see utils._get_default_target_flags).
|
|
# * --expt-extended-lambda, -lineinfo, -D_USE_MATH_DEFINES: not required
|
|
# by this single-translation-unit kernel.
|
|
return [
|
|
"-O3",
|
|
"-DNDEBUG",
|
|
"-DCUTE_USE_PACKED_TUPLE=1",
|
|
"-DCUTLASS_ENABLE_TENSOR_CORE_MMA=1",
|
|
"--use_fast_math",
|
|
]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Module loader
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@cache_once
|
|
def _jit_sparse_mla_q8kv8_prefill_module() -> Module:
|
|
with override_jit_cuda_arch(9, 0, "a"):
|
|
return load_jit(
|
|
"sparse_mla_q8kv8_prefill_sm90",
|
|
cuda_files=[
|
|
"sparse_mla_q8kv8_prefill_sm90/entry.cuh",
|
|
],
|
|
cuda_wrappers=[
|
|
("dispatch", "sparse_prefill_q8kv8_dispatch"),
|
|
("dispatch_full", "sparse_prefill_q8kv8_dispatch_full"),
|
|
],
|
|
extra_cuda_cflags=_q8kv8_cuda_flags(),
|
|
extra_dependencies=["cutlass"],
|
|
)
|
|
|
|
|
|
# Pre-resolve entry-point callables on first use to avoid per-call module
|
|
# dictionary lookups.
|
|
_resolved_entries: Optional[tuple] = None
|
|
|
|
|
|
def _get_entries() -> tuple:
|
|
global _resolved_entries
|
|
if _resolved_entries is None:
|
|
m = _jit_sparse_mla_q8kv8_prefill_module()
|
|
_resolved_entries = (
|
|
m["dispatch"],
|
|
m["dispatch_full"],
|
|
)
|
|
return _resolved_entries
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Public API
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# torch._C._cuda_getCurrentRawStream returns the cudaStream_t pointer expected
|
|
# by the JIT wrapper. torch._C._cuda_getCurrentStream returns a packed stream
|
|
# id and must not be used here.
|
|
_get_current_stream_raw = torch._C._cuda_getCurrentRawStream
|
|
|
|
|
|
# Module-level cache for kernel-write-only output tensors. The active s_q rows
|
|
# are overwritten every call; buffers grow monotonically by device/head shape.
|
|
def _check_out_buffer(
|
|
t: torch.Tensor,
|
|
name: str,
|
|
shape: tuple,
|
|
dtype: torch.dtype,
|
|
device: torch.device,
|
|
) -> None:
|
|
if tuple(t.shape) != tuple(shape):
|
|
raise ValueError(f"{name} must have shape {tuple(shape)}, got {tuple(t.shape)}")
|
|
if t.dtype != dtype:
|
|
raise ValueError(f"{name} must have dtype {dtype}, got {t.dtype}")
|
|
if t.device != device:
|
|
raise ValueError(f"{name} must be on device {device}, got {t.device}")
|
|
if not t.is_contiguous():
|
|
raise ValueError(f"{name} must be contiguous")
|
|
|
|
|
|
# Internal custom-op wrappers so the JIT kernel calls participate in
|
|
# torch.library / torch.compile tracing and kernel-API debug logging.
|
|
# The dispatch_full variant carries the optional attn_sink / topk_length
|
|
# tensors as required args; the public API chooses which op to call.
|
|
@register_custom_op(
|
|
op_name="sparse_mla_q8kv8_prefill",
|
|
mutates_args=["out", "max_logits", "lse"],
|
|
)
|
|
def _sparse_mla_q8kv8_prefill_op(
|
|
q: torch.Tensor,
|
|
kv: torch.Tensor,
|
|
indices: torch.Tensor,
|
|
q_scale: torch.Tensor,
|
|
kv_scale: torch.Tensor,
|
|
out: torch.Tensor,
|
|
max_logits: torch.Tensor,
|
|
lse: torch.Tensor,
|
|
s_q: int,
|
|
s_kv: int,
|
|
h_q: int,
|
|
h_kv: int,
|
|
d_qk: int,
|
|
d_v: int,
|
|
topk: int,
|
|
sm_scale: float,
|
|
cuda_stream: int,
|
|
) -> None:
|
|
dispatch_fn, _ = _get_entries()
|
|
dispatch_fn(
|
|
q,
|
|
kv,
|
|
indices,
|
|
q_scale,
|
|
kv_scale,
|
|
out,
|
|
max_logits,
|
|
lse,
|
|
s_q,
|
|
s_kv,
|
|
h_q,
|
|
h_kv,
|
|
d_qk,
|
|
d_v,
|
|
topk,
|
|
sm_scale,
|
|
cuda_stream,
|
|
)
|
|
|
|
|
|
@register_custom_op(
|
|
op_name="sparse_mla_q8kv8_prefill_full",
|
|
mutates_args=["out", "max_logits", "lse"],
|
|
)
|
|
def _sparse_mla_q8kv8_prefill_full_op(
|
|
q: torch.Tensor,
|
|
kv: torch.Tensor,
|
|
indices: torch.Tensor,
|
|
q_scale: torch.Tensor,
|
|
kv_scale: torch.Tensor,
|
|
attn_sink: torch.Tensor,
|
|
topk_length: torch.Tensor,
|
|
out: torch.Tensor,
|
|
max_logits: torch.Tensor,
|
|
lse: torch.Tensor,
|
|
s_q: int,
|
|
s_kv: int,
|
|
h_q: int,
|
|
h_kv: int,
|
|
d_qk: int,
|
|
d_v: int,
|
|
topk: int,
|
|
sm_scale: float,
|
|
cuda_stream: int,
|
|
) -> None:
|
|
_, dispatch_full_fn = _get_entries()
|
|
dispatch_full_fn(
|
|
q,
|
|
kv,
|
|
indices,
|
|
q_scale,
|
|
kv_scale,
|
|
attn_sink,
|
|
topk_length,
|
|
out,
|
|
max_logits,
|
|
lse,
|
|
s_q,
|
|
s_kv,
|
|
h_q,
|
|
h_kv,
|
|
d_qk,
|
|
d_v,
|
|
topk,
|
|
sm_scale,
|
|
cuda_stream,
|
|
)
|
|
|
|
|
|
@debug_kernel_api
|
|
def sparse_mla_q8kv8_prefill_fwd(
|
|
q: torch.Tensor, # [s_q, h_q, d_qk], float8_e4m3fn
|
|
kv: torch.Tensor, # [s_kv, h_kv, d_qk], float8_e4m3fn
|
|
indices: torch.Tensor, # [s_q, h_kv, topk], int32
|
|
sm_scale: float,
|
|
q_scale: torch.Tensor, # scalar tensor on GPU, float32
|
|
kv_scale: torch.Tensor, # scalar tensor on GPU, float32
|
|
d_v: int = 512,
|
|
attn_sink: Optional[torch.Tensor] = None, # [h_q], float32
|
|
topk_length: Optional[torch.Tensor] = None, # [s_q], int32
|
|
*,
|
|
out: Optional[torch.Tensor] = None, # [s_q, h_q, d_v], bfloat16
|
|
max_logits: Optional[torch.Tensor] = None, # [s_q, h_q], float32
|
|
lse: Optional[torch.Tensor] = None, # [s_q, h_q], float32
|
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
"""Run Q8KV8 (FP8) sparse prefill attention on SM90.
|
|
|
|
The kernel writes into three output tensors. By default fresh tensors
|
|
are allocated and returned; callers that want to reuse buffers (e.g.
|
|
for CUDA graph capture) may pass pre-allocated ``out`` / ``max_logits``
|
|
/ ``lse`` tensors of the expected shape/dtype/device. The three output
|
|
tensors must not alias each other.
|
|
|
|
Returns:
|
|
out: [s_q, h_q, d_v], bfloat16
|
|
max_logits: [s_q, h_q], float32
|
|
lse: [s_q, h_q], float32
|
|
"""
|
|
s_q, h_q, d_qk = q.shape
|
|
s_kv = kv.shape[0]
|
|
h_kv = kv.shape[1]
|
|
topk = indices.shape[2]
|
|
|
|
if d_v != 512:
|
|
raise ValueError(
|
|
f"sparse_mla_q8kv8_prefill_fwd only supports d_v=512, got {d_v}"
|
|
)
|
|
|
|
if (attn_sink is None) != (topk_length is None):
|
|
raise ValueError("attn_sink and topk_length must be provided together")
|
|
|
|
device = q.device
|
|
if out is None:
|
|
out = torch.empty(s_q, h_q, d_v, dtype=torch.bfloat16, device=device)
|
|
else:
|
|
_check_out_buffer(out, "out", (s_q, h_q, d_v), torch.bfloat16, device)
|
|
if max_logits is None:
|
|
max_logits = torch.empty(s_q, h_q, dtype=torch.float32, device=device)
|
|
else:
|
|
_check_out_buffer(max_logits, "max_logits", (s_q, h_q), torch.float32, device)
|
|
if lse is None:
|
|
lse = torch.empty(s_q, h_q, dtype=torch.float32, device=device)
|
|
else:
|
|
_check_out_buffer(lse, "lse", (s_q, h_q), torch.float32, device)
|
|
|
|
# The three output tensors are written independently by the kernel; any
|
|
# aliasing among them would corrupt results, so reject it explicitly.
|
|
out_ptr = out.data_ptr()
|
|
ml_ptr = max_logits.data_ptr()
|
|
lse_ptr = lse.data_ptr()
|
|
if out_ptr == ml_ptr or out_ptr == lse_ptr or ml_ptr == lse_ptr:
|
|
raise ValueError("out, max_logits and lse must not alias each other")
|
|
|
|
cuda_stream = _get_current_stream_raw(q.device.index)
|
|
|
|
if attn_sink is not None and topk_length is not None:
|
|
_sparse_mla_q8kv8_prefill_full_op(
|
|
q,
|
|
kv,
|
|
indices,
|
|
q_scale,
|
|
kv_scale,
|
|
attn_sink,
|
|
topk_length,
|
|
out,
|
|
max_logits,
|
|
lse,
|
|
s_q,
|
|
s_kv,
|
|
h_q,
|
|
h_kv,
|
|
d_qk,
|
|
d_v,
|
|
topk,
|
|
sm_scale,
|
|
cuda_stream,
|
|
)
|
|
else:
|
|
_sparse_mla_q8kv8_prefill_op(
|
|
q,
|
|
kv,
|
|
indices,
|
|
q_scale,
|
|
kv_scale,
|
|
out,
|
|
max_logits,
|
|
lse,
|
|
s_q,
|
|
s_kv,
|
|
h_q,
|
|
h_kv,
|
|
d_qk,
|
|
d_v,
|
|
topk,
|
|
sm_scale,
|
|
cuda_stream,
|
|
)
|
|
|
|
return out, max_logits, lse
|