@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / c9b38be8aa
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 05:30 UTC

[Spec Decode] Make `propose_draft_token_ids` non-blocking for lower TTFT (#23041)

Signed-off-by: Woosuk Kwon <woosuk.kwon@berkeley.edu>

Woosuk Kwon committed Aug 18, 2025 at 17:20 UTC c9b38be8aafb02b69ccb704b33d2bb4329fbb0e6
13 files changed +100 -64
tests/v1/core/test_async_scheduler.py
-1
@@ -22,7 +22,6 @@ def _make_model_runner_output(
22 for i, req_id in enumerate(req_ids)
23 },
24 sampled_token_ids=[[i] for i in range(len(req_ids))],
25 - spec_token_ids=None,
25 logprobs=None,
26 prompt_logprobs_dict={},
27 pooler_output=[],
tests/v1/core/test_scheduler.py
+3 -23
@@ -14,7 +14,7 @@ from vllm.v1.core.sched.output import CachedRequestData, SchedulerOutput
14 from vllm.v1.core.sched.scheduler import Scheduler
15 from vllm.v1.kv_cache_interface import (FullAttentionSpec, KVCacheConfig,
16 KVCacheGroupSpec)
17 -from vllm.v1.outputs import ModelRunnerOutput
17 +from vllm.v1.outputs import DraftTokenIds, ModelRunnerOutput
18 from vllm.v1.request import Request, RequestStatus
19 from vllm.v1.structured_output import StructuredOutputManager
20 from vllm.v1.structured_output.request import StructuredOutputRequest
@@ -158,7 +158,6 @@ def test_schedule_partial_requests():
158 # Only the first request has a sampled token id because
159 # the rest requests are still being prefilled.
160 sampled_token_ids=[[0], [], []],
161 - spec_token_ids=None,
161 logprobs=None,
162 prompt_logprobs_dict={},
163 pooler_output=[],
@@ -209,7 +208,6 @@ def test_no_mm_input_chunking():
208 req_ids=[request.request_id for request in requests],
209 req_id_to_index=req_to_index,
210 sampled_token_ids=[[] for _ in range(len(requests))],
212 - spec_token_ids=None,
211 logprobs=None,
212 prompt_logprobs_dict={},
213 pooler_output=[],
@@ -273,7 +271,6 @@ def test_schedule_concurrent_partial_requests(enable_prefix_caching: bool):
271 req_ids=[request.request_id for request in requests],
272 req_id_to_index=req_to_index,
273 sampled_token_ids=[[] for _ in range(len(requests))],
276 - spec_token_ids=None,
274 logprobs=None,
275 prompt_logprobs_dict={},
276 pooler_output=[],
@@ -298,7 +295,6 @@ def test_schedule_concurrent_partial_requests(enable_prefix_caching: bool):
295 req_ids=[request.request_id for request in requests],
296 req_id_to_index=req_to_index,
297 sampled_token_ids=[[0], [0]] + [[] for _ in range(len(requests) - 2)],
301 - spec_token_ids=None,
298 logprobs=None,
299 prompt_logprobs_dict={},
300 pooler_output=[],
@@ -355,7 +351,6 @@ def test_stop_via_update_from_output():
351 sampled_token_ids=[[EOS_TOKEN_ID],
352 [10,
353 11]], # First request hits EOS, second continues
358 - spec_token_ids=None,
354 logprobs=None,
355 prompt_logprobs_dict={},
356 pooler_output=[])
@@ -409,7 +404,6 @@ def test_stop_via_update_from_output():
404 },
405 sampled_token_ids=[[10, 42, 12],
406 [13, 14]], # First request hits stop token
412 - spec_token_ids=None,
407 logprobs=None,
408 prompt_logprobs_dict={},
409 pooler_output=[])
@@ -462,7 +456,6 @@ def test_stop_via_update_from_output():
456 },
457 sampled_token_ids=[[10, 11, 12],
458 [13]], # First request exceeds max_tokens
465 - spec_token_ids=None,
459 logprobs=None,
460 prompt_logprobs_dict={},
461 pooler_output=[])
@@ -505,7 +498,6 @@ def test_stop_via_update_from_output():
498 req_ids=[requests[0].request_id],
499 req_id_to_index={requests[0].request_id: 0},
500 sampled_token_ids=[[EOS_TOKEN_ID, 10, 11]],
508 - spec_token_ids=None,
501 logprobs=None,
502 prompt_logprobs_dict={},
503 pooler_output=[])
@@ -554,7 +546,6 @@ def test_schedule_concurrent_batches(enable_prefix_caching: Optional[bool],
546 req_ids=[requests[0].request_id],
547 req_id_to_index={requests[0].request_id: 0},
548 sampled_token_ids=[[0]],
557 - spec_token_ids=None,
549 logprobs=None,
550 prompt_logprobs_dict={},
551 pooler_output=[],
@@ -572,7 +563,6 @@ def test_schedule_concurrent_batches(enable_prefix_caching: Optional[bool],
563 req_ids=[requests[1].request_id],
564 req_id_to_index={requests[1].request_id: 0},
565 sampled_token_ids=[[0]],
575 - spec_token_ids=None,
566 logprobs=None,
567 prompt_logprobs_dict={},
568 pooler_output=[],
@@ -608,7 +598,6 @@ def test_preempt_during_execution():
598 req_ids=[requests[0].request_id],
599 req_id_to_index={requests[0].request_id: 0},
600 sampled_token_ids=[[0]],
611 - spec_token_ids=None,
601 logprobs=None,
602 prompt_logprobs_dict={},
603 pooler_output=[],
@@ -626,7 +615,6 @@ def test_preempt_during_execution():
615 req_ids=[requests[1].request_id],
616 req_id_to_index={requests[1].request_id: 0},
617 sampled_token_ids=[[42]],
629 - spec_token_ids=None,
618 logprobs=None,
619 prompt_logprobs_dict={},
620 pooler_output=[],
@@ -682,13 +670,14 @@ def test_schedule_spec_decoding_stats(spec_tokens, output_tokens, expected):
670 req_ids=req_ids,
671 req_id_to_index=req_to_index,
672 sampled_token_ids=[[0] for _ in range(len(requests))],
685 - spec_token_ids=spec_tokens,
673 logprobs=None,
674 prompt_logprobs_dict={},
675 pooler_output=[],
676 )
677 engine_core_outputs = scheduler.update_from_output(output,
678 model_runner_output)
679 + draft_token_ids = DraftTokenIds(req_ids, spec_tokens)
680 + scheduler.update_draft_token_ids(draft_token_ids)
681
682 for i in range(len(requests)):
683 running_req = scheduler.running[i]
@@ -722,7 +711,6 @@ def test_schedule_spec_decoding_stats(spec_tokens, output_tokens, expected):
711 req_ids=req_ids,
712 req_id_to_index=req_to_index,
713 sampled_token_ids=output_tokens,
725 - spec_token_ids=None,
714 logprobs=None,
715 prompt_logprobs_dict={},
716 pooler_output=[],
@@ -851,7 +839,6 @@ def test_kv_connector_basic():
839 req_ids=req_ids,
840 req_id_to_index=req_to_index,
841 sampled_token_ids=[[1000]] * len(req_ids),
854 - spec_token_ids=None,
842 logprobs=None,
843 prompt_logprobs_dict={},
844 pooler_output=[],
@@ -898,7 +885,6 @@ def test_kv_connector_basic():
885 req_ids=req_ids,
886 req_id_to_index=req_to_index,
887 sampled_token_ids=[[1000]] * len(req_ids),
901 - spec_token_ids=None,
888 logprobs=None,
889 prompt_logprobs_dict={},
890 pooler_output=[],
@@ -966,7 +952,6 @@ def test_kv_connector_unable_to_allocate():
952 req_ids=req_ids,
953 req_id_to_index=req_to_index,
954 sampled_token_ids=[[1000]] * len(req_ids),
969 - spec_token_ids=None,
955 logprobs=None,
956 prompt_logprobs_dict={},
957 pooler_output=[],
@@ -1048,7 +1033,6 @@ def test_kv_connector_handles_preemption():
1033 req_ids=req_ids,
1034 req_id_to_index=req_to_index,
1035 sampled_token_ids=[[1000]] * len(req_ids),
1051 - spec_token_ids=None,
1036 logprobs=None,
1037 prompt_logprobs_dict={},
1038 pooler_output=[],
@@ -1142,7 +1126,6 @@ def make_output(scheduler: Scheduler):
1126 for i, req in enumerate(scheduler.running)
1127 },
1128 sampled_token_ids=[[1000]] * len(scheduler.running),
1145 - spec_token_ids=None,
1129 logprobs=None,
1130 prompt_logprobs_dict={},
1131 pooler_output=[],
@@ -1468,7 +1451,6 @@ def test_priority_scheduling_preemption():
1451 for i, req in enumerate(low_priority_requests)
1452 },
1453 sampled_token_ids=[[100] for _ in low_priority_requests],
1471 - spec_token_ids=None,
1454 logprobs=None,
1455 prompt_logprobs_dict={},
1456 pooler_output=[],
@@ -1541,7 +1523,6 @@ def test_priority_scheduling_no_preemption_when_space_available():
1523 for i, req in enumerate(low_priority_requests)
1524 },
1525 sampled_token_ids=[[100] for _ in low_priority_requests],
1544 - spec_token_ids=None,
1526 logprobs=None,
1527 prompt_logprobs_dict={},
1528 pooler_output=[],
@@ -1783,7 +1764,6 @@ def test_priority_scheduling_heap_property():
1764 req_ids=[req.req_id],
1765 req_id_to_index={req.req_id: 0},
1766 sampled_token_ids=[[100]],
1786 - spec_token_ids=None,
1767 logprobs=None,
1768 prompt_logprobs_dict={},
1769 pooler_output=[],
tests/v1/kv_connector/unit/utils.py
-1
@@ -200,7 +200,6 @@ def create_model_runner_output(
200 req_ids=req_ids,
201 req_id_to_index=req_id_to_index,
202 sampled_token_ids=sampled_token_ids,
203 - spec_token_ids=None,
203 logprobs=None,
204 prompt_logprobs_dict={},
205 pooler_output=None,
vllm/v1/core/sched/interface.py
+9 -1
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
9 from vllm.v1.core.sched.output import SchedulerOutput
10 from vllm.v1.engine import EngineCoreOutputs
11 from vllm.v1.metrics.stats import SchedulerStats
12 - from vllm.v1.outputs import ModelRunnerOutput
12 + from vllm.v1.outputs import DraftTokenIds, ModelRunnerOutput
13 from vllm.v1.request import Request, RequestStatus
14
15
@@ -61,6 +61,14 @@ class SchedulerInterface(ABC):
61 """
62 raise NotImplementedError
63
64 + @abstractmethod
65 + def update_draft_token_ids(
66 + self,
67 + draft_token_ids: "DraftTokenIds",
68 + ) -> None:
69 + """Update the draft token ids for the scheduled requests."""
70 + raise NotImplementedError
71 +
72 @abstractmethod
73 def add_request(self, request: "Request") -> None:
74 """Add a new request to the scheduler's internal queue.
vllm/v1/core/sched/scheduler.py
+25 -14
@@ -30,7 +30,7 @@ from vllm.v1.engine import (EngineCoreEventType, EngineCoreOutput,
30 EngineCoreOutputs)
31 from vllm.v1.kv_cache_interface import KVCacheConfig
32 from vllm.v1.metrics.stats import SchedulerStats
33 -from vllm.v1.outputs import KVConnectorOutput, ModelRunnerOutput
33 +from vllm.v1.outputs import DraftTokenIds, KVConnectorOutput, ModelRunnerOutput
34 from vllm.v1.request import Request, RequestStatus
35 from vllm.v1.spec_decode.metrics import SpecDecodingStats
36 from vllm.v1.structured_output import StructuredOutputManager
@@ -141,7 +141,6 @@ class Scheduler(SchedulerInterface):
141 cache_size=encoder_cache_size)
142
143 speculative_config = vllm_config.speculative_config
144 -
144 self.use_eagle = False
145 self.num_spec_tokens = self.num_lookahead_tokens = 0
146 if speculative_config:
@@ -760,7 +759,6 @@ class Scheduler(SchedulerInterface):
759 model_runner_output: ModelRunnerOutput,
760 ) -> dict[int, EngineCoreOutputs]:
761 sampled_token_ids = model_runner_output.sampled_token_ids
763 - spec_token_ids = model_runner_output.spec_token_ids
762 logprobs = model_runner_output.logprobs
763 prompt_logprobs_dict = model_runner_output.prompt_logprobs_dict
764 num_scheduled_tokens = scheduler_output.num_scheduled_tokens
@@ -845,20 +843,9 @@ class Scheduler(SchedulerInterface):
843 request.structured_output_request.grammar.accept_tokens( # type: ignore[union-attr]
844 req_id, new_token_ids)
845
848 - # spec_token_ids comes from the model runner output
846 if num_nans_in_logits is not None and req_id in num_nans_in_logits:
847 request.num_nans_in_logits = num_nans_in_logits[req_id]
848
852 - # Add newly generated spec token ids to the request.
853 - if spec_token_ids is not None:
854 - if self.structured_output_manager.should_advance(request):
855 - metadata = request.structured_output_request
856 - # Needs to happen after new_token_ids are accepted.
857 - request.spec_token_ids = metadata.grammar.validate_tokens( # type: ignore[union-attr]
858 - spec_token_ids[req_index])
859 - else:
860 - request.spec_token_ids = spec_token_ids[req_index]
861 -
849 # Get prompt logprobs for this request.
850 prompt_logprobs_tensors = prompt_logprobs_dict.get(req_id)
851 if new_token_ids or pooler_output is not None \
@@ -963,6 +950,30 @@ class Scheduler(SchedulerInterface):
950 self.encoder_cache_manager.free_encoder_input(
951 request, input_id)
952
953 + def update_draft_token_ids(
954 + self,
955 + draft_token_ids: DraftTokenIds,
956 + ) -> None:
957 + for req_id, spec_token_ids in zip(
958 + draft_token_ids.req_ids,
959 + draft_token_ids.draft_token_ids,
960 + ):
961 + request = self.requests.get(req_id)
962 + if request is None or request.is_finished():
963 + # The request may have been finished. Skip.
964 + continue
965 +
966 + # Add newly generated spec token ids to the request.
967 + if not spec_token_ids:
968 + # NOTE(woosuk): request.spec_token_ids should be updated.
969 + request.spec_token_ids.clear()
970 + elif self.structured_output_manager.should_advance(request):
971 + metadata = request.structured_output_request
972 + request.spec_token_ids = metadata.grammar.validate_tokens( # type: ignore[union-attr]
973 + spec_token_ids)
974 + else:
975 + request.spec_token_ids = spec_token_ids
976 +
977 def get_request_counts(self) -> tuple[int, int]:
978 """Returns (num_running_reqs, num_waiting_reqs)."""
979 return len(self.running), len(self.waiting)
vllm/v1/engine/core.py
+10
@@ -126,6 +126,7 @@ class EngineCore:
126 > 1,
127 log_stats=self.log_stats,
128 )
129 + self.use_spec_decode = vllm_config.speculative_config is not None
130
131 self.mm_input_cache_server = MultiModalInputCacheServer(
132 vllm_config.model_config, MULTIMODAL_REGISTRY)
@@ -294,6 +295,13 @@ class EngineCore:
295 return (engine_core_outputs,
296 scheduler_output.total_num_scheduled_tokens > 0)
297
298 + def post_step(self, model_executed: bool) -> None:
299 + if self.use_spec_decode and model_executed:
300 + # Take the draft token ids.
301 + draft_token_ids = self.model_executor.take_draft_token_ids()
302 + if draft_token_ids is not None:
303 + self.scheduler.update_draft_token_ids(draft_token_ids)
304 +
305 def step_with_batch_queue(
306 self) -> tuple[Optional[dict[int, EngineCoreOutputs]], bool]:
307 """Schedule and execute batches with the batch queue.
@@ -746,6 +754,8 @@ class EngineCoreProc(EngineCore):
754 # Put EngineCoreOutputs into the output queue.
755 for output in (outputs.items() if outputs else ()):
756 self.output_queue.put_nowait(output)
757 + # Post-step hook.
758 + self.post_step(model_executed)
759
760 return model_executed
761
vllm/v1/executor/abstract.py
+6 -2
@@ -2,7 +2,7 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from concurrent.futures import Future
5 -from typing import Callable, Union
5 +from typing import Callable, Optional, Union
6
7 import torch
8 import torch.distributed as dist
@@ -14,7 +14,7 @@ from vllm.executor.uniproc_executor import ( # noqa
14 from vllm.executor.uniproc_executor import ( # noqa
15 UniProcExecutor as UniProcExecutorV0)
16 from vllm.v1.kv_cache_interface import KVCacheConfig, KVCacheSpec
17 -from vllm.v1.outputs import ModelRunnerOutput
17 +from vllm.v1.outputs import DraftTokenIds, ModelRunnerOutput
18
19 FailureCallback = Callable[[], None]
20
@@ -88,6 +88,10 @@ class Executor(ExecutorBase):
88 args=(scheduler_output, ))
89 return output[0]
90
91 + def take_draft_token_ids(self) -> Optional[DraftTokenIds]:
92 + output = self.collective_rpc("take_draft_token_ids")
93 + return output[0]
94 +
95 @property
96 def max_concurrent_batches(self) -> int:
97 return 1
vllm/v1/executor/multiproc_executor.py
+7 -1
@@ -33,7 +33,7 @@ from vllm.utils import (decorate_logs, get_distributed_init_method,
33 get_loopback_ip, get_mp_context, get_open_port,
34 set_process_title)
35 from vllm.v1.executor.abstract import Executor, FailureCallback
36 -from vllm.v1.outputs import ModelRunnerOutput
36 +from vllm.v1.outputs import DraftTokenIds, ModelRunnerOutput
37 from vllm.worker.worker_base import WorkerWrapperBase
38
39 logger = init_logger(__name__)
@@ -191,6 +191,12 @@ class MultiprocExecutor(Executor):
191 outputs, self.output_rank)
192 return self.kv_output_aggregator.aggregate(outputs, self.output_rank)
193
194 + def take_draft_token_ids(self) -> Optional[DraftTokenIds]:
195 + # OPTIMIZATION: Get output only from a single worker (output_rank)
196 + outputs = self.collective_rpc("take_draft_token_ids",
197 + unique_reply_rank=self.output_rank)
198 + return outputs[0]
199 +
200 def collective_rpc(self,
201 method: Union[str, Callable],
202 timeout: Optional[float] = None,
vllm/v1/outputs.py
+9 -4
@@ -94,9 +94,6 @@ class ModelRunnerOutput:
94 # each request due to speculative/jump decoding.
95 sampled_token_ids: list[list[int]]
96
97 - # num_reqs x num_spec_tokens
98 - spec_token_ids: Optional[list[list[int]]]
99 -
97 # [num_reqs, max_num_logprobs + 1]
98 # [num_reqs, max_num_logprobs + 1]
99 # [num_reqs]
@@ -117,10 +114,18 @@ class ModelRunnerOutput:
114 num_nans_in_logits: Optional[dict[str, int]] = None
115
116
117 +@dataclass
118 +class DraftTokenIds:
119 +
120 + # [num_reqs]
121 + req_ids: list[str]
122 + # num_reqs x num_draft_tokens
123 + draft_token_ids: list[list[int]]
124 +
125 +
126 EMPTY_MODEL_RUNNER_OUTPUT = ModelRunnerOutput(req_ids=[],
127 req_id_to_index={},
128 sampled_token_ids=[],
123 - spec_token_ids=None,
129 logprobs=None,
130 prompt_logprobs_dict={},
131 pooler_output=[],
vllm/v1/spec_decode/medusa.py
+3 -1
@@ -38,12 +38,14 @@ class MedusaProposer:
38 self,
39 target_hidden_states: torch.Tensor,
40 sampling_metadata: SamplingMetadata,
41 - ) -> torch.Tensor:
41 + ) -> list[list[int]]:
42 # Generate blocks and compute logits
43 blocks = self.model(target_hidden_states)
44 logits = self.model.compute_logits(blocks, None)
45
46 # Get draft tokens and transpose the result
47 + # TODO(woosuk): OPTIMIZATION: Return GPU tensor without GPU-CPU
48 + # synchronization.
49 draft_tokens = [logit.argmax(dim=-1).tolist() for logit in logits]
50 return [list(row) for row in zip(*draft_tokens)]
51
vllm/v1/worker/gpu_model_runner.py
+23 -14
@@ -65,8 +65,8 @@ from vllm.v1.kv_cache_interface import (AttentionSpec,
65 FullAttentionSpec, KVCacheConfig,
66 KVCacheSpec, MambaSpec,
67 SlidingWindowSpec)
68 -from vllm.v1.outputs import (EMPTY_MODEL_RUNNER_OUTPUT, LogprobsTensors,
69 - ModelRunnerOutput)
68 +from vllm.v1.outputs import (EMPTY_MODEL_RUNNER_OUTPUT, DraftTokenIds,
69 + LogprobsTensors, ModelRunnerOutput)
70 from vllm.v1.pool.metadata import PoolingMetadata
71 from vllm.v1.sample.logits_processor import LogitsProcessors, build_logitsprocs
72 from vllm.v1.sample.metadata import SamplingMetadata
@@ -348,6 +348,10 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
348
349 self.reorder_batch_threshold: Optional[int] = None
350
351 + # Cached outputs.
352 + self._draft_token_ids: Optional[Union[list[list[int]],
353 + torch.Tensor]] = None
354 +
355 def _init_model_kwargs(self, num_tokens: int):
356 model_kwargs = dict[str, Any]()
357 num_reqs = self.input_batch.num_reqs
@@ -1493,7 +1497,6 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1497 req_ids=self.input_batch.req_ids,
1498 req_id_to_index=self.input_batch.req_id_to_index,
1499 sampled_token_ids=[],
1496 - spec_token_ids=None,
1500 logprobs=None,
1501 prompt_logprobs_dict={},
1502 pooler_output=pooler_output,
@@ -1764,12 +1767,9 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1767 req_state = self.requests[req_id]
1768 req_state.output_token_ids.extend(sampled_ids)
1769
1767 - if not self.speculative_config:
1768 - # Speculative decoding is not enabled.
1769 - spec_token_ids = None
1770 - else:
1770 + if self.speculative_config:
1771 assert spec_decode_common_attn_metadata is not None
1772 - spec_token_ids = self.propose_draft_token_ids(
1772 + self._draft_token_ids = self.propose_draft_token_ids(
1773 scheduler_output,
1774 valid_sampled_token_ids,
1775 sampling_metadata,
@@ -1786,7 +1786,6 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1786 req_ids=self.input_batch.req_ids,
1787 req_id_to_index=self.input_batch.req_id_to_index,
1788 sampled_token_ids=valid_sampled_token_ids,
1789 - spec_token_ids=spec_token_ids,
1789 logprobs=logprobs_lists,
1790 prompt_logprobs_dict=prompt_logprobs_dict,
1791 pooler_output=[],
@@ -1794,6 +1793,17 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1793 num_nans_in_logits=num_nans_in_logits,
1794 )
1795
1796 + def take_draft_token_ids(self) -> Optional[DraftTokenIds]:
1797 + if self._draft_token_ids is None:
1798 + return None
1799 + req_ids = self.input_batch.req_ids
1800 + if isinstance(self._draft_token_ids, torch.Tensor):
1801 + draft_token_ids = self._draft_token_ids.tolist()
1802 + else:
1803 + draft_token_ids = self._draft_token_ids
1804 + self._draft_token_ids = None
1805 + return DraftTokenIds(req_ids, draft_token_ids)
1806 +
1807 def propose_draft_token_ids(
1808 self,
1809 scheduler_output: "SchedulerOutput",
@@ -1804,11 +1814,11 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1814 aux_hidden_states: Optional[torch.Tensor],
1815 spec_decode_metadata: Optional[SpecDecodeMetadata],
1816 common_attn_metadata: CommonAttentionMetadata,
1807 - ) -> list[list[int]]:
1817 + ) -> Union[list[list[int]], torch.Tensor]:
1818 num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
1819 if self.speculative_config.method == "ngram":
1820 assert isinstance(self.drafter, NgramProposer)
1811 - spec_token_ids = self.propose_ngram_draft_token_ids(
1821 + draft_token_ids = self.propose_ngram_draft_token_ids(
1822 sampled_token_ids)
1823 elif self.speculative_config.method == "medusa":
1824 assert isinstance(self.drafter, MedusaProposer)
@@ -1826,7 +1836,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1836 indices = torch.tensor(indices, device=self.device)
1837 hidden_states = sample_hidden_states[indices]
1838
1829 - spec_token_ids = self.drafter.propose(
1839 + draft_token_ids = self.drafter.propose(
1840 target_hidden_states=hidden_states,
1841 sampling_metadata=sampling_metadata,
1842 )
@@ -1897,8 +1907,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1907 common_attn_metadata=common_attn_metadata,
1908 mm_embeds=mm_embeds,
1909 )
1900 - spec_token_ids = draft_token_ids.tolist()
1901 - return spec_token_ids
1910 + return draft_token_ids
1911
1912 def propose_ngram_draft_token_ids(
1913 self,
vllm/v1/worker/gpu_worker.py
+5 -1
@@ -28,7 +28,8 @@ from vllm.tasks import SupportedTask
28 from vllm.utils import GiB_bytes, MemorySnapshot, memory_profiling
29 from vllm.v1.engine import ReconfigureDistributedRequest, ReconfigureRankType
30 from vllm.v1.kv_cache_interface import KVCacheConfig, KVCacheSpec
31 -from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT, ModelRunnerOutput
31 +from vllm.v1.outputs import (EMPTY_MODEL_RUNNER_OUTPUT, DraftTokenIds,
32 + ModelRunnerOutput)
33 from vllm.v1.utils import report_usage_stats
34 from vllm.v1.worker.gpu_model_runner import GPUModelRunner
35 from vllm.v1.worker.worker_base import WorkerBase
@@ -386,6 +387,9 @@ class Worker(WorkerBase):
387 assert isinstance(output, ModelRunnerOutput)
388 return output
389
390 + def take_draft_token_ids(self) -> Optional[DraftTokenIds]:
391 + return self.model_runner.take_draft_token_ids()
392 +
393 def profile(self, is_start: bool = True):
394 if self.profiler is None:
395 raise RuntimeError("Profiler is not enabled.")
vllm/v1/worker/tpu_model_runner.py
-1
@@ -1145,7 +1145,6 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1145 req_ids=req_ids,
1146 req_id_to_index=self.input_batch.req_id_to_index,
1147 sampled_token_ids=valid_sampled_token_ids,
1148 - spec_token_ids=None,
1148 logprobs=logprobs_lists,
1149 prompt_logprobs_dict=prompt_logprobs_dict,
1150 pooler_output=[],