Skip to content

Commit 1930088

Browse files
authored
[Feature] Support dsv4 mtp graph (vllm-project#11062)
### What this PR does / why we need it? This PR aims to supoort dsv4 mtp graph. The main change is adding proper arguments in llm_base_proposer dummy_run. As for rope, we additionaly create buffers for different draft steps, solving low acceptance rate issue. Furthermore, _pad_query_start_loc_for_fia is modified so that target and draft model have their own variables and avoid race when using copy_to_gpu. ### Does this PR introduce _any_ user-facing change? N/A ### How was this patch tested? by ci - vLLM version: v0.23.0 - vLLM main: vllm-project/vllm@dc68bd8 --------- Signed-off-by: Zetong Li <slippersss@126.com>
1 parent 6929408 commit 1930088

7 files changed

Lines changed: 132 additions & 29 deletions

File tree

tests/e2e/pull_request/four_card/test_deepseek_v4.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_deepseek_v4_w4a8_tp4_basic_greedy():
5555
compilation_config={
5656
"cudagraph_mode": "FULL_DECODE_ONLY",
5757
},
58-
speculative_config={"num_speculative_tokens": 1, "method": "mtp", "enforce_eager": True},
58+
speculative_config={"num_speculative_tokens": 1, "method": "mtp"},
5959
) as vllm_model:
6060
outputs = vllm_model.generate_greedy(example_prompts, max_tokens)
6161
expected_token_ids = [

tests/ut/spec_decode/a2/test_eagle_proposer.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,9 @@ def test_propose(self, mock_get_model, graphmode, model_type, flag_prefill_decod
11601160
original_method = self.proposer.attn_update_stack_num_spec_norm
11611161
mock_bd = MagicMock()
11621162
mock_bd.num_tokens = 16
1163+
self.proposer.query_start_loc = MagicMock()
1164+
self.proposer.query_start_loc.gpu = torch.tensor([0, 4, 8, 12, 16], device=torch.device("cpu"), dtype=torch.int32)
1165+
self.proposer.query_start_loc.cpu = torch.tensor([0, 4, 8, 12, 16], device=torch.device("cpu"), dtype=torch.int32)
11631166
self.runner.cudagraph_dispatcher.dispatch.return_value = (CUDAGraphMode.FULL, mock_bd)
11641167
self.runner._pad_query_start_loc_for_fia.return_value = 4
11651168
self.runner.query_start_loc.gpu = torch.tensor([0, 4, 8, 12, 16], device=torch.device("cpu"), dtype=torch.int32)
@@ -1583,7 +1586,7 @@ def check_mock(self):
15831586
assert hasattr(RunnerCls, "_pad_query_start_loc_for_fia")
15841587
sig = inspect.signature(RunnerCls._pad_query_start_loc_for_fia)
15851588
sig_name = self.get_param_names(sig)
1586-
assert sig_name == ['self', 'num_tokens_padded', 'num_reqs_padded', 'num_reqs', 'cudagraph_runtime_mode', 'batch_desc_num_reqs']
1589+
assert sig_name == ['self', 'query_start_loc', 'num_tokens_padded', 'num_reqs_padded', 'num_reqs', 'cudagraph_runtime_mode', 'batch_desc_num_reqs']
15871590

15881591

15891592
import vllm_ascend.spec_decode.llm_base_proposer

vllm_ascend/_310p/model_runner_310p.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ def _build_attention_metadata(self, *args: Any, **kwargs: Any):
197197

198198
def _pad_query_start_loc_for_fia(
199199
self,
200+
query_start_loc: torch.Tensor,
200201
num_tokens_padded: int,
201202
num_reqs_padded: int,
202203
num_reqs: int,
@@ -213,19 +214,19 @@ def _pad_query_start_loc_for_fia(
213214
# Uniform-batch case: num_reqs must be no greater than num_reqs_padded
214215
assert num_reqs <= num_reqs_padded
215216

216-
last_loc = self.query_start_loc.np[num_reqs]
217-
self.query_start_loc.np[num_reqs + 1 : num_reqs_padded + 1] = (
217+
last_loc = query_start_loc.np[num_reqs]
218+
query_start_loc.np[num_reqs + 1 : num_reqs_padded + 1] = (
218219
self.arange_np[1 : num_reqs_padded + 1 - num_reqs] * uniform_decode_query_len + last_loc
219220
)
220221
else:
221222
# Mixed-batch case: num_reqs must equal num_reqs_padded
222223
assert num_reqs == num_reqs_padded
223224

224225
# Insert a dummy request instead of setting query_start_loc[num_reqs] = num_tokens_padded directly
225-
self.query_start_loc.np[num_reqs_padded + 1] = num_tokens_padded
226+
query_start_loc.np[num_reqs_padded + 1] = num_tokens_padded
226227
num_reqs_padded = num_reqs_padded + 1
227228

228-
self.query_start_loc.copy_to_gpu()
229+
query_start_loc.copy_to_gpu()
229230
return num_reqs_padded
230231

231232
def _build_attn_state(self, num_reqs, num_scheduled_tokens, num_valid_tokens):

vllm_ascend/attention/dsa_v1.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ def __init__(
397397
torch.zeros(self.slot_mapping_shape, dtype=torch.int32, device=self.device)
398398
for _ in range(spec_token_num)
399399
]
400+
self.spec_sas_metadata = [
401+
torch.zeros(1024, dtype=torch.int32, device=self.device) for _ in range(spec_token_num)
402+
]
400403
self.decode_threshold += spec_token_num
401404
assert self.decode_threshold <= 16, (
402405
f"decode_threshold exceeded \
@@ -1111,7 +1114,7 @@ def build_for_drafting(
11111114
else:
11121115
# disable use_cache, otherwise, draft_index>0 will override draft_index=0
11131116
# take care of this, if full graph is needed then rope cache is inevitable
1114-
cos, sin = get_cos_and_sin_dsa(input_positions, use_cache=False)
1117+
cos, sin = get_cos_and_sin_dsa(input_positions, use_cache=True, draft_index=draft_index)
11151118

11161119
slot_mapping = common_attn_metadata.slot_mapping[:num_input_tokens]
11171120
self.spec_slot_mapping[draft_index - 1][:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping( # type: ignore[index]
@@ -1258,7 +1261,7 @@ def build_decode_metadata_for_drafting(
12581261
input_positions = common_attn_metadata.positions[:num_decode_tokens_typed].long()
12591262
# disable use_cache, otherwise, draft_index>0 will override draft_index=0
12601263
# take care of this, if full graph is needed then rope cache is inevitable
1261-
cos, sin = get_cos_and_sin_dsa(input_positions, use_cache=False)
1264+
cos, sin = get_cos_and_sin_dsa(input_positions, use_cache=True, draft_index=draft_index)
12621265

12631266
slot_mapping = self.spec_slot_mapping[draft_index - 1][:num_decode_tokens_typed] # type: ignore[index]
12641267
block_table = common_attn_metadata.block_table_tensor
@@ -1289,6 +1292,8 @@ def build_decode_metadata_for_drafting(
12891292
has_ori_kv=True,
12901293
has_cmp_kv=False,
12911294
)
1295+
self.spec_sas_metadata[draft_index - 1][:1024].copy_(decode_sas_metadata[:1024])
1296+
decode_sas_metadata = self.spec_sas_metadata[draft_index - 1]
12921297

12931298
decode_metadata = AscendDSADecodeMetadata(
12941299
input_positions=None,
@@ -1452,6 +1457,19 @@ def __init__(
14521457
False,
14531458
)
14541459

1460+
@staticmethod
1461+
def update_graph_params(
1462+
update_stream,
1463+
forward_context,
1464+
num_tokens,
1465+
vllm_config=None,
1466+
speculative_config=None,
1467+
num_dcp_pcp_tokens=None,
1468+
draft_attn_metadatas=None,
1469+
):
1470+
# dsa does not need to update graph params
1471+
pass
1472+
14551473
def _get_indexcache_topk_indices(self, num_tokens: int, offset: int = 0) -> torch.Tensor:
14561474
if self.topk_indices_buffer is None:
14571475
raise RuntimeError("IndexCache requires topk_indices_buffer when skip_topk is enabled.")

vllm_ascend/ops/rope_dsv4.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class RopeGlobalState:
1212
def __init__(self):
1313
self.full_rope_cache: dict[str, tuple[torch.Tensor, torch.Tensor]] = {}
1414
self.runtime_buffer: dict[str, dict[str, tuple[torch.Tensor, torch.Tensor]]] = {}
15+
self.spec_runtime_buffer: dict[str, dict[str, tuple[torch.Tensor, torch.Tensor]]] = {}
1516
self.layer_info: dict[str, tuple[str, list[str]]] = {}
1617
self.registry_summary: dict[str, set] = {}
1718

@@ -58,7 +59,11 @@ def __getitem__(self, index):
5859
return layer_result
5960

6061

61-
def get_cos_and_sin_dsa(positions: torch.Tensor | dict[str, torch.Tensor], use_cache: bool = False):
62+
def get_cos_and_sin_dsa(
63+
positions: torch.Tensor | dict[str, torch.Tensor],
64+
use_cache: bool = False,
65+
draft_index: int | None = None,
66+
):
6267
if isinstance(positions, torch.Tensor):
6368
pos_map = {"default": positions}
6469
else:
@@ -81,18 +86,30 @@ def get_cos_and_sin_dsa(positions: torch.Tensor | dict[str, torch.Tensor], use_c
8186
curr_sin = full_rope_sin[pos_tensor]
8287

8388
if use_cache:
84-
group_buffers = _ROPE_STATE.runtime_buffer.get(config_key, {}).get(group_name)
89+
group_buffers = (
90+
_ROPE_STATE.runtime_buffer.get(config_key, {}).get(group_name)
91+
if draft_index is None
92+
else _ROPE_STATE.spec_runtime_buffer.get(config_key, {}).get(group_name)
93+
)
8594

8695
if group_buffers is None:
8796
continue
8897

8998
buf_cos, buf_sin = group_buffers
9099
num_tokens = pos_tensor.size(0)
91100

92-
buf_cos[:num_tokens].copy_(curr_cos)
93-
buf_sin[:num_tokens].copy_(curr_sin)
101+
if draft_index is None:
102+
buf_cos[:num_tokens].copy_(curr_cos)
103+
buf_sin[:num_tokens].copy_(curr_sin)
94104

95-
batch_result[config_key][group_name] = (buf_cos[:num_tokens], buf_sin[:num_tokens])
105+
batch_result[config_key][group_name] = (buf_cos[:num_tokens], buf_sin[:num_tokens])
106+
else:
107+
buf_cos[draft_index - 1][:num_tokens].copy_(curr_cos)
108+
buf_sin[draft_index - 1][:num_tokens].copy_(curr_sin)
109+
batch_result[config_key][group_name] = (
110+
buf_cos[draft_index - 1][:num_tokens],
111+
buf_sin[draft_index - 1][:num_tokens],
112+
)
96113
else:
97114
batch_result[config_key][group_name] = (curr_cos, curr_sin)
98115

@@ -171,8 +188,17 @@ def __init__(
171188

172189
_ROPE_STATE.full_rope_cache[config_key] = (cos.unsqueeze(1).unsqueeze(1), sin.unsqueeze(1).unsqueeze(1))
173190

191+
use_eagle = (
192+
vllm_config is not None
193+
and vllm_config.speculative_config is not None
194+
and vllm_config.speculative_config.use_eagle()
195+
)
196+
num_speculative_tokens = vllm_config.speculative_config.num_speculative_tokens if use_eagle else None
197+
174198
if config_key not in _ROPE_STATE.runtime_buffer:
175199
_ROPE_STATE.runtime_buffer[config_key] = {}
200+
if num_speculative_tokens is not None:
201+
_ROPE_STATE.spec_runtime_buffer[config_key] = {}
176202

177203
target_device = current_platform.device_type
178204
max_batch_size = vllm_config.scheduler_config.max_num_batched_tokens
@@ -181,6 +207,16 @@ def __init__(
181207
buf_cos = torch.ones(max_batch_size, 1, 1, rotary_dim, dtype=torch.float32, device=target_device)
182208
buf_sin = torch.zeros(max_batch_size, 1, 1, rotary_dim, dtype=torch.float32, device=target_device)
183209
_ROPE_STATE.runtime_buffer[config_key][grp] = (buf_cos, buf_sin)
210+
if num_speculative_tokens is not None:
211+
buf_cos = [
212+
torch.ones(max_batch_size, 1, 1, rotary_dim, dtype=torch.float32, device=target_device)
213+
for _ in range(num_speculative_tokens)
214+
]
215+
buf_sin = [
216+
torch.zeros(max_batch_size, 1, 1, rotary_dim, dtype=torch.float32, device=target_device)
217+
for _ in range(num_speculative_tokens)
218+
]
219+
_ROPE_STATE.spec_runtime_buffer[config_key][grp] = (buf_cos, buf_sin)
184220

185221
@staticmethod
186222
def precompute_freqs_cis(dim, seqlen, original_seq_len, base, factor, beta_fast, beta_slow):

vllm_ascend/spec_decode/llm_base_proposer.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ def dummy_run(
551551
query_start_loc=self.query_start_loc.gpu[: num_reqs + 1],
552552
query_start_loc_cpu=self.query_start_loc.cpu[: num_reqs + 1],
553553
seq_lens_cpu=self.runner.optimistic_seq_lens_cpu,
554+
_seq_lens_cpu=self.runner.optimistic_seq_lens_cpu,
554555
seq_lens_cpu_upper_bound=self.runner.optimistic_seq_lens_cpu,
555556
seq_lens=self.runner.seq_lens[:num_reqs],
556557
num_reqs=num_reqs,
@@ -559,11 +560,14 @@ def dummy_run(
559560
max_query_len=self.num_speculative_tokens + 1,
560561
num_computed_tokens_cpu=num_computed_tokens_cpu,
561562
actual_seq_lengths_q=self.runner.actual_seq_lengths_q,
562-
block_table_tensor=self.runner.input_batch.block_table[0].get_device_tensor()[:num_reqs],
563+
block_table_tensor=self.runner.input_batch.block_table[self.kv_cache_gid].get_device_tensor()[
564+
:num_reqs
565+
],
563566
# This is used to hold a position.
564-
slot_mapping=self.runner.input_batch.block_table[0].slot_mapping.gpu,
565-
slot_mapping_cpu=self.runner.input_batch.block_table[0].slot_mapping.cpu,
567+
slot_mapping=self.runner.input_batch.block_table[self.kv_cache_gid].slot_mapping.gpu,
568+
slot_mapping_cpu=self.runner.input_batch.block_table[self.kv_cache_gid].slot_mapping.cpu,
566569
positions=self.runner.positions,
570+
positions_cpu=self.runner._dsa_positions_cpu_buf if self.use_compress else None,
567571
attn_state=self.runner.attn_state,
568572
decode_token_per_req=self.runner.decode_token_per_req,
569573
is_prefilling=torch.zeros(num_reqs, dtype=torch.bool),
@@ -575,20 +579,46 @@ def dummy_run(
575579

576580
assert len(self.draft_attn_groups) > 0
577581
builder = self.draft_attn_groups[0].get_metadata_builder()
582+
kv_cache_spec = self.draft_attn_groups[0].kv_cache_spec
578583
# update the tensor's address for each step.
579584
for draft_index in range(self.num_speculative_tokens):
580585
common_attn_metadata = self.shallow_copy_metadata(common_attn_metadata)
586+
extra_attn_metadata_args: dict = {}
587+
if self.use_compress:
588+
extra_attn_metadata_args = dict(
589+
prefill_ratio_to_sas_metadata=dict(),
590+
decode_ratio_to_sas_metadata=dict(),
591+
common_ratio_to_sas_metadata=dict(),
592+
block_size=kv_cache_spec.block_size,
593+
)
581594
# Set the real slot_mapping.
595+
slot_mapping_lens = common_attn_metadata.slot_mapping.shape[0]
596+
self.slot_mapping_group[draft_index][:slot_mapping_lens].copy_(common_attn_metadata.slot_mapping)
597+
self.slot_mapping_group[draft_index][slot_mapping_lens:].fill_(PADDING_SLOT_ID)
582598
common_attn_metadata.slot_mapping = self.slot_mapping_group[draft_index]
599+
self.seq_lens_group[draft_index][:num_reqs].copy_(common_attn_metadata.seq_lens)
600+
self.seq_lens_group[draft_index][num_reqs:].fill_(0)
583601
common_attn_metadata.seq_lens = self.seq_lens_group[draft_index][:num_reqs]
602+
self.query_start_loc_group[draft_index][: num_reqs + 1].copy_(common_attn_metadata.query_start_loc)
603+
self.query_start_loc_group[draft_index][num_reqs + 1 :].fill_(0)
584604
common_attn_metadata.query_start_loc = self.query_start_loc_group[draft_index][: num_reqs + 1]
585605
if self.pcp_size * self.dcp_size > 1 and draft_index > 0:
586606
assert self.block_table_tensor_clone is not None, "block_table_tensor_clone is not init"
587607
common_attn_metadata.block_table_tensor = self.block_table_tensor_clone[:num_reqs]
588-
attn_metadata_eagle = builder.build_for_graph_capture(
589-
common_attn_metadata,
590-
AscendAttentionState.SpecDecoding if self.method == "mtp" else AscendAttentionState.ChunkedPrefill,
591-
)
608+
if not self.use_compress or draft_index == 0:
609+
attn_metadata_eagle = builder.build_for_graph_capture(
610+
common_attn_metadata,
611+
AscendAttentionState.SpecDecoding
612+
if self.method == "mtp"
613+
else AscendAttentionState.ChunkedPrefill,
614+
**extra_attn_metadata_args,
615+
)
616+
else:
617+
attn_metadata_eagle = builder.build_for_drafting(
618+
common_attn_metadata,
619+
draft_index,
620+
**extra_attn_metadata_args,
621+
)
592622
per_layer_attn_metadata = dict()
593623
for layer_name in self.attn_layer_names:
594624
per_layer_attn_metadata[layer_name] = attn_metadata_eagle
@@ -744,16 +774,20 @@ def _propose(
744774
# is run in eager mode currently, which means `_pad_query_start_loc_for_fia` is not called,
745775
# while draft model is run in graph model, which means we should pad the `query_start_loc`.
746776
# Need to be fixed in the future.
777+
num_reqs = common_attn_metadata.query_start_loc.shape[0]
778+
self.query_start_loc.gpu[:num_reqs].copy_(common_attn_metadata.query_start_loc)
779+
self.query_start_loc.cpu[:num_reqs].copy_(common_attn_metadata.query_start_loc_cpu)
747780
num_reqs_padded = self.runner._pad_query_start_loc_for_fia(
781+
self.query_start_loc,
748782
num_input_tokens,
749783
batch_descriptor.num_reqs if batch_descriptor.num_reqs is not None else common_attn_metadata.num_reqs,
750784
common_attn_metadata.num_reqs,
751785
aclgraph_runtime_mode,
752786
batch_descriptor.num_reqs,
753787
)
754788
common_attn_metadata.num_reqs = num_reqs_padded
755-
common_attn_metadata.query_start_loc = self.runner.query_start_loc.gpu[: num_reqs_padded + 1]
756-
common_attn_metadata.query_start_loc_cpu = self.runner.query_start_loc.cpu[: num_reqs_padded + 1]
789+
common_attn_metadata.query_start_loc = self.query_start_loc.gpu[: num_reqs_padded + 1]
790+
common_attn_metadata.query_start_loc_cpu = self.query_start_loc.cpu[: num_reqs_padded + 1]
757791
slicing_length = (
758792
num_reqs_padded * self.decode_threshold if self.pcp_size * self.dcp_size > 1 else num_reqs_padded
759793
)

vllm_ascend/worker/model_runner_v1.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ def _update_states(self, scheduler_output: "SchedulerOutput") -> Callable | None
734734

735735
def _pad_query_start_loc_for_fia(
736736
self,
737+
query_start_loc: torch.Tensor,
737738
num_tokens_padded: int,
738739
num_reqs_padded: int,
739740
num_reqs: int,
@@ -762,21 +763,21 @@ def _pad_query_start_loc_for_fia(
762763
# Uniform-batch case: num_reqs must be no greater than num_reqs_padded
763764
assert num_reqs <= num_reqs_padded
764765

765-
last_loc = self.query_start_loc.np[num_reqs]
766-
self.query_start_loc.np[num_reqs + 1 : num_reqs_padded + 1] = (
766+
last_loc = query_start_loc.np[num_reqs]
767+
query_start_loc.np[num_reqs + 1 : num_reqs_padded + 1] = (
767768
self.arange_np[1 : num_reqs_padded + 1 - num_reqs] * self.uniform_decode_query_len + last_loc
768769
)
769770
else:
770771
# Mixed-batch case: num_reqs must equal num_reqs_padded
771772
assert num_reqs == num_reqs_padded
772773

773774
# Do not insert if the last value already equals the num_tokens
774-
if self.query_start_loc.np[num_reqs_padded] < num_tokens_padded:
775+
if query_start_loc.np[num_reqs_padded] < num_tokens_padded:
775776
# Insert a dummy request instead of change the last value directly
776-
self.query_start_loc.np[num_reqs_padded + 1] = num_tokens_padded
777+
query_start_loc.np[num_reqs_padded + 1] = num_tokens_padded
777778
num_reqs_padded = num_reqs_padded + 1
778779

779-
self.query_start_loc.copy_to_gpu()
780+
query_start_loc.copy_to_gpu()
780781

781782
return num_reqs_padded
782783

@@ -2208,7 +2209,12 @@ def execute_model(
22082209
# Another possible condition is num_tokens_padded != num_tokens_unpadded
22092210
# but this scope is way too big and the consequences are unpredictable
22102211
num_reqs_padded = self._pad_query_start_loc_for_fia(
2211-
num_tokens_padded, num_reqs_padded, num_reqs, cudagraph_mode, batch_desc.num_reqs
2212+
self.query_start_loc,
2213+
num_tokens_padded,
2214+
num_reqs_padded,
2215+
num_reqs,
2216+
cudagraph_mode,
2217+
batch_desc.num_reqs,
22122218
)
22132219

22142220
(attn_metadata, spec_decode_common_attn_metadata) = self._build_attention_metadata(
@@ -3486,7 +3492,12 @@ def _dummy_run(
34863492

34873493
if not profile_cpp:
34883494
num_reqs_padded = self._pad_query_start_loc_for_fia(
3489-
num_tokens_padded, num_reqs_padded, num_reqs, cudagraph_runtime_mode, batch_desc.num_reqs
3495+
self.query_start_loc,
3496+
num_tokens_padded,
3497+
num_reqs_padded,
3498+
num_reqs,
3499+
cudagraph_runtime_mode,
3500+
batch_desc.num_reqs,
34903501
)
34913502

34923503
# Dummy graph runs do not go through _prepare_inputs(), but GDN/Mamba

0 commit comments

Comments
 (0)