Skip to content

Commit 0ff1bf9

Browse files
authored
[Bugfix] Fix failure to allocate KV blocks error (vllm-project#41282)
Signed-off-by: wzhao18 <wzhao18.sz@gmail.com>
1 parent 0ab67c0 commit 0ff1bf9

4 files changed

Lines changed: 69 additions & 2 deletions

File tree

tests/v1/core/test_single_type_kv_cache_manager.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,52 @@ def test_chunked_local_attention_get_num_blocks_to_allocate():
432432
)
433433
== 15
434434
)
435+
436+
437+
def test_predictor_matches_allocator_blocks_calculation_with_admission_cap():
438+
"""In forward steps, `get_num_blocks_to_allocate` must return exactly what
439+
`allocate_new_blocks` will pull; otherwise `block_pool.get_new_blocks`
440+
raises `ValueError: Cannot get N free blocks from the pool`.
441+
"""
442+
block_size = 2
443+
sliding_window = 8 # 4-block live window
444+
cap = sliding_window // block_size
445+
446+
spec = SlidingWindowSpec(
447+
block_size=block_size,
448+
num_kv_heads=1,
449+
head_size=1,
450+
dtype=torch.float32,
451+
sliding_window=sliding_window,
452+
)
453+
block_pool = BlockPool(
454+
num_gpu_blocks=100, enable_caching=True, hash_block_size=block_size
455+
)
456+
manager = SlidingWindowManager(
457+
spec,
458+
block_pool=block_pool,
459+
enable_caching=False,
460+
kv_cache_group_id=0,
461+
max_admission_blocks_per_request=cap,
462+
)
463+
464+
request_id = "req"
465+
total_computed = 0
466+
# Walk through request forward steps. Check num_blocks returned by
467+
# `get_num_blocks_to_allocate` matches what `allocate_new_blocks` pulls
468+
for num_tokens in (4, 8, 12, 16):
469+
predicted = manager.get_num_blocks_to_allocate(
470+
request_id=request_id,
471+
num_tokens=num_tokens,
472+
new_computed_blocks=[],
473+
total_computed_tokens=total_computed,
474+
num_tokens_main_model=num_tokens,
475+
)
476+
new_blocks = manager.allocate_new_blocks(
477+
request_id, num_tokens=num_tokens, num_tokens_main_model=num_tokens
478+
)
479+
assert predicted == len(new_blocks), (
480+
f"num_tokens={num_tokens}: predictor returned {predicted} "
481+
f"but allocator pulled {len(new_blocks)}"
482+
)
483+
total_computed = num_tokens

vllm/v1/core/kv_cache_coordinator.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def get_num_blocks_to_allocate(
8585
num_encoder_tokens: int,
8686
total_computed_tokens: int,
8787
num_tokens_main_model: int,
88+
apply_admission_cap: bool = False,
8889
) -> int:
8990
"""
9091
Get the number of blocks needed to be allocated for the request.
@@ -101,6 +102,10 @@ def get_num_blocks_to_allocate(
101102
num_tokens_main_model: The number of tokens for the main model (aka target
102103
model in spec decode). w/o spec decode, it is num_tokens;
103104
with spec decode, it is num_tokens - num_lookahead_tokens.
105+
apply_admission_cap: If True, apply the recycling-aware
106+
per-request admission cap (SWA / chunked-local). Set only by
107+
the full-sequence admission gate; per-step allocation must
108+
leave it False so the predictor matches `allocate_new_blocks`.
104109
105110
Returns:
106111
The number of blocks to allocate.
@@ -111,7 +116,12 @@ def get_num_blocks_to_allocate(
111116
# For cross-attention, we issue a single static allocation
112117
# of blocks based on the number of encoder input tokens.
113118
num_blocks_to_allocate += manager.get_num_blocks_to_allocate(
114-
request_id, num_encoder_tokens, [], 0, num_encoder_tokens
119+
request_id,
120+
num_encoder_tokens,
121+
[],
122+
0,
123+
num_encoder_tokens,
124+
apply_admission_cap=apply_admission_cap,
115125
)
116126
else:
117127
num_blocks_to_allocate += manager.get_num_blocks_to_allocate(
@@ -120,6 +130,7 @@ def get_num_blocks_to_allocate(
120130
new_computed_blocks[i],
121131
total_computed_tokens,
122132
num_tokens_main_model,
133+
apply_admission_cap=apply_admission_cap,
123134
)
124135
return num_blocks_to_allocate
125136

vllm/v1/core/kv_cache_manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ def can_fit_full_sequence(
257257
num_encoder_tokens=num_encoder_tokens,
258258
total_computed_tokens=total_computed_tokens,
259259
num_tokens_main_model=full_num_tokens,
260+
apply_admission_cap=True,
260261
)
261262

262263
return num_blocks_to_allocate <= self.block_pool.get_num_free_blocks()

vllm/v1/core/single_type_kv_cache_manager.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def get_num_blocks_to_allocate(
9292
new_computed_blocks: Sequence[KVCacheBlock],
9393
total_computed_tokens: int,
9494
num_tokens_main_model: int,
95+
apply_admission_cap: bool = False,
9596
) -> int:
9697
"""
9798
Get the number of blocks needed to be allocated for the request.
@@ -107,13 +108,16 @@ def get_num_blocks_to_allocate(
107108
num_tokens_main_model: The number of tokens for the main model (aka target
108109
model in spec decode). w/o spec decode, it is num_tokens;
109110
with spec decode, it is num_tokens - num_lookahead_tokens.
111+
apply_admission_cap: If True, clamp by `num_required_blocks` by
112+
`_max_admission_blocks_per_request`for recycling-aware specs
113+
(SWA, chunked-local).
110114
111115
Returns:
112116
The number of blocks to allocate.
113117
"""
114118

115119
num_required_blocks = cdiv(num_tokens, self.block_size)
116-
if self._max_admission_blocks_per_request is not None:
120+
if apply_admission_cap and self._max_admission_blocks_per_request is not None:
117121
# Recycling-aware specs (SWA, chunked-local) cap the per-request
118122
# reservation here so admission matches the startup pool sizer
119123
# (`SlidingWindowSpec.max_admission_blocks_per_request` / its
@@ -893,6 +897,7 @@ def get_num_blocks_to_allocate(
893897
new_computed_blocks: Sequence[KVCacheBlock],
894898
total_computed_tokens: int,
895899
num_tokens_main_model: int,
900+
apply_admission_cap: bool = False,
896901
) -> int:
897902
assert isinstance(self.kv_cache_spec, MambaSpec)
898903
if (
@@ -917,6 +922,7 @@ def get_num_blocks_to_allocate(
917922
new_computed_blocks,
918923
total_computed_tokens,
919924
num_tokens_main_model,
925+
apply_admission_cap=apply_admission_cap,
920926
)
921927
else:
922928
# We don't allocate blocks for lookahead tokens in align mode, because if

0 commit comments

Comments
 (0)