Skip to content

Commit e85f52b

Browse files
committed
sm12x: cap C128A metadata kernel loop at effective_topk (no shape changes)
Cudagraph-safe retry of suggestion #2 from PR vllm-project#41834 comment 4450901180. Previous attempt (e34daef, reverted) also exposed ``c128a_*_effective_topk`` on the metadata and truncated the buffer slice inside ``deepseek_v4_attention``; that truncation baked the shape into the captured forward launch, breaking replay when ``effective_topk`` shifted between capture and replay. This version only touches the metadata builder (which already runs *outside* the captured forward), so per-call ``effective_topk`` variation is fine: 1. Pre-fill ``global_decode_buffer[:num_decode_tokens]`` and ``prefill_buffer[:num_prefill_tokens]`` with ``-1`` before launch. 2. Compute ``effective_topk_arg = cdiv(max num_compressed across in-flight tokens, BLOCK_SIZE) * BLOCK_SIZE``, capped at ``max_compressed_tokens``. 3. Kernel inner loop uses ``effective_topk`` (was ``max_compressed_tokens``); store mask uses the same. The buffer entries the kernel skips (``[effective_topk, max_compressed_tokens)``) stay at ``-1`` from the pre-fill, so downstream sparse MLA accumulate kernels (which still iterate the full ``max_compressed_tokens`` width inside the cudagraph) see only ``-1`` sentinels in the tail and short-circuit them via ``kv_index >= 0`` / ``candidate < valid_len`` checks. No tensor shape changes inside the captured forward → cudagraph capture/replay remains correct. Savings here are limited to the metadata kernel itself; the accumulate kernels' iteration count is unchanged (their loop bound is the captured ``num_candidates`` shape value, which we deliberately do not narrow). Bench at long ``max_model_len`` will confirm whether this is enough to recover a meaningful chunk of the ~27 % TPOT regression observed at ``max_model_len=131072`` vs ``8192``. Signed-off-by: jasl <jasl9187@hotmail.com>
1 parent 1796f5f commit e85f52b

1 file changed

Lines changed: 49 additions & 8 deletions

File tree

vllm/v1/attention/backends/mla/flashmla_sparse.py

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,41 @@ def build_c128a_topk_metadata(
10821082
if num_tokens == 0:
10831083
return global_decode, decode_lens, prefill_local
10841084

1085+
BLOCK_SIZE = 1024
1086+
1087+
# Compute the smallest BLOCK_SIZE-aligned width that covers every
1088+
# in-flight token's num_compressed. When ``max_model_len`` is much
1089+
# larger than the actual prompts (e.g. 1M cap with 2K inputs) the
1090+
# original ``range(0, max_compressed_tokens, BLOCK_SIZE)`` iterated
1091+
# over a tail that always wrote ``-1`` for shorter contexts. Capping
1092+
# the inner loop at ``effective_topk`` cuts those dead iterations.
1093+
#
1094+
# This builder runs OUTSIDE the CUDA-graph-captured forward pass, so
1095+
# ``effective_topk`` may vary freely per call. To keep downstream
1096+
# (which reads the full ``max_compressed_tokens`` buffer width inside
1097+
# the captured forward) correct, we pre-fill the active slice with
1098+
# ``-1`` so the kernel-skipped tail is treated as "invalid" by the
1099+
# sentinel checks in the sparse MLA accumulate kernels.
1100+
if num_decode_tokens > 0:
1101+
global_decode_buffer[:num_decode_tokens].fill_(-1)
1102+
decode_lens_buffer[:num_decode_tokens].zero_()
1103+
if num_prefill_tokens > 0:
1104+
prefill_buffer[:num_prefill_tokens].fill_(-1)
1105+
1106+
max_pos = int(positions.max().item())
1107+
max_num_compressed = min(
1108+
max((max_pos + 1) // compress_ratio, 0),
1109+
max_compressed_tokens,
1110+
)
1111+
effective_topk_arg = min(
1112+
max_compressed_tokens,
1113+
cdiv(max_num_compressed, BLOCK_SIZE) * BLOCK_SIZE,
1114+
)
1115+
if effective_topk_arg == 0:
1116+
# Nothing to write; the fill_(-1) above already produced the
1117+
# correct "all-invalid" buffer state.
1118+
return global_decode, decode_lens, prefill_local
1119+
10851120
_build_c128a_topk_metadata_kernel[(num_tokens,)](
10861121
global_decode_buffer,
10871122
global_decode_buffer.stride(0),
@@ -1090,14 +1125,14 @@ def build_c128a_topk_metadata(
10901125
prefill_buffer.stride(0),
10911126
positions,
10921127
compress_ratio,
1093-
max_compressed_tokens,
1128+
effective_topk_arg,
10941129
num_decode_tokens,
10951130
token_to_req_indices,
10961131
block_table,
10971132
block_table.stride(0),
10981133
block_size,
10991134
slot_mapping,
1100-
BLOCK_SIZE=1024,
1135+
BLOCK_SIZE=BLOCK_SIZE,
11011136
)
11021137
return global_decode, decode_lens, prefill_local
11031138

@@ -1114,7 +1149,7 @@ def _build_c128a_topk_metadata_kernel(
11141149
# Inputs
11151150
positions_ptr,
11161151
compress_ratio,
1117-
max_compressed_tokens,
1152+
effective_topk,
11181153
num_decode_tokens,
11191154
token_to_req_indices_ptr,
11201155
block_table_ptr,
@@ -1123,20 +1158,26 @@ def _build_c128a_topk_metadata_kernel(
11231158
slot_mapping_ptr,
11241159
BLOCK_SIZE: tl.constexpr,
11251160
):
1161+
# ``effective_topk`` is the BLOCK_SIZE-aligned cap that covers every
1162+
# in-flight token's ``num_compressed`` (computed by the Python
1163+
# builder). The caller pre-fills the active buffer slice with ``-1``
1164+
# so entries in ``[effective_topk, max_compressed_tokens)`` remain
1165+
# ``-1`` (the sentinel the downstream sparse MLA accumulate kernels
1166+
# use to skip invalid candidates).
11261167
token_idx = tl.program_id(0)
11271168
position = tl.load(positions_ptr + token_idx)
11281169
num_compressed = (position + 1) // compress_ratio
1129-
num_compressed = tl.minimum(num_compressed, max_compressed_tokens)
1170+
num_compressed = tl.minimum(num_compressed, effective_topk)
11301171
is_decode = token_idx < num_decode_tokens
11311172

11321173
if is_decode:
11331174
# --- Decode: block-table lookup → global slot ids + count ---
11341175
is_valid_token = tl.load(slot_mapping_ptr + token_idx) >= 0
11351176
req_idx = tl.load(token_to_req_indices_ptr + token_idx)
11361177
count = tl.zeros((), dtype=tl.int32)
1137-
for i in range(0, max_compressed_tokens, BLOCK_SIZE):
1178+
for i in range(0, effective_topk, BLOCK_SIZE):
11381179
offset = i + tl.arange(0, BLOCK_SIZE)
1139-
mask = offset < max_compressed_tokens
1180+
mask = offset < effective_topk
11401181
is_valid = offset < num_compressed
11411182

11421183
block_indices = offset // block_size
@@ -1161,9 +1202,9 @@ def _build_c128a_topk_metadata_kernel(
11611202
else:
11621203
# --- Prefill: write local indices ---
11631204
pfx_idx = token_idx - num_decode_tokens
1164-
for i in range(0, max_compressed_tokens, BLOCK_SIZE):
1205+
for i in range(0, effective_topk, BLOCK_SIZE):
11651206
offset = i + tl.arange(0, BLOCK_SIZE)
1166-
mask = offset < max_compressed_tokens
1207+
mask = offset < effective_topk
11671208
tl.store(
11681209
prefill_local_ptr + pfx_idx * prefill_local_stride + offset,
11691210
tl.where(offset < num_compressed, offset, -1),

0 commit comments

Comments
 (0)