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

383 lines
14 KiB
Python

"""FlashInfer-based kernels for GDN (Gated Delta Network) linear attention.
Both SM90 and SM100 use the same pool layout: [pool, HV, V, K] (K-last).
SM90 (Hopper): full support — decode, prefill, MTP. State dtype: fp32.
SM100 (Blackwell): full support — decode, prefill, MTP.
Requires flashinfer >= 0.6.7.
"""
import logging
import os
from typing import Optional
import torch
from sglang.srt.layers.attention.linear.kernels.kernel_backend import (
LinearAttnKernelBase,
)
from sglang.srt.utils import is_cuda
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Lazy import for FlashInfer GDN kernels
# ---------------------------------------------------------------------------
_flashinfer_gdn_available: Optional[bool] = None
_flashinfer_chunk_gated_delta_rule = None
_flashinfer_gated_delta_rule_mtp = None
_flashinfer_gated_delta_rule_decode = None
_flashinfer_gated_delta_rule_mtp_bf16 = None
def _get_flashinfer_gdn_kernels():
"""Lazy import for FlashInfer GDN prefill, decode and verify (MTP) kernels.
Returns (available, prefill_fn, mtp_fn, decode_fn, mtp_bf16_fn).
"""
global _flashinfer_gdn_available, _flashinfer_chunk_gated_delta_rule, _flashinfer_gated_delta_rule_mtp, _flashinfer_gated_delta_rule_decode, _flashinfer_gated_delta_rule_mtp_bf16
if _flashinfer_gdn_available is None:
try:
os.environ.setdefault("FLASHINFER_DISABLE_VERSION_CHECK", "1")
from flashinfer.gdn_decode import (
gated_delta_rule_decode_pretranspose,
gated_delta_rule_mtp,
)
from flashinfer.gdn_kernels.gdn_decode_bf16_state import (
gated_delta_rule_mtp as gated_delta_rule_mtp_bf16,
)
from flashinfer.gdn_prefill import chunk_gated_delta_rule
_flashinfer_chunk_gated_delta_rule = chunk_gated_delta_rule
_flashinfer_gated_delta_rule_mtp = gated_delta_rule_mtp
_flashinfer_gated_delta_rule_mtp_bf16 = gated_delta_rule_mtp_bf16
_flashinfer_gated_delta_rule_decode = gated_delta_rule_decode_pretranspose
_flashinfer_gdn_available = (
is_cuda() and torch.cuda.get_device_capability()[0] >= 9
)
if _flashinfer_gdn_available:
logger.info("FlashInfer GDN kernels loaded successfully")
except (ImportError, RuntimeError) as e:
logger.warning(f"FlashInfer GDN kernels not available: {e}")
_flashinfer_gdn_available = False
_flashinfer_gated_delta_rule_decode = None
return (
_flashinfer_gdn_available,
_flashinfer_chunk_gated_delta_rule,
_flashinfer_gated_delta_rule_mtp,
_flashinfer_gated_delta_rule_decode,
_flashinfer_gated_delta_rule_mtp_bf16,
)
def is_flashinfer_gdn_prefill_available() -> bool:
"""Return whether the kernel loader can construct the prefill path."""
available, prefill_fn, *_ = _get_flashinfer_gdn_kernels()
return bool(available and prefill_fn is not None)
# ---------------------------------------------------------------------------
# Kernel implementation
# ---------------------------------------------------------------------------
class FlashInferGDNKernel(LinearAttnKernelBase):
"""FlashInfer kernel for GDN with K-last SSM state layout.
SM90 (Hopper): decode uses gather/scatter; prefill and MTP verify supported.
SM100 (Blackwell): decode uses gather/scatter; prefill and MTP verify supported.
Requires flashinfer >= 0.6.7.
"""
def __init__(self):
(
available,
self._prefill_fn,
self._mtp_fn,
self._decode_fn,
mtp_bf16_fn,
) = _get_flashinfer_gdn_kernels()
if not available:
raise RuntimeError(
"FlashInfer GDN kernels are not available. "
"Requires SM90+ and FlashInfer with GDN kernel support."
)
if self._decode_fn is None:
raise RuntimeError("FlashInfer GDN decode kernel is unavailable.")
sm_major = torch.cuda.get_device_capability()[0]
self.use_state_pool = sm_major >= 10
self.supports_target_verify = sm_major in (9, 10)
if sm_major == 9 and self._prefill_fn is None:
raise RuntimeError("FlashInfer GDN prefill kernel is unavailable.")
if self._mtp_fn is None:
raise RuntimeError("FlashInfer GDN MTP (verify) kernel is unavailable.")
if self.use_state_pool and mtp_bf16_fn is not None:
# Adapt bf16 kernel to fp32 kernel interface so target_verify needs no branching.
def _mtp_bf16_adapted(
q,
k,
v,
initial_state,
initial_state_indices,
A_log,
a,
dt_bias,
b,
use_qk_l2norm=True,
**kw,
):
out = mtp_bf16_fn(
A_log=A_log.float(),
a=a,
dt_bias=dt_bias,
softplus_beta=1.0,
softplus_threshold=20.0,
q=q,
k=k,
v=v,
b=b,
initial_state_source=initial_state,
initial_state_indices=initial_state_indices,
use_qk_l2norm_in_kernel=use_qk_l2norm,
**kw,
)
return out, None
self._mtp_fn = _mtp_bf16_adapted
logger.info("Using FlashInfer GDN kernels")
# ---- decode ----
def decode(
self,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
a: torch.Tensor,
b: torch.Tensor,
*,
A_log: torch.Tensor,
dt_bias: torch.Tensor,
ssm_states: torch.Tensor,
cache_indices: torch.Tensor,
query_start_loc: torch.Tensor,
**kwargs,
) -> torch.Tensor:
batch_size = cache_indices.shape[0]
num_heads = q.shape[2]
head_k_dim = q.shape[3]
num_v_heads = v.shape[2]
head_v_dim = v.shape[3]
query_fi = q.view(batch_size, 1, num_heads, head_k_dim)
key_fi = k.view(batch_size, 1, num_heads, head_k_dim)
value_fi = v.view(batch_size, 1, num_v_heads, head_v_dim)
a_fi = a.view(batch_size, 1, num_v_heads)
b_fi = b.view(batch_size, 1, num_v_heads)
if self.use_state_pool:
output_fi, _ = self._decode_fn(
q=query_fi,
k=key_fi,
v=value_fi,
state=None,
A_log=A_log.detach().float(),
a=a_fi,
dt_bias=dt_bias.detach(),
b=b_fi,
use_qk_l2norm=True,
initial_state=ssm_states,
initial_state_indices=cache_indices,
)
else:
# TODO: Once FlashInfer PR#2521 is merged for SM90, gather/scatter
# will no longer be needed here.
state_batch = ssm_states[cache_indices]
output_fi, new_state = self._decode_fn(
q=query_fi,
k=key_fi,
v=value_fi,
state=state_batch,
A_log=A_log.detach(),
a=a_fi,
dt_bias=dt_bias.detach(),
b=b_fi,
scale=None,
output=None,
use_qk_l2norm=True,
)
ssm_states[cache_indices] = new_state
return output_fi.view(1, batch_size, num_v_heads, head_v_dim)
# ---- extend (prefill) ----
def extend(
self,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
g: torch.Tensor,
beta: torch.Tensor,
*,
ssm_states: torch.Tensor,
cache_indices: torch.Tensor,
query_start_loc: torch.Tensor,
**kwargs,
) -> tuple:
from sglang.srt.layers.attention.fla.l2norm import l2norm_fwd
total_seq_len = q.shape[1]
num_v_heads = v.shape[2]
head_v_dim = v.shape[3]
q_fi = l2norm_fwd(q[0].contiguous())
k_fi = l2norm_fwd(k[0].contiguous())
v_fi = v[0].contiguous()
# g (alpha) and beta: [1, seq, HV] -> [seq, HV], float32 for FlashInfer
alpha_fi = torch.exp(g[0].to(torch.float32))
beta_fi = beta[0].to(torch.float32)
if self.use_state_pool:
# Negative indices (e.g. -1) are padding markers for slots not yet
# assigned to a real sequence; clamp them to 0 (the reserved dummy
# slot) so the FlashInfer kernel never reads out-of-bounds state.
ssm_cache_indices = cache_indices.clamp(min=0).to(torch.int64)
initial_state_fi = ssm_states[ssm_cache_indices].contiguous()
# Pre-allocate bf16 output_state so the kernel compiles and writes the
# bf16 state path directly, avoiding a fp32 allocation and a subsequent
# fp32->bf16 conversion in the scatter step.
output_state_fi = torch.empty_like(initial_state_fi)
output_fi, output_state_fi = self._prefill_fn(
q=q_fi,
k=k_fi,
v=v_fi,
g=alpha_fi,
beta=beta_fi,
scale=None,
initial_state=initial_state_fi,
output_final_state=True,
cu_seqlens=query_start_loc, # already int32
use_qk_l2norm_in_kernel=False,
output_state=output_state_fi,
)
else:
# SM90: preserve original negative-index handling (remap to last slot).
ssm_cache_indices = torch.where(
cache_indices >= 0,
cache_indices,
ssm_states.shape[0] - 1,
).to(torch.int64)
# State must be float32; kernel requires int64 cu_seqlens.
initial_state_fi = ssm_states[ssm_cache_indices].to(torch.float32)
output_fi, output_state_fi = self._prefill_fn(
q=q_fi,
k=k_fi,
v=v_fi,
g=alpha_fi,
beta=beta_fi,
scale=None,
initial_state=initial_state_fi,
output_final_state=True,
cu_seqlens=query_start_loc.to(torch.int64),
use_qk_l2norm_in_kernel=False,
)
# Write back state to pool
ssm_states.index_copy_(
0,
ssm_cache_indices,
output_state_fi.to(ssm_states.dtype),
)
# Output: [seq, HV, V] -> [1, seq, HV, V]
core_attn_out = output_fi.view(1, total_seq_len, num_v_heads, head_v_dim)
# Return (output, last_recurrent_state, h) to match Triton kernel interface.
# h=None since FlashInfer doesn't provide intermediate states.
return core_attn_out, None, None
# ---- target_verify (MTP) ----
def target_verify(
self,
A_log: torch.Tensor,
dt_bias: torch.Tensor,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
a: torch.Tensor,
b: torch.Tensor,
*,
ssm_states: torch.Tensor,
cache_indices: torch.Tensor,
query_start_loc: torch.Tensor,
intermediate_states_buffer: torch.Tensor,
intermediate_state_indices: torch.Tensor,
cache_steps: int,
retrieve_parent_token: torch.Tensor,
**kwargs,
) -> torch.Tensor:
# MTP verify using FlashInfer gated_delta_rule_mtp kernel (SM90 + SM100+).
if retrieve_parent_token is not None:
raise RuntimeError(
"FlashInfer GDN verify kernel only supports topk=1 "
"(retrieve_parent_token must be None)."
)
seq_len = q.shape[1]
batch_size = query_start_loc.shape[0] - 1
draft_token_num = seq_len // batch_size
num_heads = q.shape[2]
head_k_dim = q.shape[3]
num_v_heads = v.shape[2]
head_v_dim = v.shape[3]
query_mtp = q.view(batch_size, draft_token_num, num_heads, head_k_dim)
key_mtp = k.view(batch_size, draft_token_num, num_heads, head_k_dim)
value_mtp = v.view(batch_size, draft_token_num, num_v_heads, head_v_dim)
if a is None or b is None or A_log is None or dt_bias is None:
raise RuntimeError(
"FlashInfer GDN MTP kernel requires a, b, A_log, dt_bias."
)
a_mtp = a.view(batch_size, draft_token_num, num_v_heads)
b_mtp = b.view(batch_size, draft_token_num, num_v_heads)
intermediate_states_buffer_mtp = intermediate_states_buffer
if self.use_state_pool and intermediate_states_buffer is not None:
# The SM100 bf16 MTP kernel indexes this scratch buffer by the
# per-call batch id, while SGLang's speculative state cache is
# pool-scoped and may include an extra dummy slot.
intermediate_states_buffer_mtp = intermediate_states_buffer[:batch_size]
output_fi, _ = self._mtp_fn(
q=query_mtp,
k=key_mtp,
v=value_mtp,
initial_state=ssm_states,
initial_state_indices=cache_indices,
A_log=A_log.detach(),
a=a_mtp,
dt_bias=dt_bias.detach(),
b=b_mtp,
scale=None,
output=None,
intermediate_states_buffer=intermediate_states_buffer_mtp,
disable_state_update=True,
use_qk_l2norm=True,
)
return output_fi.view(1, seq_len, num_v_heads, head_v_dim)