@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / ef64044079
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 15, 19:45 UTC

[V1] Prompt logprobs + APC compatibility; prompt logprobs reqs cannot fill APC (#13949)

afeldman-nm committed Mar 7, 2025 at 20:48 UTC ef640440791a461a181e6d497965701462c166b3
9 files changed +292 -162
tests/v1/core/test_prefix_caching.py
+110 -2
@@ -1,6 +1,8 @@
1 # SPDX-License-Identifier: Apache-2.0
2 """Compare the with and without prefix caching."""
3
4 +from typing import Optional
5 +
6 import pytest
7
8 from vllm.multimodal.inputs import MultiModalKwargs, PlaceholderRange
@@ -15,7 +17,8 @@ from vllm.v1.core.kv_cache_utils import (BlockHashType, KVCacheBlock,
17 def make_request(request_id,
18 prompt_token_ids,
19 mm_positions=None,
18 - mm_hashes=None):
20 + mm_hashes=None,
21 + prompt_logprobs: Optional[int] = None):
22 if mm_positions is None:
23 multi_modal_inputs = None
24 else:
@@ -28,7 +31,8 @@ def make_request(request_id,
31 multi_modal_inputs=multi_modal_inputs,
32 multi_modal_hashes=mm_hashes,
33 multi_modal_placeholders=mm_positions,
31 - sampling_params=SamplingParams(max_tokens=17),
34 + sampling_params=SamplingParams(max_tokens=17,
35 + prompt_logprobs=prompt_logprobs),
36 eos_token_id=100,
37 arrival_time=0,
38 lora_request=None,
@@ -144,6 +148,110 @@ def test_prefill():
148 assert manager.block_pool.free_block_queue.free_list_tail is None
149
150
151 +def test_prefill_plp():
152 + '''Test prefill with APC and some prompt logprobs (plp) requests.
153 +
154 + 1. Schedule plp request and validate APC block allocation
155 + 2. Schedule non-plp request and validate blocks
156 + 3. Schedule plp request; no hit should occur; validate blocks
157 + '''
158 + manager = KVCacheManager(
159 + block_size=16,
160 + num_gpu_blocks=10,
161 + max_model_len=8192,
162 + sliding_window=None,
163 + enable_caching=True,
164 + num_preallocate_tokens=16,
165 + )
166 +
167 + # Complete 3 blocks (48 tokens)
168 + common_token_ids = [i for i in range(3) for _ in range(16)]
169 +
170 + # Request #0 is a prompt logprobs request
171 + # Fully cache miss
172 + # Incomplete 1 block (7 tokens)
173 + unique_token_ids = [3] * 7
174 + all_token_ids = common_token_ids + unique_token_ids
175 + req0 = make_request("0", all_token_ids, prompt_logprobs=5)
176 + computed_blocks, num_computed_tokens = manager.get_computed_blocks(req0)
177 + assert len(manager.req_to_block_hashes[req0.request_id]) == 3
178 + assert not computed_blocks
179 + assert num_computed_tokens == 0
180 + blocks = manager.allocate_slots(req0, 55, computed_blocks)
181 + assert [b.block_id for b in blocks] == [0, 1, 2, 3, 4]
182 + req0_block_hashes = [b.block_hash for b in blocks]
183 +
184 + # Check full block metadata
185 + parent_block_hash = None
186 + for block_id in (0, 1, 2):
187 + block_tokens = tuple(all_token_ids[block_id * 16:(block_id + 1) * 16])
188 + block_hash = hash_block_tokens(parent_block_hash, block_tokens)
189 + assert manager.block_pool.blocks[block_id].block_hash == block_hash
190 + assert manager.block_pool.blocks[block_id].ref_cnt == 1
191 + parent_block_hash = block_hash.hash_value
192 +
193 + # Check partial/preallocated block metadata
194 + for block_id in (3, 4):
195 + assert manager.block_pool.blocks[block_id].block_hash is None
196 + assert manager.block_pool.blocks[block_id].ref_cnt == 1
197 +
198 + # Request #1 is a non-prompt-logprobs request:
199 + # Cache hit in the common prefix when the original block is still in use.
200 + # Incomplete 1 block (5 tokens)
201 + unique_token_ids = [3] * 5
202 + req1 = make_request("1", common_token_ids + unique_token_ids)
203 + computed_blocks, num_computed_tokens = manager.get_computed_blocks(req1)
204 + assert len(manager.req_to_block_hashes[req1.request_id]) == 3
205 + assert [b.block_id for b in computed_blocks] == [0, 1, 2]
206 + assert num_computed_tokens == 3 * 16
207 + num_new_tokens = 53 - 3 * 16
208 + blocks = manager.allocate_slots(req1, num_new_tokens, computed_blocks)
209 + assert [b.block_id for b in blocks] == [5, 6]
210 + for block in computed_blocks:
211 + assert block.ref_cnt == 2
212 +
213 + # At this point, we should have 3 free blocks left.
214 + assert manager.block_pool.free_block_queue.num_free_blocks == 3
215 +
216 + manager.free(req0)
217 + manager.free(req1)
218 +
219 + # All blocks should be available.
220 + assert manager.block_pool.free_block_queue.num_free_blocks == 10
221 + # The order should be
222 + # [unallocated (7, 8, 9)]
223 + # [unique_req0 (4, 3)]
224 + # [unique_req1 (6, 5)]
225 + # [common (2, 1, 0)]
226 + assert [
227 + b.block_id
228 + for b in manager.block_pool.free_block_queue.get_all_free_blocks()
229 + ] == [7, 8, 9, 4, 3, 6, 5, 2, 1, 0]
230 +
231 + # Request #2 is a prompt-logprobs request:
232 + # NO cache hit in the common prefix; duplicates request #0 cached blocks
233 + unique_token_ids = [3] * 6
234 + req2 = make_request("2",
235 + common_token_ids + unique_token_ids,
236 + prompt_logprobs=5)
237 + computed_blocks, num_computed_tokens = manager.get_computed_blocks(req2)
238 + assert len(manager.req_to_block_hashes[req2.request_id]) == 3
239 + assert not computed_blocks
240 + assert num_computed_tokens == 0
241 + blocks = manager.allocate_slots(req2, 55, computed_blocks)
242 + block_ids = [b.block_id for b in blocks]
243 + # Duplicate cached blocks have different ids but same hashes vs request #0
244 + assert [b.block_hash for b in blocks] == req0_block_hashes
245 + assert block_ids != [0, 1, 2, 3, 4]
246 +
247 + # Request #2 block hashes are valid since request #0 hashes are.
248 + # Check block reference counts.
249 + for block_id in block_ids:
250 + assert manager.block_pool.blocks[block_id].ref_cnt == 1
251 +
252 + manager.free(req2)
253 +
254 +
255 def test_decode():
256 manager = KVCacheManager(
257 block_size=16,
tests/v1/core/test_scheduler.py
+49 -12
@@ -1,6 +1,8 @@
1 # SPDX-License-Identifier: Apache-2.0
2 from typing import Optional
3
4 +import pytest
5 +
6 from vllm.config import CacheConfig, ModelConfig, SchedulerConfig, VllmConfig
7 from vllm.multimodal.inputs import MultiModalKwargs, PlaceholderRange
8 from vllm.sampling_params import SamplingParams
@@ -16,7 +18,21 @@ def create_scheduler(
18 model: str = "facebook/opt-125m",
19 max_num_seqs: int = 16,
20 max_num_batched_tokens: int = 8192,
21 + enable_prefix_caching: Optional[bool] = None,
22 ) -> Scheduler:
23 + '''Create scheduler under test.
24 +
25 + Args:
26 + model: model under test
27 + max_num_seqs: max sequences to schedule
28 + max_num_batch_tokens: max num tokens to batch
29 + enable_prefix_caching: optionally force APC config
30 + (True/False) or use default
31 + (None)
32 +
33 + Returns:
34 + :class:`Scheduler` instance
35 + '''
36 scheduler_config = SchedulerConfig(
37 max_num_seqs=max_num_seqs,
38 max_num_batched_tokens=max_num_batched_tokens,
@@ -31,11 +47,16 @@ def create_scheduler(
47 dtype="float16",
48 seed=42,
49 )
50 + # Cache config, optionally force APC
51 + kwargs_cache = ({} if enable_prefix_caching is None else {
52 + 'enable_prefix_caching': enable_prefix_caching
53 + })
54 cache_config = CacheConfig(
55 block_size=16,
56 gpu_memory_utilization=0.9,
57 swap_space=0,
58 cache_dtype="auto",
59 + **kwargs_cache,
60 )
61 vllm_config = VllmConfig(
62 scheduler_config=scheduler_config,
@@ -54,16 +75,16 @@ def create_scheduler(
75 )
76
77
57 -def create_requests(
58 - num_requests: int,
59 - num_tokens: int = 10,
60 - mm_positions: Optional[list[PlaceholderRange]] = None,
61 - max_tokens: int = 16,
62 - stop_token_ids: Optional[list[int]] = None,
63 -):
78 +def create_requests(num_requests: int,
79 + num_tokens: int = 10,
80 + mm_positions: Optional[list[PlaceholderRange]] = None,
81 + max_tokens: int = 16,
82 + stop_token_ids: Optional[list[int]] = None,
83 + prompt_logprobs: Optional[int] = None):
84 sampling_params = SamplingParams(ignore_eos=False,
85 max_tokens=max_tokens,
66 - stop_token_ids=stop_token_ids)
86 + stop_token_ids=stop_token_ids,
87 + prompt_logprobs=prompt_logprobs)
88 requests = []
89 for i in range(num_requests):
90 if mm_positions is not None:
@@ -122,9 +143,18 @@ def test_get_num_unfinished_requests():
143 assert scheduler.get_num_unfinished_requests() == len(requests) - i - 1
144
145
125 -def test_schedule():
126 - scheduler = create_scheduler()
127 - requests = create_requests(num_requests=10)
146 +@pytest.mark.parametrize("enable_prefix_caching, prompt_logprobs", [
147 + (None, None),
148 + (True, 5),
149 +])
150 +def test_schedule(enable_prefix_caching: Optional[bool],
151 + prompt_logprobs: Optional[int]):
152 + '''Test scheduling.
153 + Two cases: default APC/no prompt logprobs; APC=True + prompt logprobs
154 + '''
155 + scheduler = create_scheduler(enable_prefix_caching=enable_prefix_caching)
156 + requests = create_requests(num_requests=10,
157 + prompt_logprobs=prompt_logprobs)
158 for request in requests:
159 scheduler.add_request(request)
160
@@ -427,14 +457,21 @@ def test_stop_via_update_from_output():
457 assert list(requests[0].output_token_ids) == [EOS_TOKEN_ID, 10, 11]
458
459
430 -def test_schedule_concurrent_batches():
460 +@pytest.mark.parametrize("enable_prefix_caching, prompt_logprobs", [
461 + (None, None),
462 + (True, 5),
463 +])
464 +def test_schedule_concurrent_batches(enable_prefix_caching: Optional[bool],
465 + prompt_logprobs: Optional[int]):
466 scheduler = create_scheduler(
467 max_num_batched_tokens=1024,
468 max_num_seqs=2,
469 + enable_prefix_caching=enable_prefix_caching,
470 )
471 requests = create_requests(
472 num_requests=2,
473 num_tokens=512,
474 + prompt_logprobs=prompt_logprobs,
475 )
476
477 # Schedule the first request.
tests/v1/engine/test_async_llm.py
-36
@@ -6,7 +6,6 @@ from typing import Optional
6
7 import pytest
8
9 -from tests.v1.engine.utils import PLP_APC_UNSUPPORTED_MSG
9 from vllm import SamplingParams
10 from vllm.assets.image import ImageAsset
11 from vllm.engine.arg_utils import AsyncEngineArgs
@@ -72,41 +71,6 @@ async def generate(engine: AsyncLLM,
71 return count, request_id
72
73
75 -@pytest.mark.parametrize(
76 - "output_kind", [RequestOutputKind.DELTA, RequestOutputKind.FINAL_ONLY])
77 -@pytest.mark.asyncio
78 -async def test_async_llm_refuses_prompt_logprobs_with_apc(
79 - monkeypatch, output_kind: RequestOutputKind):
80 - """Test passes if AsyncLLM raises an exception when it is configured
81 - for automatic prefix caching and it receives a request with
82 - prompt_logprobs enabled, which is incompatible."""
83 - # TODO(rickyx): Remove monkeypatch VLLM_USE_V1 setting once we have a
84 - # better way to test V1 so that in the future when we switch, we don't
85 - # have to change all the tests.
86 - monkeypatch.setenv("VLLM_USE_V1", "1")
87 - # Create AsyncLLM engine with APC
88 - apc_engine_args = AsyncEngineArgs(model="facebook/opt-125m",
89 - enable_prefix_caching=True,
90 - gpu_memory_utilization=0.8,
91 - disable_log_requests=True)
92 - engine = AsyncLLM.from_engine_args(apc_engine_args)
93 - try:
94 - with pytest.raises(ValueError) as excinfo:
95 - # Issue a request with prompt logprobs enabled, which should fail
96 - await asyncio.create_task(
97 - generate(engine,
98 - "request-0",
99 - TEXT_PROMPT,
100 - output_kind,
101 - 10,
102 - prompt_logprobs=5))
103 - # Validate exception string is correct
104 - assert str(excinfo.value) == PLP_APC_UNSUPPORTED_MSG
105 - finally:
106 - # Shut down engine
107 - engine.shutdown()
108 -
109 -
74 @pytest.mark.parametrize(
75 "output_kind", [RequestOutputKind.DELTA, RequestOutputKind.FINAL_ONLY])
76 @pytest.mark.parametrize("engine_args_and_prompt",
tests/v1/engine/test_llm_engine.py
-15
@@ -5,7 +5,6 @@ from typing import Optional
5
6 import pytest
7
8 -from tests.v1.engine.utils import PLP_APC_UNSUPPORTED_MSG
8 from vllm import LLM, SamplingParams
9
10 MODEL = "facebook/opt-125m"
@@ -98,17 +97,3 @@ def test_parallel_sampling(vllm_model, example_prompts) -> None:
97 raise AssertionError(
98 f"{len(completion_counts)} unique completions; expected"
99 f" {n}. Repeats: {repeats}")
101 -
102 -
103 -def test_llm_engine_refuses_prompt_logprobs_with_apc(vllm_model_apc):
104 - """Test passes if LLMEngine raises an exception when it is configured
105 - for automatic prefix caching and it receives a request with
106 - prompt_logprobs enabled, which is incompatible."""
107 - model: LLM = vllm_model_apc.model
108 - with pytest.raises(ValueError) as excinfo:
109 - model.generate(
110 - "Hello, my name is",
111 - SamplingParams(temperature=0.8, top_p=0.95, prompt_logprobs=5))
112 -
113 - # Validate exception string is correct
114 - assert str(excinfo.value) == PLP_APC_UNSUPPORTED_MSG
tests/v1/engine/utils.py
-3
@@ -30,9 +30,6 @@ FULL_STRINGS = [
30 STOP_STRINGS = ["I love working on", "company by far", "brother in"]
31 PROMPT_LEN = 5
32
33 -PLP_APC_UNSUPPORTED_MSG = ("Prefix caching with prompt logprobs not yet "
34 - "supported on VLLM V1.")
35 -
33 random.seed(42)
34
35
tests/v1/sample/test_logprobs.py
+85 -60
@@ -1,24 +1,34 @@
1 # SPDX-License-Identifier: Apache-2.0
2
3 import itertools
4 +from collections.abc import Generator
5
6 import pytest
7 import torch
8
9 from tests.kernels.utils import override_backend_env_variable
10 from tests.v1.sample.utils import (
11 + BatchLogprobsComposition, BatchLogprobsSpecType,
12 assert_incr_detok_str_matches_non_incr_detok_str,
13 compute_correct_cumulative_logprob, get_test_batch)
14 from vllm import SamplingParams
15
14 -from ...conftest import VllmRunner
16 +from ...conftest import HfRunner, VllmRunner
17
18 MODEL = "meta-llama/Llama-3.2-1B-Instruct"
19 DTYPE = "half"
20
21 +NONE = BatchLogprobsComposition.NONE
22 +SAMPLE = BatchLogprobsComposition.SAMPLE
23 +PROMPT = BatchLogprobsComposition.PROMPT
24 +SAMPLE_PROMPT = BatchLogprobsComposition.SAMPLE_PROMPT
25
20 -@pytest.fixture(scope="module")
21 -def vllm_model(vllm_runner):
26 +
27 +@pytest.fixture(
28 + scope="module",
29 + # Parameterize APC
30 + params=[False, True])
31 +def vllm_model(vllm_runner, request) -> Generator[VllmRunner, None, None]:
32 with vllm_runner(
33 MODEL,
34 dtype=DTYPE,
@@ -31,22 +41,22 @@ def vllm_model(vllm_runner):
41 enforce_eager=True,
42 #TODO: enable this once we support it for
43 # prompt logprobs.
34 - enable_prefix_caching=False,
44 + enable_prefix_caching=request.param,
45 gpu_memory_utilization=0.5,
46 ) as vllm_model:
47 yield vllm_model
48
49
50 @pytest.fixture(scope="module")
41 -def hf_model(hf_runner):
51 +def hf_model(hf_runner) -> Generator[HfRunner, None, None]:
52 with hf_runner(MODEL, dtype=DTYPE) as hf_model:
53 yield hf_model
54
55
56 def _repeat_logprob_config(
57 test_prompts,
48 - logprob_prompt_logprob_list: list[tuple],
49 -) -> list[tuple]:
58 + logprob_prompt_logprob_list: BatchLogprobsSpecType,
59 +) -> BatchLogprobsSpecType:
60 """Ensure each test prompt has a logprob config.
61
62 A logprob config specifies the optional (i.e.
@@ -91,42 +101,17 @@ def _repeat_logprob_config(
101 return logprob_prompt_logprob_list
102
103
94 -def _test_case_get_logprobs_and_prompt_logprobs(
95 - hf_model,
96 - vllm_model,
97 - batch_logprobs_composition: str,
104 +def _run_and_validate(
105 + vllm_model: VllmRunner,
106 + test_prompts: list[str],
107 + vllm_sampling_params: SamplingParams,
108 + hf_logprobs: list[list[torch.Tensor]],
109 + hf_outputs: list[tuple[list[int], str]],
110 + logprob_prompt_logprob_list: BatchLogprobsSpecType,
111 temperature: float,
99 - example_prompts,
112 + max_tokens: int,
113 + do_apc: bool,
114 ) -> None:
101 - test_prompts = example_prompts
102 -
103 - max_tokens = 5
104 - hf_outputs = hf_model.generate_greedy(
105 - test_prompts,
106 - max_tokens=max_tokens,
107 - )
108 - hf_logprobs = hf_model.generate_greedy_logprobs(
109 - test_prompts,
110 - max_tokens=max_tokens,
111 - )
112 -
113 - # Batch has mixed sample params
114 - # (different logprobs/prompt logprobs combos)
115 - logprob_prompt_logprob_list = get_test_batch(batch_logprobs_composition)
116 -
117 - # Ensure that each test prompt has a logprob config for testing
118 - logprob_prompt_logprob_list = _repeat_logprob_config(
119 - test_prompts, logprob_prompt_logprob_list)
120 - # Generate SamplingParams
121 - vllm_sampling_params = [
122 - SamplingParams(max_tokens=max_tokens,
123 - logprobs=num_lp,
124 - prompt_logprobs=num_plp,
125 - temperature=temperature,
126 - seed=1984)
127 - for num_lp, num_plp in logprob_prompt_logprob_list
128 - ]
129 -
115 vllm_results = vllm_model.model.generate(
116 test_prompts, sampling_params=vllm_sampling_params)
117
@@ -267,14 +252,13 @@ def _test_case_get_logprobs_and_prompt_logprobs(
252 assert vllm_result.prompt_logprobs is None
253
254
270 -#@pytest.mark.skip_global_cleanup
255 @pytest.mark.parametrize("batch_logprobs_composition",
272 - ["NONE", "SAMPLE", "PROMPT", "SAMPLE_PROMPT"])
256 + [NONE, SAMPLE, PROMPT, SAMPLE_PROMPT])
257 @pytest.mark.parametrize("temperature", [0.0, 2.0])
258 def test_get_logprobs_and_prompt_logprobs(
259 hf_model,
260 vllm_model,
277 - batch_logprobs_composition: str,
261 + batch_logprobs_composition: BatchLogprobsComposition,
262 temperature: float,
263 example_prompts,
264 ) -> None:
@@ -292,25 +276,70 @@ def test_get_logprobs_and_prompt_logprobs(
276 batch_logprobs_composition controls the logprobs configurations for
277 requests in the batch under test.
278
279 + APC tests run two test iterations so that cache hits occur.
280 +
281 + To save time, only test one APC-enabled scenario
282 + (sample & prompt logprobs enabled, temperature>0.0).
283 +
284 Args:
296 - hf_model
297 - vllm_model
285 + hf_model: HuggingFace reference model fixture
286 + vllm_model: vLLM model fixture
287 batch_logprobs_composition: logprobs configuration for test batch
299 - example_prompts
300 - monkeypatch
288 + temperature: "temperature" sampling parameter
289 + example_prompts: example prompt fixture
290 """
302 - _test_case_get_logprobs_and_prompt_logprobs(
303 - hf_model=hf_model,
304 - vllm_model=vllm_model,
305 - batch_logprobs_composition=batch_logprobs_composition,
306 - temperature=temperature,
307 - example_prompts=example_prompts)
291 + do_apc = vllm_model.model.llm_engine.cache_config.enable_prefix_caching
292 + if do_apc and (temperature < 2.0
293 + or batch_logprobs_composition != SAMPLE_PROMPT):
294 + # Skip some test-cases to save time.
295 + pytest.skip()
296 + test_prompts = example_prompts
297 +
298 + max_tokens = 5
299 + hf_outputs = hf_model.generate_greedy(
300 + test_prompts,
301 + max_tokens=max_tokens,
302 + )
303 + hf_logprobs = hf_model.generate_greedy_logprobs(
304 + test_prompts,
305 + max_tokens=max_tokens,
306 + )
307 +
308 + # Batch has mixed sample params
309 + # (different logprobs/prompt logprobs combos)
310 + logprob_prompt_logprob_list = get_test_batch(batch_logprobs_composition)
311 +
312 + # Ensure that each test prompt has a logprob config for testing
313 + logprob_prompt_logprob_list = _repeat_logprob_config(
314 + test_prompts, logprob_prompt_logprob_list)
315 + # Generate SamplingParams
316 + vllm_sampling_params = [
317 + SamplingParams(max_tokens=max_tokens,
318 + logprobs=num_lp,
319 + prompt_logprobs=num_plp,
320 + temperature=temperature,
321 + seed=1984)
322 + for num_lp, num_plp in logprob_prompt_logprob_list
323 + ]
324 + for _ in range(2 if do_apc else 1):
325 + _run_and_validate(
326 + vllm_model=vllm_model,
327 + test_prompts=test_prompts,
328 + vllm_sampling_params=vllm_sampling_params,
329 + hf_logprobs=hf_logprobs,
330 + hf_outputs=hf_outputs,
331 + logprob_prompt_logprob_list=logprob_prompt_logprob_list,
332 + temperature=temperature,
333 + max_tokens=max_tokens,
334 + do_apc=do_apc)
335
336
337 def test_max_logprobs(monkeypatch):
338 """vLLM v1 engine should fail a request with `logprobs > max_logprobs`
339
340 Should also fail for `prompt_logprobs > max_logprobs`
341 +
342 + APC should not matter as this test checks basic request validation.
343
344 Args:
345 monkeypatch
@@ -330,14 +359,12 @@ def test_max_logprobs(monkeypatch):
359 runner.generate(["Hello world"], sampling_params=bad_sampling_params)
360
361
333 -def test_none_logprobs(vllm_model, example_prompts, monkeypatch):
362 +def test_none_logprobs(vllm_model, example_prompts):
363 """Engine should return `logprobs` and `prompt_logprobs` as `None`
364
365 Args:
366 vllm_model: vLLM model fixture
367 example_prompts: list of example prompts (test fixture)
339 - monkeypatch: supports editing env vars and rolling back changes
340 - after the test
368 """
369 max_tokens = 5
370
@@ -356,14 +383,12 @@ def test_none_logprobs(vllm_model, example_prompts, monkeypatch):
383 assert results_logprobs_none[i].prompt_logprobs is None
384
385
359 -def test_zero_logprobs(vllm_model, example_prompts, monkeypatch):
386 +def test_zero_logprobs(vllm_model, example_prompts):
387 """Engine should return sampled token and prompt token logprobs
388
389 Args:
390 vllm_model: vLLM model fixture
391 example_prompts: list of example prompts (test fixture)
365 - monkeypatch: supports editing env vars and rolling back changes
366 - after the test
392 """
393 max_tokens = 5
394
tests/v1/sample/utils.py
+24 -9
@@ -1,27 +1,42 @@
1 # SPDX-License-Identifier: Apache-2.0
2
3 import re
4 +from enum import Enum
5 +from typing import Optional
6
7 from vllm import CompletionOutput
8
9
8 -def get_test_batch(batch_logprobs_composition: str) -> list[tuple]:
10 +class BatchLogprobsComposition(Enum):
11 + """Types of logprobs configs to include in test batch"""
12 + NONE = 0
13 + SAMPLE = 1
14 + PROMPT = 2
15 + SAMPLE_PROMPT = 3
16 +
17 +
18 +BatchLogprobsSpecType = list[tuple[Optional[int], Optional[int]]]
19 +
20 +
21 +def get_test_batch(
22 + batch_logprobs_composition: BatchLogprobsComposition
23 +) -> BatchLogprobsSpecType:
24 """Generate logprobs configs for a batch of requests
25
26 A given request's logprobs configuration is (1) num_sample_logprobs and (2)
27 num_prompt_logprobs. The batch logprobs configuration is the list of request
28 logprobs configs.
29
15 - batch_logprobs_composition == "NONE" yields a batch with no sample or prompt
30 + batch_logprobs_composition == NONE yields a batch with no sample or prompt
31 logprobs
32
18 - batch_logprobs_composition == "SAMPLE" yields a batch with some requests
33 + batch_logprobs_composition == SAMPLE yields a batch with some requests
34 configured for sample logprobs only, and others configured for no logprobs
35
21 - batch_logprobs_composition == "PROMPT" yields a batch with some requests
36 + batch_logprobs_composition == PROMPT yields a batch with some requests
37 configured for prompt logprobs only, and others configured for no logprobs
38
24 - batch_logprobs_composition == "SAMPLE_PROMPT" yields a batch with some
39 + batch_logprobs_composition == SAMPLE_PROMPT yields a batch with some
40 requests configured for sample logprobs and prompt logprobs, some configured
41 for only sample logprobs or only prompt logprobs, and some configured for
42 no logprobs
@@ -34,10 +49,10 @@ def get_test_batch(batch_logprobs_composition: str) -> list[tuple]:
49 list of (Optional[num_sample_logprobs], Optional[num_prompt_logprobs])
50 tuples
51 """
37 - if batch_logprobs_composition == "NONE":
52 + if batch_logprobs_composition == BatchLogprobsComposition.NONE:
53 # No requests with sample or prompt logprobs
54 return [(None, None)]
40 - elif batch_logprobs_composition == "SAMPLE":
55 + elif batch_logprobs_composition == BatchLogprobsComposition.SAMPLE:
56 # Requests requiring sample logprobs or no logprobs
57 return [
58 (None, None),
@@ -45,7 +60,7 @@ def get_test_batch(batch_logprobs_composition: str) -> list[tuple]:
60 (5, None),
61 (3, None),
62 ]
48 - elif batch_logprobs_composition == "PROMPT":
63 + elif batch_logprobs_composition == BatchLogprobsComposition.PROMPT:
64 # Requests requiring prompt logprobs or no logprobs
65 return [
66 (None, None),
@@ -53,7 +68,7 @@ def get_test_batch(batch_logprobs_composition: str) -> list[tuple]:
68 (None, 6),
69 (None, 5),
70 ]
56 - elif batch_logprobs_composition == "SAMPLE_PROMPT":
71 + elif batch_logprobs_composition == BatchLogprobsComposition.SAMPLE_PROMPT:
72 # Requests requiring either no logprobs, just
73 # sample logprobs, just prompt logprobs, or
74 # both sample and prompt logprobs
vllm/v1/core/kv_cache_manager.py
+24 -19
@@ -105,8 +105,6 @@ class KVCacheManager:
105 # Prefix caching is disabled.
106 return [], 0
107
108 - computed_blocks = []
109 -
108 # The block hashes for the request may already be computed
109 # if the scheduler has tried to schedule the request before.
110 block_hashes = self.req_to_block_hashes[request.request_id]
@@ -114,24 +112,31 @@ class KVCacheManager:
112 block_hashes = hash_request_tokens(self.block_size, request)
113 self.req_to_block_hashes[request.request_id] = block_hashes
114
117 - for block_hash in block_hashes:
118 - # block_hashes is a chain of block hashes. If a block hash is not
119 - # in the cached_block_hash_to_id, the following block hashes are
120 - # not computed yet for sure.
121 - if cached_block := self.block_pool.get_cached_block(block_hash):
122 - computed_blocks.append(cached_block)
123 - else:
124 - break
125 -
115 self.prefix_cache_stats.requests += 1
127 - self.prefix_cache_stats.queries += len(block_hashes)
128 - self.prefix_cache_stats.hits += len(computed_blocks)
129 -
130 - # NOTE(woosuk): Since incomplete blocks are not eligible for
131 - # sharing, `num_computed_tokens` is always a multiple of
132 - # `block_size`.
133 - num_computed_tokens = len(computed_blocks) * self.block_size
134 - return computed_blocks, num_computed_tokens
116 + if request.sampling_params.prompt_logprobs is None:
117 + # Check for cache hits
118 + computed_blocks = []
119 + for block_hash in block_hashes:
120 + # block_hashes is a chain of block hashes. If a block hash
121 + # is not in the cached_block_hash_to_id, the following
122 + # block hashes are not computed yet for sure.
123 + if cached_block := self.block_pool.get_cached_block(
124 + block_hash):
125 + computed_blocks.append(cached_block)
126 + else:
127 + break
128 +
129 + self.prefix_cache_stats.queries += len(block_hashes)
130 + self.prefix_cache_stats.hits += len(computed_blocks)
131 +
132 + # NOTE(woosuk): Since incomplete blocks are not eligible for
133 + # sharing, `num_computed_tokens` is always a multiple of
134 + # `block_size`.
135 + num_computed_tokens = len(computed_blocks) * self.block_size
136 + return computed_blocks, num_computed_tokens
137 + else:
138 + # Skip cache hits for prompt logprobs
139 + return [], 0
140
141 def allocate_slots(
142 self,
vllm/v1/engine/processor.py
-6
@@ -72,12 +72,6 @@ class Processor:
72 f"Requested prompt logprobs of {params.prompt_logprobs}, "
73 f"which is greater than max allowed: {max_logprobs}")
74
75 - # TODO(andy): enable this in follow up by recomputing.
76 - if (params.prompt_logprobs is not None
77 - and self.cache_config.enable_prefix_caching):
78 - raise ValueError("Prefix caching with prompt logprobs not yet "
79 - "supported on VLLM V1.")
80 -
75 def _validate_sampling_params(
76 self,
77 params: SamplingParams,