chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:16 +08:00
commit 94057c3d3e
7152 changed files with 2120455 additions and 0 deletions
@@ -0,0 +1,419 @@
# Copyright 2025 XunhaoLai. All rights reserved.
from typing import Optional
import torch
import triton
import triton.language as tl
from ..common.utils import check_sparse_kv_fp8, robust_allocator
@triton.heuristics(
{
"BLOCK_SIZE_H": lambda args: max(
16, triton.next_power_of_2(args["gqa_group_size"])
),
"BLOCK_SIZE_D": lambda args: triton.next_power_of_2(args["head_dim"]),
"BLOCK_SIZE_T": lambda args: triton.next_power_of_2(args["max_topk"]),
"HAS_SINK": lambda args: args["sink_ptr"] is not None,
"BATCH_SIZE_BUCKET": lambda args: triton.next_power_of_2(args["batch_size"]),
}
)
@triton.autotune(
configs=[
triton.Config({}, num_warps=nw, num_stages=ns)
for nw in [4, 8]
for ns in [2, 3, 4, 5]
],
key=["BATCH_SIZE_BUCKET", "gqa_group_size", "head_dim", "block_size", "HAS_SINK"],
)
@triton.jit
def _gqa_share_sparse_decode_kernel(
q_ptr, # Q: b x qh x d
sink_ptr, # Sink: qh x d
k_cache_ptr, # K paged: max_slots x kh x d
v_cache_ptr, # V paged: max_slots x kh x d
req_to_token_ptr, # req_to_token: max_reqs x max_kv_len
idx_ptr, # topk index: qh x b x topk
o_ptr, # O partial: c x b x qh x d
lse_ptr, # lse partial: c x b x qh
seq_lens,
slot_ids,
# shape
max_slots,
batch_size,
gqa_group_size,
head_dim,
max_topk,
max_kv_len,
# sm_scale
sm_scale,
# stride
stride_q_b,
stride_q_h,
stride_q_d,
stride_sink_h,
stride_sink_d,
stride_k_s,
stride_k_h,
stride_k_d,
stride_v_s,
stride_v_h,
stride_v_d,
stride_r2t_b,
stride_ti_h,
stride_ti_b,
stride_ti_t,
stride_o_c,
stride_o_b,
stride_o_h,
stride_o_d,
stride_l_c,
stride_l_b,
stride_l_h,
# META parameters
BATCH_SIZE_BUCKET: tl.constexpr,
BLOCK_SIZE_H: tl.constexpr,
BLOCK_SIZE_N: tl.constexpr,
BLOCK_SIZE_D: tl.constexpr,
BLOCK_SIZE_T: tl.constexpr,
NUM_TOPK_CHUNKS: tl.constexpr,
HAS_SINK: tl.constexpr,
IS_FP8: tl.constexpr,
):
# decode program ids: split-K over the topk dimension to give every SM
# something to do at small batch. pid(0) folds (batch, chunk) together so
# the grid size = batch_size * NUM_TOPK_CHUNKS.
pid_bc, pid_kh = tl.program_id(0), tl.program_id(1)
pid_b = pid_bc % batch_size
pid_c = pid_bc // batch_size
pid_h = pid_kh * gqa_group_size
# per-chunk topk range. chunk_size is *runtime* (depends on max_topk which
# is a runtime arg, not constexpr), so don't annotate as tl.constexpr —
# doing so produces undefined behavior in Triton.
chunk_size_topk = (max_topk + NUM_TOPK_CHUNKS - 1) // NUM_TOPK_CHUNKS
chunk_start_topk = pid_c * chunk_size_topk
chunk_end_topk_compiletime = chunk_start_topk + chunk_size_topk
# get q k start and len after rmpad
seq_len = tl.minimum(tl.load(seq_lens + pid_b), max_kv_len)
sid = (
tl.load(slot_ids + pid_b).to(tl.int64) + max_slots
) % max_slots # to avoid bugs when slot_ids is negative
# get real topk
off_t = tl.arange(0, BLOCK_SIZE_T)
idx_base = idx_ptr + pid_kh * stride_ti_h + pid_b * stride_ti_b
topk_idx = tl.load(idx_base + off_t * stride_ti_t, mask=off_t < max_topk, other=-1)
valid_idx = tl.where(topk_idx >= 0, off_t, -1)
real_topk = tl.sum(valid_idx != -1, axis=0)
chunk_end_topk = tl.minimum(chunk_end_topk_compiletime, real_topk)
# init pointer
off_n = tl.arange(0, BLOCK_SIZE_N)
off_d = tl.arange(0, BLOCK_SIZE_D)
dim_mask = off_d < head_dim
# init statistics — kept at -inf so empty chunks (chunk_start >= real_topk)
# naturally fall out as weight=0 in the merge step.
if HAS_SINK and pid_c == 0:
q_ptrs = tl.make_block_ptr(
base=q_ptr + pid_b * stride_q_b + pid_h * stride_q_h,
shape=(gqa_group_size, head_dim),
strides=(stride_q_h, stride_q_d),
offsets=(0, 0),
block_shape=(BLOCK_SIZE_H, BLOCK_SIZE_D),
order=(1, 0),
)
q = tl.load(q_ptrs, boundary_check=(0, 1), padding_option="zero")
sink_ptrs = tl.make_block_ptr(
base=sink_ptr + pid_h * stride_sink_h,
shape=(gqa_group_size, head_dim),
strides=(stride_sink_h, stride_sink_d),
offsets=(0, 0),
block_shape=(BLOCK_SIZE_H, BLOCK_SIZE_D),
order=(1, 0),
)
sink = tl.load(sink_ptrs, boundary_check=(0, 1), padding_option="zero").to(
tl.float32
)
qsink = tl.sum(q.to(tl.float32) * sink, axis=1) * sm_scale # (BLOCK_SIZE_H,)
m_i = qsink
lse_i = qsink
else:
m_i = tl.full((BLOCK_SIZE_H,), float("-inf"), dtype=tl.float32)
lse_i = tl.full((BLOCK_SIZE_H,), float("-inf"), dtype=tl.float32)
q_ptrs = tl.make_block_ptr(
base=q_ptr + pid_b * stride_q_b + pid_h * stride_q_h,
shape=(gqa_group_size, head_dim),
strides=(stride_q_h, stride_q_d),
offsets=(0, 0),
block_shape=(BLOCK_SIZE_H, BLOCK_SIZE_D),
order=(1, 0),
)
q = tl.load(q_ptrs, boundary_check=(0, 1), padding_option="zero")
acc_o = tl.full((BLOCK_SIZE_H, BLOCK_SIZE_D), 0, dtype=tl.float32)
# only iterate over this chunk's topk slice. the load must respect the
# per-chunk start offset.
cur_idx_ptr = idx_base + chunk_start_topk * stride_ti_t
for _ in tl.range(chunk_start_topk, chunk_end_topk):
# load index
c = tl.load(cur_idx_ptr).to(tl.int32) * BLOCK_SIZE_N
cur_idx_ptr = cur_idx_ptr + stride_ti_t
# resolve slots for this block via req_to_token
pos = c + off_n
pos_mask = pos < seq_len
slots = tl.load(
req_to_token_ptr + sid * stride_r2t_b + pos,
mask=pos_mask,
other=0,
).to(tl.int64)
slots = (slots + max_slots) % max_slots # safety against negative
# load K as (head_dim, BLOCK_SIZE_N) via indirect addressing
k_off = (
slots[None, :] * stride_k_s
+ pid_kh * stride_k_h
+ off_d[:, None] * stride_k_d
)
k = tl.load(
k_cache_ptr + k_off,
mask=dim_mask[:, None] & pos_mask[None, :],
other=0.0,
)
if IS_FP8:
# fp8 KV cache is unit-scaled (set_kv_buffer casts bf16->fp8 with no
# scale), so dequant is just a widening cast to the Q compute dtype
# before the tl.dot. Matches the bf16 path bit-for-bit when the cache
# is bf16 (IS_FP8 False -> this branch is compiled out).
k = k.to(q.dtype)
# load V as (BLOCK_SIZE_N, head_dim) via indirect addressing
v_off = (
slots[:, None] * stride_v_s
+ pid_kh * stride_v_h
+ off_d[None, :] * stride_v_d
)
v = tl.load(
v_cache_ptr + v_off,
mask=pos_mask[:, None] & dim_mask[None, :],
other=0.0,
)
if IS_FP8:
# Widen V before the P@V dot. This also makes the `p.to(v.dtype)`
# below cast P to the compute dtype (not to fp8, which would be
# catastrophic precision loss on the attention weights).
v = v.to(q.dtype)
# compute qk
qk = tl.zeros((BLOCK_SIZE_H, BLOCK_SIZE_N), dtype=tl.float32)
qk += tl.where(off_n[None, :] < seq_len - c, 0, float("-inf"))
# [H, D], [D, N] -> [H, N]
qk += tl.dot(q, k) * sm_scale
# compute m_ij and l_ij
m_ij = tl.maximum(m_i, tl.max(qk, axis=1))
p = tl.exp(qk - m_ij[:, None])
l_ij = tl.sum(p, axis=1)
# scale acc_o
acc_o_scale = tl.exp(m_i - m_ij)
acc_o = acc_o * acc_o_scale[:, None]
# load v and update acc_o
p = p.to(v.dtype)
# [H, N], [N, D] -> [H, D]
acc_o += tl.dot(p.to(v.dtype), v)
# update statistics
m_i = m_ij
lse_i = m_ij + tl.log(tl.exp(lse_i - m_ij) + l_ij)
# final scale (matches the old non-split kernel for chunks where lse_i>-inf).
# For empty chunks (chunk_start_topk >= real_topk) the inner loop never
# runs, so m_i = lse_i = -inf and naive `tl.exp(m_i - lse_i)` would compute
# exp(-inf - (-inf)) = exp(NaN) = NaN, then 0 * NaN = NaN poisons o_partial
# and the merge result. Gate the scale with tl.where so empty chunks emit a
# clean zero (lse_i stays -inf which the merge correctly turns into weight=0).
scale = tl.where(
lse_i > float("-inf"),
tl.exp(m_i - lse_i),
tl.zeros_like(lse_i),
)
acc_o = acc_o * scale[:, None]
# save partial output and lse for the merge step
o_ptrs = tl.make_block_ptr(
base=o_ptr + pid_c * stride_o_c + pid_b * stride_o_b + pid_h * stride_o_h,
shape=(gqa_group_size, head_dim),
strides=(stride_o_h, stride_o_d),
offsets=(0, 0),
block_shape=(BLOCK_SIZE_H, BLOCK_SIZE_D),
order=(1, 0),
)
tl.store(o_ptrs, acc_o.to(o_ptr.dtype.element_ty), boundary_check=(0, 1))
lse_ptrs = tl.make_block_ptr(
base=lse_ptr + pid_c * stride_l_c + pid_b * stride_l_b + pid_h * stride_l_h,
shape=(gqa_group_size,),
strides=(stride_l_h,),
offsets=(0,),
block_shape=(BLOCK_SIZE_H,),
order=(0,),
)
tl.store(lse_ptrs, lse_i.to(lse_ptr.dtype.element_ty), boundary_check=(0,))
@triton.heuristics(
{
"BLOCK_SIZE_D": lambda args: triton.next_power_of_2(args["head_dim"]),
}
)
@triton.jit
def _merge_topk_attn_out_kernel(
o_ptr, # [NUM_TOPK_CHUNKS, BS, NQH, D] — partials in, merged out at chunk 0
lse_ptr, # [NUM_TOPK_CHUNKS, BS, NQH]
head_dim,
stride_o_c,
stride_o_b,
stride_o_h,
stride_o_d,
stride_l_c,
stride_l_b,
stride_l_h,
NUM_TOPK_CHUNKS: tl.constexpr,
BLOCK_SIZE_D: tl.constexpr,
):
pid_b, pid_h = tl.program_id(0), tl.program_id(1)
off_c = tl.arange(0, NUM_TOPK_CHUNKS)
off_d = tl.arange(0, BLOCK_SIZE_D)
o_ptrs = tl.make_block_ptr(
base=o_ptr + pid_b * stride_o_b + pid_h * stride_o_h,
shape=(NUM_TOPK_CHUNKS, head_dim),
strides=(stride_o_c, stride_o_d),
offsets=(0, 0),
block_shape=(NUM_TOPK_CHUNKS, BLOCK_SIZE_D),
order=(1, 0),
)
lse_ptrs = lse_ptr + pid_b * stride_l_b + pid_h * stride_l_h + off_c * stride_l_c
o = tl.load(o_ptrs, boundary_check=(0, 1), padding_option="zero")
lse = tl.load(lse_ptrs) # empty chunks contribute -inf -> weight 0
# standard flash-decoding merge in linear (not log2) space, matching the
# decode kernel which uses tl.exp / tl.log.
lse_max = tl.max(lse, axis=0)
weights = tl.exp(lse - lse_max)
weights = weights / tl.sum(weights, axis=0)
o_merged = tl.sum(o * weights[:, None], axis=0)
o_out_ptrs = o_ptr + pid_b * stride_o_b + pid_h * stride_o_h + off_d * stride_o_d
tl.store(o_out_ptrs, o_merged.to(o_ptr.dtype.element_ty), mask=off_d < head_dim)
@torch.no_grad()
def flash_decode_with_gqa_share_sparse(
q: torch.Tensor, # [batch_size, num_q_heads, head_dim]
sink: Optional[torch.Tensor],
k_cache: torch.Tensor, # [max_slots, num_kv_heads, head_dim] (paged)
v_cache: torch.Tensor, # [max_slots, num_kv_heads, head_dim] (paged)
req_to_token: torch.Tensor, # [max_reqs, max_kv_len]
seq_lens: torch.Tensor, # [batch_size, ]
slot_ids: torch.Tensor, # [batch_size, ]
block_size: int,
topk_idx: torch.Tensor, # [num_kv_heads, batch_size, topk]
sm_scale: Optional[float] = None,
use_tma: bool = True,
) -> torch.Tensor:
triton.set_allocator(robust_allocator)
is_fp8 = check_sparse_kv_fp8(q, k_cache, v_cache, label="decode")
# shape
batch_size, num_q_heads, head_dim = q.shape
max_slots, num_kv_heads, _ = k_cache.shape
assert slot_ids.shape[0] == batch_size and seq_lens.shape[0] == batch_size
assert topk_idx.shape[0] == num_kv_heads
assert (
triton.next_power_of_2(block_size) == block_size
), f"block_size must be a power of 2, but got {block_size}"
# assert slot_ids.max() < max_slots, f"get slot_ids {slot_ids}, but kv_cache shape is {kv_cache.shape}"
max_kv_len = req_to_token.shape[1]
# gqa
assert num_q_heads % num_kv_heads == 0
gqa_group_size = num_q_heads // num_kv_heads
max_topk = topk_idx.shape[2]
# sm scale
if sm_scale is None:
sm_scale = head_dim**-0.5
# Pick NUM_TOPK_CHUNKS so total grid ≈ TARGET_GRID. Same constraints as
# flash_decode_with_topk_idx: must be power of 2 (Triton arange) and must
# only depend on shape constants (so grid is fixed within a cuda graph).
# Capped by max_topk because chunks beyond real_topk early-fall-through to
# the merge-as-zero path; capping avoids wasting blocks at tiny topk.
TARGET_GRID = 256
target = max(
1,
min(max_topk, TARGET_GRID // max(1, batch_size * num_kv_heads)),
)
NUM_TOPK_CHUNKS = 1 << (target.bit_length() - 1)
# output tensor: split-K partials, merged into chunk 0 by the merge kernel
o_partial = torch.empty(
NUM_TOPK_CHUNKS,
batch_size,
num_q_heads,
head_dim,
dtype=q.dtype,
device=q.device,
)
lse_partial = torch.empty(
NUM_TOPK_CHUNKS,
batch_size,
num_q_heads,
dtype=torch.float32,
device=q.device,
)
# launch attention kernel
grid = (batch_size * NUM_TOPK_CHUNKS, num_kv_heads)
_gqa_share_sparse_decode_kernel[grid](
q,
sink,
k_cache,
v_cache,
req_to_token,
topk_idx,
o_partial,
lse_partial,
seq_lens,
slot_ids,
max_slots,
batch_size,
gqa_group_size,
head_dim,
max_topk,
max_kv_len,
sm_scale,
q.stride(0),
q.stride(1),
q.stride(2),
sink.stride(0) if sink is not None else 0,
sink.stride(1) if sink is not None else 0,
k_cache.stride(0),
k_cache.stride(1),
k_cache.stride(2),
v_cache.stride(0),
v_cache.stride(1),
v_cache.stride(2),
req_to_token.stride(0),
topk_idx.stride(0),
topk_idx.stride(1),
topk_idx.stride(2),
o_partial.stride(0),
o_partial.stride(1),
o_partial.stride(2),
o_partial.stride(3),
lse_partial.stride(0),
lse_partial.stride(1),
lse_partial.stride(2),
BLOCK_SIZE_N=block_size,
NUM_TOPK_CHUNKS=NUM_TOPK_CHUNKS,
IS_FP8=is_fp8,
)
# merge partials into chunk 0
merge_grid = (batch_size, num_q_heads)
_merge_topk_attn_out_kernel[merge_grid](
o_partial,
lse_partial,
head_dim,
o_partial.stride(0),
o_partial.stride(1),
o_partial.stride(2),
o_partial.stride(3),
lse_partial.stride(0),
lse_partial.stride(1),
lse_partial.stride(2),
NUM_TOPK_CHUNKS=NUM_TOPK_CHUNKS,
)
return o_partial[0].contiguous()