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, 06:00 UTC
[TPU]Fix KV cache sharing tests (#19371)
Siyuan Liu committed
Jun 9, 2025 at 15:38 UTC
7d44c469fe3b59f931f9904bba46a7d0d53778fa
1 file changed
+52
-60
tests/v1/tpu/worker/test_tpu_model_runner.py
+52
-60
@@ -1,6 +1,5 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-import unittest.mock as mock
3
4
import pytest
5
@@ -17,24 +16,8 @@ from vllm.v1.worker.tpu_model_runner import (
16
TPUModelRunner, _get_padded_num_reqs_with_upper_limit,
17
_get_padded_token_len, _get_req_paddings, _get_token_paddings)
18
20
-# Mock torch_xla module since it may not be available in the test environments
21
-torch_xla_patcher = mock.patch.dict(
22
- "sys.modules", {
23
- "torch_xla": mock.MagicMock(),
24
- "torch_xla.core.xla_model": mock.MagicMock(),
25
- "torch_xla.runtime": mock.MagicMock(),
26
- })
27
-torch_xla_patcher.start()
19
29
-# Mock the PallasAttentionBackend
30
-pallas_attention_backend_patcher = mock.patch(
31
- "vllm.v1.worker.tpu_model_runner.PallasAttentionBackend", )
32
-pallas_attention_backend_patcher.start()
33
-
34
-
35
-@pytest.fixture
36
-def model_runner():
37
- # Patchers have already been started at module level.
20
+def get_vllm_config():
21
scheduler_config = SchedulerConfig(
22
max_num_seqs=10,
23
max_num_batched_tokens=512,
@@ -60,18 +43,19 @@ def model_runner():
43
cache_config=cache_config,
44
scheduler_config=scheduler_config,
45
)
46
+ return vllm_config
47
+
48
+
49
+def get_model_runner(vllm_config):
50
device = "xla:0" # Mocking TPU device
64
- with mock.patch("vllm.v1.worker.tpu_model_runner.torch"), \
65
- mock.patch("vllm.v1.worker.tpu_model_runner.xm"), \
66
- mock.patch("vllm.v1.worker.tpu_model_runner.xr"):
67
- return TPUModelRunner(vllm_config, device)
51
+ return TPUModelRunner(vllm_config, device)
52
53
70
-@pytest.fixture(autouse=True, scope="session")
71
-def cleanup_patches():
72
- yield
73
- torch_xla_patcher.stop()
74
- pallas_attention_backend_patcher.stop()
54
+@pytest.fixture
55
+def model_runner():
56
+ # Patchers have already been started at module level.
57
+ vllm_config = get_vllm_config()
58
+ return get_model_runner(vllm_config)
59
60
61
def _schedule_new_request(*req_ids: str) -> SchedulerOutput:
@@ -370,12 +354,14 @@ def test_get_req_paddings():
354
assert _get_req_paddings(8, 36) == [8, 16, 32, 36]
355
356
373
-@pytest.mark.skip(reason="Test is broken on TPU when it's added.")
374
-def test_init_kv_cache_with_kv_sharing_invalid_target_layer_order():
357
+def test_init_kv_cache_with_kv_sharing_invalid_target_layer_order(
358
+ model_runner):
359
layer_0 = "model.layers.0.self_attn.attn"
360
layer_1 = "model.layers.1.self_attn.attn"
361
error_msg = f"{layer_1} must come before the current layer"
378
- with pytest.raises(ValueError, match=error_msg):
362
+ vllm_config = model_runner.vllm_config
363
+ with pytest.raises(ValueError, match=error_msg), \
364
+ set_current_vllm_config(vllm_config):
365
fwd_context = {
366
# initialization below will fail because target layer is invalid;
367
# the target layer needs to come before layer 1
@@ -399,13 +385,14 @@ def test_init_kv_cache_with_kv_sharing_invalid_target_layer_order():
385
assert fwd_context is not None
386
387
402
-@pytest.mark.skip(reason="Test is broken on TPU when it's added.")
403
-def test_init_kv_cache_with_kv_sharing_target_layer_not_exist():
388
+def test_init_kv_cache_with_kv_sharing_target_layer_not_exist(model_runner):
389
layer_0 = "model.layers.0.self_attn.attn"
390
layer_1 = "model.layers.1.self_attn.attn"
391
invalid_layer = "model.layers.0.cross_attn.attn"
392
error_msg = f"{invalid_layer} is not a valid Attention layer in the model"
408
- with pytest.raises(ValueError, match=error_msg):
393
+ vllm_config = model_runner.vllm_config
394
+ with pytest.raises(ValueError, match=error_msg), \
395
+ set_current_vllm_config(vllm_config):
396
fwd_context = {
397
layer_0:
398
Attention(
@@ -428,12 +415,13 @@ def test_init_kv_cache_with_kv_sharing_target_layer_not_exist():
415
assert fwd_context is not None
416
417
431
-@pytest.mark.skip(reason="Test is broken on TPU when it's added.")
432
-def test_init_kv_cache_with_kv_sharing_target_same_as_current():
418
+def test_init_kv_cache_with_kv_sharing_target_same_as_current(model_runner):
419
layer_0 = "model.layers.0.self_attn.attn"
420
layer_1 = "model.layers.1.self_attn.attn"
421
error_msg = f"{layer_1} cannot be the same as the current layer"
436
- with pytest.raises(ValueError, match=error_msg):
422
+ vllm_config = model_runner.vllm_config
423
+ with pytest.raises(ValueError, match=error_msg), \
424
+ set_current_vllm_config(vllm_config):
425
fwd_context = {
426
# initialization below will fail because target layer is invalid;
427
# the target layer needs to come before layer 1
@@ -457,11 +445,10 @@ def test_init_kv_cache_with_kv_sharing_target_same_as_current():
445
assert fwd_context is not None
446
447
460
-@pytest.mark.skip(reason="Test is broken on TPU when it's added.")
461
-def test_init_kv_cache_without_kv_sharing(model_runner):
448
+def test_init_kv_cache_without_kv_sharing():
449
layer_0 = "model.layers.0.self_attn.attn"
450
layer_1 = "model.layers.1.self_attn.attn"
464
- vllm_config = model_runner.vllm_config
451
+ vllm_config = get_vllm_config()
452
with set_current_vllm_config(vllm_config):
453
fwd_context = {
454
layer_0:
@@ -482,33 +469,38 @@ def test_init_kv_cache_without_kv_sharing(model_runner):
469
# suppress var not used error
470
assert fwd_context is not None
471
# Set high context length to test max context length estimation
485
- vllm_config.model_config.max_model_len = 3_000_000
472
+ vllm_config.model_config.max_model_len = 1_000_000
473
vllm_ctx = vllm_config.compilation_config.static_forward_context
474
+ model_runner = get_model_runner(vllm_config)
475
kv_cache_spec = model_runner.get_kv_cache_spec()
476
assert len(kv_cache_spec) == 2
477
assert len(model_runner.shared_kv_cache_layers) == 0
478
479
available_memory = 20 * GiB_bytes
492
- # page size for layer 0's kv_cache_spec is 32KB
493
- num_expected_blocks = 327680 # 20GB / 32KB / 2 (num layers)
480
+ # page size for each layer KV can be calculated as
481
+ # 2 (non-MLA) * 8 (num_heads) * 128 (head_dim)
482
+ # * 2 (bfloat16, kv_cache dtype) * 128 (block_size) = 512KB
483
+ num_expected_blocks = 20480 # 20GB / 512KB / 2 (num layers)
484
kv_cache_config = get_kv_cache_config(vllm_config, kv_cache_spec,
485
available_memory)
486
assert kv_cache_config.num_blocks == num_expected_blocks
497
- assert len(kv_cache_config.tensors) == 2
498
- assert kv_cache_config.tensors[layer_0].size == available_memory // 2
499
- assert kv_cache_config.tensors[layer_1].size == available_memory // 2
487
+ assert len(kv_cache_config.kv_cache_tensors) == 2
488
+ assert kv_cache_config.kv_cache_tensors[0].size == available_memory // 2
489
+ assert kv_cache_config.kv_cache_tensors[1].size == available_memory // 2
490
491
max_context_len =\
492
estimate_max_model_len(vllm_config, kv_cache_spec, 5 * GiB_bytes)
493
# max context len with KV sharing should be 2x as large as without
504
- assert max_context_len == 1310720
494
+ # max_context_len = available_memory / (page_size / block_size) / num_caches
495
+ # max_context_len = 5GB / (512KB / 128) / 2 = 655360
496
+ assert max_context_len == 655360
497
498
# important: override tensor size to prevent large mem alloc during test
507
- # this will only allocate 2 block worth of memory (2 * 32kb)
499
+ # this will only allocate 2 block worth of memory (2 * 512kb)
500
kv_cache_config.num_blocks = 1
509
- for layer in kv_cache_config.tensors:
510
- kv_cache_config.tensors[layer].size =\
511
- kv_cache_spec[layer].page_size_bytes
501
+ for kv_cache_tensor in kv_cache_config.kv_cache_tensors:
502
+ kv_cache_tensor.size = (
503
+ kv_cache_spec[kv_cache_tensor.shared_by[0]].page_size_bytes)
504
505
model_runner.initialize_kv_cache(kv_cache_config)
506
@@ -524,11 +516,10 @@ def test_init_kv_cache_without_kv_sharing(model_runner):
516
assert kv_cache_config.kv_cache_groups[0].layer_names[1] == layer_1
517
518
527
-@pytest.mark.skip(reason="Test is broken on TPU when it's added.")
528
-def test_init_kv_cache_with_kv_sharing_valid(model_runner):
519
+def test_init_kv_cache_with_kv_sharing_valid():
520
layer_0 = "model.layers.0.self_attn.attn"
521
layer_1 = "model.layers.1.self_attn.attn"
531
- vllm_config = model_runner.vllm_config
522
+ vllm_config = get_vllm_config()
523
with set_current_vllm_config(vllm_config):
524
fwd_context = {
525
layer_0:
@@ -552,33 +543,34 @@ def test_init_kv_cache_with_kv_sharing_valid(model_runner):
543
# Set high context length to test max context length estimation
544
vllm_config.model_config.max_model_len = 3_000_000
545
vllm_ctx = vllm_config.compilation_config.static_forward_context
546
+ model_runner = get_model_runner(vllm_config)
547
kv_cache_spec = model_runner.get_kv_cache_spec()
548
assert len(kv_cache_spec) == 1
549
assert layer_0 in kv_cache_spec
550
assert model_runner.shared_kv_cache_layers[layer_1] == layer_0
551
552
available_memory = 20 * GiB_bytes
561
- # page size for layer 0's kv_cache_spec is 32KB
553
+ # page size for layer 0's kv_cache_spec is 512KB
554
# with KV sharing, we can allocate (available_mem//page_size//1) blocks
555
# which is twice as many as without KV sharing
564
- num_expected_blocks = 655360 # 20GB / 32KB
556
+ num_expected_blocks = 2 * 20480 # 20GB / 512KB
557
kv_cache_config = get_kv_cache_config(vllm_config, kv_cache_spec,
558
available_memory)
559
assert kv_cache_config.num_blocks == num_expected_blocks
568
- assert len(kv_cache_config.tensors) == 1
560
+ assert len(kv_cache_config.kv_cache_tensors) == 1
561
# Each layer now has twice the available memory for KV cache
562
# compared to no KV sharing
571
- assert kv_cache_config.tensors[layer_0].size == available_memory
563
+ assert kv_cache_config.kv_cache_tensors[0].size == available_memory
564
565
max_context_len =\
566
estimate_max_model_len(vllm_config, kv_cache_spec, 5 * GiB_bytes)
567
# max context len with KV sharing should be 2x as large as without
576
- assert max_context_len == 2 * 1310720
568
+ assert max_context_len == (2 * 655360)
569
570
# important: override tensor size to prevent large mem alloc during test
579
- # this will only allocate 1 block worth of memory (32kb)
571
+ # this will only allocate 1 block worth of memory (512kb)
572
kv_cache_config.num_blocks = 1
581
- kv_cache_config.tensors[layer_0].size =\
573
+ kv_cache_config.kv_cache_tensors[0].size =\
574
kv_cache_spec[layer_0].page_size_bytes
575
576
model_runner.initialize_kv_cache(kv_cache_config)