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
[V1][spec decode] return logprobs for spec decoding (#26060)
Signed-off-by: Giancarlo Delfin <gdelfin@meta.com> Co-authored-by: Nick Hill <nhill@redhat.com>
Giancarlo Delfin committed
Oct 22, 2025 at 22:59 UTC
6644796bf445ad6fb34b25c6efe297ce8fe31f81
8 files changed
+393
-187
tests/v1/sample/test_logprobs.py
+94
@@ -2,12 +2,14 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import itertools
5
+import math
6
from collections.abc import Generator
7
from typing import get_args
8
9
import pytest
10
import torch
11
12
+from tests.utils import large_gpu_mark
13
from tests.v1.sample.utils import (
14
BatchLogprobsComposition,
15
BatchLogprobsSpecType,
@@ -17,6 +19,7 @@ from tests.v1.sample.utils import (
19
)
20
from vllm import SamplingParams
21
from vllm.config.model import LogprobsMode
22
+from vllm.distributed import cleanup_dist_env_and_memory
23
24
from ...conftest import HfRunner, VllmRunner
25
@@ -508,3 +511,94 @@ def test_logprobs_mode(logprobs_mode: LogprobsMode):
511
if logprobs_mode in ("raw_logits", "processed_logits"):
512
assert positive_values > 0
513
del llm
514
+
515
+
516
+@pytest.mark.parametrize("logprobs_mode", get_args(LogprobsMode))
517
+@pytest.mark.parametrize(
518
+ "model_setup",
519
+ [
520
+ pytest.param(
521
+ (
522
+ "eagle",
523
+ "meta-llama/Llama-3.1-8B-Instruct",
524
+ "yuhuili/EAGLE-LLaMA3.1-Instruct-8B",
525
+ ),
526
+ marks=large_gpu_mark(min_gb=32),
527
+ ),
528
+ ],
529
+)
530
+def test_spec_decode_logprobs(
531
+ logprobs_mode: LogprobsMode,
532
+ model_setup: tuple[str, str, str],
533
+ monkeypatch: pytest.MonkeyPatch,
534
+):
535
+ """Spec decode logprobs should match those of the base model.
536
+
537
+ Args:
538
+ logprobs_mode: logprobs mode.
539
+ model_setup: Spec decode method, base model name, and
540
+ draft model name.
541
+ """
542
+ from vllm import LLM
543
+
544
+ with monkeypatch.context() as m:
545
+ m.setenv("VLLM_USE_V1", "1")
546
+ prompt = "Hello world"
547
+ sampling_params = SamplingParams(
548
+ temperature=0, logprobs=3, max_tokens=10, ignore_eos=False
549
+ )
550
+ method, model_name, spec_model_name = model_setup
551
+ max_model_len = 256
552
+
553
+ # Run base LLM.
554
+ ref_llm = LLM(
555
+ model=model_name,
556
+ max_logprobs=5,
557
+ max_model_len=max_model_len,
558
+ seed=42,
559
+ logprobs_mode=logprobs_mode,
560
+ gpu_memory_utilization=0.4,
561
+ )
562
+ ref_results = ref_llm.generate([prompt], sampling_params)
563
+ # Collect logprobs outputs from reference LLM.
564
+ ref_logprobs = []
565
+ for output in ref_results[0].outputs:
566
+ for logprobs in output.logprobs:
567
+ for token_id in logprobs:
568
+ ref_logprobs.append(logprobs[token_id])
569
+ del ref_llm
570
+ torch.cuda.empty_cache()
571
+ cleanup_dist_env_and_memory()
572
+
573
+ # Run spec decode LLM.
574
+ spec_llm = LLM(
575
+ model_name,
576
+ speculative_config={
577
+ "method": method,
578
+ "model": spec_model_name,
579
+ "num_speculative_tokens": 3,
580
+ "max_model_len": max_model_len,
581
+ },
582
+ max_logprobs=5,
583
+ max_model_len=max_model_len,
584
+ seed=42,
585
+ logprobs_mode=logprobs_mode,
586
+ gpu_memory_utilization=0.4,
587
+ )
588
+ spec_results = spec_llm.generate([prompt], sampling_params)
589
+ # Collect logprobs outputs from spec decode LLM.
590
+ spec_logprobs = []
591
+ for output in spec_results[0].outputs:
592
+ for logprobs in output.logprobs:
593
+ for token_id in logprobs:
594
+ spec_logprobs.append(logprobs[token_id])
595
+ del spec_llm
596
+ torch.cuda.empty_cache()
597
+ cleanup_dist_env_and_memory()
598
+
599
+ # Per-token logprobs are expected to be the same.
600
+ assert len(ref_logprobs) == len(spec_logprobs)
601
+ for ref_logprob, spec_logprob in zip(ref_logprobs, spec_logprobs):
602
+ assert math.isclose(ref_logprob.logprob, spec_logprob.logprob, abs_tol=1e-3)
603
+ assert ref_logprob.rank == spec_logprob.rank
604
+ assert ref_logprob.decoded_token == spec_logprob.decoded_token
tests/v1/sample/test_rejection_sampler.py
+85
-79
@@ -1,6 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
from typing import Any
4
+from unittest.mock import Mock
5
6
import pytest
7
import torch
@@ -11,6 +12,7 @@ from vllm.platforms import current_platform
12
from vllm.v1.sample.logits_processor import LogitsProcessors
13
from vllm.v1.sample.metadata import SamplingMetadata
14
from vllm.v1.sample.rejection_sampler import PLACEHOLDER_TOKEN_ID, RejectionSampler
15
+from vllm.v1.sample.sampler import Sampler, SamplerOutput
16
from vllm.v1.spec_decode.metadata import SpecDecodeMetadata
17
18
DEVICE = current_platform.device_type
@@ -18,7 +20,28 @@ DEVICE = current_platform.device_type
20
21
@pytest.fixture
22
def rejection_sampler():
21
- return RejectionSampler()
23
+ mock_sampler = Mock(spec=Sampler)
24
+ mock_sampler.logprobs_mode = "raw_logprobs"
25
+ return RejectionSampler(mock_sampler)
26
+
27
+
28
+def mock_sampler_output(
29
+ rejection_sampler: RejectionSampler, bonus_token_ids: torch.Tensor
30
+):
31
+ rejection_sampler.sampler.return_value = SamplerOutput(
32
+ sampled_token_ids=bonus_token_ids, logprobs_tensors=None
33
+ )
34
+
35
+
36
+def create_spec_decode_metadata(
37
+ spec_tokens: list[list[int]], logits: torch.Tensor
38
+) -> SpecDecodeMetadata:
39
+ metadata = SpecDecodeMetadata.make_dummy(spec_tokens, device=logits.device)
40
+ metadata.target_logits_indices = torch.arange(logits.shape[0])
41
+ # Output bonus token ids are mocked, so the bonus logit indices should
42
+ # be empty.
43
+ metadata.bonus_logits_indices = torch.empty(0, dtype=torch.int32)
44
+ return metadata
45
46
47
def create_logits_tensor(
@@ -111,19 +134,17 @@ def test_perfect_match(rejection_sampler):
134
metadata = create_sampling_metadata(all_greedy=True)
135
logits = create_logits_tensor(output_tokens)
136
bonus_token_tensor = torch.tensor([output_tokens[0][-1]], device=logits.device)
114
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
115
- spec_tokens, device=logits.device
116
- )
137
+ spec_decode_metadata = create_spec_decode_metadata(spec_tokens, logits)
138
139
+ mock_sampler_output(rejection_sampler, bonus_token_tensor)
140
output = rejection_sampler(
141
spec_decode_metadata,
142
draft_probs=None,
121
- target_logits=logits,
122
- bonus_token_ids=bonus_token_tensor,
143
+ logits=logits,
144
sampling_metadata=metadata,
145
)
146
expected = torch.tensor([[1, 2, 3, 4]], dtype=torch.int, device=logits.device)
126
- assert torch.equal(output, expected)
147
+ assert torch.equal(output.sampled_token_ids, expected)
148
149
150
def test_early_mismatch(rejection_sampler):
@@ -134,15 +155,13 @@ def test_early_mismatch(rejection_sampler):
155
metadata = create_sampling_metadata(all_greedy=True)
156
logits = create_logits_tensor(output_tokens)
157
bonus_token_tensor = torch.tensor([output_tokens[0][-1]], device=logits.device)
137
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
138
- spec_tokens, device=logits.device
139
- )
158
+ spec_decode_metadata = create_spec_decode_metadata(spec_tokens, logits)
159
160
+ mock_sampler_output(rejection_sampler, bonus_token_tensor)
161
output = rejection_sampler(
162
spec_decode_metadata,
163
draft_probs=None,
144
- target_logits=logits,
145
- bonus_token_ids=bonus_token_tensor,
164
+ logits=logits,
165
sampling_metadata=metadata,
166
)
167
expected = torch.tensor(
@@ -150,7 +169,7 @@ def test_early_mismatch(rejection_sampler):
169
dtype=torch.int,
170
device=logits.device,
171
)
153
- assert torch.equal(output, expected)
172
+ assert torch.equal(output.sampled_token_ids, expected)
173
174
175
def test_multiple_sequences(rejection_sampler):
@@ -163,21 +182,19 @@ def test_multiple_sequences(rejection_sampler):
182
bonus_token_tensor = torch.tensor(
183
[output_tokens[0][-1], output_tokens[1][-1]], device=logits.device
184
)
166
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
167
- spec_tokens, device=logits.device
168
- )
185
+ spec_decode_metadata = create_spec_decode_metadata(spec_tokens, logits)
186
187
+ mock_sampler_output(rejection_sampler, bonus_token_tensor)
188
output = rejection_sampler(
189
spec_decode_metadata,
190
draft_probs=None,
173
- target_logits=logits,
174
- bonus_token_ids=bonus_token_tensor,
191
+ logits=logits,
192
sampling_metadata=metadata,
193
)
194
expected = torch.tensor(
195
[[1, 2, 5], [3, 4, PLACEHOLDER_TOKEN_ID]], dtype=torch.int, device=logits.device
196
)
180
- assert torch.equal(output, expected)
197
+ assert torch.equal(output.sampled_token_ids, expected)
198
199
200
def test_single_token_sequence(rejection_sampler):
@@ -188,19 +205,17 @@ def test_single_token_sequence(rejection_sampler):
205
metadata = create_sampling_metadata(all_greedy=True)
206
logits = create_logits_tensor(output_tokens)
207
bonus_token_tensor = torch.tensor([output_tokens[0][-1]], device=logits.device)
191
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
192
- spec_tokens, device=logits.device
193
- )
208
+ spec_decode_metadata = create_spec_decode_metadata(spec_tokens, logits)
209
210
+ mock_sampler_output(rejection_sampler, bonus_token_tensor)
211
output = rejection_sampler(
212
spec_decode_metadata,
213
draft_probs=None,
198
- target_logits=logits,
199
- bonus_token_ids=bonus_token_tensor,
214
+ logits=logits,
215
sampling_metadata=metadata,
216
)
217
expected = torch.tensor([[1, 2]], dtype=torch.int, device=logits.device)
203
- assert torch.equal(output, expected)
218
+ assert torch.equal(output.sampled_token_ids, expected)
219
220
221
def test_empty_sequence(rejection_sampler):
@@ -211,19 +226,17 @@ def test_empty_sequence(rejection_sampler):
226
metadata = create_sampling_metadata(all_greedy=True)
227
logits = create_logits_tensor(output_tokens)
228
bonus_token_tensor = torch.tensor([output_tokens[0][-1]], device=logits.device)
214
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
215
- spec_tokens, device=logits.device
216
- )
229
+ spec_decode_metadata = create_spec_decode_metadata(spec_tokens, logits)
230
231
+ mock_sampler_output(rejection_sampler, bonus_token_tensor)
232
output = rejection_sampler(
233
spec_decode_metadata,
234
draft_probs=None,
221
- target_logits=logits,
222
- bonus_token_ids=bonus_token_tensor,
235
+ logits=logits,
236
sampling_metadata=metadata,
237
)
238
expected = torch.tensor([[5]], dtype=torch.int, device=logits.device)
226
- assert torch.equal(output, expected)
239
+ assert torch.equal(output.sampled_token_ids, expected)
240
241
242
def test_multiple_mismatches(rejection_sampler):
@@ -236,15 +249,13 @@ def test_multiple_mismatches(rejection_sampler):
249
bonus_token_tensor = torch.tensor(
250
[output_tokens[0][-1], output_tokens[1][-1]], device=logits.device
251
)
239
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
240
- spec_tokens, device=logits.device
241
- )
252
+ spec_decode_metadata = create_spec_decode_metadata(spec_tokens, logits)
253
254
+ mock_sampler_output(rejection_sampler, bonus_token_tensor)
255
output = rejection_sampler(
256
spec_decode_metadata,
257
draft_probs=None,
246
- target_logits=logits,
247
- bonus_token_ids=bonus_token_tensor,
258
+ logits=logits,
259
sampling_metadata=metadata,
260
)
261
expected = torch.tensor(
@@ -255,7 +266,7 @@ def test_multiple_mismatches(rejection_sampler):
266
dtype=torch.int,
267
device=logits.device,
268
)
258
- assert torch.equal(output, expected)
269
+ assert torch.equal(output.sampled_token_ids, expected)
270
271
272
@pytest.mark.parametrize(
@@ -277,19 +288,17 @@ def test_parametrized_cases(rejection_sampler, spec_tokens, output_tokens, expec
288
bonus_token_tensor = torch.tensor(
289
[tokens[-1] for tokens in output_tokens], device=logits.device
290
)
280
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
281
- spec_tokens, device=logits.device
282
- )
291
+ spec_decode_metadata = create_spec_decode_metadata(spec_tokens, logits)
292
293
+ mock_sampler_output(rejection_sampler, bonus_token_tensor)
294
output = rejection_sampler(
295
spec_decode_metadata,
296
draft_probs=None,
287
- target_logits=logits,
288
- bonus_token_ids=bonus_token_tensor,
297
+ logits=logits,
298
sampling_metadata=metadata,
299
)
300
expected_tensor = torch.tensor(expected, dtype=torch.int, device=logits.device)
292
- assert torch.equal(output, expected_tensor)
301
+ assert torch.equal(output.sampled_token_ids, expected_tensor)
302
303
304
########################### Tests for Random Sampling ###################
@@ -331,18 +340,19 @@ def test_deterministic_when_seeded(
340
sampling_metadata = create_sampling_metadata(
341
all_greedy=False, temperature=temperature, generators=seeded_seqs
342
)
334
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
335
- draft_token_ids.tolist(), device=DEVICE
343
+ spec_decode_metadata = create_spec_decode_metadata(
344
+ draft_token_ids.tolist(), target_logits
345
)
346
+
347
+ mock_sampler_output(rejection_sampler, bonus_token_ids)
348
rep_result = rejection_sampler(
349
spec_decode_metadata,
339
- draft_probs=draft_probs,
340
- target_logits=target_logits,
341
- bonus_token_ids=bonus_token_ids,
350
+ draft_probs=None,
351
+ logits=target_logits,
352
sampling_metadata=sampling_metadata,
353
)
354
345
- results.append(rep_result)
355
+ results.append(rep_result.sampled_token_ids)
356
357
for i in range(batch_size):
358
if seeded_mask[i]:
@@ -460,7 +470,9 @@ def estimate_rejection_sampling_pdf(
470
Returns:
471
Estimated probability distribution of the output tokens.
472
"""
463
- rejection_sampler = RejectionSampler()
473
+ mock_sampler = Mock(spec=Sampler)
474
+ mock_sampler.logprobs_mode = "raw_logprobs"
475
+ rejection_sampler = RejectionSampler(mock_sampler)
476
num_tokens = num_samples * k
477
# Repeat draft probs num_samples * k times.
478
draft_probs = draft_probs.reshape(1, 1, vocab_size).repeat(num_samples, k, 1)
@@ -483,17 +495,18 @@ def estimate_rejection_sampling_pdf(
495
sampling_metadata = create_sampling_metadata(
496
all_greedy=False, temperature=temperature
497
)
486
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
487
- draft_token_ids.tolist(), device=bonus_token_ids.device
498
+ spec_decode_metadata = create_spec_decode_metadata(
499
+ draft_token_ids.tolist(), target_logits
500
)
489
- output_token_ids = rejection_sampler(
501
+
502
+ mock_sampler_output(rejection_sampler, bonus_token_ids)
503
+ sampler_output = rejection_sampler(
504
spec_decode_metadata,
505
draft_probs=draft_probs,
492
- target_logits=target_logits,
493
- bonus_token_ids=bonus_token_ids,
506
+ logits=target_logits,
507
sampling_metadata=sampling_metadata,
508
)
496
- output_token_ids = output_token_ids[:, :-1].flatten()
509
+ output_token_ids = sampler_output.sampled_token_ids[:, :-1].flatten()
510
511
hist = torch.histogram(
512
output_token_ids.to(dtype=torch.float, device="cpu"),
@@ -532,22 +545,19 @@ def _test_masked_logits(
545
bonus_token_ids = torch.zeros((batch_size, 1), dtype=torch.int64, device=DEVICE)
546
547
# Create spec decode metadata
535
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
536
- draft_token_ids,
537
- device=DEVICE,
538
- )
548
+ spec_decode_metadata = create_spec_decode_metadata(draft_token_ids, target_logits)
549
550
# Run rejection sampling
541
- output_token_ids = rejection_sampler(
551
+ mock_sampler_output(rejection_sampler, bonus_token_ids)
552
+ output = rejection_sampler(
553
spec_decode_metadata,
554
draft_probs=draft_probs,
544
- target_logits=target_logits,
545
- bonus_token_ids=bonus_token_ids,
555
+ logits=target_logits,
556
sampling_metadata=sampling_metadata,
557
)
558
559
# Remove bonus tokens and reshape
550
- output_token_ids = output_token_ids[:, :-1].flatten().tolist()
560
+ output_token_ids = output.sampled_token_ids[:, :-1].flatten().tolist()
561
562
# Check that all sampled tokens are within the unmasked indices.
563
for i in range(num_tokens):
@@ -665,11 +675,11 @@ def test_frequency_penalties(rejection_sampler):
675
spec_decode_metadata = SpecDecodeMetadata.make_dummy(
676
spec_tokens, device=logits.device
677
)
678
+ mock_sampler_output(rejection_sampler, bonus_token_tensor)
679
output = rejection_sampler(
680
spec_decode_metadata,
681
draft_probs=None,
671
- target_logits=logits,
672
- bonus_token_ids=bonus_token_tensor,
682
+ logits=logits,
683
sampling_metadata=metadata,
684
)
685
expected = torch.tensor(
@@ -677,7 +687,7 @@ def test_frequency_penalties(rejection_sampler):
687
dtype=torch.int,
688
device=logits.device,
689
)
680
- assert torch.equal(output, expected)
690
+ assert torch.equal(output.sampled_token_ids, expected)
691
692
693
def test_bad_words(rejection_sampler):
@@ -707,14 +717,12 @@ def test_bad_words(rejection_sampler):
717
bonus_token_tensor = torch.tensor(
718
[output_tokens[i][-1] for i in range(len(output_tokens))], device=logits.device
719
)
710
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
711
- spec_tokens, device=logits.device
712
- )
720
+ spec_decode_metadata = create_spec_decode_metadata(spec_tokens, logits)
721
+ mock_sampler_output(rejection_sampler, bonus_token_tensor)
722
output = rejection_sampler(
723
spec_decode_metadata,
724
draft_probs=None,
716
- target_logits=logits,
717
- bonus_token_ids=bonus_token_tensor,
725
+ logits=logits,
726
sampling_metadata=metadata,
727
)
728
@@ -723,7 +731,7 @@ def test_bad_words(rejection_sampler):
731
dtype=torch.int,
732
device=logits.device,
733
)
726
- assert torch.equal(output, expected)
734
+ assert torch.equal(output.sampled_token_ids, expected)
735
736
737
def test_allowed_token_ids(rejection_sampler):
@@ -756,14 +764,12 @@ def test_allowed_token_ids(rejection_sampler):
764
bonus_token_tensor = torch.tensor(
765
[output_tokens[i][-1] for i in range(len(output_tokens))], device=logits.device
766
)
759
- spec_decode_metadata = SpecDecodeMetadata.make_dummy(
760
- spec_tokens, device=logits.device
761
- )
767
+ spec_decode_metadata = create_spec_decode_metadata(spec_tokens, logits)
768
+ mock_sampler_output(rejection_sampler, bonus_token_tensor)
769
output = rejection_sampler(
770
spec_decode_metadata,
771
draft_probs=None,
765
- target_logits=logits,
766
- bonus_token_ids=bonus_token_tensor,
772
+ logits=logits,
773
sampling_metadata=metadata,
774
)
775
@@ -772,4 +778,4 @@ def test_allowed_token_ids(rejection_sampler):
778
dtype=torch.int,
779
device=logits.device,
780
)
775
- assert torch.equal(output, expected)
781
+ assert torch.equal(output.sampled_token_ids, expected)
vllm/v1/engine/logprobs.py
+1
-1
@@ -66,7 +66,7 @@ class LogprobsProcessor:
66
assert self.logprobs is not None
67
assert self.cumulative_logprob is not None
68
69
- token_ids_lst, logprobs_lst, ranks_lst = logprobs_lists
69
+ token_ids_lst, logprobs_lst, ranks_lst, _ = logprobs_lists
70
71
for rank, logprobs, token_ids in zip(ranks_lst, logprobs_lst, token_ids_lst):
72
# Detokenize (non-incrementally).
vllm/v1/outputs.py
+24
-9
@@ -14,34 +14,49 @@ else:
14
15
16
class LogprobsLists(NamedTuple):
17
- # [num_reqs, max_num_logprobs + 1]
17
+ # [num_reqs x num_generated_tokens, max_num_logprobs + 1]
18
logprob_token_ids: list[list[int]]
19
- # [num_reqs, max_num_logprobs + 1]
19
+ # [num_reqs x num_generated_tokens, max_num_logprobs + 1]
20
logprobs: list[list[float]]
21
- # [num_reqs]
21
+ # [num_reqs x num_generated_tokens]
22
sampled_token_ranks: list[int]
23
-
24
- def slice(self, start: int, end: int):
23
+ # [num_reqs]
24
+ # Used for slicing the logprobs in cases like speculative
25
+ # decoding where the number of generated tokens may be
26
+ # different for each request.
27
+ cu_num_generated_tokens: list[int] | None = None
28
+
29
+ def slice(self, start_req_idx: int, end_req_idx: int):
30
+ if self.cu_num_generated_tokens:
31
+ start = self.cu_num_generated_tokens[start_req_idx]
32
+ end = self.cu_num_generated_tokens[end_req_idx]
33
+ else:
34
+ start = start_req_idx
35
+ end = end_req_idx
36
return LogprobsLists(
37
self.logprob_token_ids[start:end],
38
self.logprobs[start:end],
39
self.sampled_token_ranks[start:end],
40
+ self.cu_num_generated_tokens[start_req_idx:end_req_idx]
41
+ if self.cu_num_generated_tokens
42
+ else None,
43
)
44
45
46
class LogprobsTensors(NamedTuple):
33
- # [num_reqs, max_num_logprobs + 1]
47
+ # [num_reqs x num_generated_tokens, max_num_logprobs + 1]
48
logprob_token_ids: torch.Tensor
35
- # [num_reqs, max_num_logprobs + 1]
49
+ # [num_reqs x num_generated_tokens, max_num_logprobs + 1]
50
logprobs: torch.Tensor
37
- # [num_reqs]
51
+ # [num_reqs x num_generated_tokens]
52
selected_token_ranks: torch.Tensor
53
40
- def tolists(self):
54
+ def tolists(self, cu_num_generated_tokens: list[int] | None = None):
55
return LogprobsLists(
56
self.logprob_token_ids.tolist(),
57
self.logprobs.tolist(),
58
self.selected_token_ranks.tolist(),
59
+ cu_num_generated_tokens,
60
)
61
62
@staticmethod
vllm/v1/sample/rejection_sampler.py
+121
-39
@@ -1,15 +1,19 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
+from dataclasses import replace
5
+
6
import torch
7
import torch.nn as nn
8
9
from vllm.logger import init_logger
10
from vllm.triton_utils import tl, triton
11
+from vllm.v1.outputs import LogprobsTensors, SamplerOutput
12
from vllm.v1.sample.metadata import SamplingMetadata
13
from vllm.v1.sample.ops.bad_words import apply_bad_words_with_drafts
14
from vllm.v1.sample.ops.penalties import apply_all_penalties
15
from vllm.v1.sample.ops.topk_topp_sampler import apply_top_k_top_p
16
+from vllm.v1.sample.sampler import Sampler
17
from vllm.v1.spec_decode.metadata import SpecDecodeMetadata
18
19
logger = init_logger(__name__)
@@ -44,17 +48,22 @@ class RejectionSampler(nn.Module):
48
output tokens = accepted tokens + recovered tokens + bonus tokens
49
"""
50
51
+ def __init__(self, sampler: Sampler):
52
+ super().__init__()
53
+ self.sampler = sampler
54
+ logprobs_mode = self.sampler.logprobs_mode
55
+ self.is_processed_logprobs_mode = logprobs_mode.startswith("processed")
56
+ self.is_logits_logprobs_mode = logprobs_mode.endswith("logits")
57
+
58
def forward(
59
self,
60
metadata: SpecDecodeMetadata,
61
# [num_tokens, vocab_size]
62
draft_probs: torch.Tensor | None,
52
- # [num_tokens, vocab_size]
53
- target_logits: torch.Tensor,
54
- # [batch_size, 1]
55
- bonus_token_ids: torch.Tensor,
63
+ # [num_tokens + batch_size, vocab_size]
64
+ logits: torch.Tensor,
65
sampling_metadata: SamplingMetadata,
57
- ) -> torch.Tensor:
66
+ ) -> SamplerOutput:
67
"""
68
Args:
69
metadata:
@@ -63,43 +72,65 @@ class RejectionSampler(nn.Module):
72
Probability distribution for the draft tokens. Shape is
73
[num_tokens, vocab_size]. Can be None if probabilities are
74
not provided, which is the case for ngram spec decode.
66
- target_logits (torch.Tensor):
75
+ logits (torch.Tensor):
76
Target model's logits probability distribution.
68
- Shape is [num_tokens, vocab_size]. Here, probabilities from
69
- different requests are flattened into a single tensor because
70
- this is the shape of the output logits.
71
- NOTE: `target_logits` can be updated in place to save memory.
72
- bonus_token_ids (torch.Tensor):
73
- A tensor containing bonus tokens. Shape is [batch_size, 1].
74
- Bonus tokens are added to the end of the sequence if all
75
- proposed tokens are accepted. We generate the bonus tokens
76
- outside of the rejection sampler with the default sampling
77
- strategy. It allows for more flexibility in the sampling
78
- process such as top_p, top_k sampling.
77
+ Shape is [num_tokens + batch_size, vocab_size]. Here,
78
+ probabilities from different requests are flattened into a
79
+ single tensor because this is the shape of the output logits.
80
+ NOTE: `logits` can be updated in place to save memory.
81
sampling_metadata (vllm.v1.sample.metadata.SamplingMetadata):
82
Additional metadata needed for sampling, such as temperature,
83
top-k/top-p parameters, or other relevant information.
84
Returns:
83
- output_token_ids (torch.Tensor):
84
- A tensor containing the final output token IDs.
85
+ SamplerOutput:
86
+ Contains the final output token IDs and their logprobs if
87
+ requested.
88
"""
89
assert metadata.max_spec_len <= MAX_SPEC_LEN
90
88
- # Use float32 for the target_logits.
89
- target_logits = target_logits.to(torch.float32)
91
+ bonus_logits_indices = metadata.bonus_logits_indices
92
+ target_logits_indices = metadata.target_logits_indices
93
+
94
+ # When indexing with a tensor (bonus_logits_indices), PyTorch
95
+ # creates a new tensor with separate storage from the original
96
+ # logits tensor. This means any in-place operations on bonus_logits
97
+ # won't affect the original logits tensor.
98
+ assert logits is not None
99
+ bonus_logits = logits[bonus_logits_indices]
100
+ bonus_sampler_output = self.sampler(
101
+ logits=bonus_logits,
102
+ sampling_metadata=replace(
103
+ sampling_metadata,
104
+ max_num_logprobs=-1,
105
+ ),
106
+ predict_bonus_token=True,
107
+ # Override the logprobs mode to return logits because they are
108
+ # needed later to compute the accepted token logprobs.
109
+ logprobs_mode_override="processed_logits"
110
+ if self.is_processed_logprobs_mode
111
+ else "raw_logits",
112
+ )
113
+ bonus_token_ids = bonus_sampler_output.sampled_token_ids
114
115
+ # Just like `bonus_logits`, `target_logits` is a new tensor with
116
+ # separate storage from the original `logits` tensor. Therefore,
117
+ # it is safe to update `target_logits` in place.
118
+ raw_target_logits = logits[target_logits_indices]
119
+ # Use float32 for the target_logits.
120
+ raw_target_logits = raw_target_logits.to(torch.float32)
121
target_logits = self.apply_logits_processors(
92
- target_logits, sampling_metadata, metadata
122
+ raw_target_logits, sampling_metadata, metadata
123
)
94
-
124
# [num_tokens, vocab_size]
125
# NOTE(woosuk): `target_logits` can be updated in place inside the
97
- # `compute_probs` function.
98
- target_probs = compute_probs(
126
+ # `apply_sampling_constraints` function.
127
+ target_logits = apply_sampling_constraints(
128
target_logits,
129
metadata.cu_num_draft_tokens,
130
sampling_metadata,
131
)
132
+ # Compute probability distribution from target logits.
133
+ target_probs = target_logits.softmax(dim=-1, dtype=torch.float32)
134
135
output_token_ids = rejection_sample(
136
metadata.draft_token_ids,
@@ -111,7 +142,63 @@ class RejectionSampler(nn.Module):
142
bonus_token_ids,
143
sampling_metadata,
144
)
114
- return output_token_ids
145
+
146
+ logprobs_tensors = None
147
+ if sampling_metadata.max_num_logprobs:
148
+ logprobs_tensors = self._get_logprobs_tensors(
149
+ sampling_metadata.max_num_logprobs,
150
+ metadata,
151
+ logits,
152
+ target_logits if self.is_processed_logprobs_mode else raw_target_logits,
153
+ bonus_sampler_output.logprobs_tensors.logprobs,
154
+ output_token_ids,
155
+ )
156
+
157
+ return SamplerOutput(
158
+ sampled_token_ids=output_token_ids,
159
+ logprobs_tensors=logprobs_tensors,
160
+ )
161
+
162
+ def _get_logprobs_tensors(
163
+ self,
164
+ max_num_logprobs: int,
165
+ metadata: SpecDecodeMetadata,
166
+ logits: torch.Tensor,
167
+ target_logits: torch.Tensor,
168
+ bonus_logits: torch.Tensor,
169
+ sampled_token_ids: torch.Tensor,
170
+ ) -> LogprobsTensors:
171
+ cu_num_sampled_tokens = torch.zeros_like(metadata.cu_num_sampled_tokens)
172
+ cu_num_sampled_tokens[1:] = metadata.cu_num_sampled_tokens[:-1]
173
+
174
+ # Collect target and bonus logits.
175
+ bonus_logits_indices = metadata.bonus_logits_indices
176
+ target_logits_indices = metadata.target_logits_indices
177
+ final_logits = torch.zeros_like(logits, dtype=torch.float32)
178
+ final_logits[target_logits_indices] = target_logits.to(torch.float32)
179
+ final_logits[bonus_logits_indices] = bonus_logits.to(torch.float32)
180
+
181
+ # Compute accepted token indices.
182
+ accepted_mask = sampled_token_ids != PLACEHOLDER_TOKEN_ID
183
+ num_accepted_tokens = accepted_mask.sum(dim=-1)
184
+ accepted_logit_indices = accepted_mask.nonzero(as_tuple=True)[1]
185
+ accepted_logit_indices += cu_num_sampled_tokens.repeat_interleave(
186
+ num_accepted_tokens
187
+ )
188
+
189
+ # Compute logprobs for accepted tokens.
190
+ accepted_logits = final_logits[accepted_logit_indices]
191
+ accepted_logprobs = (
192
+ accepted_logits
193
+ if self.is_logits_logprobs_mode
194
+ else self.sampler.compute_logprobs(accepted_logits)
195
+ )
196
+ accepted_tokens = sampled_token_ids[accepted_mask]
197
+ return self.sampler.gather_logprobs(
198
+ accepted_logprobs,
199
+ max_num_logprobs,
200
+ accepted_tokens.to(torch.int64),
201
+ )
202
203
@staticmethod
204
def parse_output(
@@ -119,14 +206,12 @@ class RejectionSampler(nn.Module):
206
vocab_size: int,
207
) -> list[list[int]]:
208
"""Parse the output of the rejection sampler.
122
-
209
Args:
210
output_token_ids: The sampled token IDs in shape
211
[batch_size, max_spec_len + 1]. The rejected tokens are
212
replaced with `PLACEHOLDER_TOKEN_ID` by the rejection sampler
213
and will be filtered out in this function.
214
vocab_size: The size of the vocabulary.
129
-
215
Returns:
216
A list of lists of token IDs.
217
"""
@@ -328,27 +413,26 @@ def rejection_sample(
413
return output_token_ids
414
415
331
-def compute_probs(
416
+def apply_sampling_constraints(
417
logits: torch.Tensor, # [num_tokens, vocab_size]
418
cu_num_draft_tokens: torch.Tensor, # [batch_size]
419
sampling_metadata: SamplingMetadata,
420
) -> torch.Tensor:
336
- """Compute probability distribution from logits based on sampling metadata.
421
+ """Process logits based on sampling metadata.
422
338
- This function applies temperature scaling to the logits and converts
339
- them to probabilities using softmax. For greedy decoding, it returns
423
+ This function applies temperature scaling to the logits,
424
+ as well as top-k and top-p. For greedy decoding, it returns
425
the original logits.
426
427
Args:
343
- logits: Input logits tensor to be converted to probabilities.
428
+ logits: Input logits tensor to be processed.
429
cu_num_draft_tokens: Cumulative number of draft tokens.
430
sampling_metadata: Metadata containing sampling parameters such as
431
temperature and whether greedy sampling is used.
432
433
Returns:
349
- torch.Tensor: Probability distribution (softmax of scaled logits)
350
- if non-greedy sampling is used, otherwise returns the
351
- original logits.
434
+ torch.Tensor: Processed logits if non-greedy sampling is used,
435
+ otherwise returns the original logits.
436
"""
437
assert logits.ndim == 2
438
assert cu_num_draft_tokens.ndim == 1
@@ -384,9 +468,7 @@ def compute_probs(
468
469
# NOTE(woosuk): `apply_top_k_top_p` uses sorting to calculate the mask,
470
# which is slow for large vocab sizes. This may cause performance issues.
387
- logits = apply_top_k_top_p(logits, top_k, top_p)
388
- output_prob = logits.softmax(dim=-1, dtype=torch.float32)
389
- return output_prob
471
+ return apply_top_k_top_p(logits, top_k, top_p)
472
473
474
def expand_batch_to_tokens(
vllm/v1/sample/sampler.py
+20
-11
@@ -69,16 +69,18 @@ class Sampler(nn.Module):
69
logits: torch.Tensor,
70
sampling_metadata: SamplingMetadata,
71
predict_bonus_token: bool = False,
72
+ logprobs_mode_override: LogprobsMode | None = None,
73
) -> SamplerOutput:
74
+ logprobs_mode = logprobs_mode_override or self.logprobs_mode
75
# NOTE(woosuk): Use the original logits (before any penalties or
76
# temperature scaling) for the top-k logprobs.
77
# This is different from the V0 sampler, which uses the logits that
78
# is used for sampling (after penalties and temperature scaling).
79
num_logprobs = sampling_metadata.max_num_logprobs
80
if num_logprobs is not None:
79
- if self.logprobs_mode == "raw_logprobs":
81
+ if logprobs_mode == "raw_logprobs":
82
raw_logprobs = self.compute_logprobs(logits)
81
- elif self.logprobs_mode == "raw_logits":
83
+ elif logprobs_mode == "raw_logits":
84
raw_logprobs = logits.clone()
85
86
# Use float32 for the logits.
@@ -97,13 +99,18 @@ class Sampler(nn.Module):
99
# return int32 (while PyTorch argmax and topk return int64).
100
sampled = sampled.long()
101
100
- # Gather the logprobs of the topk and sampled token (if requested).
101
- # Get logprobs and rank tensors (if requested)
102
- logprobs_tensors = (
103
- None
104
- if num_logprobs is None
105
- else self.gather_logprobs(raw_logprobs, num_logprobs, token_ids=sampled)
106
- )
102
+ if num_logprobs is None:
103
+ logprobs_tensors = None
104
+ elif num_logprobs == -1:
105
+ # Return the full unsorted and unranked logprobs.
106
+ logprobs_tensors = LogprobsTensors(
107
+ torch.empty(0), raw_logprobs, torch.empty(0)
108
+ )
109
+ else:
110
+ # Gather the logprobs and ranks of the topk and sampled token.
111
+ logprobs_tensors = self.gather_logprobs(
112
+ raw_logprobs, num_logprobs, token_ids=sampled
113
+ )
114
115
# Use int32 to reduce the tensor size.
116
sampled = sampled.to(torch.int32)
@@ -138,6 +145,7 @@ class Sampler(nn.Module):
145
self,
146
logits: torch.Tensor,
147
sampling_metadata: SamplingMetadata,
148
+ logprobs_mode_override: LogprobsMode | None = None,
149
) -> tuple[torch.Tensor, torch.Tensor | None]:
150
"""Sample logits based on sampling metadata.
151
@@ -145,6 +153,7 @@ class Sampler(nn.Module):
153
may update the logits tensor in-place.
154
"""
155
156
+ logprobs_mode = logprobs_mode_override or self.logprobs_mode
157
assert not (sampling_metadata.all_greedy and sampling_metadata.all_random)
158
if sampling_metadata.all_random:
159
greedy_sampled = None
@@ -153,9 +162,9 @@ class Sampler(nn.Module):
162
if sampling_metadata.all_greedy:
163
processed_logprobs = None
164
if sampling_metadata.max_num_logprobs is not None:
156
- if self.logprobs_mode == "processed_logits":
165
+ if logprobs_mode == "processed_logits":
166
processed_logprobs = logits
158
- elif self.logprobs_mode == "processed_logprobs":
167
+ elif logprobs_mode == "processed_logprobs":
168
processed_logprobs = self.compute_logprobs(logits)
169
return greedy_sampled, processed_logprobs
170
vllm/v1/spec_decode/metadata.py
+8
@@ -14,6 +14,8 @@ class SpecDecodeMetadata:
14
num_draft_tokens: list[int]
15
# [batch_size]
16
cu_num_draft_tokens: torch.Tensor
17
+ # [batch_size]
18
+ cu_num_sampled_tokens: torch.Tensor
19
# [num_tokens]
20
target_logits_indices: torch.Tensor
21
# [batch_size]
@@ -32,6 +34,7 @@ class SpecDecodeMetadata:
34
) -> "SpecDecodeMetadata":
35
batch_size = len(draft_token_ids)
36
num_draft_tokens = [len(ids) for ids in draft_token_ids]
37
+ num_sampled_tokens = [len(ids) + 1 for ids in draft_token_ids]
38
flattened_draft_token_ids = sum(draft_token_ids, [])
39
num_tokens = len(flattened_draft_token_ids)
40
@@ -40,6 +43,10 @@ class SpecDecodeMetadata:
43
)
44
cu_num_draft_tokens = np.cumsum(num_draft_tokens, dtype=np.int32)
45
cu_num_draft_tokens_tensor = torch.from_numpy(cu_num_draft_tokens).to(device)
46
+ cu_num_sampled_tokens = np.cumsum(num_sampled_tokens, dtype=np.int32)
47
+ cu_num_sampled_tokens_tensor = torch.from_numpy(cu_num_sampled_tokens).to(
48
+ device
49
+ )
50
51
target_logits_indices = torch.zeros(
52
num_tokens, dtype=torch.int32, device=device
@@ -52,6 +59,7 @@ class SpecDecodeMetadata:
59
draft_token_ids=draft_token_ids_tensor,
60
num_draft_tokens=num_draft_tokens,
61
cu_num_draft_tokens=cu_num_draft_tokens_tensor,
62
+ cu_num_sampled_tokens=cu_num_sampled_tokens_tensor,
63
target_logits_indices=target_logits_indices,
64
bonus_logits_indices=bonus_logits_indices,
65
logits_indices=logits_indices,
vllm/v1/worker/gpu_model_runner.py
+40
-48
@@ -327,7 +327,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
327
"Unknown speculative decoding method: "
328
f"{self.speculative_config.method}"
329
)
330
- self.rejection_sampler = RejectionSampler()
330
+ self.rejection_sampler = RejectionSampler(self.sampler)
331
332
# Request states.
333
self.requests: dict[str, CachedRequestState] = {}
@@ -1624,6 +1624,9 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1624
cu_num_draft_tokens = torch.from_numpy(cu_num_draft_tokens).to(
1625
self.device, non_blocking=True
1626
)
1627
+ cu_num_sampled_tokens = torch.from_numpy(cu_num_sampled_tokens).to(
1628
+ self.device, non_blocking=True
1629
+ )
1630
logits_indices = torch.from_numpy(logits_indices).to(
1631
self.device, non_blocking=True
1632
)
@@ -1639,15 +1642,15 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1642
draft_token_ids = self.input_ids.gpu[logits_indices]
1643
draft_token_ids = draft_token_ids[target_logits_indices + 1]
1644
1642
- metadata = SpecDecodeMetadata(
1645
+ return SpecDecodeMetadata(
1646
draft_token_ids=draft_token_ids,
1647
num_draft_tokens=num_draft_tokens.tolist(),
1648
cu_num_draft_tokens=cu_num_draft_tokens,
1649
+ cu_num_sampled_tokens=cu_num_sampled_tokens,
1650
target_logits_indices=target_logits_indices,
1651
bonus_logits_indices=bonus_logits_indices,
1652
logits_indices=logits_indices,
1653
)
1650
- return metadata
1654
1655
def _prepare_kv_sharing_fast_prefill(
1656
self,
@@ -2221,32 +2224,13 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2224
sampling_metadata=sampling_metadata,
2225
)
2226
2224
- # When indexing with a tensor (bonus_logits_indices), PyTorch
2225
- # creates a new tensor with separate storage from the original
2226
- # logits tensor. This means any in-place operations on bonus_logits
2227
- # won't affect the original logits tensor.
2228
- assert logits is not None
2229
- bonus_logits = logits[spec_decode_metadata.bonus_logits_indices]
2230
- sampler_output = self.sampler(
2231
- logits=bonus_logits,
2232
- sampling_metadata=sampling_metadata,
2233
- predict_bonus_token=True,
2234
- )
2235
- bonus_token_ids = sampler_output.sampled_token_ids
2236
-
2237
- # Just like `bonus_logits`, `target_logits` is a new tensor with
2238
- # separate storage from the original `logits` tensor. Therefore,
2239
- # it is safe to update `target_logits` in place.
2240
- target_logits = logits[spec_decode_metadata.target_logits_indices]
2241
- output_token_ids = self.rejection_sampler(
2227
+ sampler_output = self.rejection_sampler(
2228
spec_decode_metadata,
2229
None, # draft_probs
2244
- target_logits,
2245
- bonus_token_ids,
2230
+ logits,
2231
sampling_metadata,
2232
)
2248
- sampler_output.sampled_token_ids = output_token_ids
2249
- self._update_states_after_model_execute(output_token_ids)
2233
+ self._update_states_after_model_execute(sampler_output.sampled_token_ids)
2234
return sampler_output
2235
2236
def _bookkeeping_sync(
@@ -2256,6 +2240,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2240
logits: torch.Tensor | None,
2241
hidden_states: torch.Tensor,
2242
num_scheduled_tokens: int,
2243
+ spec_decode_metadata: SpecDecodeMetadata | None,
2244
) -> tuple[
2245
dict[str, int],
2246
LogprobsLists | None,
@@ -2282,19 +2267,6 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2267
req_ids_output_copy = self.input_batch.req_ids.copy()
2268
req_id_to_index_output_copy = self.input_batch.req_id_to_index.copy()
2269
2285
- # NOTE: GPU -> CPU Sync happens here.
2286
- # Move as many CPU operations as possible before this sync point.
2287
- logprobs_tensors = sampler_output.logprobs_tensors
2288
- logprobs_lists = (
2289
- logprobs_tensors.tolists() if logprobs_tensors is not None else None
2290
- )
2291
-
2292
- # Compute prompt logprobs if needed.
2293
- prompt_logprobs_dict = self._get_prompt_logprobs_dict(
2294
- hidden_states[:num_scheduled_tokens],
2295
- scheduler_output.num_scheduled_tokens,
2296
- )
2297
-
2270
num_sampled_tokens = sampler_output.sampled_token_ids.shape[0]
2271
sampled_token_ids = sampler_output.sampled_token_ids
2272
invalid_req_indices = []
@@ -2335,6 +2307,10 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2307
# the sampled tokens back, because there's no direct communication
2308
# between the first-stage worker and the last-stage worker.
2309
req_ids = self.input_batch.req_ids
2310
+ logprobs_tensors = sampler_output.logprobs_tensors
2311
+ cu_num_accepted_tokens = (
2312
+ [0] if spec_decode_metadata and logprobs_tensors else None
2313
+ )
2314
for req_idx in range(num_sampled_tokens):
2315
if self.use_async_scheduling:
2316
sampled_ids = [-1] if req_idx not in invalid_req_indices_set else None
@@ -2360,6 +2336,25 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2336
req_state = self.requests[req_id]
2337
req_state.output_token_ids.extend(sampled_ids)
2338
2339
+ if cu_num_accepted_tokens is not None:
2340
+ cu_num_accepted_tokens.append(
2341
+ cu_num_accepted_tokens[-1] + len(sampled_ids)
2342
+ )
2343
+
2344
+ # NOTE: GPU -> CPU Sync happens here.
2345
+ # Move as many CPU operations as possible before this sync point.
2346
+ logprobs_lists = (
2347
+ logprobs_tensors.tolists(cu_num_accepted_tokens)
2348
+ if logprobs_tensors is not None
2349
+ else None
2350
+ )
2351
+
2352
+ # Compute prompt logprobs if needed.
2353
+ prompt_logprobs_dict = self._get_prompt_logprobs_dict(
2354
+ hidden_states[:num_scheduled_tokens],
2355
+ scheduler_output.num_scheduled_tokens,
2356
+ )
2357
+
2358
return (
2359
num_nans_in_logits,
2360
logprobs_lists,
@@ -2644,6 +2639,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2639
logits,
2640
hidden_states,
2641
num_scheduled_tokens,
2642
+ spec_decode_metadata,
2643
)
2644
2645
if (
@@ -3560,20 +3556,16 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3556
# num_tokens, logits.shape[-1], device=self.device,
3557
# dtype=logits.dtype)
3558
draft_probs = None
3563
- target_logits = torch.randn(
3564
- num_tokens, logits.shape[-1], device=self.device, dtype=logits.dtype
3565
- )
3566
- # NOTE(woosuk): Here, we should use int32 because the sampler uses
3567
- # int32 for bonus_token_ids. If the dtype mismatches, re-compilation
3568
- # will occur at runtime.
3569
- bonus_token_ids = torch.zeros(
3570
- num_reqs, device=self.device, dtype=torch.int32
3559
+ logits = torch.randn(
3560
+ num_tokens + num_reqs,
3561
+ logits.shape[-1],
3562
+ device=self.device,
3563
+ dtype=logits.dtype,
3564
)
3565
self.rejection_sampler(
3566
dummy_spec_decode_metadata,
3567
draft_probs,
3575
- target_logits,
3576
- bonus_token_ids,
3568
+ logits,
3569
dummy_metadata,
3570
)
3571
return sampler_output