main
py 170 lines 5.56 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from contextlib import AbstractContextManager
5 from types import SimpleNamespace
6 from typing import Any, cast
7
8 import pytest
9 import torch
10
11 import vllm.v1.hisparse.binding as hisparse_binding
12 import vllm.v1.worker.gpu.attn_utils as attn_utils
13 import vllm.v1.worker.gpu_model_runner as gpu_model_runner
14 from vllm.v1.worker.gpu_worker import Worker
15
16
17 class _AllocationScope(AbstractContextManager):
18 def __init__(self) -> None:
19 self.active = False
20
21 def __enter__(self):
22 assert not self.active
23 self.active = True
24 return self
25
26 def __exit__(self, *args: Any) -> None:
27 assert self.active
28 self.active = False
29
30
31 @pytest.mark.parametrize("hisparse", [False, True])
32 def test_mrv2_kv_pool_only_wraps_backing_allocation(monkeypatch, hisparse) -> None:
33 scope = _AllocationScope()
34 kv_caches = {"layer": torch.empty(0)}
35
36 def allocate(*args, **kwargs):
37 assert scope.active
38 return kv_caches
39
40 def bind(*args, **kwargs):
41 assert not scope.active
42
43 monkeypatch.setattr(attn_utils, "allocate_kv_cache", allocate)
44 monkeypatch.setattr(attn_utils, "bind_kv_cache_to_layers", bind)
45 monkeypatch.setattr(attn_utils, "get_shared_kv_cache_layers", lambda config: {})
46
47 hisparse_bindings = []
48 if hisparse:
49 host_pool = SimpleNamespace()
50
51 def bind_hisparse(**kwargs):
52 hisparse_bindings.append(kwargs)
53 assert scope.active
54 assert kwargs["kv_caches"] is kv_caches
55 assert kwargs["host_pool"] is host_pool
56 return [
57 SimpleNamespace(
58 view=SimpleNamespace(cache=torch.empty(1, 1, 1), block_size=1),
59 runtime=SimpleNamespace(),
60 )
61 ]
62
63 monkeypatch.setattr(
64 hisparse_binding,
65 "HiSparseHostPool",
66 lambda vllm_config, kv_cache_config: host_pool,
67 )
68 monkeypatch.setattr(hisparse_binding, "allocate_hisparse_kv_caches", allocate)
69 monkeypatch.setattr(hisparse_binding, "bind_hisparse_kv_caches", bind_hisparse)
70
71 config = SimpleNamespace(
72 attention_config=SimpleNamespace(
73 hisparse_config=object() if hisparse else None
74 ),
75 scheduler_config=SimpleNamespace(max_num_seqs=1, max_num_batched_tokens=1),
76 cache_config=SimpleNamespace(get_resolved_kv_cache_layout=lambda: None),
77 model_config=SimpleNamespace(hf_config=SimpleNamespace(model_type="test")),
78 )
79 result = attn_utils.init_kv_cache(
80 {},
81 SimpleNamespace(kv_cache_groups=[]),
82 torch.device("cpu"),
83 [],
84 config,
85 kv_cache_allocation_context=scope,
86 block_tables=SimpleNamespace(),
87 )
88
89 assert len(hisparse_bindings) == int(hisparse)
90 assert result is kv_caches
91 assert not scope.active
92
93
94 def test_mrv1_kv_pool_only_wraps_backing_allocation(monkeypatch) -> None:
95 scope = _AllocationScope()
96 kv_caches = {"layer": torch.empty(0)}
97
98 def allocate(*args, **kwargs):
99 assert scope.active
100 return kv_caches
101
102 def bind(*args, **kwargs):
103 assert not scope.active
104
105 monkeypatch.setattr(gpu_model_runner, "allocate_kv_cache", allocate)
106 monkeypatch.setattr(gpu_model_runner, "bind_kv_cache", bind)
107
108 runner = SimpleNamespace(
109 device=torch.device("cpu"),
110 cache_config=SimpleNamespace(get_resolved_kv_cache_layout=lambda: None),
111 shared_kv_cache_layers={},
112 model_config=SimpleNamespace(hf_config=SimpleNamespace(model_type="test")),
113 compilation_config=SimpleNamespace(static_forward_context={}),
114 kv_caches=[],
115 )
116 result = gpu_model_runner.GPUModelRunner.initialize_kv_cache_tensors(
117 runner,
118 SimpleNamespace(kv_cache_groups=[]),
119 [],
120 kv_cache_allocation_context=scope,
121 )
122
123 assert result is kv_caches
124 assert not scope.active
125
126
127 def test_kv_wake_does_not_run_model_runner_recovery() -> None:
128 model = torch.nn.Module()
129 model.register_buffer("_k_scale", torch.tensor(0.5))
130 model.register_buffer("_v_scale", torch.tensor(0.25))
131
132 class Runner:
133 def __init__(self) -> None:
134 self.model = model
135 self.layout_tensors = tuple(torch.tensor([i]) for i in range(5))
136 self.recovery_calls = 0
137
138 def post_kv_cache_wake_up(self) -> None:
139 self.recovery_calls += 1
140 self.model.get_buffer("_k_scale").fill_(1.0)
141 self.model.get_buffer("_v_scale").fill_(1.0)
142 self.layout_tensors = tuple(torch.tensor([i]) for i in range(5))
143
144 runner = Runner()
145 worker = cast(
146 Worker,
147 SimpleNamespace(
148 sleep_mode_backend=SimpleNamespace(resume=lambda tags: None),
149 _sleep_saved_buffers={},
150 _sleep_saved_draft_buffers={},
151 model_runner=runner,
152 synchronize_device=lambda: None,
153 vllm_config=SimpleNamespace(
154 model_config=SimpleNamespace(enable_nccl_comm_suspend=False)
155 ),
156 ),
157 )
158 layout_tensors = runner.layout_tensors
159 layout_ptrs = tuple(t.data_ptr() for t in layout_tensors)
160
161 Worker.wake_up(worker, tags=["kv_cache"])
162
163 assert runner.recovery_calls == 0
164 assert model.get_buffer("_k_scale").item() == 0.5
165 assert model.get_buffer("_v_scale").item() == 0.25
166 assert all(
167 actual is expected
168 for actual, expected in zip(runner.layout_tensors, layout_tensors, strict=True)
169 )
170 assert tuple(t.data_ptr() for t in runner.layout_tensors) == layout_ptrs