main
py 89 lines 3.31 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Unit tests for LogprobsProcessor.
4
5 These tests exercise the truncation invariant that the MRV2 sampler relies
6 on: when the sampler returns a row wider than a request's own
7 `num_logprobs + 1` (because another request in the batch needed a wider
8 row), the trailing positions are populated with sentinel values
9 (`token_id=0`, `logprob=-inf`). LogprobsProcessor must read only the first
10 `num_logprobs + 1` entries so those sentinels never reach the user.
11 """
12
13 import numpy as np
14 import torch
15
16 from vllm.logprobs import create_sample_logprobs
17 from vllm.v1.engine import EngineCoreOutput
18 from vllm.v1.engine.logprobs import LogprobsProcessor
19 from vllm.v1.outputs import LogprobsLists
20
21
22 def _make_processor(num_logprobs: int) -> LogprobsProcessor:
23 return LogprobsProcessor(
24 tokenizer=None,
25 logprobs=create_sample_logprobs(flat_logprobs=False),
26 prompt_logprobs=None,
27 cumulative_logprob=0.0,
28 num_logprobs=num_logprobs,
29 num_prompt_logprobs=None,
30 )
31
32
33 def test_drops_trailing_sentinel_columns():
34 """A request that asked for 3 custom token logprobs but ended up in a
35 batch padded to width 5 must not surface the trailing -inf entries."""
36 processor = _make_processor(num_logprobs=3)
37
38 sampled = 42
39 # Layout: [sampled, custom_1, custom_2, custom_3, SENTINEL, SENTINEL]
40 # Use float32-exact values so cumulative_logprob compares cleanly.
41 token_ids = np.array([[sampled, 100, 200, 300, 0, 0]], dtype=np.int32)
42 logprobs = np.array([[-0.5, -1.0, -2.0, -3.0, -np.inf, -np.inf]], dtype=np.float32)
43 ranks = np.array([1], dtype=np.int32)
44
45 processor._update_sample_logprobs(LogprobsLists(token_ids, logprobs, ranks))
46
47 assert len(processor.logprobs) == 1
48 pos = processor.logprobs[0]
49 # Exactly sampled + 3 requested tokens; trailing sentinels dropped.
50 assert set(pos.keys()) == {sampled, 100, 200, 300}
51 assert 0 not in pos
52 assert all(np.isfinite(lp.logprob) for lp in pos.values())
53 # cumulative_logprob comes from the sampled token's logprob only.
54 assert processor.cumulative_logprob == -0.5
55
56
57 def test_accepts_exactly_sized_row():
58 """When the row is exactly num_logprobs+1, no truncation needed."""
59 processor = _make_processor(num_logprobs=2)
60
61 token_ids = np.array([[7, 11, 13]], dtype=np.int32)
62 logprobs = np.array([[-0.5, -1.5, -2.5]], dtype=np.float32)
63 ranks = np.array([1], dtype=np.int32)
64
65 processor._update_sample_logprobs(LogprobsLists(token_ids, logprobs, ranks))
66
67 pos = processor.logprobs[0]
68 assert set(pos.keys()) == {7, 11, 13}
69
70
71 def test_prompt_token_id_logprobs_are_popped_once():
72 """DELTA outputs carry fixed-ID prompt scores exactly once."""
73 processor = _make_processor(num_logprobs=1)
74 assert processor.pop_prompt_token_id_logprobs() is None
75
76 processor.update_from_output(
77 EngineCoreOutput(
78 request_id="req-0",
79 new_token_ids=[],
80 prompt_token_id_logprobs=torch.tensor(
81 [[-0.5, -1.5], [-2.5, -3.5]], dtype=torch.float32
82 ),
83 )
84 )
85
86 scores = processor.pop_prompt_token_id_logprobs()
87 assert scores is not None
88 assert scores.tolist() == [[-0.5, -1.5], [-2.5, -3.5]]
89 assert processor.pop_prompt_token_id_logprobs() is None