Skip to content

Commit 8f8c56f

Browse files
dsxstevenwu-yushanYzTongNiarOrla-see
authored
[BugFix][SpecDecode] Fix low MTP acceptance rate for SFA+DSA_CP (MTP>1) (vllm-project#10878)
### 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. Align Ascend attention builders' build_for_drafting with vllm's base convention (common_attn_metadata, draft_index) so all spec-decode draft paths go through one consistent entry point. - sfa_v1.py: split build() into build()/build_for_drafting()/_build(); pre-allocate per-draft-step spec_actual_seq_lengths_query/key buffers so each draft step owns independent metadata (no cross-step aliasing); recompute rotary (use_cache=False) while drafting. - llm_base_proposer.py: drop the else->build(0,...,model) branch; route all draft steps through build_for_drafting(common_attn_metadata, draft_index, **extra). Standard-attention drafters (eagle/eagle3/mtp on Qwen/Llama) now hit the base build_for_drafting -> build(0, m, fast_build=True), fixing the 'int' object has no attribute 'num_reqs' crash from the swapped-positional elif. Rename draft_step -> draft_index. - dsa_v1.py / dsa_cp.py: swap build_for_drafting arg order to (common_attn_metadata, draft_index, ...) and rename draft_step -> draft_index for consistency. - vLLM version: v0.23.0 - 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>
1 parent c093794 commit 8f8c56f

4 files changed

Lines changed: 94 additions & 65 deletions

File tree

vllm_ascend/attention/context_parallel/dsa_cp.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,8 @@ def build(
352352

353353
def build_for_drafting(
354354
self,
355-
draft_step: int,
356355
common_attn_metadata: AscendCommonAttentionMetadata,
356+
draft_index: int,
357357
fast_build: bool = False,
358358
**kwargs,
359359
) -> AscendDSAMetadata:
@@ -381,13 +381,13 @@ def build_for_drafting(
381381
slot_mapping = common_attn_metadata.slot_mapping[:num_input_tokens]
382382

383383
assert self.spec_slot_mapping is not None
384-
self.spec_slot_mapping[draft_step - 1][:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping(
384+
self.spec_slot_mapping[draft_index - 1][:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping(
385385
slot_mapping, self.block_size
386386
)
387387

388388
self.block_table = common_attn_metadata.block_table_tensor[:num_reqs]
389389
req_metadata = self.build_req_metadata_for_drafting(
390-
draft_step=draft_step,
390+
draft_index=draft_index,
391391
common_attn_metadata=common_attn_metadata,
392392
input_positions=input_positions,
393393
num_input_tokens=num_input_tokens,
@@ -413,7 +413,7 @@ def build_for_drafting(
413413

414414
def build_req_metadata_for_drafting(
415415
self,
416-
draft_step: int,
416+
draft_index: int,
417417
common_attn_metadata: AscendCommonAttentionMetadata,
418418
input_positions: torch.Tensor,
419419
num_input_tokens: int,
@@ -442,8 +442,8 @@ def build_req_metadata_for_drafting(
442442
query_start_loc=query_start_loc,
443443
seq_lens=self.seq_lens[:num_reqs],
444444
use_cache=False,
445-
local_query_start_loc=self.spec_local_query_start_loc[draft_step - 1],
446-
local_seq_lens=self.spec_local_seq_lens[draft_step - 1],
445+
local_query_start_loc=self.spec_local_query_start_loc[draft_index - 1],
446+
local_seq_lens=self.spec_local_seq_lens[draft_index - 1],
447447
)
448448
local_query_start_loc = local_query_start_loc.clone()
449449
local_seq_lens = local_seq_lens.clone()
@@ -463,7 +463,7 @@ def build_req_metadata_for_drafting(
463463
start_pos = self.seq_lens[:num_reqs] - seq_lens_q
464464

465465
assert self.spec_slot_mapping is not None
466-
slot_mapping = self.spec_slot_mapping[draft_step - 1][: self.num_actual_tokens]
466+
slot_mapping = self.spec_slot_mapping[draft_index - 1][: self.num_actual_tokens]
467467

468468
num_heads = self.model_config.hf_config.num_attention_heads
469469
metadata_op = DeviceOperator.get_dsa_sparse_attn_metadata_op()

vllm_ascend/attention/dsa_v1.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,8 +1133,8 @@ def _get_compressed_decode_token_start(decode_input_positions, compress_ratio):
11331133

11341134
def build_for_drafting(
11351135
self,
1136-
draft_step: int,
11371136
common_attn_metadata: AscendCommonAttentionMetadata,
1137+
draft_index: int,
11381138
fast_build: bool = False,
11391139
**kwargs,
11401140
) -> AscendDSADecodeMetadata:
@@ -1147,19 +1147,19 @@ def build_for_drafting(
11471147
if num_prefills:
11481148
cos, sin = get_cos_and_sin_dsa(input_positions)
11491149
else:
1150-
# disable use_cache, otherwise, draft_step>0 will override draft_step=0
1150+
# disable use_cache, otherwise, draft_index>0 will override draft_index=0
11511151
# take care of this, if full graph is needed then rope cache is inevitable
11521152
cos, sin = get_cos_and_sin_dsa(input_positions, use_cache=False)
11531153

11541154
slot_mapping = common_attn_metadata.slot_mapping[:num_input_tokens]
1155-
self.spec_slot_mapping[draft_step - 1][:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping( # type: ignore[index]
1155+
self.spec_slot_mapping[draft_index - 1][:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping( # type: ignore[index]
11561156
slot_mapping, self.block_size
11571157
)
11581158

11591159
prefill_metadata = None
11601160
if num_prefills > 0:
11611161
prefill_metadata = self.build_prefill_metadata_for_drafting(
1162-
draft_step=draft_step,
1162+
draft_index=draft_index,
11631163
common_attn_metadata=common_attn_metadata,
11641164
reqs_start=num_decodes,
11651165
tokens_start=num_decode_tokens,
@@ -1169,7 +1169,7 @@ def build_for_drafting(
11691169
decode_metadata = None
11701170
if num_decodes > 0:
11711171
decode_metadata = self.build_decode_metadata_for_drafting(
1172-
draft_step=draft_step,
1172+
draft_index=draft_index,
11731173
common_attn_metadata=common_attn_metadata,
11741174
num_decodes=num_decodes,
11751175
num_decode_tokens=num_decode_tokens,
@@ -1198,7 +1198,7 @@ def build_for_drafting(
11981198

11991199
def build_prefill_metadata_for_drafting(
12001200
self,
1201-
draft_step: int,
1201+
draft_index: int,
12021202
common_attn_metadata: AscendCommonAttentionMetadata,
12031203
**kwargs,
12041204
) -> AscendDSAPrefillMetadata:
@@ -1218,7 +1218,7 @@ def build_prefill_metadata_for_drafting(
12181218
prefill_input_positions = input_positions[tokens_start:]
12191219
cos, sin = get_cos_and_sin_dsa(prefill_input_positions)
12201220

1221-
prefill_slot_mapping = self.spec_slot_mapping[draft_step - 1][tokens_start:num_prefill_tokens] # type: ignore[index]
1221+
prefill_slot_mapping = self.spec_slot_mapping[draft_index - 1][tokens_start:num_prefill_tokens] # type: ignore[index]
12221222
block_table = common_attn_metadata.block_table_tensor[: common_attn_metadata.num_reqs]
12231223

12241224
metadata_op = DeviceOperator.get_dsa_sparse_attn_metadata_op()
@@ -1270,7 +1270,7 @@ def build_prefill_metadata_for_drafting(
12701270

12711271
def build_decode_metadata_for_drafting(
12721272
self,
1273-
draft_step: int,
1273+
draft_index: int,
12741274
common_attn_metadata: AscendCommonAttentionMetadata,
12751275
**kwargs,
12761276
) -> AscendDSADecodeMetadata:
@@ -1295,11 +1295,11 @@ def build_decode_metadata_for_drafting(
12951295
max_seqlen_kv = torch.max(_seq_lens_cpu[:num_decodes]).item()
12961296

12971297
input_positions = common_attn_metadata.positions[:num_decode_tokens_typed].long()
1298-
# disable use_cache, otherwise, draft_step>0 will override draft_step=0
1298+
# disable use_cache, otherwise, draft_index>0 will override draft_index=0
12991299
# take care of this, if full graph is needed then rope cache is inevitable
13001300
cos, sin = get_cos_and_sin_dsa(input_positions, use_cache=False)
13011301

1302-
slot_mapping = self.spec_slot_mapping[draft_step - 1][:num_decode_tokens_typed] # type: ignore[index]
1302+
slot_mapping = self.spec_slot_mapping[draft_index - 1][:num_decode_tokens_typed] # type: ignore[index]
13031303
block_table = common_attn_metadata.block_table_tensor
13041304

13051305
metadata_op = DeviceOperator.get_dsa_sparse_attn_metadata_op()

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_index=None)
256+
257+
def build_for_drafting(
258+
self,
259+
common_attn_metadata: AscendCommonAttentionMetadata,
260+
draft_index: int,
261+
**kwargs,
262+
) -> AscendSFAMetadata:
263+
return self._build(common_attn_metadata, draft_index=draft_index)
264+
265+
def _build(
266+
self,
267+
common_attn_metadata: AscendCommonAttentionMetadata,
268+
draft_index: 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_index 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_index 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_index - 1]
342+
actual_seq_lengths_key = self.spec_actual_seq_lengths_key[draft_index - 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

0 commit comments

Comments
 (0)