Files
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

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