Skip to content

Commit ab613f9

Browse files
committed
Fix FlashInfer int workspace reservation
Signed-off-by: lesj0610 <lesj0610@users.noreply.github.com>
1 parent 3f77f18 commit ab613f9

2 files changed

Lines changed: 386 additions & 57 deletions

File tree

tests/v1/worker/test_cudagraph_memory_profiling.py

Lines changed: 176 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,44 @@ def _attention_spec(head_size: int, head_size_v: int | None = None):
2323
)
2424

2525

26+
class _FakeFlashInferWrapper:
27+
def __init__(
28+
self,
29+
float_workspace_buffer: torch.Tensor | None = None,
30+
int_workspace_bytes: int = 1,
31+
) -> None:
32+
self._float_workspace_buffer = (
33+
float_workspace_buffer
34+
if float_workspace_buffer is not None
35+
else torch.empty(1, dtype=torch.uint8)
36+
)
37+
self._int_workspace_buffer = torch.empty(
38+
max(int_workspace_bytes, 1), dtype=torch.uint8
39+
)
40+
self._vllm_flashinfer_int_workspace_finalized = False
41+
self.is_cuda_graph_enabled = False
42+
self.reset_calls = 0
43+
44+
def reset_workspace_buffer(
45+
self,
46+
float_workspace_buffer: torch.Tensor,
47+
int_workspace_buffer: torch.Tensor,
48+
) -> None:
49+
self._float_workspace_buffer = float_workspace_buffer
50+
self._int_workspace_buffer = int_workspace_buffer
51+
self.reset_calls += 1
52+
53+
54+
def _make_flashinfer_builder(flashinfer_backend):
55+
FlashInferMetadataBuilder = flashinfer_backend.FlashInferMetadataBuilder
56+
builder = FlashInferMetadataBuilder.__new__(FlashInferMetadataBuilder)
57+
builder._workspace_buffer = None
58+
builder._workspace_state = flashinfer_backend._FlashInferWorkspaceState()
59+
builder.device = torch.device("cpu")
60+
builder.use_dcp = False
61+
return builder
62+
63+
2664
def test_flashinfer_separate_cudagraph_memory_profile_gate():
2765
pytest.importorskip("flashinfer")
2866
from vllm.v1.attention.backends.flashinfer import FlashInferMetadataBuilder
@@ -53,26 +91,16 @@ def test_flashinfer_workspace_buffer_uses_workspace_manager():
5391
pytest.importorskip("flashinfer")
5492
from vllm.v1.attention.backends import flashinfer as flashinfer_backend
5593

56-
FlashInferMetadataBuilder = flashinfer_backend.FlashInferMetadataBuilder
57-
58-
def make_builder():
59-
builder = FlashInferMetadataBuilder.__new__(FlashInferMetadataBuilder)
60-
builder._workspace_buffer = None
61-
builder._workspace_state = flashinfer_backend._FlashInferWorkspaceState()
62-
builder.device = torch.device("cpu")
63-
builder.use_dcp = False
64-
return builder
65-
6694
reset_workspace_manager()
6795
init_workspace_manager(torch.device("cpu"))
6896
try:
69-
first_builder = make_builder()
97+
first_builder = _make_flashinfer_builder(flashinfer_backend)
7098
first_state = first_builder.get_workspace_buffer_state()
7199
first = first_builder._get_workspace_buffer(
72100
first_builder._native_initial_workspace_buffer_size()
73101
)
74102

75-
second_builder = make_builder()
103+
second_builder = _make_flashinfer_builder(flashinfer_backend)
76104
second_builder.set_workspace_buffer_state(first_state)
77105
second = second_builder._get_workspace_buffer(
78106
second_builder._native_initial_workspace_buffer_size()
@@ -90,41 +118,31 @@ def test_flashinfer_workspace_buffer_growth_resets_registered_wrappers():
90118
pytest.importorskip("flashinfer")
91119
from vllm.v1.attention.backends import flashinfer as flashinfer_backend
92120

93-
class FakeWrapper:
94-
def __init__(self, float_workspace_buffer):
95-
self._float_workspace_buffer = float_workspace_buffer
96-
self._int_workspace_buffer = torch.empty(1, dtype=torch.uint8)
97-
self.reset_calls = 0
98-
99-
def reset_workspace_buffer(self, float_workspace_buffer, int_workspace_buffer):
100-
self._float_workspace_buffer = float_workspace_buffer
101-
self._int_workspace_buffer = int_workspace_buffer
102-
self.reset_calls += 1
103-
104-
FlashInferMetadataBuilder = flashinfer_backend.FlashInferMetadataBuilder
105-
builder = FlashInferMetadataBuilder.__new__(FlashInferMetadataBuilder)
106-
builder._workspace_buffer = None
107-
builder._workspace_state = flashinfer_backend._FlashInferWorkspaceState()
108-
builder.device = torch.device("cpu")
109-
builder.use_dcp = False
121+
WorkspaceSizes = flashinfer_backend.WorkspaceSizes
122+
builder = _make_flashinfer_builder(flashinfer_backend)
110123

111124
reset_workspace_manager()
112125
init_workspace_manager(torch.device("cpu"))
113126
try:
114-
wrapper = FakeWrapper(
127+
wrapper = _FakeFlashInferWrapper(
115128
builder._get_workspace_buffer(
116129
builder._native_initial_workspace_buffer_size()
117130
)
118131
)
119132
builder._register_workspace_wrapper(wrapper)
120-
builder._ensure_flashinfer_wrapper_workspace(wrapper, 1024)
133+
builder._ensure_flashinfer_wrapper_workspace(wrapper, WorkspaceSizes(1024, 16))
121134

122135
assert builder._workspace_buffer.numel() == 1024
123136
assert wrapper._float_workspace_buffer.data_ptr() == (
124137
builder._workspace_buffer.data_ptr()
125138
)
126139
assert wrapper._float_workspace_buffer.numel() == 1024
127-
assert wrapper.reset_calls >= 1
140+
assert wrapper._int_workspace_buffer.numel() == 16
141+
reset_calls = wrapper.reset_calls
142+
assert reset_calls >= 1
143+
144+
builder._ensure_flashinfer_wrapper_workspace(wrapper, WorkspaceSizes(1024, 16))
145+
assert wrapper.reset_calls == reset_calls
128146

129147
wrapper_ref = weakref.ref(wrapper)
130148
del wrapper
@@ -137,10 +155,87 @@ def reset_workspace_buffer(self, float_workspace_buffer, int_workspace_buffer):
137155
reset_workspace_manager()
138156

139157

158+
def test_flashinfer_int_workspace_is_per_wrapper():
159+
pytest.importorskip("flashinfer")
160+
from vllm.v1.attention.backends import flashinfer as flashinfer_backend
161+
162+
WorkspaceSizes = flashinfer_backend.WorkspaceSizes
163+
builder = _make_flashinfer_builder(flashinfer_backend)
164+
165+
reset_workspace_manager()
166+
init_workspace_manager(torch.device("cpu"))
167+
try:
168+
first = _FakeFlashInferWrapper()
169+
second = _FakeFlashInferWrapper()
170+
171+
builder._ensure_flashinfer_wrapper_workspace(first, WorkspaceSizes(1024, 32))
172+
builder._ensure_flashinfer_wrapper_workspace(second, WorkspaceSizes(1024, 32))
173+
174+
assert first._float_workspace_buffer.data_ptr() == (
175+
second._float_workspace_buffer.data_ptr()
176+
)
177+
assert first._int_workspace_buffer.data_ptr() != (
178+
second._int_workspace_buffer.data_ptr()
179+
)
180+
assert first._int_workspace_buffer.numel() == 32
181+
assert second._int_workspace_buffer.numel() == 32
182+
finally:
183+
reset_workspace_manager()
184+
185+
186+
def test_flashinfer_finalized_int_workspace_cannot_grow():
187+
pytest.importorskip("flashinfer")
188+
from vllm.v1.attention.backends import flashinfer as flashinfer_backend
189+
190+
WorkspaceSizes = flashinfer_backend.WorkspaceSizes
191+
builder = _make_flashinfer_builder(flashinfer_backend)
192+
wrapper = _FakeFlashInferWrapper(int_workspace_bytes=8)
193+
wrapper._vllm_flashinfer_int_workspace_finalized = True
194+
195+
reset_workspace_manager()
196+
init_workspace_manager(torch.device("cpu"))
197+
try:
198+
with pytest.raises(AssertionError, match="int workspace is finalized"):
199+
builder._ensure_flashinfer_wrapper_workspace(
200+
wrapper, WorkspaceSizes(1024, 16)
201+
)
202+
finally:
203+
reset_workspace_manager()
204+
205+
206+
def test_flashinfer_non_cudagraph_int_workspace_can_grow(monkeypatch):
207+
pytest.importorskip("flashinfer")
208+
from vllm.v1.attention.backends import flashinfer as flashinfer_backend
209+
210+
WorkspaceSizes = flashinfer_backend.WorkspaceSizes
211+
builder = _make_flashinfer_builder(flashinfer_backend)
212+
wrapper = _FakeFlashInferWrapper(int_workspace_bytes=8)
213+
warnings = []
214+
215+
monkeypatch.setattr(
216+
flashinfer_backend.logger,
217+
"warning",
218+
lambda msg, *args: warnings.append(msg),
219+
)
220+
221+
reset_workspace_manager()
222+
init_workspace_manager(torch.device("cpu"))
223+
try:
224+
builder._ensure_flashinfer_wrapper_workspace(wrapper, WorkspaceSizes(1024, 8))
225+
builder._ensure_flashinfer_wrapper_workspace(wrapper, WorkspaceSizes(1024, 16))
226+
227+
assert wrapper._int_workspace_buffer.numel() == 16
228+
assert wrapper.reset_calls == 2
229+
assert any("Growing FlashInfer int workspace" in msg for msg in warnings)
230+
finally:
231+
reset_workspace_manager()
232+
233+
140234
def test_flashinfer_reserves_prefill_tail_workspace(monkeypatch):
141235
pytest.importorskip("flashinfer")
142236
from vllm.v1.attention.backends import flashinfer as flashinfer_backend
143237

238+
WorkspaceSizes = flashinfer_backend.WorkspaceSizes
144239
FlashInferMetadataBuilder = flashinfer_backend.FlashInferMetadataBuilder
145240
builder = FlashInferMetadataBuilder.__new__(FlashInferMetadataBuilder)
146241
builder._workspace_buffer = None
@@ -152,7 +247,8 @@ def test_flashinfer_reserves_prefill_tail_workspace(monkeypatch):
152247
scheduler_config=SimpleNamespace(
153248
max_num_batched_tokens=8,
154249
max_num_seqs=4,
155-
)
250+
),
251+
speculative_config=None,
156252
)
157253
builder.q_data_type = torch.float16
158254
builder.kv_cache_dtype = torch.uint8
@@ -171,7 +267,7 @@ def fake_workspace_size(**kwargs):
171267
qo_indptr = kwargs["qo_indptr_cpu"]
172268
query_lens = torch.diff(qo_indptr).tolist()
173269
observed_query_lens.extend(query_lens)
174-
return 4096 if query_lens == [3] else 0
270+
return WorkspaceSizes(4096, 64) if query_lens == [3] else WorkspaceSizes(0, 0)
175271

176272
monkeypatch.setattr(
177273
builder,
@@ -187,19 +283,63 @@ def fake_workspace_size(**kwargs):
187283
"_ensure_flashinfer_wrapper_workspace",
188284
lambda wrapper, size: ensured.append(size),
189285
)
286+
monkeypatch.setattr(
287+
builder,
288+
"_reserve_decode_wrapper_workspace",
289+
lambda **kwargs: WorkspaceSizes(0, 0),
290+
)
291+
builder.enable_cuda_graph = False
190292

191293
reset_workspace_manager()
192294
init_workspace_manager(torch.device("cpu"))
193295
try:
194-
assert builder.reserve_workspace_for_cudagraph_capture() == 4096
296+
assert builder.reserve_workspace_for_cudagraph_capture() == 4160
195297
finally:
196298
reset_workspace_manager()
197299

198-
assert ensured == [4096]
300+
assert ensured == [WorkspaceSizes(4096, 64)]
199301
assert 3 in observed_query_lens
200302
assert 8 in observed_query_lens
201303

202304

305+
def test_flashinfer_reserves_decode_cudagraph_int_workspace(monkeypatch):
306+
pytest.importorskip("flashinfer")
307+
from vllm.v1.attention.backends import flashinfer as flashinfer_backend
308+
309+
WorkspaceSizes = flashinfer_backend.WorkspaceSizes
310+
builder = _make_flashinfer_builder(flashinfer_backend)
311+
builder.decode_fixed_split_size = -1
312+
builder.disable_split_kv = False
313+
314+
wrapper = _FakeFlashInferWrapper()
315+
wrapper.is_cuda_graph_enabled = True
316+
317+
monkeypatch.setattr(builder, "_get_decode_wrapper", lambda *args: wrapper)
318+
monkeypatch.setattr(
319+
builder,
320+
"_get_decode_workspace_size",
321+
lambda **kwargs: WorkspaceSizes(128, 32),
322+
)
323+
324+
reset_workspace_manager()
325+
init_workspace_manager(torch.device("cpu"))
326+
try:
327+
sizes = builder._reserve_decode_wrapper_workspace(
328+
batch_size=4,
329+
num_pages=8,
330+
last_page_len=16,
331+
use_cudagraph=True,
332+
)
333+
finally:
334+
reset_workspace_manager()
335+
336+
assert sizes == WorkspaceSizes(128, 32)
337+
assert wrapper._float_workspace_buffer.numel() == 128
338+
assert wrapper._int_workspace_buffer.numel() == 32
339+
assert wrapper.reset_calls == 1
340+
assert wrapper._vllm_flashinfer_int_workspace_finalized
341+
342+
203343
def test_flashinfer_workspace_query_len_candidates():
204344
pytest.importorskip("flashinfer")
205345
from vllm.v1.attention.backends import flashinfer as flashinfer_backend

0 commit comments

Comments
 (0)