@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 98b4d389ed
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 13, 04:30 UTC

[Redo] #26368 (#28771)

Signed-off-by: Jialin Ouyang <Jialin.Ouyang@gmail.com> Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk> Co-authored-by: Jialin Ouyang <Jialin.Ouyang@gmail.com>

Cyrus Leung committed Nov 15, 2025 at 14:47 UTC 98b4d389ed27f09fd185ade889a02f640a3ff0b4
15 files changed +122 -91
tests/v1/core/test_async_scheduler.py
+2 -1
@@ -2,6 +2,7 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from collections import deque
4
5 +import numpy as np
6 import pytest
7
8 from vllm.v1.core.sched.output import SchedulerOutput
@@ -21,7 +22,7 @@ def _make_model_runner_output(
22 return ModelRunnerOutput(
23 req_ids=req_ids,
24 req_id_to_index={req_id: i for i, req_id in enumerate(req_ids)},
24 - sampled_token_ids=[[i] for i in range(len(req_ids))],
25 + sampled_token_ids=[np.array([i]) for i in range(len(req_ids))],
26 logprobs=None,
27 prompt_logprobs_dict={},
28 pooler_output=[],
tests/v1/core/test_priority_scheduler_random.py
+4 -2
@@ -3,6 +3,7 @@
3 import random
4 import uuid
5
6 +import numpy as np
7 import pytest
8
9 from vllm.config import VllmConfig
@@ -99,8 +100,7 @@ def _mock_execute_model(
100 random.randint(*num_output_tokens_range) for _ in range(len(request_ids))
101 ]
102 sampled_token_ids = [
102 - [random.randint(0, 100) for _ in range(num_tokens)]
103 - for num_tokens in num_output_tokens
103 + np.random.randint(0, 100, size=num_tokens) for num_tokens in num_output_tokens
104 ]
105
106 return ModelRunnerOutput(
@@ -196,6 +196,8 @@ def test_priority_scheduling_blast(
196 num_blocks: int,
197 ):
198 random.seed(42)
199 + np.random.seed(42)
200 +
201 seen_request_prompt_length = dict[str, int]()
202 seen_request_ids = set[str]()
203 seen_mm_hashes = set[str]()
tests/v1/core/test_scheduler.py
+50 -38
@@ -3,6 +3,7 @@
3 import dataclasses
4 from unittest.mock import Mock
5
6 +import numpy as np
7 import pytest
8 import torch
9
@@ -169,7 +170,7 @@ def test_schedule_partial_requests():
170 req_id_to_index=req_to_index,
171 # Only the first request has a sampled token id because
172 # the rest requests are still being prefilled.
172 - sampled_token_ids=[[0], [], []],
173 + sampled_token_ids=[np.array([0]), np.array([]), np.array([])],
174 logprobs=None,
175 prompt_logprobs_dict={},
176 pooler_output=[],
@@ -216,7 +217,7 @@ def test_no_mm_input_chunking():
217 model_runner_output = ModelRunnerOutput(
218 req_ids=[request.request_id for request in requests],
219 req_id_to_index=req_to_index,
219 - sampled_token_ids=[[] for _ in range(len(requests))],
220 + sampled_token_ids=[np.array([]) for _ in range(len(requests))],
221 logprobs=None,
222 prompt_logprobs_dict={},
223 pooler_output=[],
@@ -276,7 +277,7 @@ def test_schedule_concurrent_partial_requests(enable_prefix_caching: bool):
277 model_runner_output = ModelRunnerOutput(
278 req_ids=[request.request_id for request in requests],
279 req_id_to_index=req_to_index,
279 - sampled_token_ids=[[] for _ in range(len(requests))],
280 + sampled_token_ids=[np.array([]) for _ in range(len(requests))],
281 logprobs=None,
282 prompt_logprobs_dict={},
283 pooler_output=[],
@@ -300,7 +301,8 @@ def test_schedule_concurrent_partial_requests(enable_prefix_caching: bool):
301 model_runner_output = ModelRunnerOutput(
302 req_ids=[request.request_id for request in requests],
303 req_id_to_index=req_to_index,
303 - sampled_token_ids=[[0], [0]] + [[] for _ in range(len(requests) - 2)],
304 + sampled_token_ids=[np.array([0]), np.array([0])]
305 + + [np.array([]) for _ in range(len(requests) - 2)],
306 logprobs=None,
307 prompt_logprobs_dict={},
308 pooler_output=[],
@@ -347,8 +349,8 @@ def test_stop_via_update_from_output():
349 req_ids=[req.request_id for req in requests],
350 req_id_to_index={req.request_id: i for i, req in enumerate(requests)},
351 sampled_token_ids=[
350 - [EOS_TOKEN_ID],
351 - [10, 11],
352 + np.array([EOS_TOKEN_ID]),
353 + np.array([10, 11]),
354 ], # First request hits EOS, second continues
355 logprobs=None,
356 prompt_logprobs_dict={},
@@ -392,7 +394,10 @@ def test_stop_via_update_from_output():
394 model_output = ModelRunnerOutput(
395 req_ids=[req.request_id for req in requests],
396 req_id_to_index={req.request_id: i for i, req in enumerate(requests)},
395 - sampled_token_ids=[[10, 42, 12], [13, 14]], # First request hits stop token
397 + sampled_token_ids=[
398 + np.array([10, 42, 12]),
399 + np.array([13, 14]),
400 + ], # First request hits stop token
401 logprobs=None,
402 prompt_logprobs_dict={},
403 pooler_output=[],
@@ -436,7 +441,10 @@ def test_stop_via_update_from_output():
441 model_output = ModelRunnerOutput(
442 req_ids=[req.request_id for req in requests],
443 req_id_to_index={req.request_id: i for i, req in enumerate(requests)},
439 - sampled_token_ids=[[10, 11, 12], [13]], # First request exceeds max_tokens
444 + sampled_token_ids=[
445 + np.array([10, 11, 12]),
446 + np.array([13]),
447 + ], # First request exceeds max_tokens
448 logprobs=None,
449 prompt_logprobs_dict={},
450 pooler_output=[],
@@ -475,7 +483,7 @@ def test_stop_via_update_from_output():
483 model_output = ModelRunnerOutput(
484 req_ids=[requests[0].request_id],
485 req_id_to_index={requests[0].request_id: 0},
478 - sampled_token_ids=[[EOS_TOKEN_ID, 10, 11]],
486 + sampled_token_ids=[np.array([EOS_TOKEN_ID, 10, 11])],
487 logprobs=None,
488 prompt_logprobs_dict={},
489 pooler_output=[],
@@ -616,7 +624,7 @@ def test_schedule_concurrent_batches(
624 model_runner_output = ModelRunnerOutput(
625 req_ids=[requests[0].request_id],
626 req_id_to_index={requests[0].request_id: 0},
619 - sampled_token_ids=[[0]],
627 + sampled_token_ids=[np.array([0])],
628 logprobs=None,
629 prompt_logprobs_dict={},
630 pooler_output=[],
@@ -633,7 +641,7 @@ def test_schedule_concurrent_batches(
641 model_runner_output = ModelRunnerOutput(
642 req_ids=[requests[1].request_id],
643 req_id_to_index={requests[1].request_id: 0},
636 - sampled_token_ids=[[0]],
644 + sampled_token_ids=[np.array([0])],
645 logprobs=None,
646 prompt_logprobs_dict={},
647 pooler_output=[],
@@ -670,7 +678,7 @@ def test_preempt_during_execution():
678 model_runner_output0 = ModelRunnerOutput(
679 req_ids=[requests[0].request_id],
680 req_id_to_index={requests[0].request_id: 0},
673 - sampled_token_ids=[[0]],
681 + sampled_token_ids=[np.array([0])],
682 logprobs=None,
683 prompt_logprobs_dict={},
684 pooler_output=[],
@@ -687,7 +695,7 @@ def test_preempt_during_execution():
695 model_runner_output1 = ModelRunnerOutput(
696 req_ids=[requests[1].request_id],
697 req_id_to_index={requests[1].request_id: 0},
690 - sampled_token_ids=[[42]],
698 + sampled_token_ids=[np.array([42])],
699 logprobs=None,
700 prompt_logprobs_dict={},
701 pooler_output=[],
@@ -704,14 +712,18 @@ def test_preempt_during_execution():
712 @pytest.mark.parametrize(
713 "spec_tokens,output_tokens,expected",
714 [
707 - ([[1, 2, 3]], [[1, 2, 3, 4]], (1, 3, 3, [1, 1, 1])), # perfect match
708 - ([[1, 2, 3]], [[1, 5]], (1, 3, 1, [1, 0, 0])), # early mismatch
709 - ([[1, 2], [3]], [[1, 2, 5], [3, 4]], (2, 3, 3, [2, 1])), # multiple sequences
710 - ([[1]], [[1, 2]], (1, 1, 1, [1])), # single token sequence
711 - ([[]], [[5]], (0, 0, 0, [0])), # empty sequence
715 + ([[1, 2, 3]], [np.array([1, 2, 3, 4])], (1, 3, 3, [1, 1, 1])), # perfect match
716 + ([[1, 2, 3]], [np.array([1, 5])], (1, 3, 1, [1, 0, 0])), # early mismatch
717 + (
718 + [[1, 2], [3]],
719 + [np.array([1, 2, 5]), np.array([3, 4])],
720 + (2, 3, 3, [2, 1]),
721 + ), # multiple sequences
722 + ([[1]], [np.array([1, 2])], (1, 1, 1, [1])), # single token sequence
723 + ([[]], [np.array([5])], (0, 0, 0, [0])), # empty sequence
724 (
725 [[1, 2, 3], [4, 5, 6]],
714 - [[1, 2, 7], [4, 8]],
726 + [np.array([1, 2, 7]), np.array([4, 8])],
727 (2, 6, 3, [2, 1, 0]),
728 ), # multiple mismatches
729 ],
@@ -745,7 +757,7 @@ def test_schedule_spec_decoding_stats(spec_tokens, output_tokens, expected):
757 model_runner_output = ModelRunnerOutput(
758 req_ids=req_ids,
759 req_id_to_index=req_to_index,
748 - sampled_token_ids=[[0] for _ in range(len(requests))],
760 + sampled_token_ids=[np.array([0]) for _ in range(len(requests))],
761 logprobs=None,
762 prompt_logprobs_dict={},
763 pooler_output=[],
@@ -972,7 +984,7 @@ def test_kv_connector_basic(is_async: bool):
984 MODEL_RUNNER_OUTPUT = ModelRunnerOutput(
985 req_ids=req_ids,
986 req_id_to_index=req_to_index,
975 - sampled_token_ids=[[1000]] * len(req_ids),
987 + sampled_token_ids=[np.array([1000])] * len(req_ids),
988 logprobs=None,
989 prompt_logprobs_dict={},
990 pooler_output=[],
@@ -1025,7 +1037,7 @@ def test_kv_connector_basic(is_async: bool):
1037 MODEL_RUNNER_OUTPUT = ModelRunnerOutput(
1038 req_ids=req_ids,
1039 req_id_to_index=req_to_index,
1028 - sampled_token_ids=[[1000]] * len(req_ids),
1040 + sampled_token_ids=[np.array([1000])] * len(req_ids),
1041 logprobs=None,
1042 prompt_logprobs_dict={},
1043 pooler_output=[],
@@ -1088,7 +1100,7 @@ def test_external_prefix_cache_metrics():
1100 MODEL_RUNNER_OUTPUT = ModelRunnerOutput(
1101 req_ids=[r.request_id for r in requests],
1102 req_id_to_index={r.request_id: i for i, r in enumerate(requests)},
1091 - sampled_token_ids=[[1000]] * NUM_REQUESTS,
1103 + sampled_token_ids=[np.array([1000])] * NUM_REQUESTS,
1104 logprobs=None,
1105 prompt_logprobs_dict={},
1106 pooler_output=[],
@@ -1154,7 +1166,7 @@ def test_kv_connector_unable_to_allocate(use_ec_connector, ec_role):
1166 MODEL_RUNNER_OUTPUT = ModelRunnerOutput(
1167 req_ids=req_ids,
1168 req_id_to_index=req_to_index,
1157 - sampled_token_ids=[[1000]] * len(req_ids),
1169 + sampled_token_ids=[np.array([1000])] * len(req_ids),
1170 logprobs=None,
1171 prompt_logprobs_dict={},
1172 pooler_output=[],
@@ -1239,7 +1251,7 @@ def test_kv_connector_handles_preemption(use_ec_connector, ec_role):
1251 MODEL_RUNNER_OUTPUT = ModelRunnerOutput(
1252 req_ids=req_ids,
1253 req_id_to_index=req_to_index,
1242 - sampled_token_ids=[[1000]] * len(req_ids),
1254 + sampled_token_ids=[np.array([1000])] * len(req_ids),
1255 logprobs=None,
1256 prompt_logprobs_dict={},
1257 pooler_output=[],
@@ -1332,7 +1344,7 @@ def make_output(scheduler: Scheduler):
1344 return ModelRunnerOutput(
1345 req_ids=[req.request_id for req in scheduler.running],
1346 req_id_to_index={req.request_id: i for i, req in enumerate(scheduler.running)},
1335 - sampled_token_ids=[[1000]] * len(scheduler.running),
1347 + sampled_token_ids=[np.array([1000])] * len(scheduler.running),
1348 logprobs=None,
1349 prompt_logprobs_dict={},
1350 pooler_output=[],
@@ -1749,7 +1761,7 @@ def test_priority_scheduling_preemption():
1761 req_id_to_index={
1762 req.request_id: i for i, req in enumerate(low_priority_requests)
1763 },
1752 - sampled_token_ids=[[100] for _ in low_priority_requests],
1764 + sampled_token_ids=[np.array([100]) for _ in low_priority_requests],
1765 logprobs=None,
1766 prompt_logprobs_dict={},
1767 pooler_output=[],
@@ -1818,7 +1830,7 @@ def test_priority_scheduling_no_preemption_when_space_available():
1830 req_id_to_index={
1831 req.request_id: i for i, req in enumerate(low_priority_requests)
1832 },
1821 - sampled_token_ids=[[100] for _ in low_priority_requests],
1833 + sampled_token_ids=[np.array([100]) for _ in low_priority_requests],
1834 logprobs=None,
1835 prompt_logprobs_dict={},
1836 pooler_output=[],
@@ -2064,7 +2076,7 @@ def test_priority_scheduling_heap_property():
2076 model_output = ModelRunnerOutput(
2077 req_ids=[req.req_id],
2078 req_id_to_index={req.req_id: 0},
2067 - sampled_token_ids=[[100]],
2079 + sampled_token_ids=[np.array([100])],
2080 logprobs=None,
2081 prompt_logprobs_dict={},
2082 pooler_output=[],
@@ -2150,7 +2162,7 @@ def test_priority_scheduling_preemption_and_resumption_when_out_of_kv(
2162 model_output = ModelRunnerOutput(
2163 req_ids=[request_low.request_id],
2164 req_id_to_index={request_low.request_id: 0},
2153 - sampled_token_ids=[[100]],
2165 + sampled_token_ids=[np.array([100])],
2166 # spec_token_ids=None,
2167 logprobs=None,
2168 prompt_logprobs_dict={},
@@ -2181,7 +2193,7 @@ def test_priority_scheduling_preemption_and_resumption_when_out_of_kv(
2193 model_output = ModelRunnerOutput(
2194 req_ids=[req.request_id for req in requests],
2195 req_id_to_index={req.request_id: i for i, req in enumerate(requests)},
2184 - sampled_token_ids=[[100] for _ in requests],
2196 + sampled_token_ids=[np.array([100]) for _ in requests],
2197 # spec_token_ids=None,
2198 logprobs=None,
2199 prompt_logprobs_dict={},
@@ -2207,7 +2219,7 @@ def test_priority_scheduling_preemption_and_resumption_when_out_of_kv(
2219 model_output = ModelRunnerOutput(
2220 req_ids=[req.request_id for req in requests],
2221 req_id_to_index={req.request_id: i for i, req in enumerate(requests)},
2210 - sampled_token_ids=[[], [100]],
2222 + sampled_token_ids=[np.array([]), np.array([100])],
2223 # spec_token_ids=None,
2224 logprobs=None,
2225 prompt_logprobs_dict={},
@@ -2624,7 +2636,7 @@ def test_ec_connector_with_partial_cache_hit_multi_round(use_kv_connector):
2636 model_output = ModelRunnerOutput(
2637 req_ids=[request1.request_id],
2638 req_id_to_index={request1.request_id: 0},
2627 - sampled_token_ids=[[100]],
2639 + sampled_token_ids=[np.array([100])],
2640 # spec_token_ids=None,
2641 logprobs=None,
2642 prompt_logprobs_dict={},
@@ -2830,7 +2842,7 @@ def test_ec_connector_unable_to_allocate(use_kv_connector):
2842 MODEL_RUNNER_OUTPUT = ModelRunnerOutput(
2843 req_ids=req_ids,
2844 req_id_to_index=req_to_index,
2833 - sampled_token_ids=[[1000]] * len(req_ids),
2845 + sampled_token_ids=[np.array([1000])] * len(req_ids),
2846 logprobs=None,
2847 prompt_logprobs_dict={},
2848 pooler_output=[],
@@ -2943,7 +2955,7 @@ def test_priority_scheduling_ec_connector_preemption_and_resumption(
2955 model_output = ModelRunnerOutput(
2956 req_ids=[request_low.request_id],
2957 req_id_to_index={request_low.request_id: 0},
2946 - sampled_token_ids=[[100]],
2958 + sampled_token_ids=[np.array([100])],
2959 # spec_token_ids=None,
2960 logprobs=None,
2961 prompt_logprobs_dict={},
@@ -2994,7 +3006,7 @@ def test_priority_scheduling_ec_connector_preemption_and_resumption(
3006 model_output = ModelRunnerOutput(
3007 req_ids=[req.request_id for req in requests],
3008 req_id_to_index={req.request_id: i for i, req in enumerate(requests)},
2997 - sampled_token_ids=[[100] for _ in requests],
3009 + sampled_token_ids=[np.array([100]) for _ in requests],
3010 # spec_token_ids=None,
3011 logprobs=None,
3012 prompt_logprobs_dict={},
@@ -3029,7 +3041,7 @@ def test_priority_scheduling_ec_connector_preemption_and_resumption(
3041 model_output = ModelRunnerOutput(
3042 req_ids=[req.request_id for req in requests],
3043 req_id_to_index={req.request_id: i for i, req in enumerate(requests)},
3032 - sampled_token_ids=[[100], [100, 200]],
3044 + sampled_token_ids=[np.array([100]), np.array([100, 200])],
3045 # spec_token_ids=None,
3046 logprobs=None,
3047 prompt_logprobs_dict={},
@@ -3215,7 +3227,7 @@ def test_ec_connector_allocate_encoder_tokens_with_external_load(use_kv_connecto
3227 model_output = ModelRunnerOutput(
3228 req_ids=[request1.request_id, request2.request_id],
3229 req_id_to_index={request1.request_id: 0, request2.request_id: 1},
3218 - sampled_token_ids=[[100], [121]],
3230 + sampled_token_ids=[np.array([100]), np.array([121])],
3231 # spec_token_ids=None,
3232 logprobs=None,
3233 prompt_logprobs_dict={},
tests/v1/kv_connector/unit/test_nixl_connector.py
+4 -3
@@ -11,6 +11,7 @@ import uuid
11 from collections import defaultdict
12 from unittest.mock import patch
13
14 +import numpy as np
15 import pytest
16 import ray
17 import torch
@@ -826,7 +827,7 @@ def test_kv_connector_stats_aggregation():
827 output = ModelRunnerOutput(
828 req_ids=[f"req_{i}"],
829 req_id_to_index={f"req_{i}": 0},
829 - sampled_token_ids=[[123]], # dummy token
830 + sampled_token_ids=[np.array([123])], # dummy token
831 logprobs=None,
832 prompt_logprobs_dict={},
833 pooler_output=[None],
@@ -907,7 +908,7 @@ def test_multi_kv_connector_stats_aggregation():
908 output = ModelRunnerOutput(
909 req_ids=[f"req_{i}"],
910 req_id_to_index={f"req_{i}": 0},
910 - sampled_token_ids=[[123]],
911 + sampled_token_ids=[np.array([123])],
912 logprobs=None,
913 prompt_logprobs_dict={},
914 pooler_output=[None],
@@ -965,7 +966,7 @@ def test_scheduler_kv_connector_stats_aggregation():
966 model_output = ModelRunnerOutput(
967 req_ids=["req_0"],
968 req_id_to_index={"req_0": 0},
968 - sampled_token_ids=[[123]],
969 + sampled_token_ids=[np.array([123])],
970 logprobs=None,
971 prompt_logprobs_dict={},
972 pooler_output=[None],
tests/v1/kv_connector/unit/utils.py
+2 -1
@@ -7,6 +7,7 @@ from dataclasses import dataclass
7 from itertools import chain, count
8 from typing import Any
9
10 +import numpy as np
11 import torch
12
13 from vllm import SamplingParams
@@ -228,7 +229,7 @@ def create_model_runner_output(
229
230 # Make sampled tokens.
231 sampled_token = EOS_TOKEN_ID if use_eos else token_id
231 - sampled_token_ids = [[sampled_token] for _ in req_ids]
232 + sampled_token_ids = [np.array([sampled_token]) for _ in req_ids]
233
234 kv_connector_output = (
235 None
tests/v1/spec_decode/test_eagle.py
+4 -1
@@ -3,6 +3,7 @@
3
4 from unittest import mock
5
6 +import numpy as np
7 import pytest
8 import torch
9
@@ -112,7 +113,9 @@ def test_prepare_next_token_ids():
113 sampled_token_ids_tensor = torch.tensor(
114 sampled_token_ids, dtype=torch.int32, device=device
115 )
115 - sampled_token_ids_cpu = [[i for i in seq if i != -1] for seq in sampled_token_ids]
116 + sampled_token_ids_cpu = [
117 + np.array([i for i in seq if i != -1]) for seq in sampled_token_ids
118 + ]
119
120 expected_next_token_ids_cpu = [1, 4, 30, 40]
121 expected_next_token_ids_tensor = torch.tensor(
tests/v1/spec_decode/test_ngram.py
+9 -9
@@ -77,7 +77,7 @@ def test_ngram_proposer():
77 # No match.
78 token_ids_cpu = np.array([[1, 2, 3, 4, 5]])
79 result = get_ngram_proposer(min_n=2, max_n=2, k=2).propose(
80 - sampled_token_ids=[[0]],
80 + sampled_token_ids=[np.array([0])],
81 req_ids=["0"],
82 num_tokens_no_spec=np.array([len(c) for c in token_ids_cpu]),
83 token_ids_cpu=token_ids_cpu,
@@ -88,7 +88,7 @@ def test_ngram_proposer():
88 # No match for 4-gram.
89 token_ids_cpu = np.array([[1, 2, 3, 4, 1, 2, 3]])
90 result = get_ngram_proposer(min_n=4, max_n=4, k=2).propose(
91 - sampled_token_ids=[[0]],
91 + sampled_token_ids=[np.array([0])],
92 req_ids=["0"],
93 num_tokens_no_spec=np.array([len(c) for c in token_ids_cpu]),
94 token_ids_cpu=token_ids_cpu,
@@ -99,7 +99,7 @@ def test_ngram_proposer():
99 # No match for 4-gram but match for 3-gram.
100 token_ids_cpu = np.array([[1, 2, 3, 4, 1, 2, 3]])
101 result = get_ngram_proposer(min_n=3, max_n=4, k=2).propose(
102 - sampled_token_ids=[[0]],
102 + sampled_token_ids=[np.array([0])],
103 req_ids=["0"],
104 num_tokens_no_spec=np.array([len(c) for c in token_ids_cpu]),
105 token_ids_cpu=token_ids_cpu,
@@ -111,7 +111,7 @@ def test_ngram_proposer():
111 # In this case, the proposer should return the 4-gram match.
112 token_ids_cpu = np.array([[2, 3, 4, 5, 1, 2, 3, 4, 1, 2, 3, 4]])
113 result = get_ngram_proposer(min_n=3, max_n=4, k=2).propose(
114 - sampled_token_ids=[[0]],
114 + sampled_token_ids=[np.array([0])],
115 req_ids=["0"],
116 num_tokens_no_spec=np.array([len(c) for c in token_ids_cpu]),
117 token_ids_cpu=token_ids_cpu,
@@ -122,7 +122,7 @@ def test_ngram_proposer():
122 # Match for 2-gram and 3-gram, but not 4-gram.
123 token_ids_cpu = np.array([[3, 4, 5, 2, 3, 4, 1, 2, 3, 4]])
124 result = get_ngram_proposer(min_n=2, max_n=4, k=2).propose(
125 - sampled_token_ids=[[0]],
125 + sampled_token_ids=[np.array([0])],
126 req_ids=["0"],
127 num_tokens_no_spec=np.array([len(c) for c in token_ids_cpu]),
128 token_ids_cpu=token_ids_cpu,
@@ -133,7 +133,7 @@ def test_ngram_proposer():
133 # Multiple 3-gram matched, but always pick the first one.
134 token_ids_cpu = np.array([[1, 2, 3, 100, 1, 2, 3, 200, 1, 2, 3, 300, 1, 2, 3]])
135 result = get_ngram_proposer(min_n=3, max_n=3, k=2).propose(
136 - sampled_token_ids=[[0]],
136 + sampled_token_ids=[np.array([0])],
137 req_ids=["0"],
138 num_tokens_no_spec=np.array([len(c) for c in token_ids_cpu]),
139 token_ids_cpu=token_ids_cpu,
@@ -144,7 +144,7 @@ def test_ngram_proposer():
144 # check empty input
145 token_ids_cpu = np.array([[]])
146 result = get_ngram_proposer(min_n=2, max_n=2, k=2).propose(
147 - sampled_token_ids=[[0]],
147 + sampled_token_ids=[np.array([0])],
148 req_ids=["0"],
149 num_tokens_no_spec=np.array([len(c) for c in token_ids_cpu]),
150 token_ids_cpu=token_ids_cpu,
@@ -157,7 +157,7 @@ def test_ngram_proposer():
157 # second request has 3 tokens and no match. Padded with -1 for max len 5
158 token_ids_cpu = np.array([[1, 2, 3, 1, 2], [4, 5, 6, -1, -1]])
159 result = get_ngram_proposer(min_n=2, max_n=2, k=2).propose(
160 - sampled_token_ids=[[0], [1]],
160 + sampled_token_ids=[np.array([0]), np.array([1])],
161 req_ids=["0", "1"],
162 num_tokens_no_spec=np.array([5, 3]),
163 token_ids_cpu=token_ids_cpu,
@@ -181,7 +181,7 @@ def test_ngram_proposer():
181 input_2[:3] = [4, 5, 6]
182 token_ids_cpu = np.array([input_1, input_2])
183 result = ngram_proposer.propose(
184 - sampled_token_ids=[[0], [1]],
184 + sampled_token_ids=[np.array([0]), np.array([1])],
185 req_ids=["0", "1"],
186 num_tokens_no_spec=np.array([len(input_1), 3]),
187 token_ids_cpu=token_ids_cpu,
vllm/v1/core/sched/scheduler.py
+2 -2
@@ -1010,8 +1010,8 @@ class Scheduler(SchedulerInterface):
1010 continue
1011
1012 req_index = model_runner_output.req_id_to_index[req_id]
1013 - generated_token_ids = (
1014 - sampled_token_ids[req_index] if sampled_token_ids else []
1013 + generated_token_ids: list[int] = (
1014 + sampled_token_ids[req_index].tolist() if sampled_token_ids else []
1015 )
1016
1017 scheduled_spec_token_ids = (
vllm/v1/outputs.py
+2 -2
@@ -158,7 +158,7 @@ class ModelRunnerOutput:
158 # num_generated_tokens is the number of tokens
159 # generated in the current step. It can be different for
160 # each request due to speculative/jump decoding.
161 - sampled_token_ids: list[list[int]]
161 + sampled_token_ids: list[np.ndarray]
162
163 # [num_reqs, max_num_logprobs + 1]
164 # [num_reqs, max_num_logprobs + 1]
@@ -220,7 +220,7 @@ def make_empty_encoder_model_runner_output(
220 req_id_to_index: dict[str, int] = {rid: idx for idx, rid in enumerate(req_ids)}
221
222 # No tokens generated yet ⇒ one empty list per request
223 - sampled_token_ids: list[list[int]] = [[0] for _ in req_ids]
223 + sampled_token_ids: list[list[int]] = [np.array([0]) for _ in req_ids]
224
225 # Pooler outputs are not available yet ⇒ use None placeholders
226 pooler_output: list[torch.Tensor | None] = [None for _ in req_ids]
vllm/v1/sample/rejection_sampler.py
+3 -5
@@ -3,6 +3,7 @@
3
4 from dataclasses import replace
5
6 +import numpy as np
7 import torch
8 import torch.nn as nn
9
@@ -204,7 +205,7 @@ class RejectionSampler(nn.Module):
205 def parse_output(
206 output_token_ids: torch.Tensor,
207 vocab_size: int,
207 - ) -> list[list[int]]:
208 + ) -> list[np.ndarray]:
209 """Parse the output of the rejection sampler.
210 Args:
211 output_token_ids: The sampled token IDs in shape
@@ -220,10 +221,7 @@ class RejectionSampler(nn.Module):
221 valid_mask = (output_token_ids_np != PLACEHOLDER_TOKEN_ID) & (
222 output_token_ids_np < vocab_size
223 )
223 - outputs = [
224 - row[valid_mask[i]].tolist() for i, row in enumerate(output_token_ids_np)
225 - ]
226 - return outputs
224 + return [row[valid_mask[i]] for i, row in enumerate(output_token_ids_np)]
225
226 def apply_logits_processors(
227 self,
vllm/v1/spec_decode/eagle.py
+3 -4
@@ -484,7 +484,7 @@ class EagleProposer:
484
485 def prepare_next_token_ids_cpu(
486 self,
487 - sampled_token_ids: list[list[int]],
487 + sampled_token_ids: list[np.ndarray],
488 requests: dict[str, CachedRequestState],
489 gpu_input_batch: InputBatch,
490 num_scheduled_tokens: dict[str, int],
@@ -499,7 +499,7 @@ class EagleProposer:
499 req_ids = gpu_input_batch.req_ids
500 next_token_ids: list[int] = []
501 for i, token_ids in enumerate(sampled_token_ids):
502 - if token_ids:
502 + if token_ids.shape[0] > 0:
503 # Common case.
504 next_token_id = token_ids[-1]
505 else:
@@ -510,10 +510,9 @@ class EagleProposer:
510 seq_len = req_state.num_computed_tokens + num_scheduled_tokens[req_id]
511 next_token_id = req_state.get_token_id(seq_len)
512 next_token_ids.append(next_token_id)
513 - next_token_ids = torch.tensor(
513 + return torch.tensor(
514 next_token_ids, dtype=torch.int32, device=self.input_ids.device
515 )
516 - return next_token_ids
516
517 def prepare_next_token_ids_padded(
518 self,
vllm/v1/spec_decode/ngram_proposer.py
+3 -3
@@ -54,7 +54,7 @@ class NgramProposer:
54 # Trigger Numba JIT compilation for N-gram proposer.
55 # This usually takes less than 1 second.
56 self.propose(
57 - [[]] * 1024,
57 + [np.array([])] * 1024,
58 [""] * 1024,
59 np.zeros(1024, dtype=np.int32),
60 np.zeros((1024, self.max_model_len), dtype=np.int32),
@@ -131,7 +131,7 @@ class NgramProposer:
131
132 def propose(
133 self,
134 - sampled_token_ids: list[list[int]],
134 + sampled_token_ids: list[np.ndarray],
135 req_ids: list[str],
136 num_tokens_no_spec: np.ndarray,
137 token_ids_cpu: np.ndarray,
@@ -140,7 +140,7 @@ class NgramProposer:
140 # find which requests need ngram proposals
141 valid_ngram_requests = []
142 for i, sampled_ids in enumerate(sampled_token_ids):
143 - num_sampled_ids = len(sampled_ids)
143 + num_sampled_ids = sampled_ids.shape[0]
144 if not num_sampled_ids:
145 # Skip speculative decoding.
146 continue
vllm/v1/spec_decode/suffix_decoding.py
+6 -4
@@ -1,5 +1,7 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +import numpy as np
4 +
5 from vllm.config import VllmConfig
6 from vllm.v1.worker.gpu_input_batch import InputBatch
7
@@ -32,16 +34,16 @@ class SuffixDecodingProposer:
34 def propose(
35 self,
36 input_batch: InputBatch,
35 - sampled_token_ids: list[list[int]],
37 + sampled_token_ids: list[np.ndarray],
38 ) -> list[list[int]]:
39 """
40 Propose speculative tokens for each request in the input batch. Suffix Decoding
41 will speculate a dynamic number of tokens for each request every decoding step,
42 so each entry in the returned list may have different lengths.
43 """
42 - draft_token_ids: list[list[int]] = []
44 + draft_token_ids: list[np.ndarray] = []
45 for i, sampled_ids in enumerate(sampled_token_ids):
44 - if not sampled_ids:
46 + if sampled_ids.shape[0] == 0:
47 # Skip speculative decoding for partial prefills.
48 draft_token_ids.append([])
49 continue
@@ -70,7 +72,7 @@ class SuffixDecodingProposer:
72 self.suffix_cache.start_request(req_id, prompt_token_ids)
73
74 # Append the newly sampled ids to the suffix cache for this request.
73 - self.suffix_cache.add_active_response(req_id, sampled_ids)
75 + self.suffix_cache.add_active_response(req_id, sampled_ids.tolist())
76
77 # Suffix decoding only uses the most recent tokens up to max_tree_depth, so
78 # we extract the pattern from the end of the input.
vllm/v1/worker/gpu_model_runner.py
+23 -13
@@ -216,9 +216,11 @@ class AsyncGPUModelRunnerOutput(AsyncModelRunnerOutput):
216 del self._logprobs_tensors
217 del self._sampled_token_ids
218
219 - valid_sampled_token_ids = self.sampled_token_ids_cpu.tolist()
219 + valid_sampled_token_ids: list[np.ndarray] = [
220 + row for row in self.sampled_token_ids_cpu.numpy()
221 + ]
222 for i in self._invalid_req_indices:
221 - valid_sampled_token_ids[i].clear()
223 + valid_sampled_token_ids[i] = np.array([])
224
225 output = self._model_runner_output
226 output.sampled_token_ids = valid_sampled_token_ids
@@ -2339,7 +2341,7 @@ class GPUModelRunner(
2341 ) -> tuple[
2342 dict[str, int],
2343 LogprobsLists | None,
2342 - list[list[int]],
2344 + list[np.ndarray],
2345 dict[str, LogprobsTensors | None],
2346 list[str],
2347 dict[str, int],
@@ -2365,6 +2367,7 @@ class GPUModelRunner(
2367 num_sampled_tokens = sampler_output.sampled_token_ids.shape[0]
2368 sampled_token_ids = sampler_output.sampled_token_ids
2369 invalid_req_indices = []
2370 + valid_sampled_token_ids: list[np.ndarray]
2371 if not self.use_async_scheduling:
2372 # Get the valid generated tokens.
2373 max_gen_len = sampled_token_ids.shape[-1]
@@ -2379,7 +2382,7 @@ class GPUModelRunner(
2382 )
2383 # Mask out the sampled tokens that should not be sampled.
2384 for i in discard_sampled_tokens_req_indices:
2382 - valid_sampled_token_ids[int(i)].clear()
2385 + valid_sampled_token_ids[int(i)] = np.array([])
2386 else:
2387 valid_sampled_token_ids = []
2388 invalid_req_indices = discard_sampled_tokens_req_indices.tolist()
@@ -2407,19 +2410,24 @@ class GPUModelRunner(
2410 [0] if spec_decode_metadata and logprobs_tensors else None
2411 )
2412 for req_idx in range(num_sampled_tokens):
2413 + sampled_ids: np.ndarray | None
2414 if self.use_async_scheduling:
2411 - sampled_ids = [-1] if req_idx not in invalid_req_indices_set else None
2415 + sampled_ids = (
2416 + np.array([-1]) if req_idx not in invalid_req_indices_set else None
2417 + )
2418 else:
2419 sampled_ids = valid_sampled_token_ids[req_idx]
2420
2415 - num_sampled_ids: int = len(sampled_ids) if sampled_ids else 0
2421 + num_sampled_ids: int = (
2422 + sampled_ids.shape[0] if sampled_ids is not None else 0
2423 + )
2424
2425 if cu_num_accepted_tokens is not None:
2426 cu_num_accepted_tokens.append(
2427 cu_num_accepted_tokens[-1] + num_sampled_ids
2428 )
2429
2422 - if not sampled_ids:
2430 + if sampled_ids is None or num_sampled_ids == 0:
2431 continue
2432
2433 start_idx = self.input_batch.num_tokens_no_spec[req_idx]
@@ -2761,7 +2769,9 @@ class GPUModelRunner(
2769 with record_function_or_nullcontext("gpu_model_runner: sample"):
2770 sampler_output = self._sample(logits, spec_decode_metadata)
2771
2764 - def propose_draft_token_ids(sampled_token_ids):
2772 + def propose_draft_token_ids(
2773 + sampled_token_ids: torch.Tensor | list[np.ndarray],
2774 + ) -> None:
2775 assert spec_decode_common_attn_metadata is not None
2776 with record_function_or_nullcontext("gpu_model_runner: draft"):
2777 self._draft_token_ids = self.propose_draft_token_ids(
@@ -2883,14 +2893,14 @@ class GPUModelRunner(
2893 def propose_draft_token_ids(
2894 self,
2895 scheduler_output: "SchedulerOutput",
2886 - sampled_token_ids: torch.Tensor | list[list[int]],
2896 + sampled_token_ids: torch.Tensor | list[np.ndarray],
2897 sampling_metadata: SamplingMetadata,
2898 hidden_states: torch.Tensor,
2899 sample_hidden_states: torch.Tensor,
2900 aux_hidden_states: list[torch.Tensor] | None,
2901 spec_decode_metadata: SpecDecodeMetadata | None,
2902 common_attn_metadata: CommonAttentionMetadata,
2893 - ) -> list[list[int]] | torch.Tensor:
2903 + ) -> torch.Tensor | list[list[int]]:
2904 num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
2905 if self.speculative_config.method == "ngram":
2906 assert isinstance(sampled_token_ids, list)
@@ -2922,7 +2932,7 @@ class GPUModelRunner(
2932 for num_draft, tokens in zip(
2933 spec_decode_metadata.num_draft_tokens, sampled_token_ids
2934 ):
2925 - indices.append(offset + len(tokens) - 1)
2935 + indices.append(offset + tokens.shape[0] - 1)
2936 offset += num_draft + 1
2937 indices = torch.tensor(indices, device=self.device)
2938 hidden_states = sample_hidden_states[indices]
@@ -4862,7 +4872,7 @@ class GPUModelRunner(
4872
4873 return kv_cache_spec
4874
4865 - def _to_list(self, sampled_token_ids: torch.Tensor) -> list[list[int]]:
4875 + def _to_list(self, sampled_token_ids: torch.Tensor) -> list[np.ndarray]:
4876 # This is a short term mitigation for issue mentioned in
4877 # https://github.com/vllm-project/vllm/issues/22754.
4878 # `tolist` would trigger a cuda wise stream sync, which
@@ -4875,4 +4885,4 @@ class GPUModelRunner(
4885 pinned.copy_(sampled_token_ids, non_blocking=True)
4886 self.transfer_event.record()
4887 self.transfer_event.synchronize()
4878 - return pinned.tolist()
4888 + return [row for row in pinned.numpy()]
vllm/v1/worker/tpu_model_runner.py
+5 -3
@@ -1254,13 +1254,15 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1254
1255 max_gen_len = selected_token_ids.shape[-1]
1256 if max_gen_len == 1:
1257 - valid_sampled_token_ids = selected_token_ids.tolist()
1257 + valid_sampled_token_ids: list[np.ndarray] = [
1258 + row for row in selected_token_ids.numpy()
1259 + ]
1260
1261 # Mask out the sampled tokens that should not be sampled.
1262 # TODO: Keep in sync with gpu_model_runner.py, in particular
1263 # the "else" case here
1264 for i in discard_sampled_tokens_req_indices:
1263 - valid_sampled_token_ids[i].clear()
1265 + valid_sampled_token_ids[i] = np.array([])
1266
1267 # Append sampled tokens
1268 for i, req_state, seq_len in request_seq_lens:
@@ -1273,7 +1275,7 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1275 valid_mask = selected_token_ids != INVALID_TOKEN_ID
1276 gen_lens = valid_mask.sum(dim=1).tolist()
1277 valid_sampled_token_ids = [
1276 - seq.tolist() for seq in selected_token_ids[valid_mask].split(gen_lens)
1278 + seq.numpy() for seq in selected_token_ids[valid_mask].split(gen_lens)
1279 ]
1280 self.input_batch.num_tokens[:num_reqs] += gen_lens
1281 for i, req_state, seq_len in request_seq_lens: