[Refactor] attention: Route every FlashAttention call through the selected kernel (#46695)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Alex Nails <alex.nails@radixark.ai>
This commit is contained in:
authored and GitHub committed 2026-10-27 17:11:58 -07:00
1 parent 20b439ca77
commit 586355a83b
3 files changed
+105 -74

No files matched your search

@@ -80,6 +80,7 @@ def flash_attn_varlen_func(
num_splits: int = 1,
pack_gqa: Optional[bool] = None,
score_mod: Optional[Callable] = None,
mask_mod: Optional[Callable] = None,
aux_tensors: Optional[list] = None,
q_descale: Optional[torch.Tensor] = None,
k_descale: Optional[torch.Tensor] = None,
@@ -162,6 +163,7 @@ def flash_attn_varlen_func(
num_splits=num_splits,
pack_gqa=pack_gqa,
score_mod=score_mod,
mask_mod=mask_mod,
aux_tensors=aux_tensors,
return_lse=return_softmax_lse,
out=out,
@@ -211,6 +213,7 @@ def flash_attn_with_kvcache(
sm_margin: int = 0,
sinks: Optional[torch.Tensor] = None,
score_mod: Optional[Callable] = None,
mask_mod: Optional[Callable] = None,
aux_tensors: Optional[list] = None,
sfq: Optional[torch.Tensor] = None,
sfk: Optional[torch.Tensor] = None,
@@ -246,6 +249,7 @@ def flash_attn_with_kvcache(
for value in (
qv,
score_mod,
mask_mod,
aux_tensors,
q_descale,
k_descale,
@@ -304,6 +308,7 @@ def flash_attn_with_kvcache(
pack_gqa=pack_gqa,
learnable_sink=sinks,
score_mod=score_mod,
mask_mod=mask_mod,
aux_tensors=aux_tensors,
q_descale=q_descale,
k_descale=k_descale,
@@ -61,11 +61,6 @@ if TYPE_CHECKING:
from sgl_kernel import merge_state_v2
from sglang.kernels.ops.attention.flash_attention import (
flash_attn_varlen_func,
flash_attn_with_kvcache,
)
def _should_disable_scheduler_metadata_precompute() -> bool:
return bool(get_parallel().enable_prefill_cp or get_parallel().attn_dp_enabled)
@@ -279,10 +274,11 @@ class FlashAttentionBackend(AttentionBackend):
self.fa_impl_ver = fa_impl_ver
device_capability = get_device_capability()
if self.fa_impl_ver == 3:
from sgl_kernel.flash_attn import (
from sgl_kernel.flash_attn import get_scheduler_metadata
from sglang.kernels.ops.attention.flash_attention_v3 import (
flash_attn_varlen_func,
flash_attn_with_kvcache,
get_scheduler_metadata,
)
self._get_scheduler_metadata = get_scheduler_metadata
@@ -317,6 +313,7 @@ class FlashAttentionBackend(AttentionBackend):
else:
raise ValueError(f"Invalid version: {self.fa_impl_ver=}")
# Every kernel call goes through these; a subclass swaps the FA build here.
self.flash_attn_varlen_func = flash_attn_varlen_func
self.flash_attn_with_kvcache = flash_attn_with_kvcache
@@ -1701,7 +1698,7 @@ class FlashAttentionBackend(AttentionBackend):
def _fa_cp_attn(
q_chunk, cu_seqlens_q_cp, cache_seqlens_cp, max_seqlen_q_cp
):
return flash_attn_with_kvcache(
return self.flash_attn_with_kvcache(
q=q_chunk,
k_cache=key_cache,
v_cache=value_cache,
@@ -1716,7 +1713,6 @@ class FlashAttentionBackend(AttentionBackend):
softcap=layer.logit_cap,
return_softmax_lse=use_cascade_attn,
num_splits=self.num_splits,
ver=self.fa_impl_ver,
**kwargs,
)
@@ -1786,7 +1782,7 @@ class FlashAttentionBackend(AttentionBackend):
**kwargs,
)
else:
result = flash_attn_with_kvcache(
result = self.flash_attn_with_kvcache(
q=q.contiguous().view(-1, layer.tp_q_head_num, layer.head_dim),
k_cache=key_cache,
v_cache=value_cache,
@@ -1802,7 +1798,6 @@ class FlashAttentionBackend(AttentionBackend):
return_softmax_lse=use_cascade_attn or return_lse,
num_splits=self.num_splits,
out=_fa_out,
ver=self.fa_impl_ver,
**kwargs,
)
@@ -1837,30 +1832,31 @@ class FlashAttentionBackend(AttentionBackend):
softmax_scale=layer.scaling,
)
elif use_cascade_attn:
o_expand, softmax_lse_expand, *rest_expand = flash_attn_with_kvcache(
q=q.contiguous().view(-1, layer.tp_q_head_num, layer.head_dim),
# The suffix table stores physical token slots, so expose
# the paged cache as page-size-one blocks.
k_cache=paged_view(
key_cache.view(-1, layer.tp_k_head_num, layer.head_dim), 1
),
v_cache=paged_view(
value_cache.view(-1, layer.tp_v_head_num, layer.v_head_dim),
1,
),
page_table=self.forward_metadata_spec_decode_expand.page_table,
cache_seqlens=self.forward_metadata_spec_decode_expand.cache_seqlens_int32,
cu_seqlens_q=self.forward_metadata_spec_decode_expand.cu_seqlens_q,
cu_seqlens_k_new=self.forward_metadata_spec_decode_expand.cu_seqlens_k,
max_seqlen_q=self.forward_metadata_spec_decode_expand.max_seq_len_q,
softmax_scale=layer.scaling,
causal=False,
window_size=window_size,
softcap=layer.logit_cap,
return_softmax_lse=True,
num_splits=self.num_splits,
ver=self.fa_impl_ver,
**kwargs,
o_expand, softmax_lse_expand, *rest_expand = (
self.flash_attn_with_kvcache(
q=q.contiguous().view(-1, layer.tp_q_head_num, layer.head_dim),
# The suffix table stores physical token slots, so expose
# the paged cache as page-size-one blocks.
k_cache=paged_view(
key_cache.view(-1, layer.tp_k_head_num, layer.head_dim), 1
),
v_cache=paged_view(
value_cache.view(-1, layer.tp_v_head_num, layer.v_head_dim),
1,
),
page_table=self.forward_metadata_spec_decode_expand.page_table,
cache_seqlens=self.forward_metadata_spec_decode_expand.cache_seqlens_int32,
cu_seqlens_q=self.forward_metadata_spec_decode_expand.cu_seqlens_q,
cu_seqlens_k_new=self.forward_metadata_spec_decode_expand.cu_seqlens_k,
max_seqlen_q=self.forward_metadata_spec_decode_expand.max_seq_len_q,
softmax_scale=layer.scaling,
causal=False,
window_size=window_size,
softcap=layer.logit_cap,
return_softmax_lse=True,
num_splits=self.num_splits,
**kwargs,
)
)
o, _ = merge_state_v2_wrapper(
o,
@@ -1888,7 +1884,7 @@ class FlashAttentionBackend(AttentionBackend):
assert chunk_idx >= 0
assert forward_batch.mha_return_lse
output = flash_attn_varlen_func(
output = self.flash_attn_varlen_func(
q=q.view(-1, layer.tp_q_head_num, layer.head_dim),
k=k.view(-1, layer.tp_k_head_num, layer.head_dim).to(q.dtype),
v=v.view(-1, layer.tp_k_head_num, layer.v_head_dim).to(q.dtype),
@@ -1900,7 +1896,6 @@ class FlashAttentionBackend(AttentionBackend):
causal=False,
return_softmax_lse=True,
out=_fa_out,
ver=self.fa_impl_ver,
**kwargs,
)
else:
@@ -1915,7 +1910,7 @@ class FlashAttentionBackend(AttentionBackend):
if not forward_batch.mha_one_shot
else metadata.max_seq_len_k
)
output = flash_attn_varlen_func(
output = self.flash_attn_varlen_func(
q=q.view(-1, layer.tp_q_head_num, layer.head_dim),
k=k.view(-1, layer.tp_k_head_num, layer.head_dim).to(q.dtype),
v=v.view(-1, layer.tp_k_head_num, layer.v_head_dim).to(q.dtype),
@@ -1927,7 +1922,6 @@ class FlashAttentionBackend(AttentionBackend):
causal=True,
return_softmax_lse=forward_batch.mha_return_lse,
out=_fa_out,
ver=self.fa_impl_ver,
**kwargs,
)
if forward_batch.mha_return_lse:
@@ -1978,7 +1972,7 @@ class FlashAttentionBackend(AttentionBackend):
):
q_nope_chunk = q_chunk[..., : layer.v_head_dim]
q_rope_chunk = q_chunk[..., layer.v_head_dim :]
return flash_attn_with_kvcache(
return self.flash_attn_with_kvcache(
q=q_rope_chunk,
qv=q_nope_chunk,
k_cache=k_rope_cache,
@@ -1996,7 +1990,6 @@ class FlashAttentionBackend(AttentionBackend):
k_descale=fa_k_descale,
v_descale=fa_v_descale,
num_splits=self.num_splits,
ver=self.fa_impl_ver,
)
cp_strategy = get_cp_strategy()
@@ -2009,7 +2002,7 @@ class FlashAttentionBackend(AttentionBackend):
attention_backend=CPAttentionBackendKind.FLASH_ATTENTION,
)
else:
result = flash_attn_with_kvcache(
result = self.flash_attn_with_kvcache(
q=q_rope,
k_cache=k_rope_cache,
v_cache=c_kv_cache,
@@ -2026,12 +2019,11 @@ class FlashAttentionBackend(AttentionBackend):
v_descale=fa_v_descale,
return_softmax_lse=use_cascade_attn,
num_splits=self.num_splits,
ver=self.fa_impl_ver,
)
if use_cascade_attn:
o, softmax_lse, *rest = result
o_expand, softmax_lse_expand, *rest_expand = (
flash_attn_with_kvcache(
self.flash_attn_with_kvcache(
q=q_rope,
k_cache=k_rope_cache,
v_cache=c_kv_cache,
@@ -2049,7 +2041,6 @@ class FlashAttentionBackend(AttentionBackend):
v_descale=fa_v_descale,
return_softmax_lse=True,
num_splits=self.num_splits,
ver=self.fa_impl_ver,
)
)
o, _ = merge_state_v2_wrapper(
@@ -2206,7 +2197,7 @@ class FlashAttentionBackend(AttentionBackend):
# Always use non-chunked logic for cross-attention
if self._decode_uses_static_max_seqlen_k:
kwargs["max_seqlen_k"] = metadata.encoder_max_seq_len_k
o = flash_attn_with_kvcache(
o = self.flash_attn_with_kvcache(
q=q.contiguous().view(-1, layer.tp_q_head_num, layer.head_dim),
k_cache=key_cache,
v_cache=value_cache,
@@ -2220,14 +2211,13 @@ class FlashAttentionBackend(AttentionBackend):
window_size=(-1, -1),
softcap=layer.logit_cap,
num_splits=self.num_splits,
ver=self.fa_impl_ver,
**kwargs,
)
elif use_local_attn:
# Use chunked (local) attention batching for self-attention
if self._decode_uses_static_max_seqlen_k:
kwargs["max_seqlen_k"] = local_attn_metadata.local_max_seq_len
o = flash_attn_with_kvcache(
o = self.flash_attn_with_kvcache(
q=q.contiguous().view(-1, layer.tp_q_head_num, layer.head_dim),
k_cache=key_cache,
v_cache=value_cache,
@@ -2241,7 +2231,6 @@ class FlashAttentionBackend(AttentionBackend):
window_size=(-1, -1),
softcap=layer.logit_cap,
num_splits=self.num_splits,
ver=self.fa_impl_ver,
**kwargs,
)
else:
@@ -2282,7 +2271,7 @@ class FlashAttentionBackend(AttentionBackend):
sched_meta = metadata.scheduler_metadata
if self._decode_uses_static_max_seqlen_k:
kwargs["max_seqlen_k"] = metadata.max_seq_len_k
result = flash_attn_with_kvcache(
result = self.flash_attn_with_kvcache(
q=q_reshaped,
k_cache=key_cache,
v_cache=value_cache,
@@ -2303,7 +2292,6 @@ class FlashAttentionBackend(AttentionBackend):
else self.num_splits
),
out=_fa_out,
ver=self.fa_impl_ver,
scheduler_metadata=sched_meta,
**kwargs,
)
@@ -2318,7 +2306,7 @@ class FlashAttentionBackend(AttentionBackend):
self.forward_metadata_spec_decode_expand.max_seq_len_k
)
o_expand, softmax_lse_expand, *rest_expand = (
flash_attn_with_kvcache(
self.flash_attn_with_kvcache(
q=q_reshaped,
k_cache=key_cache,
v_cache=value_cache,
@@ -2333,7 +2321,6 @@ class FlashAttentionBackend(AttentionBackend):
softcap=layer.logit_cap,
return_softmax_lse=True,
num_splits=self.num_splits,
ver=self.fa_impl_ver,
**kwargs,
)
)
@@ -2363,7 +2350,7 @@ class FlashAttentionBackend(AttentionBackend):
q_nope = q_all[:, :, : layer.v_head_dim]
q_rope = q_all[:, :, layer.v_head_dim :]
max_seqlen_q = metadata.max_seq_len_q
result = flash_attn_with_kvcache(
result = self.flash_attn_with_kvcache(
q=q_rope,
k_cache=k_rope_cache,
v_cache=c_kv_cache,
@@ -2380,29 +2367,29 @@ class FlashAttentionBackend(AttentionBackend):
v_descale=fa_v_descale,
return_softmax_lse=use_cascade_attn, # softmax_lse is needed for merge states
num_splits=self.num_splits,
ver=self.fa_impl_ver,
)
if use_cascade_attn:
o, softmax_lse, *rest = result
o_expand, softmax_lse_expand, *rest_expand = flash_attn_with_kvcache(
q=q_rope,
k_cache=k_rope_cache,
v_cache=c_kv_cache,
qv=q_nope,
page_table=self.forward_metadata_spec_decode_expand.page_table,
cache_seqlens=self.forward_metadata_spec_decode_expand.cache_seqlens_int32,
cu_seqlens_q=self.forward_metadata_spec_decode_expand.cu_seqlens_q,
cu_seqlens_k_new=self.forward_metadata_spec_decode_expand.cu_seqlens_k,
max_seqlen_q=self.forward_metadata_spec_decode_expand.max_seq_len_q,
softmax_scale=layer.scaling,
causal=False,
window_size=window_size,
softcap=layer.logit_cap,
k_descale=fa_k_descale,
v_descale=fa_v_descale,
return_softmax_lse=True,
num_splits=self.num_splits,
ver=self.fa_impl_ver,
o_expand, softmax_lse_expand, *rest_expand = (
self.flash_attn_with_kvcache(
q=q_rope,
k_cache=k_rope_cache,
v_cache=c_kv_cache,
qv=q_nope,
page_table=self.forward_metadata_spec_decode_expand.page_table,
cache_seqlens=self.forward_metadata_spec_decode_expand.cache_seqlens_int32,
cu_seqlens_q=self.forward_metadata_spec_decode_expand.cu_seqlens_q,
cu_seqlens_k_new=self.forward_metadata_spec_decode_expand.cu_seqlens_k,
max_seqlen_q=self.forward_metadata_spec_decode_expand.max_seq_len_q,
softmax_scale=layer.scaling,
causal=False,
window_size=window_size,
softcap=layer.logit_cap,
k_descale=fa_k_descale,
v_descale=fa_v_descale,
return_softmax_lse=True,
num_splits=self.num_splits,
)
)
o, _ = merge_state_v2(
o,
@@ -0,0 +1,39 @@
import ast
import unittest
from pathlib import Path
import sglang
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
_BACKEND_SOURCE = (
Path(sglang.__file__).parent / "srt/layers/attention/flashattention_backend.py"
)
_ENTRY_POINTS = ("flash_attn_with_kvcache", "flash_attn_varlen_func")
class TestFlashAttentionKernelDispatch(CustomTestCase):
def test_kernel_calls_use_selected_entry_points(self):
# A call through any name other than self.flash_attn_* bypasses the build
# __init__ selected, e.g. generic FA4 on SM12x, or FA3 when `ver` is omitted.
tree = ast.parse(_BACKEND_SOURCE.read_text())
bypasses = []
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
func = node.func
if isinstance(func, ast.Name) and func.id in _ENTRY_POINTS:
bypasses.append(f"line {node.lineno}: {func.id}(...)")
elif (
isinstance(func, ast.Attribute)
and func.attr in _ENTRY_POINTS
and not (isinstance(func.value, ast.Name) and func.value.id == "self")
):
bypasses.append(f"line {node.lineno}: {ast.unparse(func)}(...)")
self.assertEqual(bypasses, [])
if __name__ == "__main__":
unittest.main()