Skip to content

Commit 21e8dcf

Browse files
dsxstevenwu-yushanYzTongNiarOrla-seeLookAround0301
authored
[BugFix][SpecDecode] Fix low MTP acceptance rate for SFA+DSA_CP (MTP>1) (vllm-project#10825)
### What this PR does / why we need it? Fixes a degraded multi-token-prediction (MTP) / speculative-decoding acceptance rate on Ascend when num_speculative_tokens > 1 when enable dsa_cp for sfa models. Root cause: Root cause: In the SFA v1 attention-metadata builder (vllm_ascend/attention/sfa_v1.py), the actual_seq_lengths_query / actual_seq_lengths_key buffers handed to lightning indexer shared the same memory buffer across all draft steps. With MTP > 1, the attention-metadata is overwritten and only the last step is kept, which leads to wrong inputs. Meanwhile, rotary cos/sin were also fetched from an out-dated cache while drafting. Changes: - vllm_ascend/attention/sfa_v1.py: pre-allocate per-draft-step buffers spec_actual_seq_lengths_query/key (one tensor per step, sized max_num_reqs * (num_speculative_tokens + 1) + 1) so each step owns independent, with no step overwrites another's metadata. Split the builder into build() (non-draft/verify path), build_for_drafting(draft_step, ...), and a shared _build(..., draft_step) that selects the correct per-step buffer; pass use_cache=False to get_cos_and_sin_mla while drafting so the rotary tables are recomputed for each step. - vllm_ascend/spec_decode/llm_base_proposer.py: when a builder exposes build_for_drafting, route each draft step's attention-metadata build through it instead of calling build(0, ...) for every step, so the per-step buffers are actually used and cross-step aliasing is avoided. Effect: correct per-step attention metadata and rotary embeddings → restored MTP/spec-decode acceptance rate for num_speculative_tokens > 1. ### Does this PR introduce _any_ user-facing change? No ### How was this patch tested? - vLLM version: v0.22.1 - vLLM main: vllm-project/vllm@967c5c3 Co-authored-by: wu-yushan <wuyushan1@huawei.com> Co-authored-by: YzTongNiar <1667927948@qq.com> Co-authored-by: Orla-see <lidan276@huawei.com> Co-authored-by: LookAround0301 <lixushi@huawei.com>
1 parent 620555f commit 21e8dcf

2 files changed

Lines changed: 48 additions & 7 deletions

File tree

vllm_ascend/attention/sfa_v1.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ def __init__(
200200

201201
self.speculative_config = vllm_config.speculative_config
202202
self.decode_threshold = 1
203+
max_num_reqs = vllm_config.scheduler_config.max_num_seqs
204+
self.actual_seq_lengths_query = torch.zeros(max_num_reqs + 1, dtype=torch.int32, device=device)
205+
self.actual_seq_lengths_key = torch.empty_like(self.actual_seq_lengths_query)
206+
self.spec_actual_seq_lengths_query: list[torch.Tensor] | None = None
207+
self.spec_actual_seq_lengths_key: list[torch.Tensor] | None = None
203208
if self.speculative_config:
204209
spec_token_num = self.speculative_config.num_speculative_tokens
205210
self.decode_threshold += spec_token_num
@@ -208,15 +213,20 @@ def __init__(
208213
npu_fused_infer_attention_score TND layout's limit of 16, \
209214
got {self.decode_threshold}"
210215
)
216+
self.spec_actual_seq_lengths_query = [
217+
torch.zeros(max_num_reqs * (spec_token_num + 1) + 1, dtype=torch.int32, device=device)
218+
for _ in range(spec_token_num)
219+
]
220+
self.spec_actual_seq_lengths_key = [
221+
torch.zeros(max_num_reqs * (spec_token_num + 1) + 1, dtype=torch.int32, device=device)
222+
for _ in range(spec_token_num)
223+
]
224+
211225
self.reorder_batch_threshold = self.decode_threshold
212226
self.attn_mask_builder = AttentionMaskBuilder(self.device)
213227
self.rope_dim = self.model_config.hf_text_config.qk_rope_head_dim
214228
self.enable_dsa_cp = enable_dsa_cp()
215229

216-
max_num_reqs = vllm_config.scheduler_config.max_num_seqs
217-
self.actual_seq_lengths_query = torch.zeros(max_num_reqs + 1, dtype=torch.int32, device=device)
218-
self.actual_seq_lengths_key = torch.empty_like(self.actual_seq_lengths_query)
219-
220230
@staticmethod
221231
def determine_chunked_prefill_workspace_size(vllm_config: VllmConfig) -> int:
222232
return ascend_chunked_prefill_workspace_size(vllm_config)
@@ -240,6 +250,22 @@ def build(
240250
common_prefix_len: int,
241251
common_attn_metadata: AscendCommonAttentionMetadata,
242252
fast_build: bool = False,
253+
) -> AscendSFAMetadata:
254+
# common_prefix_len / fast_build are unused; kept for API compatibility.
255+
return self._build(common_attn_metadata, draft_step=None)
256+
257+
def build_for_drafting(
258+
self,
259+
draft_step: int,
260+
common_attn_metadata: AscendCommonAttentionMetadata,
261+
**kwargs,
262+
) -> AscendSFAMetadata:
263+
return self._build(common_attn_metadata, draft_step=draft_step)
264+
265+
def _build(
266+
self,
267+
common_attn_metadata: AscendCommonAttentionMetadata,
268+
draft_step: int | None = None,
243269
) -> AscendSFAMetadata:
244270
num_reqs = common_attn_metadata.num_reqs
245271
num_actual_tokens = common_attn_metadata.num_actual_tokens
@@ -265,7 +291,7 @@ def build(
265291
else:
266292
seq_lens_cpu = common_attn_metadata.seq_lens[:num_reqs].to("cpu")
267293

268-
cos, sin = get_cos_and_sin_mla(input_positions, True)
294+
cos, sin = get_cos_and_sin_mla(input_positions, use_cache=(draft_step is None))
269295

270296
dsa_cp_context = None
271297
if self.enable_dsa_cp:
@@ -307,8 +333,16 @@ def build(
307333
got {slot_mapping.shape[0]} and {num_tokens_pad}"
308334
)
309335

310-
actual_seq_lengths_query = self.actual_seq_lengths_query
311-
actual_seq_lengths_key = self.actual_seq_lengths_key
336+
if draft_step is not None:
337+
assert self.spec_actual_seq_lengths_query is not None
338+
assert self.spec_actual_seq_lengths_key is not None
339+
# Per-draft-step buffers: independent, graph-stable storage so
340+
# later draft steps don't clobber earlier ones' metadata.
341+
actual_seq_lengths_query = self.spec_actual_seq_lengths_query[draft_step - 1]
342+
actual_seq_lengths_key = self.spec_actual_seq_lengths_key[draft_step - 1]
343+
else:
344+
actual_seq_lengths_query = self.actual_seq_lengths_query
345+
actual_seq_lengths_key = self.actual_seq_lengths_key
312346

313347
num_segs = cum_query_lens.shape[0]
314348
last_token = 0

vllm_ascend/spec_decode/llm_base_proposer.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,6 +1652,13 @@ def attn_update_stack_num_spec_norm(
16521652
common_attn_metadata,
16531653
**extra_attn_metadata_args,
16541654
)
1655+
elif hasattr(attn_metadata_builder, "build_for_drafting"):
1656+
# e.g. SFA (dsa_cp): route draft steps through a draft-aware build so
1657+
# per-draft-step buffers are used and cross-step aliasing is avoided.
1658+
attn_metadata = attn_metadata_builder.build_for_drafting(
1659+
draft_step,
1660+
common_attn_metadata,
1661+
)
16551662
else:
16561663
attn_metadata = attn_metadata_builder.build(
16571664
0,

0 commit comments

Comments
 (0)