main
py 583 lines 18.1 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import threading
5 from dataclasses import dataclass
6 from typing import Any
7
8 import pytest
9 import torch
10
11 from vllm import envs
12 from vllm.model_executor.warmup import jit_warmup_triton_helper
13 from vllm.model_executor.warmup.jit_warmup import (
14 JitWarmupRegistry,
15 WarmupChoices,
16 WarmupIntRange,
17 kernel_launcher,
18 )
19 from vllm.model_executor.warmup.jit_warmup_triton_helper import (
20 LaunchSpec,
21 TritonJitKey,
22 TritonWarmupTensor,
23 VllmTritonJitKernel,
24 triton_kernel_dispatcher_with_warmup,
25 triton_warmup_inputs,
26 )
27 from vllm.platforms import current_platform
28 from vllm.triton_utils import tl, triton
29
30
31 @triton.jit
32 def _binder_test_kernel(x, value, BLOCK: tl.constexpr):
33 pass
34
35
36 @triton.jit
37 def _second_binder_test_kernel(x, value, BLOCK: tl.constexpr):
38 pass
39
40
41 @triton.jit
42 def _pointer_group_test_kernel(x_ptr, value, BLOCK: tl.constexpr):
43 pass
44
45
46 class _FakeTritonKernel:
47 arg_names: tuple[str, ...] = ("first", "second", "CONST")
48
49 def __init__(self) -> None:
50 self.warmup_calls: list[dict[str, Any]] = []
51 self.runtime_calls: list[tuple[Any, tuple[Any, ...], dict[str, Any]]] = []
52
53 def warmup(self, **kwargs: Any) -> None:
54 self.warmup_calls.append(kwargs)
55
56 def __getitem__(self, grid: Any) -> Any:
57 def launch(*args: Any, **kwargs: Any) -> None:
58 self.runtime_calls.append((grid, args, kwargs))
59
60 return launch
61
62
63 def _patch_key_deriver(
64 monkeypatch: pytest.MonkeyPatch,
65 derive: Any,
66 ) -> None:
67 monkeypatch.setattr(
68 jit_warmup_triton_helper,
69 "_triton_key_deriver",
70 lambda kernel: lambda kwargs: derive(kernel, kwargs),
71 )
72
73
74 class _TestTritonKernel(VllmTritonJitKernel["_TestTritonKernel.CompileKey"]):
75 kernel = _FakeTritonKernel()
76
77 @dataclass(frozen=True)
78 class CompileKey:
79 value: int
80
81 def dispatch(self, *, value: int) -> CompileKey:
82 return self.CompileKey(value=value)
83
84 def get_warmup_keys(self) -> list[CompileKey]:
85 return self._trace_dispatch(self.dispatch)(value=1)
86
87 def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
88 return dict(first="warmup", second=compile_key.value, runtime_launcher=None)
89
90 @kernel_launcher
91 def __call__(
92 self,
93 first: str,
94 second: int,
95 runtime_launcher: Any,
96 ) -> LaunchSpec:
97 return (2,), dict(
98 CONST=7,
99 _runtime_launcher=runtime_launcher,
100 _runtime_launcher_arg_count=2,
101 )
102
103
104 def test_triton_launcher_supports_compile_and_runtime_adapters() -> None:
105 owner = _TestTritonKernel()
106 owner.kernel.warmup_calls.clear()
107
108 owner.compile(owner.CompileKey(value=1))
109 assert owner.kernel.warmup_calls == [
110 {"grid": (1,), "first": "warmup", "second": 1, "CONST": 7}
111 ]
112
113 runtime_calls: list[tuple[Any, ...]] = []
114
115 def runtime_launcher(kernel: Any, grid: Any, *args: Any, **kwargs: Any) -> None:
116 runtime_calls.append((kernel, grid, args, kwargs))
117
118 owner("runtime", 2, runtime_launcher)
119 assert runtime_calls == [(owner.kernel, (2,), ("runtime", 2), {"CONST": 7})]
120
121
122 @pytest.mark.skipif(
123 not current_platform.is_cuda() or not hasattr(triton, "AsyncCompileMode"),
124 reason="Requires CUDA Triton async compilation",
125 )
126 def test_failed_parallel_warmup_does_not_leak_into_runtime(monkeypatch) -> None:
127 from triton.runtime._async_compile import active_mode
128
129 monkeypatch.setattr(envs, "VLLM_TRITON_JIT_WARMUP_NUM_THREADS", 2)
130 owner = _TestTritonKernel()
131 owner.kernel = _FakeTritonKernel()
132
133 def fail():
134 raise RuntimeError("warmup failed")
135
136 def warmup(**kwargs):
137 active_mode.get().submit(kwargs["second"], fail, lambda result: None)
138
139 monkeypatch.setattr(owner.kernel, "warmup", warmup)
140 with pytest.raises(RuntimeError, match="warmup failed"):
141 owner.compile_many(owner.CompileKey(value=i) for i in range(2))
142
143 assert active_mode.get() is None
144 owner("runtime", 3, None)
145 assert len(owner.kernel.runtime_calls) == 1
146
147
148 def test_autotuning_warmup_stays_serial(monkeypatch) -> None:
149 owner = _TestTritonKernel()
150 owner.kernel = _FakeTritonKernel()
151 owner._run_autotune = True
152 monkeypatch.setattr(envs, "VLLM_TRITON_JIT_WARMUP_NUM_THREADS", 4)
153
154 def unexpected_async(*args):
155 pytest.fail("Autotuning must not use asynchronous compilation")
156
157 monkeypatch.setattr(triton, "AsyncCompileMode", unexpected_async, raising=False)
158 owner.compile_many(owner.CompileKey(value=i) for i in range(2))
159 assert len(owner.kernel.runtime_calls) == 2
160
161
162 @triton.jit
163 def _warmup_store_kernel(out, VALUE: tl.constexpr):
164 tl.store(out, VALUE)
165
166
167 @pytest.mark.skipif(
168 not current_platform.is_cuda() or not hasattr(triton, "AsyncCompileMode"),
169 reason="Requires CUDA Triton async compilation",
170 )
171 def test_registered_warmup_parallelizes_compile_only_and_runtime_stays_serial(
172 monkeypatch,
173 ) -> None:
174 from triton.runtime._async_compile import active_mode
175
176 monkeypatch.setattr(envs, "VLLM_TRITON_JIT_WARMUP_NUM_THREADS", 2)
177
178 def warmup_inputs():
179 return dict(out=TritonWarmupTensor(torch.int32), value=WarmupChoices(1, 2))
180
181 @triton_kernel_dispatcher_with_warmup(
182 kernel=_warmup_store_kernel, warmup_inputs=warmup_inputs
183 )
184 def store(out, value):
185 return (1,), dict(VALUE=value)
186
187 store.get_warmup_keys() # Initialize Triton's binder before wrapping its compiler.
188 caller = threading.get_ident()
189 barrier = threading.Barrier(2, timeout=10)
190 compiled_on = []
191 compile_kernel = _warmup_store_kernel.compile
192
193 def record_compile(*args, **kwargs):
194 if threading.get_ident() != caller:
195 barrier.wait()
196 result = compile_kernel(*args, **kwargs)
197 compiled_on.append(threading.get_ident())
198 return result
199
200 monkeypatch.setattr(_warmup_store_kernel, "compile", record_compile)
201 registry = JitWarmupRegistry(None)
202 with registry.activate():
203 store.register_warmup()
204 registry.warmup()
205
206 assert len(compiled_on) == 2
207 assert all(thread != caller for thread in compiled_on)
208 assert active_mode.get() is None
209 out = torch.empty(1, dtype=torch.int32, device=current_platform.device_type)
210 for value in (1, 2):
211 store(out, value)
212 assert out.item() == value
213 assert len(compiled_on) == 2
214
215 # A specialization first seen during inference uses the normal blocking JIT.
216 store(out, 3)
217 assert out.item() == 3
218 assert compiled_on[2:] == [caller]
219
220
221 def test_triton_launcher_supports_cpu_function_wrappers() -> None:
222 calls: list[tuple[Any, ...]] = []
223
224 def kernel(first: str, second: int, CONST: int) -> None:
225 calls.append((first, second, CONST))
226
227 class FuncWrapper:
228 def __init__(self) -> None:
229 self.func = kernel
230
231 def __getitem__(self, _grid: Any) -> Any:
232 return self.func
233
234 class TestCpuKernel(_TestTritonKernel):
235 kernel: Any = FuncWrapper()
236
237 owner = TestCpuKernel()
238
239 owner("runtime", 2, None)
240 assert calls == [("runtime", 2, 7)]
241
242
243 def test_triton_kernel_decorator_returns_launcher(
244 monkeypatch: pytest.MonkeyPatch,
245 ) -> None:
246 kernel = _FakeTritonKernel()
247 kernel.arg_names = (
248 "FIRST_PTR",
249 "FIRST_STRIDE0",
250 "aliased_ptr",
251 "aliased_stride_0",
252 "SECOND",
253 "CONST",
254 )
255
256 def warmup_inputs() -> dict[str, Any]:
257 return dict(
258 first=TritonWarmupTensor(torch.float32, shape=(2, 3), strides=(5, 1)),
259 second=WarmupChoices(1, 2),
260 )
261
262 @triton_kernel_dispatcher_with_warmup(kernel=kernel, warmup_inputs=warmup_inputs)
263 def launch(first: str, second: int = 2, config: int = 7) -> LaunchSpec:
264 return (2,), dict(aliased_ptr=first, CONST=config)
265
266 def fake_keys(kernel: Any, kwargs: Any) -> set[TritonJitKey]:
267 return {TritonJitKey(id(kernel), "fake", 0, kwargs["SECOND"])}
268
269 _patch_key_deriver(monkeypatch, fake_keys)
270
271 keys = launch.get_warmup_keys()
272 keys_with_config = launch.get_warmup_keys(vllm_config=object())
273 assert keys_with_config == keys
274 assert [dict(key.inputs)["second"] for key in keys] == [1, 2]
275 launch.compile(keys[0])
276 assert kernel.warmup_calls == [
277 {
278 "grid": (1,),
279 "FIRST_PTR": TritonWarmupTensor(
280 torch.float32, shape=(2, 3), strides=(5, 1)
281 ),
282 "FIRST_STRIDE0": 5,
283 "aliased_ptr": TritonWarmupTensor(
284 torch.float32, shape=(2, 3), strides=(5, 1)
285 ),
286 "aliased_stride_0": 5,
287 "SECOND": 1,
288 "CONST": 7,
289 }
290 ]
291 first = torch.empty_strided((2, 3), (5, 1))
292 launch(first)
293 assert kernel.runtime_calls == [
294 (
295 (2,),
296 (),
297 {
298 "FIRST_PTR": first,
299 "FIRST_STRIDE0": 5,
300 "aliased_ptr": first,
301 "aliased_stride_0": 5,
302 "SECOND": 2,
303 "CONST": 7,
304 },
305 )
306 ]
307 assert launch.__name__ == "launch"
308 with pytest.raises(TypeError, match="unexpected keyword"):
309 launch(first, 1, 7, stale_constexpr=True)
310
311
312 def test_triton_kernel_decorator_without_triton(
313 monkeypatch: pytest.MonkeyPatch,
314 ) -> None:
315 monkeypatch.setattr(jit_warmup_triton_helper, "HAS_TRITON", False)
316 monkeypatch.setattr(jit_warmup_triton_helper, "triton", object())
317 kernel = _FakeTritonKernel()
318
319 def warmup_inputs() -> dict[str, Any]:
320 return dict(first="warmup", second=1)
321
322 @triton_kernel_dispatcher_with_warmup(kernel=kernel, warmup_inputs=warmup_inputs)
323 def launch(first: str, second: int) -> LaunchSpec:
324 return (2,), dict(CONST=7)
325
326 launch("runtime", 2)
327 assert kernel.runtime_calls == [
328 ((2,), (), {"first": "runtime", "second": 2, "CONST": 7})
329 ]
330
331
332 def test_triton_kernel_decorator_exhausts_large_ranges_before_deduplication(
333 monkeypatch: pytest.MonkeyPatch,
334 ) -> None:
335 kernel = _FakeTritonKernel()
336 dispatched: list[int] = []
337
338 def warmup_inputs() -> dict[str, Any]:
339 tokens: Any = WarmupIntRange(1, 8193)
340 return dict(first="warmup", second=tokens)
341
342 def specialization(second: int) -> int:
343 return 7 if second <= 97 else 8
344
345 @triton_kernel_dispatcher_with_warmup(kernel=kernel, warmup_inputs=warmup_inputs)
346 def dispatch(first: str, second: int) -> LaunchSpec:
347 dispatched.append(second)
348 return (second,), dict(CONST=specialization(second))
349
350 def fake_keys(kernel: Any, kwargs: Any) -> set[TritonJitKey]:
351 return {TritonJitKey(id(kernel), "fake", 0, kwargs["CONST"])}
352
353 _patch_key_deriver(monkeypatch, fake_keys)
354
355 keys = dispatch.get_warmup_keys()
356 assert dispatched == list(range(1, 8193))
357 assert len(keys) == 2
358 assert {specialization(dict(key.inputs)["second"]) for key in keys} == {7, 8}
359
360
361 def test_triton_kernel_decorator_propagates_dispatch_assertions(
362 monkeypatch: pytest.MonkeyPatch,
363 ) -> None:
364 kernel = _FakeTritonKernel()
365
366 def warmup_inputs() -> dict[str, Any]:
367 return dict(first="warmup", second=WarmupChoices(1, 2))
368
369 @triton_kernel_dispatcher_with_warmup(kernel=kernel, warmup_inputs=warmup_inputs)
370 def dispatch(first: str, second: int) -> LaunchSpec:
371 assert second != 2, "broken dispatch"
372 return (1,), dict(CONST=second)
373
374 _patch_key_deriver(
375 monkeypatch,
376 lambda kernel, kwargs: {TritonJitKey(id(kernel), "fake", 0, kwargs["CONST"])},
377 )
378
379 with pytest.raises(AssertionError, match="broken dispatch"):
380 dispatch.get_warmup_keys()
381
382
383 def test_triton_kernel_dispatch_uses_device_fake_tensors(
384 monkeypatch: pytest.MonkeyPatch,
385 ) -> None:
386 kernel = _FakeTritonKernel()
387 device_type = current_platform.device_type
388
389 def warmup_inputs() -> dict[str, Any]:
390 return dict(
391 first=TritonWarmupTensor(torch.float32, shape=(2, 3)),
392 second=1,
393 )
394
395 @triton_kernel_dispatcher_with_warmup(kernel=kernel, warmup_inputs=warmup_inputs)
396 def dispatch(first: torch.Tensor, second: int) -> LaunchSpec:
397 assert isinstance(first, torch.Tensor)
398 assert first.device.type == device_type
399 assert first[0].is_contiguous()
400 return (first.shape[0],), dict(CONST=first[0].numel())
401
402 _patch_key_deriver(
403 monkeypatch,
404 lambda kernel, kwargs: {TritonJitKey(id(kernel), "fake", 0, kwargs["CONST"])},
405 )
406
407 keys = dispatch.get_warmup_keys()
408 assert len(keys) == 1
409 assert dict(keys[0].inputs)["first"].device.type == device_type
410
411
412 def test_triton_kernel_decorates_native_launchers(
413 monkeypatch: pytest.MonkeyPatch,
414 ) -> None:
415 kernel = _FakeTritonKernel()
416
417 def warmup_inputs(kernel: Any = kernel) -> dict[str, Any]:
418 return triton_warmup_inputs(
419 kernel,
420 "warmup",
421 WarmupChoices(1, 2),
422 grid=(2,),
423 CONST=7,
424 )
425
426 launcher = triton_kernel_dispatcher_with_warmup(warmup_inputs=warmup_inputs)(kernel)
427 launcher[(3,)]("runtime", 4, CONST=8)
428
429 assert kernel.runtime_calls == [((3,), ("runtime", 4), {"CONST": 8})]
430
431 _patch_key_deriver(
432 monkeypatch,
433 lambda kernel, kwargs: {
434 TritonJitKey(id(kernel), "fake", 0, (kwargs["second"], kwargs["CONST"]))
435 },
436 )
437
438 keys = launcher.get_warmup_keys()
439 assert len(keys) == 2
440 launcher.compile(keys[0])
441 assert kernel.warmup_calls == [
442 {"grid": (1,), "first": "warmup", "second": 1, "CONST": 7}
443 ]
444
445
446 def test_triton_warmup_inputs_expands_explicit_pointer_dtypes() -> None:
447 inputs = triton_warmup_inputs(
448 _pointer_group_test_kernel,
449 grid=(1,),
450 pointer_dtypes={torch.float32: ("x_ptr",)},
451 value=2,
452 BLOCK=16,
453 )
454
455 assert inputs["x_ptr"] == TritonWarmupTensor(torch.float32)
456 assert inputs["value"] == 2
457
458 with pytest.raises(ValueError, match="not a pointer: value"):
459 triton_warmup_inputs(
460 _pointer_group_test_kernel,
461 grid=(1,),
462 pointer_dtypes={torch.float32: ("value",)},
463 x_ptr=TritonWarmupTensor(torch.float32),
464 BLOCK=16,
465 )
466 with pytest.raises(ValueError, match="Missing Triton pointer inputs: x_ptr"):
467 triton_warmup_inputs(
468 _pointer_group_test_kernel,
469 grid=(1,),
470 pointer_dtypes={},
471 value=2,
472 BLOCK=16,
473 )
474
475
476 def test_triton_key_derivation_applies_wrappers_and_runtime_options(
477 monkeypatch: pytest.MonkeyPatch,
478 ) -> None:
479 knobs = triton.knobs
480 driver = triton.runtime.driver
481
482 device = "test-device"
483 monkeypatch.setattr(driver.active, "get_current_device", lambda: device)
484 calls: list[dict[str, Any]] = []
485
486 def binder(x: Any, value: int, BLOCK: int, **options: Any) -> Any:
487 calls.append(dict(BLOCK=BLOCK, **options))
488 specialization = [("pointer", x.dtype), ("i32", value), ("constexpr", BLOCK)]
489 return {}, specialization, options
490
491 cache: dict[Any, Any] = {}
492 _binder_test_kernel.device_caches[device] = ({}, cache, None, None, binder)
493 heuristic_kernel = triton.heuristics(
494 {"BLOCK": lambda args: 16 if args["value"] <= 16 else 32}
495 )(_binder_test_kernel)
496
497 keys = jit_warmup_triton_helper._triton_key_deriver(heuristic_kernel)(
498 {"x": TritonWarmupTensor(tl.float32), "value": 2}
499 )
500
501 assert len(keys) == 1
502 expected_options = {
503 "debug": knobs.runtime.debug,
504 "instrumentation_mode": knobs.compilation.instrumentation_mode,
505 }
506 if hasattr(knobs.compilation, "fpsan_homomorphic_casts"):
507 expected_options["fpsan_homomorphic_casts"] = (
508 knobs.compilation.fpsan_homomorphic_casts
509 )
510 assert calls == [{"BLOCK": 16, **expected_options}]
511
512
513 def test_triton_key_derivation_covers_autotune_configs_and_jit_identity(
514 monkeypatch: pytest.MonkeyPatch,
515 ) -> None:
516 driver = triton.runtime.driver
517
518 device = "test-device"
519 monkeypatch.setattr(driver.active, "get_current_device", lambda: device)
520
521 def binder(x: Any, value: int, BLOCK: int, **options: Any) -> Any:
522 specialization = [("pointer", x.dtype), ("i32", value), ("constexpr", BLOCK)]
523 return {}, specialization, options
524
525 for kernel in (_binder_test_kernel, _second_binder_test_kernel):
526 kernel.device_caches[device] = ({}, {}, None, None, binder)
527 autotuned = triton.autotune(
528 configs=[
529 triton.Config({"BLOCK": 16}, num_warps=2),
530 triton.Config({"BLOCK": 32}, num_warps=4),
531 ],
532 key=["value"],
533 )(_binder_test_kernel)
534 inputs = {"x": TritonWarmupTensor(tl.float32), "value": 2}
535
536 autotune_keys = jit_warmup_triton_helper._triton_key_deriver(autotuned)(inputs)
537 other_kernel_keys = jit_warmup_triton_helper._triton_key_deriver(
538 _second_binder_test_kernel
539 )(inputs | {"BLOCK": 16})
540
541 assert len(autotune_keys) == 2
542 assert {key.jit_function_key for key in autotune_keys} == {
543 _binder_test_kernel.cache_key
544 }
545 assert {key.jit_function_id for key in autotune_keys} == {id(_binder_test_kernel)}
546 assert autotune_keys.isdisjoint(other_kernel_keys)
547
548
549 def test_compute_slot_mapping_uses_named_launcher_inputs(monkeypatch) -> None:
550 from vllm.v1.attention.backends.utils import PAD_SLOT_ID
551 from vllm.v1.worker.block_table import ComputeSlotMappingKernel
552
553 owner = ComputeSlotMappingKernel()
554 compile_key = owner.CompileKey(
555 kv_cache_block_size=16,
556 blocks_per_kv_block=1,
557 total_cp_world_size=2,
558 total_cp_rank=1,
559 cp_kv_cache_interleave_size=1,
560 block_table_stride=128,
561 block_size=16,
562 )
563 launches: list[tuple[Any, ...]] = []
564
565 def launch(launch_spec: Any, inputs: Any) -> None:
566 launches.append((launch_spec, inputs))
567
568 monkeypatch.setattr(owner, "launch", launch)
569 owner.compile(compile_key)
570
571 (grid, kwargs), inputs = launches[0]
572 assert grid == (2,)
573 assert inputs["num_tokens"] == 2
574 assert inputs["block_table_stride"] == 128
575 assert kwargs == {
576 "KV_CACHE_BLOCK_SIZE": 16,
577 "BLOCKS_PER_KV_BLOCK": 1,
578 "TOTAL_CP_WORLD_SIZE": 2,
579 "TOTAL_CP_RANK": 1,
580 "CP_KV_CACHE_INTERLEAVE_SIZE": 1,
581 "PAD_ID": PAD_SLOT_ID,
582 "BLOCK_SIZE": owner.triton_block_size,
583 }