[Attn] Fix aiter MLA verify kv_indices under-alloc + shared assert_buffer_fits guard (#27485)

This commit is contained in:
Liangsheng Yin
2026-06-07 17:06:25 -07:00
committed by GitHub
parent f68c79675f
commit 303757ccd8
5 changed files with 70 additions and 26 deletions
@@ -18,6 +18,7 @@ from sglang.srt.layers.attention.triton_ops.aiter_unified_attention import (
scatter_req_to_token_to_page_table_kernel, scatter_req_to_token_to_page_table_kernel,
) )
from sglang.srt.layers.attention.utils import ( from sglang.srt.layers.attention.utils import (
assert_buffer_fits,
create_flashinfer_kv_indices_triton, create_flashinfer_kv_indices_triton,
create_flashmla_kv_indices_triton, create_flashmla_kv_indices_triton,
get_num_kv_index_blocks_flashmla, get_num_kv_index_blocks_flashmla,
@@ -1450,7 +1451,13 @@ class AiterAttnBackend(AttentionBackend):
# metadata sites + paged_attention_ragged call site + FP8 KV # metadata sites + paged_attention_ragged call site + FP8 KV
# coordination, after which this allocation can revert to # coordination, after which this allocation can revert to
# per-page (gated on use_mla). # per-page (gated on use_mla).
buffer_numel = max_bs * max_num_blocks_per_seq * self.page_size # Reserve draft slack: MLA target_verify writes seq_len +
# num_draft_tokens per row; without it a near-full sequence
# overflows the buffer. Mirrors dsa / flashmla.
draft_slack = self.num_draft_tokens or 0
buffer_numel = max_bs * (
max_num_blocks_per_seq * self.page_size + draft_slack
)
self.cuda_graph_kv_indices = torch.zeros( self.cuda_graph_kv_indices = torch.zeros(
(buffer_numel,), (buffer_numel,),
dtype=torch.int32, dtype=torch.int32,
@@ -1691,6 +1698,18 @@ class AiterAttnBackend(AttentionBackend):
kv_indptr = self.kv_indptr[: bs + 1] kv_indptr = self.kv_indptr[: bs + 1]
kv_indptr[1 : bs + 1] = torch.cumsum(kv_lens, dim=0) kv_indptr[1 : bs + 1] = torch.cumsum(kv_lens, dim=0)
kv_indices = self.cuda_graph_kv_indices kv_indices = self.cuda_graph_kv_indices
# seq_lens_sum is None at capture (dummy seq_lens); only check on replay.
if seq_lens_sum is not None:
kv_indices_used = seq_lens_sum + (
self.num_draft_tokens * bs if self.use_mla else 0
)
assert_buffer_fits(
kv_indices_used,
kv_indices.numel(),
"aiter target_verify kv_indices",
bs=bs,
seq_lens_sum=seq_lens_sum,
)
create_flashinfer_kv_indices_triton[(bs,)]( create_flashinfer_kv_indices_triton[(bs,)](
self.req_to_token, self.req_to_token,
req_pool_indices, req_pool_indices,
@@ -12,6 +12,7 @@ from sglang.srt.layers.attention.triton_ops.metadata import (
normal_decode_set_metadata, normal_decode_set_metadata,
prepare_swa_spec_page_table_triton, prepare_swa_spec_page_table_triton,
) )
from sglang.srt.layers.attention.utils import assert_buffer_fits
from sglang.srt.layers.radix_attention import AttentionType from sglang.srt.layers.radix_attention import AttentionType
from sglang.srt.layers.utils.cp_utils import ( from sglang.srt.layers.utils.cp_utils import (
cp_allgather_and_save_kv_cache, cp_allgather_and_save_kv_cache,
@@ -2050,6 +2051,11 @@ class FlashAttentionBackend(AttentionBackend):
metadata.max_seq_len_k + self.page_size - 1 metadata.max_seq_len_k + self.page_size - 1
) // self.page_size ) // self.page_size
assert_buffer_fits(
max_seq_pages,
metadata.page_table.shape[1],
"FA3 draft-decode page_table",
)
normal_decode_set_metadata( normal_decode_set_metadata(
metadata.cache_seqlens_int32, metadata.cache_seqlens_int32,
metadata.cu_seqlens_k, metadata.cu_seqlens_k,
@@ -2107,14 +2113,10 @@ class FlashAttentionBackend(AttentionBackend):
# most decode_length of the (decode_length + 1) expand page_table # most decode_length of the (decode_length + 1) expand page_table
# columns -- without this the extra distinct pages overflow the row. # columns -- without this the extra distinct pages overflow the row.
cache_loc = cache_loc[:, :decode_length] cache_loc = cache_loc[:, :decode_length]
assert ( assert_buffer_fits(
cache_loc.shape[1] <= metadata_expand.page_table.shape[1] cache_loc.shape[1],
), ( metadata_expand.page_table.shape[1],
f"draft expand page_table too narrow: cache_loc width " "draft expand page_table (width decode_length + 1)",
f"{cache_loc.shape[1]} > "
f"{metadata_expand.page_table.shape[1]} columns "
f"(decode_length + 1); page_size={self.page_size}, "
f"topk={self.topk}, num_steps={self.speculative_num_steps}"
) )
draft_decode_set_expand_metadata( draft_decode_set_expand_metadata(
cache_seqlens_int32=metadata_expand.cache_seqlens_int32, cache_seqlens_int32=metadata_expand.cache_seqlens_int32,
@@ -2138,6 +2140,11 @@ class FlashAttentionBackend(AttentionBackend):
max_seq_pages = (max_len + self.page_size - 1) // self.page_size max_seq_pages = (max_len + self.page_size - 1) // self.page_size
metadata.max_seq_len_k = max_len metadata.max_seq_len_k = max_len
assert_buffer_fits(
max_seq_pages,
metadata.page_table.shape[1],
"FA3 decode page_table",
)
normal_decode_set_metadata( normal_decode_set_metadata(
metadata.cache_seqlens_int32, metadata.cache_seqlens_int32,
metadata.cu_seqlens_k, metadata.cu_seqlens_k,
@@ -2637,6 +2644,11 @@ class FlashAttentionBackend(AttentionBackend):
if metadata_swa is None if metadata_swa is None
else metadata_swa.page_table else metadata_swa.page_table
) )
assert_buffer_fits(
metadata.max_seq_len_k + metadata_expand.page_table.shape[1],
page_table.shape[1],
"FA3 swa-spec page_table",
)
page_table_a = metadata.page_table page_table_a = metadata.page_table
page_table_b = metadata_expand.page_table page_table_b = metadata_expand.page_table
@@ -21,7 +21,10 @@ from sglang.srt.compilation.piecewise_context_manager import is_in_piecewise_cud
from sglang.srt.dllm.config import DllmConfig from sglang.srt.dllm.config import DllmConfig
from sglang.srt.environ import envs from sglang.srt.environ import envs
from sglang.srt.layers.attention.base_attn_backend import AttentionBackend from sglang.srt.layers.attention.base_attn_backend import AttentionBackend
from sglang.srt.layers.attention.utils import create_flashinfer_kv_indices_triton from sglang.srt.layers.attention.utils import (
assert_buffer_fits,
create_flashinfer_kv_indices_triton,
)
from sglang.srt.layers.dp_attention import get_attention_tp_size from sglang.srt.layers.dp_attention import get_attention_tp_size
from sglang.srt.layers.radix_attention import AttentionType from sglang.srt.layers.radix_attention import AttentionType
from sglang.srt.mem_cache.allocator.swa import SWATokenToKVPoolAllocator from sglang.srt.mem_cache.allocator.swa import SWATokenToKVPoolAllocator
@@ -1587,17 +1590,15 @@ class FlashInferMultiStepDraftBackend:
bs = self.topk * num_seqs bs = self.topk * num_seqs
seq_lens_sum = forward_batch.seq_lens_sum seq_lens_sum = forward_batch.seq_lens_sum
# Fail fast on an undersized kv_indices row: the kernel would otherwise write
# OOB and *silently* corrupt memory, only sometimes surfacing as a crash.
required_kv_indices_len = draft_kv_indices_used_len( required_kv_indices_len = draft_kv_indices_used_len(
seq_lens_sum, self.topk, bs, self.speculative_num_steps seq_lens_sum, self.topk, bs, self.speculative_num_steps
) )
assert required_kv_indices_len <= kv_indices_buffer.shape[1], ( assert_buffer_fits(
f"EAGLE draft kv_indices row too small: need {required_kv_indices_len} " required_kv_indices_len,
f"but row width is {kv_indices_buffer.shape[1]} (topk={self.topk}, " kv_indices_buffer.shape[1],
f"num_seqs={num_seqs}, seq_lens_sum={seq_lens_sum}, " "EAGLE draft kv_indices row (size max_bs * topk * max_context_len)",
f"num_steps={self.speculative_num_steps}); the buffer must be sized " bs=bs,
f"max_bs * topk * max_context_len." seq_lens_sum=seq_lens_sum,
) )
self.generate_draft_decode_kv_indices[ self.generate_draft_decode_kv_indices[
@@ -21,6 +21,7 @@ from sglang.srt.layers.attention.base_attn_backend import AttentionBackend
from sglang.srt.layers.attention.flashinfer_backend import ( from sglang.srt.layers.attention.flashinfer_backend import (
create_flashinfer_kv_indices_triton, create_flashinfer_kv_indices_triton,
) )
from sglang.srt.layers.attention.utils import assert_buffer_fits
from sglang.srt.layers.dp_attention import get_attention_tp_size from sglang.srt.layers.dp_attention import get_attention_tp_size
from sglang.srt.model_executor.forward_batch_info import ForwardBatch, ForwardMode from sglang.srt.model_executor.forward_batch_info import ForwardBatch, ForwardMode
from sglang.srt.server_args import get_global_server_args from sglang.srt.server_args import get_global_server_args
@@ -946,17 +947,15 @@ class FlashInferMLAMultiStepDraftBackend:
bs = self.topk * num_seqs bs = self.topk * num_seqs
seq_lens_sum = forward_batch.seq_lens_sum seq_lens_sum = forward_batch.seq_lens_sum
# Fail fast on an undersized kv_indices row: the kernel would otherwise
# write OOB and silently corrupt memory.
required_kv_indices_len = draft_kv_indices_used_len( required_kv_indices_len = draft_kv_indices_used_len(
seq_lens_sum, self.topk, bs, self.speculative_num_steps seq_lens_sum, self.topk, bs, self.speculative_num_steps
) )
assert required_kv_indices_len <= kv_indices_buffer.shape[1], ( assert_buffer_fits(
f"EAGLE draft kv_indices row too small: need {required_kv_indices_len} " required_kv_indices_len,
f"but row width is {kv_indices_buffer.shape[1]} (topk={self.topk}, " kv_indices_buffer.shape[1],
f"num_seqs={num_seqs}, seq_lens_sum={seq_lens_sum}, " "EAGLE draft kv_indices row (size max_bs * topk * max_context_len)",
f"num_steps={self.speculative_num_steps}); the buffer must be sized " bs=bs,
f"max_bs * topk * max_context_len." seq_lens_sum=seq_lens_sum,
) )
self.generate_draft_decode_kv_indices[ self.generate_draft_decode_kv_indices[
@@ -174,3 +174,16 @@ def concat_mla_absorb_q_general(q_nope, q_rope):
return concat_mla_absorb_q(q_nope, q_rope) return concat_mla_absorb_q(q_nope, q_rope)
else: else:
return torch.cat([q_nope, q_rope], dim=-1) return torch.cat([q_nope, q_rope], dim=-1)
def assert_buffer_fits(used: int, capacity: int, what: str, **context) -> None:
"""Safety guard: a preallocated cuda-graph buffer must hold the runtime write.
The kv_indices / page_table scatter kernels bound writes only per-row, not
against the destination buffer, so an undersized buffer silently overflows
into the adjacent row. Fail fast on the host-known extent instead. All args
are host ints, so this is always-on (no device sync, unlike async probes).
"""
assert used <= capacity, f"{what}: used {used} > capacity {capacity}" + (
f" ({', '.join(f'{k}={v}' for k, v in context.items())})" if context else ""
)