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, 09:45 UTC
Revert "[v1] Support multiple KV cache groups in GPU model runner (#17945) (#18459)
Signed-off-by: Mark McLoughlin <markmc@redhat.com>
Mark McLoughlin committed
May 21, 2025 at 18:25 UTC
bb0a3112130a077e557b36632de3f1b5836b4c40
15 files changed
+214
-481
tests/v1/core/test_kv_cache_utils.py
+3
-68
@@ -19,8 +19,7 @@ from vllm.v1.core.kv_cache_utils import (FreeKVCacheBlockQueue, KVCacheBlock,
19
hash_request_tokens,
20
unify_kv_cache_configs)
21
from vllm.v1.kv_cache_interface import (FullAttentionSpec, KVCacheConfig,
22
- KVCacheGroupSpec, KVCacheTensor,
23
- SlidingWindowSpec)
22
+ KVCacheGroupSpec, KVCacheTensor)
23
from vllm.v1.metrics.stats import PrefixCacheStats
24
from vllm.v1.request import Request
25
@@ -55,14 +54,12 @@ def new_kv_cache_spec(block_size=16,
54
num_kv_heads=2,
55
head_size=64,
56
dtype=torch.float32,
58
- use_mla=False,
59
- sliding_window=None):
57
+ use_mla=False):
58
return FullAttentionSpec(block_size=block_size,
59
num_kv_heads=num_kv_heads,
60
head_size=head_size,
61
dtype=dtype,
64
- use_mla=use_mla,
65
- sliding_window=sliding_window)
62
+ use_mla=use_mla)
63
64
65
def test_none_hash(monkeypatch):
@@ -495,68 +492,6 @@ def test_unify_kv_cache_configs():
492
unify_kv_cache_configs(diff_kv_cache_config)
493
494
498
-def test_merge_kv_cache_spec():
499
- same_layer_specs = [
500
- new_kv_cache_spec(num_kv_heads=32),
501
- new_kv_cache_spec(num_kv_heads=32),
502
- ]
503
- merged_layer_spec = same_layer_specs[0].merge(same_layer_specs)
504
- assert merged_layer_spec.block_size == 16
505
- assert merged_layer_spec.num_kv_heads == 32
506
- assert merged_layer_spec.head_size == 64
507
- assert merged_layer_spec.dtype == torch.float32
508
- assert merged_layer_spec.sliding_window is None
509
-
510
- different_layer_specs = [
511
- new_kv_cache_spec(num_kv_heads=32),
512
- new_kv_cache_spec(num_kv_heads=16),
513
- ]
514
- with pytest.raises(AssertionError):
515
- different_layer_specs[0].merge(different_layer_specs)
516
-
517
- full_spec = new_kv_cache_spec(num_kv_heads=32)
518
- different_type_layer_specs = [
519
- full_spec,
520
- SlidingWindowSpec(
521
- block_size=full_spec.block_size,
522
- num_kv_heads=full_spec.num_kv_heads,
523
- head_size=full_spec.head_size,
524
- dtype=full_spec.dtype,
525
- use_mla=full_spec.use_mla,
526
- sliding_window=1,
527
- ),
528
- ]
529
- with pytest.raises(AssertionError):
530
- different_type_layer_specs[0].merge(different_type_layer_specs)
531
- with pytest.raises(AssertionError):
532
- different_type_layer_specs[1].merge(different_type_layer_specs)
533
-
534
- different_sliding_window_layer_specs = [
535
- new_kv_cache_spec(num_kv_heads=32),
536
- new_kv_cache_spec(num_kv_heads=32, sliding_window=1),
537
- new_kv_cache_spec(num_kv_heads=32, sliding_window=2),
538
- ]
539
- with pytest.raises(ValueError):
540
- different_sliding_window_layer_specs[0].merge(
541
- different_sliding_window_layer_specs)
542
-
543
- same_sliding_window_layer_specs = [
544
- new_kv_cache_spec(num_kv_heads=32, sliding_window=1),
545
- new_kv_cache_spec(num_kv_heads=32, sliding_window=1),
546
- ]
547
- merged_layer_spec = same_sliding_window_layer_specs[0].merge(
548
- same_sliding_window_layer_specs)
549
- assert merged_layer_spec.sliding_window == 1
550
-
551
- same_sliding_window_layer_spec_with_none = [
552
- new_kv_cache_spec(num_kv_heads=32, sliding_window=1),
553
- new_kv_cache_spec(num_kv_heads=32, sliding_window=None),
554
- ]
555
- merged_layer_spec = same_sliding_window_layer_spec_with_none[0].merge(
556
- same_sliding_window_layer_spec_with_none)
557
- assert merged_layer_spec.sliding_window == 1
558
-
559
-
495
@pytest.mark.parametrize(
496
("model_id", "max_model_len", "want_estimated_max_len"), [
497
("Qwen/Qwen1.5-7B", 16385, 16384),
tests/v1/core/test_prefix_caching.py
+18
-18
@@ -84,7 +84,7 @@ def test_prefill(hash_algo):
84
blocks = manager.allocate_slots(req0, 55,
85
len(computed_blocks.blocks) * 16,
86
computed_blocks)
87
- assert blocks.get_block_ids() == [[1, 2, 3, 4]]
87
+ assert blocks.get_block_ids() == [1, 2, 3, 4]
88
89
# Check full block metadata
90
parent_block_hash = None
@@ -107,13 +107,13 @@ def test_prefill(hash_algo):
107
req1 = make_request("1", common_token_ids + unique_token_ids)
108
computed_blocks, num_computed_tokens = manager.get_computed_blocks(req1)
109
assert len(manager.req_to_block_hashes[req1.request_id]) == 3
110
- assert computed_blocks.get_block_ids() == [[1, 2, 3]]
110
+ assert computed_blocks.get_block_ids() == [1, 2, 3]
111
assert num_computed_tokens == 3 * 16
112
num_new_tokens = 53 - 3 * 16
113
blocks = manager.allocate_slots(req1, num_new_tokens,
114
len(computed_blocks.blocks) * 16,
115
computed_blocks)
116
- assert blocks.get_block_ids() == [[5]]
116
+ assert blocks.get_block_ids() == [5]
117
for block in computed_blocks.blocks:
118
assert block.ref_cnt == 2
119
@@ -141,13 +141,13 @@ def test_prefill(hash_algo):
141
req2 = make_request("2", common_token_ids + unique_token_ids)
142
computed_blocks, num_computed_tokens = manager.get_computed_blocks(req2)
143
assert len(manager.req_to_block_hashes[req2.request_id]) == 3
144
- assert computed_blocks.get_block_ids() == [[1, 2, 3]]
144
+ assert computed_blocks.get_block_ids() == [1, 2, 3]
145
assert num_computed_tokens == 3 * 16
146
num_new_tokens = 53 - 3 * 16
147
blocks = manager.allocate_slots(req2, num_new_tokens,
148
len(computed_blocks.blocks) * 16,
149
computed_blocks)
150
- assert blocks.get_block_ids() == [[6]]
150
+ assert blocks.get_block_ids() == [6]
151
152
# Although we only have 6 free blocks, we have 8 blocks in
153
# the free block queue due to lazy removal.
@@ -171,7 +171,7 @@ def test_prefill(hash_algo):
171
len(computed_blocks.blocks) * 16,
172
computed_blocks)
173
# This block ID order also checks the eviction order.
174
- assert blocks.get_block_ids() == [[7, 8, 9, 10, 4, 5, 6, 3, 2, 1]]
174
+ assert blocks.get_block_ids() == [7, 8, 9, 10, 4, 5, 6, 3, 2, 1]
175
assert manager.block_pool.free_block_queue.num_free_blocks == 0
176
assert manager.block_pool.free_block_queue.free_list_head is None
177
assert manager.block_pool.free_block_queue.free_list_tail is None
@@ -208,7 +208,7 @@ def test_prefill_plp():
208
blocks = manager.allocate_slots(req0, 55,
209
len(computed_blocks.blocks) * 16,
210
computed_blocks)
211
- assert blocks.get_block_ids() == [[1, 2, 3, 4]]
211
+ assert blocks.get_block_ids() == [1, 2, 3, 4]
212
req0_block_hashes = [b.block_hash for b in blocks.blocks]
213
214
# Check full block metadata
@@ -233,13 +233,13 @@ def test_prefill_plp():
233
req1 = make_request("1", common_token_ids + unique_token_ids)
234
computed_blocks, num_computed_tokens = manager.get_computed_blocks(req1)
235
assert len(manager.req_to_block_hashes[req1.request_id]) == 3
236
- assert computed_blocks.get_block_ids() == [[1, 2, 3]]
236
+ assert computed_blocks.get_block_ids() == [1, 2, 3]
237
assert num_computed_tokens == 3 * 16
238
num_new_tokens = 53 - 3 * 16
239
blocks = manager.allocate_slots(req1, num_new_tokens,
240
len(computed_blocks.blocks) * 16,
241
computed_blocks)
242
- assert blocks.get_block_ids() == [[5]]
242
+ assert blocks.get_block_ids() == [5]
243
for block in computed_blocks.blocks:
244
assert block.ref_cnt == 2
245
@@ -277,11 +277,11 @@ def test_prefill_plp():
277
block_ids = blocks.get_block_ids()
278
# Duplicate cached blocks have different ids but same hashes vs request #0
279
assert [b.block_hash for b in blocks.blocks] == req0_block_hashes
280
- assert block_ids != [[1, 2, 3, 4]]
280
+ assert block_ids != [1, 2, 3, 4]
281
282
# Request #2 block hashes are valid since request #0 hashes are.
283
# Check block reference counts.
284
- for block_id in block_ids[0]:
284
+ for block_id in block_ids:
285
assert manager.block_pool.blocks[block_id].ref_cnt == 1
286
287
manager.free(req2)
@@ -307,7 +307,7 @@ def test_decode():
307
blocks = manager.allocate_slots(req0, 55,
308
len(computed_blocks.blocks) * 16,
309
computed_blocks)
310
- assert blocks.get_block_ids() == [[1, 2, 3, 4]]
310
+ assert blocks.get_block_ids() == [1, 2, 3, 4]
311
312
# Append slots without allocating a new block.
313
req0.num_computed_tokens = 55
@@ -379,12 +379,12 @@ def test_evict():
379
# Touch the first 2 blocks.
380
req2 = make_request("2", list(range(2 * 16 + 3)))
381
computed_blocks, num_computed_tokens = manager.get_computed_blocks(req2)
382
- assert computed_blocks.get_block_ids() == [[1, 2]]
382
+ assert computed_blocks.get_block_ids() == [1, 2]
383
assert num_computed_tokens == 2 * 16
384
blocks = manager.allocate_slots(req2, 3,
385
len(computed_blocks.blocks) * 16,
386
computed_blocks)
387
- assert blocks.get_block_ids() == [[10]]
387
+ assert blocks.get_block_ids() == [10]
388
assert manager.block_pool.free_block_queue.num_free_blocks == 7
389
390
@@ -625,7 +625,7 @@ def test_mm_prefix_caching():
625
blocks = manager.allocate_slots(req0, 59,
626
len(computed_blocks.blocks) * 16,
627
computed_blocks)
628
- assert blocks.get_block_ids() == [[1, 2, 3, 4]]
628
+ assert blocks.get_block_ids() == [1, 2, 3, 4]
629
req0.num_computed_tokens = 59
630
631
# Append slots without allocating a new block.
@@ -686,7 +686,7 @@ def test_cache_key_salting():
686
blocks = manager.allocate_slots(req0, 59,
687
len(computed_blocks.blocks) * 16,
688
computed_blocks)
689
- assert blocks.get_block_ids() == [[1, 2, 3, 4]]
689
+ assert blocks.get_block_ids() == [1, 2, 3, 4]
690
req0.num_computed_tokens = 59
691
692
# Append slots without allocating a new block.
@@ -797,7 +797,7 @@ def test_reset_prefix_cache():
797
all_token_ids = full_block_token_ids + unique_token_ids
798
req0 = make_request("0", all_token_ids)
799
blocks = manager.allocate_slots(req0, 55)
800
- assert blocks.get_block_ids() == [[1, 2, 3, 4]]
800
+ assert blocks.get_block_ids() == [1, 2, 3, 4]
801
802
unique_token_ids = [4] * 7
803
all_token_ids = full_block_token_ids + unique_token_ids
@@ -808,7 +808,7 @@ def test_reset_prefix_cache():
808
blocks = manager.allocate_slots(req1, 7,
809
len(computed_blocks.blocks) * 16,
810
computed_blocks)
811
- assert blocks.get_block_ids() == [[5]]
811
+ assert blocks.get_block_ids() == [5]
812
813
# Failed to reset prefix cache because some blocks are not freed yet.
814
assert not manager.reset_prefix_cache()
tests/v1/worker/test_gpu_input_batch.py
+6
-33
@@ -9,11 +9,9 @@ import torch
9
10
from vllm.sampling_params import SamplingParams
11
from vllm.utils import is_pin_memory_available, make_tensor_with_pad
12
-from vllm.v1.kv_cache_interface import (FullAttentionSpec, KVCacheConfig,
13
- KVCacheGroupSpec, KVCacheTensor)
12
from vllm.v1.sample.metadata import SamplingMetadata
15
-from vllm.v1.worker.block_table import BlockTable, MultiGroupBlockTable
16
-from vllm.v1.worker.gpu_input_batch import CachedRequestState, InputBatch
13
+from vllm.v1.worker.gpu_input_batch import (BlockTable, CachedRequestState,
14
+ InputBatch)
15
16
VOCAB_SIZE = 1024
17
NUM_OUTPUT_TOKENS = 20
@@ -24,27 +22,6 @@ CUDA_DEVICES = [
22
MAX_NUM_PROMPT_TOKENS = 64
23
24
27
-def get_kv_cache_config() -> KVCacheConfig:
28
- return KVCacheConfig(
29
- num_blocks=10,
30
- tensors={
31
- "layer.0": KVCacheTensor(size=1024),
32
- },
33
- kv_cache_groups=[
34
- KVCacheGroupSpec(
35
- layer_names=["layer.0"],
36
- kv_cache_spec=FullAttentionSpec(
37
- block_size=1,
38
- num_kv_heads=1,
39
- head_size=16,
40
- dtype=torch.float16,
41
- use_mla=False,
42
- ),
43
- ),
44
- ],
45
- )
46
-
47
-
25
def _compare_objs(obj1, obj2):
26
attrs = inspect.getmembers(obj1, lambda a: not (inspect.isroutine(a)))
27
attr_names = set([
@@ -64,10 +41,6 @@ def _compare_objs(obj1, obj2):
41
elif isinstance(a, np.ndarray):
42
if np.allclose(a, b):
43
is_same = True
67
- elif isinstance(a, MultiGroupBlockTable):
68
- for a_i, b_i in zip(a.block_tables, b.block_tables):
69
- _compare_objs(a_i, b_i)
70
- is_same = True
44
elif isinstance(a, (BlockTable, SamplingMetadata)):
45
_compare_objs(a, b)
46
is_same = True # if we make it here must be same
@@ -225,7 +198,7 @@ def _construct_cached_request_state(req_id_suffix: int):
198
sampling_params=_create_sampling_params(),
199
mm_inputs=[],
200
mm_positions=[],
228
- block_ids=[[]],
201
+ block_ids=[],
202
generator=None,
203
num_computed_tokens=len(output_token_ids),
204
output_token_ids=output_token_ids,
@@ -247,11 +220,11 @@ def test_sampling_metadata_in_input_batch(device: str, batch_size: int):
220
input_batch: InputBatch = InputBatch(
221
max_num_reqs=batch_size,
222
max_model_len=1024,
223
+ max_num_blocks_per_req=10,
224
max_num_batched_tokens=1024,
225
device=torch.device(device),
226
pin_memory=is_pin_memory_available(),
227
vocab_size=1024,
254
- kv_cache_config=get_kv_cache_config(),
228
)
229
reqs: list[CachedRequestState] = []
230
req_id_reqs = {}
@@ -337,20 +310,20 @@ def test_swap_states_in_input_batch(device: str, batch_size: int,
310
input_batch: InputBatch = InputBatch(
311
max_num_reqs=batch_size,
312
max_model_len=1024,
313
+ max_num_blocks_per_req=10,
314
max_num_batched_tokens=1024,
315
device=torch.device(device),
316
pin_memory=is_pin_memory_available(),
317
vocab_size=1024,
344
- kv_cache_config=get_kv_cache_config(),
318
)
319
ref_input_batch: InputBatch = InputBatch(
320
max_num_reqs=batch_size,
321
max_model_len=1024,
322
+ max_num_blocks_per_req=10,
323
max_num_batched_tokens=1024,
324
device=torch.device(device),
325
pin_memory=is_pin_memory_available(),
326
vocab_size=1024,
353
- kv_cache_config=get_kv_cache_config(),
327
)
328
329
reqs: list[CachedRequestState] = []
tests/v1/worker/test_gpu_model_runner.py
+16
-41
@@ -1,16 +1,15 @@
1
# SPDX-License-Identifier: Apache-2.0
2
+import weakref
3
4
import pytest
5
+import torch
6
5
-from vllm.config import (CacheConfig, ModelConfig, ParallelConfig,
6
- SchedulerConfig, VllmConfig)
7
+from vllm.config import CacheConfig, ModelConfig, SchedulerConfig, VllmConfig
8
from vllm.sampling_params import SamplingParams
9
from vllm.v1.core.sched.output import (CachedRequestData, NewRequestData,
10
SchedulerOutput)
10
-from vllm.v1.kv_cache_interface import (FullAttentionSpec, KVCacheConfig,
11
- KVCacheGroupSpec, KVCacheTensor)
11
+from vllm.v1.kv_cache_interface import FullAttentionSpec
12
from vllm.v1.sample.metadata import SamplingMetadata
13
-from vllm.v1.worker.gpu_input_batch import InputBatch
13
from vllm.v1.worker.gpu_model_runner import GPUModelRunner
14
15
@@ -18,34 +17,13 @@ def initialize_kv_cache(runner: GPUModelRunner):
17
"""
18
Only perform necessary steps in GPUModelRunner.initialize_kv_cache()
19
"""
21
- kv_cache_config = KVCacheConfig(
22
- num_blocks=10,
23
- tensors={
24
- "layer.0": KVCacheTensor(size=1024),
25
- },
26
- kv_cache_groups=[
27
- KVCacheGroupSpec(
28
- layer_names=["layer.0"],
29
- kv_cache_spec=FullAttentionSpec(
30
- block_size=16,
31
- num_kv_heads=runner.model_config.get_num_kv_heads(
32
- runner.parallel_config),
33
- head_size=runner.model_config.get_head_size(),
34
- dtype=runner.kv_cache_dtype,
35
- use_mla=False,
36
- ))
37
- ])
38
- runner.kv_cache_config = kv_cache_config
39
- runner.input_batch = InputBatch(
40
- max_num_reqs=runner.max_num_reqs,
41
- max_model_len=runner.max_model_len,
42
- max_num_batched_tokens=runner.max_num_tokens,
43
- device=runner.device,
44
- pin_memory=runner.pin_memory,
45
- vocab_size=runner.model_config.get_vocab_size(),
46
- kv_cache_config=kv_cache_config,
47
- )
48
- runner.initialize_attn_backend(kv_cache_config)
20
+ kv_cache_spec = FullAttentionSpec(block_size=16,
21
+ num_kv_heads=1,
22
+ head_size=64,
23
+ dtype=torch.float16,
24
+ use_mla=False)
25
+ runner.attn_metadata_builder = runner.attn_backend.get_builder_cls()(
26
+ weakref.proxy(runner), kv_cache_spec, runner.input_batch.block_table)
27
28
29
@pytest.fixture
@@ -70,12 +48,10 @@ def model_runner():
48
swap_space=0,
49
cache_dtype="auto",
50
)
73
- parallel_config = ParallelConfig()
51
vllm_config = VllmConfig(
52
model_config=model_config,
53
cache_config=cache_config,
54
scheduler_config=scheduler_config,
78
- parallel_config=parallel_config,
55
)
56
57
device = "cuda"
@@ -97,7 +73,7 @@ def _schedule_new_request(*req_ids: str) -> SchedulerOutput:
73
mm_hashes=[],
74
mm_positions=[],
75
sampling_params=SamplingParams(),
100
- block_ids=[[0]],
76
+ block_ids=[0],
77
num_computed_tokens=0,
78
lora_request=None,
79
))
@@ -135,14 +111,13 @@ def _is_sampling_metadata_changed(model_runner,
111
112
def _is_req_state_block_table_match(model_runner, req_id: str) -> bool:
113
req_index = model_runner.input_batch.req_id_to_index[req_id]
138
- block_table = model_runner.input_batch.block_table[0]
114
+ block_table = model_runner.input_batch.block_table
115
req_state = model_runner.requests[req_id]
140
- if block_table.num_blocks_per_row[req_index] != len(
141
- req_state.block_ids[0]):
116
+ if block_table.num_blocks_per_row[req_index] != len(req_state.block_ids):
117
return False
118
num_blocks = block_table.num_blocks_per_row[req_index]
119
return (block_table.block_table_np[req_index, :num_blocks] ==
145
- req_state.block_ids[0]).all()
120
+ req_state.block_ids).all()
121
122
123
def test_update_states_new_request(model_runner):
@@ -225,7 +200,7 @@ def test_update_states_request_resumed(model_runner):
200
req_id=req_id,
201
resumed_from_preemption=False,
202
new_token_ids=[],
228
- new_block_ids=[[]],
203
+ new_block_ids=[],
204
num_computed_tokens=0,
205
)
206
vllm/distributed/kv_transfer/kv_connector/v1/shared_storage_connector.py
+3
-3
@@ -288,7 +288,7 @@ class SharedStorageConnector(KVConnectorBase_V1):
288
for new_req in scheduler_output.scheduled_new_reqs:
289
if new_req.req_id in self._requests_need_load:
290
meta.add_request(token_ids=new_req.prompt_token_ids,
291
- block_ids=new_req.block_ids[0],
291
+ block_ids=new_req.block_ids,
292
block_size=self._block_size,
293
is_store=False)
294
total_need_load += 1
@@ -299,7 +299,7 @@ class SharedStorageConnector(KVConnectorBase_V1):
299
# the original prompt tokens.
300
if not self._found_match_for_request(new_req):
301
meta.add_request(token_ids=new_req.prompt_token_ids,
302
- block_ids=new_req.block_ids[0],
302
+ block_ids=new_req.block_ids,
303
block_size=self._block_size,
304
is_store=True)
305
@@ -319,7 +319,7 @@ class SharedStorageConnector(KVConnectorBase_V1):
319
320
# NOTE(rob): For resumed req, new_block_ids is all
321
# of the block_ids for the request.
322
- block_ids = cached_req.new_block_ids[0]
322
+ block_ids = cached_req.new_block_ids
323
324
meta.add_request(token_ids=token_ids,
325
block_ids=block_ids,
vllm/v1/attention/backends/mla/rocm_aiter_mla.py
+2
-2
@@ -67,13 +67,13 @@ class AiterMLAMetadataBuilder(MLACommonMetadataBuilder[AiterMLAMetadata]):
67
max_model_len = self.runner.model_config.max_model_len
68
assert max_model_len == 32768,\
69
"AITER MLA requires max_model_len=32768"
70
- assert self.kv_cache_spec.block_size == 1, "AITER MLA" \
70
+ assert self.runner.block_size == 1, "AITER MLA" \
71
"only supports block size 1."
72
73
def _get_paged_kv_tensors(
74
self, block_table: torch.Tensor,
75
seq_lens: torch.Tensor) -> tuple[torch.Tensor, ...]:
76
- page_size = self.kv_cache_spec.block_size
76
+ page_size = self.runner.block_size
77
block_table_bounds = (seq_lens + page_size - 1) // page_size
78
79
mask = (torch.arange(block_table.size(1),
vllm/v1/core/kv_cache_manager.py
+13
-21
@@ -32,16 +32,9 @@ class KVCacheBlocks:
32
"""Creates a new KVCacheBlocks instance with no blocks."""
33
return cls([])
34
35
- def get_block_ids(self) -> list[list[int]]:
36
- """
37
- Converts the KVCacheBlocks instance to block_ids.
38
-
39
- Returns:
40
- list[list[int]]: A two-level list where
41
- * the outer list corresponds to KV cache groups (only 1 group now)
42
- * each inner list contains the block_ids of the blocks in that group
43
- """
44
- return [[block.block_id for block in self.blocks]]
35
+ def get_block_ids(self) -> list[int]:
36
+ """Converts the KVCacheBlocks instance to a list of block IDs."""
37
+ return [block.block_id for block in self.blocks]
38
39
def get_unhashed_block_ids(self) -> list[int]:
40
"""Get block_ids of unhashed blocks from KVCacheBlocks instance."""
@@ -307,9 +300,9 @@ class KVCacheManager:
300
self,
301
request: Request,
302
num_running_requests: int,
310
- ) -> list[int]:
303
+ ) -> int:
304
"""Calculate the number of common prefix blocks shared by all requests
312
- in the RUNNING state for each kv cache group.
305
+ in the RUNNING state.
306
307
The function determines this by selecting any request and iterating
308
through its blocks. A block is considered a common prefix block if its
@@ -339,14 +332,11 @@ class KVCacheManager:
332
requests in the current step.
333
334
Returns:
342
- list[int]: The number of common prefix blocks for each kv cache
343
- group.
335
+ int: The number of common prefix blocks.
336
"""
337
assert request.status == RequestStatus.RUNNING
346
- return [
347
- self.single_type_manager.get_num_common_prefix_blocks(
348
- request.request_id, num_running_requests)
349
- ]
338
+ return self.single_type_manager.get_num_common_prefix_blocks(
339
+ request.request_id, num_running_requests)
340
341
def free_block_hashes(self, request: Request) -> None:
342
"""Discard the block hashes for the request.
@@ -364,8 +354,10 @@ class KVCacheManager:
354
"""
355
return self.block_pool.take_events()
356
367
- def get_block_ids(self, request_id: str) -> list[list[int]]:
357
+ def get_block_ids(self, request_id: str) -> list[int]:
358
"""Get the block ids of a request."""
359
assert request_id in self.single_type_manager.req_to_blocks
370
- return KVCacheBlocks(self.single_type_manager.req_to_blocks[request_id]
371
- ).get_block_ids()
360
+ return [
361
+ block.block_id
362
+ for block in self.single_type_manager.req_to_blocks[request_id]
363
+ ]
vllm/v1/core/kv_cache_utils.py
+7
-6
@@ -577,12 +577,14 @@ def create_kv_cache_group_specs(
577
"""
578
kv_cache_groups = []
579
for layer_names_one_group in grouped_layer_names:
580
- layer_specs = [
581
- kv_cache_spec[layer_name] for layer_name in layer_names_one_group
582
- ]
583
- merged_layer_spec = layer_specs[0].merge(layer_specs)
580
+ layer_spec = kv_cache_spec[layer_names_one_group[0]]
581
+ assert all(
582
+ kv_cache_spec[layer_name] == layer_spec
583
+ for layer_name in layer_names_one_group[1:]), (
584
+ "All layers in the same KV cache group must share the same "
585
+ "KVCacheSpec.")
586
kv_cache_groups.append(
585
- KVCacheGroupSpec(layer_names_one_group, merged_layer_spec))
587
+ KVCacheGroupSpec(layer_names_one_group, layer_spec))
588
return kv_cache_groups
589
590
@@ -681,7 +683,6 @@ def unify_hybrid_kv_cache_specs(kv_cache_spec: dict[str, KVCacheSpec]):
683
head_size=spec.head_size,
684
dtype=spec.dtype,
685
use_mla=spec.use_mla,
684
- sliding_window=spec.sliding_window,
686
)
687
688
vllm/v1/core/sched/output.py
+6
-6
@@ -26,7 +26,7 @@ class NewRequestData:
26
mm_hashes: list[str]
27
mm_positions: list[PlaceholderRange]
28
sampling_params: SamplingParams
29
- block_ids: list[list[int]]
29
+ block_ids: list[int]
30
num_computed_tokens: int
31
lora_request: Optional[LoRARequest]
32
@@ -34,7 +34,7 @@ class NewRequestData:
34
def from_request(
35
cls,
36
request: Request,
37
- block_ids: list[list[int]],
37
+ block_ids: list[int],
38
) -> NewRequestData:
39
return cls(
40
req_id=request.request_id,
@@ -85,7 +85,7 @@ class CachedRequestData:
85
# request's block IDs instead of appending to the existing block IDs.
86
resumed_from_preemption: bool
87
new_token_ids: list[int]
88
- new_block_ids: list[list[int]]
88
+ new_block_ids: list[int]
89
num_computed_tokens: int
90
91
@classmethod
@@ -94,7 +94,7 @@ class CachedRequestData:
94
request: Request,
95
resumed_from_preemption: bool,
96
new_token_ids: list[int],
97
- new_block_ids: list[list[int]],
97
+ new_block_ids: list[int],
98
) -> CachedRequestData:
99
return cls(
100
req_id=request.request_id,
@@ -131,9 +131,9 @@ class SchedulerOutput:
131
# E.g., if a request has [0, 1], it could mean the vision encoder needs
132
# to process that the request's 0-th and 1-th images in the current step.
133
scheduled_encoder_inputs: dict[str, list[int]]
134
- # Number of common prefix blocks for all requests in each KV cache group.
134
+ # Number of common prefix blocks for all requests.
135
# This can be used for cascade attention.
136
- num_common_prefix_blocks: list[int]
136
+ num_common_prefix_blocks: int
137
138
# Request IDs that are finished in between the previous and the current
139
# steps. This is used to notify the workers about the finished requests
vllm/v1/core/sched/scheduler.py
+6
-10
@@ -173,7 +173,7 @@ class Scheduler(SchedulerInterface):
173
# uses structured decoding.
174
structured_output_request_ids: dict[str, int] = {}
175
176
- req_to_new_block_ids: dict[str, list[list[int]]] = {}
176
+ req_to_new_block_ids: dict[str, list[int]] = {}
177
num_scheduled_tokens: dict[str, int] = {}
178
token_budget = self.max_num_scheduled_tokens
179
# Encoder-related.
@@ -484,8 +484,7 @@ class Scheduler(SchedulerInterface):
484
485
# Get the longest common prefix among all requests in the running queue.
486
# This can be potentially used for cascade attention.
487
- num_common_prefix_blocks = [0] * len(
488
- self.kv_cache_config.kv_cache_groups)
487
+ num_common_prefix_blocks = 0
488
if self.running:
489
any_request = self.running[0]
490
num_common_prefix_blocks = (
@@ -572,7 +571,7 @@ class Scheduler(SchedulerInterface):
571
request: Request,
572
num_scheduled_tokens: int,
573
num_scheduled_spec_tokens: int,
575
- new_block_ids: list[list[int]],
574
+ new_block_ids: list[int],
575
resumed_from_preemption: bool,
576
) -> CachedRequestData:
577
# OPTIMIZATION: Cache the CachedRequestData objects to avoid creating
@@ -947,9 +946,7 @@ class Scheduler(SchedulerInterface):
946
"""
947
if self.connector is None:
948
return False, None
950
- assert len(self.kv_cache_config.kv_cache_groups
951
- ) == 1, "KV connector only supports one KV cache group now"
952
- block_ids = self.kv_cache_manager.get_block_ids(request.request_id)[0]
949
+ block_ids = self.kv_cache_manager.get_block_ids(request.request_id)
950
return self.connector.request_finished(request, block_ids)
951
952
def _update_waiting_for_remote_kv(self, request: Request) -> bool:
@@ -966,10 +963,9 @@ class Scheduler(SchedulerInterface):
963
"""
964
if request.request_id not in self.finished_recving_kv_req_ids:
965
return False
969
- assert len(self.kv_cache_config.kv_cache_groups
970
- ) == 1, "KV connector only supports one KV cache group now"
966
+
967
# Now that the blocks are ready, actually cache them.
972
- block_ids = self.kv_cache_manager.get_block_ids(request.request_id)[0]
968
+ block_ids = self.kv_cache_manager.get_block_ids(request.request_id)
969
num_computed_tokens = len(block_ids) * self.block_size
970
if num_computed_tokens == request.num_tokens:
971
num_computed_tokens -= 1
vllm/v1/kv_cache_interface.py
-42
@@ -1,11 +1,8 @@
1
# SPDX-License-Identifier: Apache-2.0
2
3
-import copy
3
from dataclasses import dataclass
5
-from typing import Optional
4
5
import torch
8
-from typing_extensions import Self
6
7
from vllm.config import VllmConfig
8
from vllm.logger import init_logger
@@ -56,16 +53,6 @@ class KVCacheSpec:
53
"""
54
raise NotImplementedError
55
59
- @classmethod
60
- def merge(cls, specs: list[Self]) -> Self:
61
- """
62
- Merge a list of KVCacheSpec objects into a single KVCacheSpec object.
63
- """
64
- assert all(spec.type_id == specs[0].type_id for spec in specs[1:]), (
65
- "All layers in the same KV cache group must share the same "
66
- "type_id.")
67
- return copy.deepcopy(specs[0])
68
-
56
57
@dataclass
58
class AttentionSpec(KVCacheSpec):
@@ -84,16 +71,6 @@ class AttentionSpec(KVCacheSpec):
71
72
@dataclass
73
class FullAttentionSpec(AttentionSpec):
87
- sliding_window: Optional[int] = None
88
- """
89
- When hybrid allocator is disabled and the model contains both full
90
- attention layers and sliding window attention layers, sliding
91
- window attention are regarded as full attention in KV cache manager
92
- (blocks are allocated for all tokens), while computed as sliding window
93
- attention in model runner.
94
- In this case, we use FullAttentionSpec and record the sliding window size.
95
- Default to None for not using sliding window attention.
96
- """
74
75
@property
76
def type_id(self) -> str:
@@ -103,25 +80,6 @@ class FullAttentionSpec(AttentionSpec):
80
max_model_len = vllm_config.model_config.max_model_len
81
return cdiv(max_model_len, self.block_size) * self.page_size_bytes
82
106
- @classmethod
107
- def merge(cls, specs: list[Self]) -> Self:
108
- """
109
- Merge a list of FullAttentionSpec objects into a single
110
- FullAttentionSpec object.
111
- """
112
- merged_spec = super().merge(specs)
113
- sliding_window = set(spec.sliding_window for spec in specs
114
- if spec.sliding_window is not None)
115
- if len(sliding_window) == 0:
116
- merged_spec.sliding_window = None
117
- elif len(sliding_window) == 1:
118
- merged_spec.sliding_window = sliding_window.pop()
119
- else:
120
- raise ValueError(
121
- "All sliding window layers in the same KV cache group "
122
- "must have the same window size.")
123
- return merged_spec
124
-
83
84
@dataclass
85
class SlidingWindowSpec(AttentionSpec):
vllm/v1/worker/block_table.py
-47
@@ -4,8 +4,6 @@ import numpy as np
4
import torch
5
6
from vllm.logger import init_logger
7
-from vllm.utils import cdiv
8
-from vllm.v1.kv_cache_interface import KVCacheConfig
7
8
logger = init_logger(__name__)
9
@@ -98,48 +96,3 @@ class BlockTable:
96
def get_numpy_array(self) -> np.ndarray:
97
"""Returns the numpy array of the block table."""
98
return self.block_table_np
101
-
102
-
103
-class MultiGroupBlockTable:
104
- """The BlockTables for each KV cache group."""
105
-
106
- def __init__(self, max_num_reqs: int, max_model_len: int,
107
- max_num_batched_tokens: int, pin_memory: bool,
108
- device: torch.device, kv_cache_config: KVCacheConfig) -> None:
109
- max_num_blocks_per_req = [
110
- cdiv(max_model_len, g.kv_cache_spec.block_size)
111
- for g in kv_cache_config.kv_cache_groups
112
- ]
113
- self.block_tables = [
114
- BlockTable(max_num_reqs, max_num_blocks_per_req[i],
115
- max_num_batched_tokens, pin_memory, device)
116
- for i in range(len(kv_cache_config.kv_cache_groups))
117
- ]
118
-
119
- def append_row(self, block_ids: list[list[int]], row_idx: int) -> None:
120
- for i, block_table in enumerate(self.block_tables):
121
- block_table.append_row(block_ids[i], row_idx)
122
-
123
- def add_row(self, block_ids: list[list[int]], row_idx: int) -> None:
124
- for i, block_table in enumerate(self.block_tables):
125
- block_table.add_row(block_ids[i], row_idx)
126
-
127
- def move_row(self, src: int, tgt: int) -> None:
128
- for block_table in self.block_tables:
129
- block_table.move_row(src, tgt)
130
-
131
- def swap_row(self, src: int, tgt: int) -> None:
132
- for block_table in self.block_tables:
133
- block_table.swap_row(src, tgt)
134
-
135
- def commit(self, num_reqs: int) -> None:
136
- for block_table in self.block_tables:
137
- block_table.commit(num_reqs)
138
-
139
- def clear(self) -> None:
140
- for block_table in self.block_tables:
141
- block_table.clear()
142
-
143
- def __getitem__(self, idx: int) -> "BlockTable":
144
- """Returns the BlockTable for the i-th KV cache group."""
145
- return self.block_tables[idx]
vllm/v1/worker/gpu_input_batch.py
+6
-7
@@ -11,11 +11,10 @@ from vllm.lora.request import LoRARequest
11
from vllm.multimodal.inputs import MultiModalKwargs, PlaceholderRange
12
from vllm.sampling_params import SamplingParams, SamplingType
13
from vllm.utils import swap_dict_values
14
-from vllm.v1.kv_cache_interface import KVCacheConfig
14
from vllm.v1.outputs import LogprobsTensors
15
from vllm.v1.sample.metadata import SamplingMetadata
16
from vllm.v1.utils import copy_slice
18
-from vllm.v1.worker.block_table import MultiGroupBlockTable
17
+from vllm.v1.worker.block_table import BlockTable
18
19
_SAMPLING_EPS = 1e-5
20
@@ -30,7 +29,7 @@ class CachedRequestState:
29
sampling_params: SamplingParams
30
generator: Optional[torch.Generator]
31
33
- block_ids: list[list[int]]
32
+ block_ids: list[int]
33
num_computed_tokens: int
34
output_token_ids: list[int]
35
@@ -59,14 +58,15 @@ class InputBatch:
58
self,
59
max_num_reqs: int,
60
max_model_len: int,
61
+ max_num_blocks_per_req: int,
62
max_num_batched_tokens: int,
63
device: torch.device,
64
pin_memory: bool,
65
vocab_size: int,
66
- kv_cache_config: KVCacheConfig,
66
):
67
self.max_num_reqs = max_num_reqs
68
self.max_model_len = max_model_len
69
+ self.max_num_blocks_per_req = max_num_blocks_per_req
70
self.max_num_batched_tokens = max_num_batched_tokens
71
self.device = device
72
self.pin_memory = pin_memory
@@ -99,13 +99,12 @@ class InputBatch:
99
self.num_computed_tokens_cpu_tensor.numpy()
100
101
# Block table.
102
- self.block_table = MultiGroupBlockTable(
102
+ self.block_table = BlockTable(
103
max_num_reqs=max_num_reqs,
104
- max_model_len=max_model_len,
104
+ max_num_blocks_per_req=max_num_blocks_per_req,
105
max_num_batched_tokens=max_num_batched_tokens,
106
pin_memory=pin_memory,
107
device=device,
108
- kv_cache_config=kv_cache_config,
108
)
109
110
# Sampling-related.
vllm/v1/worker/gpu_model_runner.py
+112
-158
@@ -12,8 +12,6 @@ import torch.distributed
12
import torch.nn as nn
13
14
from vllm.attention import AttentionType, get_attn_backend
15
-from vllm.attention.backends.abstract import (AttentionBackend,
16
- AttentionMetadataBuilder)
15
from vllm.attention.layer import Attention
16
from vllm.attention.utils.fa_utils import get_flash_attn_version
17
from vllm.config import (CompilationLevel, VllmConfig,
@@ -34,8 +32,8 @@ from vllm.multimodal.utils import group_mm_inputs_by_modality
32
from vllm.sampling_params import SamplingType
33
from vllm.sequence import IntermediateTensors
34
from vllm.utils import (STR_DTYPE_TO_TORCH_DTYPE, DeviceMemoryProfiler,
37
- GiB_bytes, LazyLoader, cdiv, check_use_alibi,
38
- is_pin_memory_available)
35
+ GiB_bytes, LayerBlockType, LazyLoader, cdiv,
36
+ check_use_alibi, is_pin_memory_available)
37
from vllm.v1.attention.backends.flash_attn import FlashAttentionMetadata
38
from vllm.v1.attention.backends.utils import CommonAttentionMetadata
39
from vllm.v1.core.encoder_cache_manager import compute_encoder_budget
@@ -53,7 +51,6 @@ from vllm.v1.spec_decode.metadata import SpecDecodeMetadata
51
from vllm.v1.spec_decode.ngram_proposer import NgramProposer
52
from vllm.v1.spec_decode.utils import is_spec_decode_supported
53
from vllm.v1.utils import bind_kv_cache
56
-from vllm.v1.worker.block_table import BlockTable
54
from vllm.v1.worker.gpu_input_batch import CachedRequestState, InputBatch
55
from vllm.v1.worker.lora_model_runner_mixin import LoRAModelRunnerMixin
56
@@ -105,17 +102,59 @@ class GPUModelRunner(LoRAModelRunnerMixin):
102
self.kv_cache_dtype = STR_DTYPE_TO_TORCH_DTYPE[
103
cache_config.cache_dtype]
104
105
+ # NOTE(woosuk): sliding_window is None for models with interleaved
106
+ # attention. Use interleaved_sliding_window instead.
107
+ self.sliding_window = model_config.get_sliding_window()
108
+ self.interleaved_sliding_window = getattr(
109
+ model_config.hf_text_config, "interleaved_sliding_window", None)
110
+ self.window_size = (self.sliding_window
111
+ or self.interleaved_sliding_window)
112
+
113
self.is_multimodal_model = model_config.is_multimodal_model
114
+ self.block_size = cache_config.block_size
115
self.max_model_len = model_config.max_model_len
116
+ self.max_num_blocks_per_req = cdiv(self.max_model_len, self.block_size)
117
self.max_num_tokens = scheduler_config.max_num_batched_tokens
118
self.max_num_reqs = scheduler_config.max_num_seqs
119
120
# Model-related.
121
+ self.num_attn_layers = model_config.get_num_layers_by_block_type(
122
+ parallel_config, LayerBlockType.attention)
123
self.num_query_heads = model_config.get_num_attention_heads(
124
parallel_config)
125
+ self.num_kv_heads = model_config.get_num_kv_heads(parallel_config)
126
+ self.head_size = model_config.get_head_size()
127
self.hidden_size = model_config.get_hidden_size()
128
self.attention_chunk_size = model_config.attention_chunk_size
129
130
+ self.attn_backend = get_attn_backend(
131
+ self.head_size,
132
+ self.dtype,
133
+ self.kv_cache_dtype,
134
+ self.block_size,
135
+ self.model_config.is_attention_free,
136
+ use_mla=self.model_config.use_mla,
137
+ )
138
+ if self.attn_backend is None:
139
+ error_msg = (
140
+ f"Error with get_att_backend: {self.head_size=}, "
141
+ f"{self.dtype=}, {self.kv_cache_dtype=}, {self.block_size=}, "
142
+ f"{self.model_config.is_attention_free=}, "
143
+ f"{self.model_config.use_mla=}")
144
+ logger.error(error_msg)
145
+ raise NotImplementedError(
146
+ "Non-Attention backend is not supported by V1 GPUModelRunner.")
147
+
148
+ if self.vllm_config.compilation_config.full_cuda_graph:
149
+ attn_backend_name = self.attn_backend.__name__
150
+ flash_attn_version = get_flash_attn_version()
151
+ if attn_backend_name != "FlashAttentionBackend" or \
152
+ flash_attn_version != 3:
153
+ raise ValueError(
154
+ f"full_cuda_graph is only supported with "
155
+ f"FA3. Current attention backend is {attn_backend_name}, "
156
+ f"FlashAttention version is {flash_attn_version}.")
157
+
158
self.cascade_attn_enabled = not self.model_config.disable_cascade_attn
159
160
# Multi-modal data support
@@ -137,10 +176,8 @@ class GPUModelRunner(LoRAModelRunnerMixin):
176
# self.model: nn.Module # Set after load_model
177
# Initialize in initialize_kv_cache
178
self.kv_caches: list[torch.Tensor] = []
140
- self.attn_metadata_builders: list[AttentionMetadataBuilder] = []
141
- self.attn_backends: list[type[AttentionBackend]] = []
179
# self.kv_cache_config: KVCacheConfig
143
- # self.input_batch: InputBatch # Persistent batch.
180
+ # self.attn_metadata_builder: type[AttentionMetadataBuilder]
181
182
# req_id -> (input_id -> encoder_output)
183
self.encoder_cache: dict[str, dict[int, torch.Tensor]] = {}
@@ -169,6 +206,16 @@ class GPUModelRunner(LoRAModelRunnerMixin):
206
207
# Request states.
208
self.requests: dict[str, CachedRequestState] = {}
209
+ # Persistent batch.
210
+ self.input_batch = InputBatch(
211
+ max_num_reqs=self.max_num_reqs,
212
+ max_model_len=self.max_model_len,
213
+ max_num_blocks_per_req=self.max_num_blocks_per_req,
214
+ max_num_batched_tokens=self.max_num_tokens,
215
+ device=self.device,
216
+ pin_memory=self.pin_memory,
217
+ vocab_size=model_config.get_vocab_size(),
218
+ )
219
220
self.use_cuda_graph = (self.vllm_config.compilation_config.level
221
== CompilationLevel.PIECEWISE
@@ -263,31 +310,6 @@ class GPUModelRunner(LoRAModelRunnerMixin):
310
pin_memory=self.pin_memory)
311
self.seq_lens_np = self.seq_lens_cpu.numpy()
312
266
- def _may_reorder_batch(self, scheduler_output: "SchedulerOutput") -> bool:
267
- """
268
- Update the order of requests in the batch based on the attention
269
- backend's needs. For example, some attention backends (namely MLA) may
270
- want to separate requests based on if the attention computation will be
271
- compute-bound or memory-bound.
272
-
273
- Args:
274
- scheduler_output: The scheduler output.
275
-
276
- Returns:
277
- True if the batch was reordered, False otherwise.
278
- """
279
- batch_reordered = self.attn_metadata_builders[0].reorder_batch(
280
- self.input_batch, scheduler_output)
281
-
282
- # For models with multiple KV cache groups, the groups should agree on
283
- # the same order of requests. We ensure this by only allowing the first
284
- # group to reorder the batch and asserting that all other groups do not
285
- # reorder the batch.
286
- for i in range(1, len(self.kv_cache_config.kv_cache_groups)):
287
- assert not self.attn_metadata_builders[i].reorder_batch(
288
- self.input_batch, scheduler_output)
289
- return batch_reordered
290
-
313
def _update_states(self, scheduler_output: "SchedulerOutput") -> None:
314
"""Update the cached states and the persistent batch with the scheduler
315
output.
@@ -424,8 +446,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
446
# Update the block IDs.
447
if not req_data.resumed_from_preemption:
448
# Append the new blocks to the existing block IDs.
427
- for i in range(len(self.kv_cache_config.kv_cache_groups)):
428
- req_state.block_ids[i].extend(req_data.new_block_ids[i])
449
+ req_state.block_ids.extend(req_data.new_block_ids)
450
else:
451
# The request is resumed from preemption.
452
# Replace the existing block IDs with the new ones.
@@ -483,7 +504,11 @@ class GPUModelRunner(LoRAModelRunnerMixin):
504
if removed_req_indices:
505
self.input_batch.condense(removed_req_indices)
506
486
- batch_reordered = self._may_reorder_batch(scheduler_output)
507
+ # Some attention backends (namely MLA) may want to separate requests
508
+ # based on if the attention computation will be compute-bound or
509
+ # memory-bound. This gives them a hook to do that.
510
+ batch_reordered = self.attn_metadata_builder.reorder_batch(
511
+ self.input_batch, scheduler_output)
512
513
if batch_changed or batch_reordered:
514
self.input_batch.refresh_sampling_metadata()
@@ -551,29 +576,21 @@ class GPUModelRunner(LoRAModelRunnerMixin):
576
torch.from_numpy(token_indices),
577
out=self.input_ids_cpu[:total_num_scheduled_tokens])
578
554
- # Calculate the slot mapping for each KV cache group.
555
- for kv_cache_group_id, kv_cache_group_spec in enumerate(
556
- self.kv_cache_config.kv_cache_groups):
557
- block_size = kv_cache_group_spec.kv_cache_spec.block_size
558
- block_table: BlockTable = self.input_batch.block_table[
559
- kv_cache_group_id]
560
- # E.g., [0, 1, 0, 1, 2, 3, 4, 0, 1, 2]
561
- # -> [0, 0, K, K, K + 1, K + 1, K + 2, 2 * K, 2 * K, 2 * K + 1]
562
- # where K is the max_num_blocks_per_req and the block size is 2.
563
- # NOTE(woosuk): We can't simply use `token_indices // block_size`
564
- # here because M (max_model_len) is not necessarily divisible by
565
- # block_size.
566
- block_table_indices = (
567
- req_indices * block_table.max_num_blocks_per_req +
568
- positions_np // block_size)
569
- block_table_cpu = block_table.get_cpu_tensor()
570
- block_numbers = block_table_cpu.flatten(
571
- )[block_table_indices].numpy()
572
- block_offsets = positions_np % block_size
573
- np.add(
574
- block_numbers * block_size,
575
- block_offsets,
576
- out=block_table.slot_mapping_np[:total_num_scheduled_tokens])
579
+ # Calculate the slot mapping.
580
+ # E.g., [0, 1, 0, 1, 2, 3, 4, 0, 1, 2]
581
+ # -> [0, 0, K, K, K + 1, K + 1, K + 2, 2 * K, 2 * K, 2 * K + 1]
582
+ # where K is the max_num_blocks_per_req and the block size is 2.
583
+ # NOTE(woosuk): We can't simply use `token_indices // block_size` here
584
+ # because M (max_model_len) is not necessarily divisible by block_size.
585
+ block_table_indices = (req_indices * self.max_num_blocks_per_req +
586
+ positions_np // self.block_size)
587
+ block_table_cpu = self.input_batch.block_table.get_cpu_tensor()
588
+ block_numbers = block_table_cpu.flatten()[block_table_indices].numpy()
589
+ block_offsets = positions_np % self.block_size
590
+ np.add(block_numbers * self.block_size,
591
+ block_offsets,
592
+ out=self.input_batch.block_table.
593
+ slot_mapping_np[:total_num_scheduled_tokens])
594
595
# Prepare the attention metadata.
596
self.query_start_loc_np[0] = 0
@@ -615,6 +632,10 @@ class GPUModelRunner(LoRAModelRunnerMixin):
632
attn_metadata: dict[str, FlashAttentionMetadata] = {}
633
# Prepare the attention metadata for each KV cache group and make layers
634
# in the same group share the same metadata.
635
+ # NOTE(Chen): there is exactly one KV cache group that contains all
636
+ # attetnion layers in the model for now, so the current logic for
637
+ # getting attn_metadata is not related to kv_cache_group information.
638
+ # Will extend this part to support multiple KV cache groups later.
639
for kv_cache_group_id, kv_cache_group_spec in enumerate(
640
self.kv_cache_config.kv_cache_groups):
641
@@ -623,19 +644,15 @@ class GPUModelRunner(LoRAModelRunnerMixin):
644
if self.cascade_attn_enabled:
645
common_prefix_len = self._compute_cascade_attn_prefix_len(
646
num_scheduled_tokens,
626
- scheduler_output.
627
- num_common_prefix_blocks[kv_cache_group_id],
628
- kv_cache_group_spec.kv_cache_spec,
629
- self.attn_metadata_builders[kv_cache_group_id],
647
+ scheduler_output.num_common_prefix_blocks,
648
)
649
632
- attn_metadata_i = (
633
- self.attn_metadata_builders[kv_cache_group_id].build(
634
- num_reqs=num_reqs,
635
- num_actual_tokens=total_num_scheduled_tokens,
636
- max_query_len=max_num_scheduled_tokens,
637
- common_prefix_len=common_prefix_len,
638
- common_attn_metadata=common_attn_metadata))
650
+ attn_metadata_i = self.attn_metadata_builder.build(
651
+ num_reqs=num_reqs,
652
+ num_actual_tokens=total_num_scheduled_tokens,
653
+ max_query_len=max_num_scheduled_tokens,
654
+ common_prefix_len=common_prefix_len,
655
+ common_attn_metadata=common_attn_metadata)
656
for layer_name in kv_cache_group_spec.layer_names:
657
attn_metadata[layer_name] = attn_metadata_i
658
@@ -673,8 +690,6 @@ class GPUModelRunner(LoRAModelRunnerMixin):
690
self,
691
num_scheduled_tokens: np.ndarray,
692
num_common_prefix_blocks: int,
676
- kv_cache_spec: KVCacheSpec,
677
- attn_metadata_builder: AttentionMetadataBuilder,
693
) -> int:
694
"""Compute the length of the common prefix for cascade attention.
695
@@ -693,7 +708,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
708
Returns:
709
int: Length of common prefix in tokens.
710
"""
696
- common_prefix_len = num_common_prefix_blocks * kv_cache_spec.block_size
711
+ common_prefix_len = num_common_prefix_blocks * self.block_size
712
if common_prefix_len == 0:
713
# Common case.
714
return 0
@@ -742,19 +757,15 @@ class GPUModelRunner(LoRAModelRunnerMixin):
757
common_prefix_len,
758
self.input_batch.num_computed_tokens_cpu[:num_reqs].min())
759
# common_prefix_len should be a multiple of the block size.
745
- common_prefix_len = (common_prefix_len // kv_cache_spec.block_size *
746
- kv_cache_spec.block_size)
747
- use_sliding_window = (isinstance(kv_cache_spec, SlidingWindowSpec) or
748
- (isinstance(kv_cache_spec, FullAttentionSpec)
749
- and kv_cache_spec.sliding_window is not None))
750
- assert isinstance(kv_cache_spec, AttentionSpec)
751
- use_cascade = attn_metadata_builder.use_cascade_attention(
760
+ common_prefix_len = (common_prefix_len // self.block_size *
761
+ self.block_size)
762
+ use_cascade = self.attn_metadata_builder.use_cascade_attention(
763
common_prefix_len=common_prefix_len,
764
query_lens=num_scheduled_tokens,
765
num_query_heads=self.num_query_heads,
755
- num_kv_heads=kv_cache_spec.num_kv_heads,
766
+ num_kv_heads=self.num_kv_heads,
767
use_alibi=self.use_alibi,
757
- use_sliding_window=use_sliding_window,
768
+ use_sliding_window=self.window_size is not None,
769
num_sms=self.num_sms,
770
)
771
return common_prefix_len if use_cascade else 0
@@ -1640,7 +1651,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1651
dtype=np.int32)
1652
1653
if skip_attn:
1643
- attn_metadata: Optional[dict[str, FlashAttentionMetadata]] = None
1654
+ attn_metadata = None
1655
else:
1656
query_start_loc = self.query_start_loc[:num_reqs + 1]
1657
seq_lens = self.seq_lens[:num_reqs]
@@ -1648,19 +1659,13 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1659
common_attn_metadata = CommonAttentionMetadata(
1660
query_start_loc=query_start_loc, seq_lens=seq_lens)
1661
1651
- attn_metadata = {}
1652
- for kv_cache_group_id, kv_cache_group_spec in enumerate(
1653
- self.kv_cache_config.kv_cache_groups):
1654
- attn_metadata_i = (
1655
- self.attn_metadata_builders[kv_cache_group_id].build(
1656
- num_reqs=num_tokens,
1657
- num_actual_tokens=num_tokens,
1658
- max_query_len=num_tokens,
1659
- common_prefix_len=0,
1660
- common_attn_metadata=common_attn_metadata,
1661
- ))
1662
- for layer_name in kv_cache_group_spec.layer_names:
1663
- attn_metadata[layer_name] = attn_metadata_i
1662
+ attn_metadata = self.attn_metadata_builder.build(
1663
+ num_reqs=num_tokens,
1664
+ num_actual_tokens=num_tokens,
1665
+ max_query_len=num_tokens,
1666
+ common_prefix_len=0,
1667
+ common_attn_metadata=common_attn_metadata,
1668
+ )
1669
1670
with self.maybe_dummy_run_with_lora(self.lora_config,
1671
num_scheduled_tokens):
@@ -1890,56 +1895,6 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1895
logger.info("Graph capturing finished in %.0f secs, took %.2f GiB",
1896
elapsed_time, cuda_graph_size / (1 << 30))
1897
1893
- def initialize_attn_backend(self, kv_cache_config: KVCacheConfig) -> None:
1894
- """
1895
- Initialize the attention backends and attention metadata builders.
1896
- """
1897
- assert len(self.attn_backends) == 0 and len(
1898
- self.attn_metadata_builders
1899
- ) == 0, "Attention backends are already initialized"
1900
- for i, kv_cache_group_spec in enumerate(
1901
- kv_cache_config.kv_cache_groups):
1902
- kv_cache_spec = kv_cache_group_spec.kv_cache_spec
1903
- if not isinstance(kv_cache_spec, AttentionSpec):
1904
- raise NotImplementedError(
1905
- "Only AttentionSpec is supported for now.")
1906
- attn_backend_i = get_attn_backend(
1907
- kv_cache_spec.head_size,
1908
- self.dtype,
1909
- kv_cache_spec.dtype,
1910
- kv_cache_spec.block_size,
1911
- self.model_config.is_attention_free,
1912
- use_mla=kv_cache_spec.use_mla,
1913
- )
1914
- if attn_backend_i is None:
1915
- error_msg = (
1916
- f"Error with get_attn_backend: {kv_cache_spec.head_size=}, "
1917
- f"{self.dtype=}, {kv_cache_spec.dtype=}, "
1918
- f"{kv_cache_spec.block_size=}, "
1919
- f"{self.model_config.is_attention_free=}, "
1920
- f"{kv_cache_spec.use_mla=}")
1921
- logger.error(error_msg)
1922
- raise NotImplementedError(
1923
- "Non-Attention backend is not supported by V1 "
1924
- "GPUModelRunner.")
1925
-
1926
- if self.vllm_config.compilation_config.full_cuda_graph:
1927
- attn_backend_name = attn_backend_i.__name__
1928
- flash_attn_version = get_flash_attn_version()
1929
- if attn_backend_name != "FlashAttentionBackend" or \
1930
- flash_attn_version != 3:
1931
- raise ValueError(
1932
- f"full_cuda_graph is only supported with "
1933
- f"FA3. Current attention backend is "
1934
- f"{attn_backend_name}, FlashAttention version is "
1935
- f"{flash_attn_version}.")
1936
-
1937
- block_table_i = self.input_batch.block_table[i]
1938
- attn_metadata_builder_i = attn_backend_i.get_builder_cls()(
1939
- weakref.proxy(self), kv_cache_spec, block_table_i)
1940
- self.attn_backends.append(attn_backend_i)
1941
- self.attn_metadata_builders.append(attn_metadata_builder_i)
1942
-
1898
def initialize_kv_cache(self, kv_cache_config: KVCacheConfig) -> None:
1899
"""
1900
Initialize KV cache based on `kv_cache_config`.
@@ -1947,21 +1902,15 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1902
kv_cache_config: Configuration for the KV cache, including the KV
1903
cache size of each layer
1904
"""
1905
+ if len(kv_cache_config.kv_cache_groups) > 1:
1906
+ raise NotImplementedError(
1907
+ "Hybrid models with more than one KV cache type are not "
1908
+ "supported yet.")
1909
self.kv_cache_config = kv_cache_config
1951
- self.input_batch = InputBatch(
1952
- max_num_reqs=self.max_num_reqs,
1953
- max_model_len=self.max_model_len,
1954
- max_num_batched_tokens=self.max_num_tokens,
1955
- device=self.device,
1956
- pin_memory=self.pin_memory,
1957
- vocab_size=self.model_config.get_vocab_size(),
1958
- kv_cache_config=kv_cache_config,
1959
- )
1960
- self.initialize_attn_backend(kv_cache_config)
1910
1911
kv_caches: dict[str, torch.Tensor] = {}
1912
1964
- for i, kv_cache_group in enumerate(kv_cache_config.kv_cache_groups):
1913
+ for kv_cache_group in kv_cache_config.kv_cache_groups:
1914
kv_cache_spec = kv_cache_group.kv_cache_spec
1915
for layer_name in kv_cache_group.layer_names:
1916
tensor_config = kv_cache_config.tensors[layer_name]
@@ -1976,7 +1925,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1925
# the min of all `num_blocks`. Verify it here.
1926
assert num_blocks >= kv_cache_config.num_blocks
1927
if isinstance(kv_cache_spec, AttentionSpec):
1979
- kv_cache_shape = self.attn_backends[i].get_kv_cache_shape(
1928
+ kv_cache_shape = self.attn_backend.get_kv_cache_shape(
1929
num_blocks, kv_cache_spec.block_size,
1930
kv_cache_spec.num_kv_heads, kv_cache_spec.head_size)
1931
dtype = kv_cache_spec.dtype
@@ -1996,6 +1945,11 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1945
if has_kv_transfer_group():
1946
get_kv_transfer_group().register_kv_caches(kv_caches)
1947
1948
+ self.attn_metadata_builder = self.attn_backend.get_builder_cls()(
1949
+ weakref.proxy(self),
1950
+ kv_cache_config.kv_cache_groups[0].kv_cache_spec,
1951
+ self.input_batch.block_table)
1952
+
1953
def get_kv_cache_spec(self) -> dict[str, KVCacheSpec]:
1954
"""
1955
Generates the KVCacheSpec by parsing the kv cache format from each
vllm/v1/worker/tpu_model_runner.py
+16
-19
@@ -171,10 +171,19 @@ class TPUModelRunner(LoRAModelRunnerMixin):
171
self.kv_caches: list[torch.Tensor] = []
172
# req_id -> (input_id -> encoder_output)
173
self.encoder_cache: dict[str, dict[int, torch.Tensor]] = {}
174
- # self.input_batch: InputBatch # Persistent batch.
174
175
# Request states.
176
self.requests: dict[str, CachedRequestState] = {}
177
+ # Persistent batch.
178
+ self.input_batch = InputBatch(
179
+ max_num_reqs=self.max_num_reqs,
180
+ max_model_len=self.max_model_len,
181
+ max_num_blocks_per_req=self.max_num_blocks_per_req,
182
+ max_num_batched_tokens=self.max_num_tokens,
183
+ device=self.device,
184
+ pin_memory=self.pin_memory,
185
+ vocab_size=self.vocab_size,
186
+ )
187
188
# Cached torch/numpy tensor
189
# The pytorch tensor and numpy array share the same buffer.
@@ -190,7 +199,7 @@ class TPUModelRunner(LoRAModelRunnerMixin):
199
200
self.block_table_cpu = torch.zeros(
201
(self.max_num_reqs, self.max_num_blocks_per_req),
193
- dtype=torch.int32,
202
+ dtype=self.input_batch.block_table.get_cpu_tensor().dtype,
203
device="cpu")
204
205
self.query_start_loc_cpu = torch.zeros(self.max_num_tokens + 1,
@@ -515,12 +524,12 @@ class TPUModelRunner(LoRAModelRunnerMixin):
524
# NOTE(woosuk): We use torch.index_select instead of np.take here
525
# because torch.index_select is much faster than np.take for large
526
# tensors.
518
- block_table_cpu = self.input_batch.block_table[0].get_cpu_tensor()
527
+ block_table_cpu = self.input_batch.block_table.get_cpu_tensor()
528
block_numbers = block_table_cpu.flatten()[block_table_indices].numpy()
529
block_offsets = positions_np % self.block_size
530
np.add(block_numbers * self.block_size,
531
block_offsets,
523
- out=self.input_batch.block_table[0].
532
+ out=self.input_batch.block_table.
533
slot_mapping_np[:total_num_scheduled_tokens])
534
535
# Prepare the attention metadata.
@@ -545,15 +554,15 @@ class TPUModelRunner(LoRAModelRunnerMixin):
554
self.position_ids = self.positions_cpu[:
555
padded_total_num_scheduled_tokens].to(
556
self.device)
548
- self.input_batch.block_table[0].slot_mapping_cpu[
557
+ self.input_batch.block_table.slot_mapping_cpu[
558
total_num_scheduled_tokens:] = _PAD_SLOT_ID
559
slot_mapping = (
551
- self.input_batch.block_table[0].
560
+ self.input_batch.block_table.
561
slot_mapping_cpu[:padded_total_num_scheduled_tokens].to(
562
self.device))
563
block_tables = self.block_table_cpu[:self.max_num_reqs]
564
block_tables[:num_reqs, :self.max_num_blocks_per_req] = (
556
- self.input_batch.block_table[0].get_cpu_tensor()[:num_reqs])
565
+ self.input_batch.block_table.get_cpu_tensor()[:num_reqs])
566
block_tables = block_tables.to(self.device)
567
query_start_loc = self.query_start_loc_cpu[:self.max_num_reqs + 1].to(
568
self.device)
@@ -1254,18 +1263,6 @@ class TPUModelRunner(LoRAModelRunnerMixin):
1263
"Hybrid models with more than one KV cache type are not "
1264
"supported yet.")
1265
1257
- self.input_batch = InputBatch(
1258
- max_num_reqs=self.max_num_reqs,
1259
- max_model_len=self.max_model_len,
1260
- max_num_batched_tokens=self.max_num_tokens,
1261
- device=self.device,
1262
- pin_memory=self.pin_memory,
1263
- vocab_size=self.model_config.get_vocab_size(),
1264
- kv_cache_config=kv_cache_config,
1265
- )
1266
- assert self.block_table_cpu.dtype == self.input_batch.block_table[
1267
- 0].get_cpu_tensor().dtype
1268
-
1266
kv_caches: dict[str, torch.Tensor] = {}
1267
1268
for kv_cache_group in kv_cache_config.kv_cache_groups: