[6/N][warmup][DSv4] Migrate sampling, and DFlash JIT kernels (#56323)
Signed-off-by: Lucas Wilkinson <lwilkins@redhat.com> Signed-off-by: LopezCastroRoberto <rocastro@redhat.com> Co-authored-by: Lucas Wilkinson <lwilkins@redhat.com> Co-authored-by: OpenAI Codex <codex@openai.com>
Roberto L. Castro committed
Sep 13, 2026 at 02:24 UTC
aed894c190aa3db74c502d521b3bc3935ad85de1
27 files changed
+2286
-556
.buildkite/test_areas/models_basic.yaml
+1
-1
@@ -41,7 +41,7 @@ steps:
41
- label: ":nvidia: (H200 MIG 35GB) Basic Models (Other)"
42
device: h200_35gb
43
key: basic-models-tests-other
44
- timeout_in_minutes: 35
44
+ timeout_in_minutes: 38
45
source_file_dependencies:
46
- vllm/
47
- "!vllm/distributed/kv_transfer/"
.buildkite/test_areas/samplers.yaml
+1
-1
@@ -5,7 +5,7 @@ steps:
5
- label: ":nvidia: (H200 MIG 35GB) Samplers"
6
device: h200_35gb
7
key: samplers-test
8
- timeout_in_minutes: 40
8
+ timeout_in_minutes: 60
9
source_file_dependencies:
10
- vllm/model_executor/layers
11
- vllm/sampling_metadata.py
tests/model_executor/test_jit_warmup.py
+32
@@ -171,6 +171,19 @@ def test_warmup_range_validates_custom_advancement() -> None:
171
)
172
173
174
+def test_warmup_cases_support_lambda_advancement() -> None:
175
+ def warmup_inputs() -> dict[str, Any]:
176
+ value: Any = WarmupIntRange(1, 9, advance=lambda value: value * 2)
177
+ return dict(value=value)
178
+
179
+ assert list(ToyKernel()._expand_warmup_cases(warmup_inputs)) == [
180
+ {"value": 1},
181
+ {"value": 2},
182
+ {"value": 4},
183
+ {"value": 8},
184
+ ]
185
+
186
+
187
def test_compile_key_uses_defaults_locals_attributes_and_expressions() -> None:
188
cfg = _config(bias=3, disabled=True, name="cfg", vectorized=True)
189
@@ -577,6 +590,25 @@ def test_registry_records_only_inside_model_setup_context() -> None:
590
assert kernel.compiled == []
591
592
593
+def test_registry_capture() -> None:
594
+ class Owner:
595
+ @JitWarmupRegistry.capture
596
+ def __init__(
597
+ self,
598
+ vllm_config: Any,
599
+ kernel: RecordingToyKernel,
600
+ ) -> None:
601
+ kernel.register_warmup(3, vllm_config)
602
+
603
+ kernel = RecordingToyKernel()
604
+ config = _config()
605
+ owner = Owner(config, kernel)
606
+
607
+ assert len(owner.jit_warmup_registry) == 1 # type: ignore[attr-defined]
608
+ kernel.register_warmup(5, config)
609
+ assert len(owner.jit_warmup_registry) == 1 # type: ignore[attr-defined]
610
+
611
+
612
def test_registry_expands_requests_and_deduplicates_owner_keys() -> None:
613
registry = JitWarmupRegistry(_config())
614
kernel = RecordingToyKernel()
tests/model_executor/test_jit_warmup_triton_launcher.py
+333
-2
@@ -4,22 +4,68 @@
4
from dataclasses import dataclass
5
from typing import Any
6
7
-from vllm.model_executor.warmup.jit_warmup import kernel_launcher
7
+import pytest
8
+import torch
9
+
10
+from vllm.model_executor.warmup import jit_warmup_triton_helper
11
+from vllm.model_executor.warmup.jit_warmup import (
12
+ WarmupChoices,
13
+ WarmupIntRange,
14
+ kernel_launcher,
15
+)
16
from vllm.model_executor.warmup.jit_warmup_triton_helper import (
17
LaunchSpec,
18
+ TritonJitKey,
19
+ TritonWarmupTensor,
20
VllmTritonJitKernel,
21
+ triton_kernel_dispatcher_with_warmup,
22
+ triton_warmup_inputs,
23
)
24
+from vllm.triton_utils import tl, triton
25
+
26
+
27
+@triton.jit
28
+def _binder_test_kernel(x, value, BLOCK: tl.constexpr):
29
+ pass
30
+
31
+
32
+@triton.jit
33
+def _second_binder_test_kernel(x, value, BLOCK: tl.constexpr):
34
+ pass
35
+
36
+
37
+@triton.jit
38
+def _pointer_group_test_kernel(x_ptr, value, BLOCK: tl.constexpr):
39
+ pass
40
41
42
class _FakeTritonKernel:
15
- arg_names = ("first", "second", "CONST")
43
+ arg_names: tuple[str, ...] = ("first", "second", "CONST")
44
45
def __init__(self) -> None:
46
self.warmup_calls: list[dict[str, Any]] = []
47
+ self.runtime_calls: list[tuple[Any, tuple[Any, ...], dict[str, Any]]] = []
48
49
def warmup(self, **kwargs: Any) -> None:
50
self.warmup_calls.append(kwargs)
51
52
+ def __getitem__(self, grid: Any) -> Any:
53
+ def launch(*args: Any, **kwargs: Any) -> None:
54
+ self.runtime_calls.append((grid, args, kwargs))
55
+
56
+ return launch
57
+
58
+
59
+def _patch_key_deriver(
60
+ monkeypatch: pytest.MonkeyPatch,
61
+ derive: Any,
62
+) -> None:
63
+ monkeypatch.setattr(
64
+ jit_warmup_triton_helper,
65
+ "_triton_key_deriver",
66
+ lambda kernel: lambda kwargs: derive(kernel, kwargs),
67
+ )
68
+
69
70
class _TestTritonKernel(VllmTritonJitKernel["_TestTritonKernel.CompileKey"]):
71
kernel = _FakeTritonKernel()
@@ -91,6 +137,291 @@ def test_triton_launcher_supports_cpu_function_wrappers() -> None:
137
assert calls == [("runtime", 2, 7)]
138
139
140
+def test_triton_kernel_decorator_returns_launcher(
141
+ monkeypatch: pytest.MonkeyPatch,
142
+) -> None:
143
+ kernel = _FakeTritonKernel()
144
+ kernel.arg_names = (
145
+ "FIRST_PTR",
146
+ "FIRST_STRIDE0",
147
+ "aliased_ptr",
148
+ "aliased_stride_0",
149
+ "SECOND",
150
+ "CONST",
151
+ )
152
+
153
+ def warmup_inputs() -> dict[str, Any]:
154
+ return dict(
155
+ first=TritonWarmupTensor(torch.float32, shape=(2, 3), strides=(5, 1)),
156
+ second=WarmupChoices(1, 2),
157
+ )
158
+
159
+ @triton_kernel_dispatcher_with_warmup(kernel=kernel, warmup_inputs=warmup_inputs)
160
+ def launch(first: str, second: int = 2, config: int = 7) -> LaunchSpec:
161
+ return (2,), dict(aliased_ptr=first, CONST=config)
162
+
163
+ def fake_keys(kernel: Any, kwargs: Any) -> set[TritonJitKey]:
164
+ return {TritonJitKey(id(kernel), "fake", 0, kwargs["SECOND"])}
165
+
166
+ _patch_key_deriver(monkeypatch, fake_keys)
167
+
168
+ keys = launch.get_warmup_keys()
169
+ keys_with_config = launch.get_warmup_keys(vllm_config=object())
170
+ assert keys_with_config == keys
171
+ assert [dict(key.inputs)["second"] for key in keys] == [1, 2]
172
+ launch.compile(keys[0])
173
+ assert kernel.warmup_calls == [
174
+ {
175
+ "grid": (1,),
176
+ "FIRST_PTR": TritonWarmupTensor(
177
+ torch.float32, shape=(2, 3), strides=(5, 1)
178
+ ),
179
+ "FIRST_STRIDE0": 5,
180
+ "aliased_ptr": TritonWarmupTensor(
181
+ torch.float32, shape=(2, 3), strides=(5, 1)
182
+ ),
183
+ "aliased_stride_0": 5,
184
+ "SECOND": 1,
185
+ "CONST": 7,
186
+ }
187
+ ]
188
+ first = torch.empty_strided((2, 3), (5, 1))
189
+ launch(first)
190
+ assert kernel.runtime_calls == [
191
+ (
192
+ (2,),
193
+ (),
194
+ {
195
+ "FIRST_PTR": first,
196
+ "FIRST_STRIDE0": 5,
197
+ "aliased_ptr": first,
198
+ "aliased_stride_0": 5,
199
+ "SECOND": 2,
200
+ "CONST": 7,
201
+ },
202
+ )
203
+ ]
204
+ assert launch.__name__ == "launch"
205
+ with pytest.raises(TypeError, match="unexpected keyword"):
206
+ launch(first, 1, 7, stale_constexpr=True)
207
+
208
+
209
+def test_triton_kernel_decorator_exhausts_large_ranges_before_deduplication(
210
+ monkeypatch: pytest.MonkeyPatch,
211
+) -> None:
212
+ kernel = _FakeTritonKernel()
213
+ dispatched: list[int] = []
214
+
215
+ def warmup_inputs() -> dict[str, Any]:
216
+ tokens: Any = WarmupIntRange(1, 8193)
217
+ return dict(first="warmup", second=tokens)
218
+
219
+ def specialization(second: int) -> int:
220
+ return 7 if second <= 97 else 8
221
+
222
+ @triton_kernel_dispatcher_with_warmup(kernel=kernel, warmup_inputs=warmup_inputs)
223
+ def dispatch(first: str, second: int) -> LaunchSpec:
224
+ dispatched.append(second)
225
+ return (second,), dict(CONST=specialization(second))
226
+
227
+ def fake_keys(kernel: Any, kwargs: Any) -> set[TritonJitKey]:
228
+ return {TritonJitKey(id(kernel), "fake", 0, kwargs["CONST"])}
229
+
230
+ _patch_key_deriver(monkeypatch, fake_keys)
231
+
232
+ keys = dispatch.get_warmup_keys()
233
+ assert dispatched == list(range(1, 8193))
234
+ assert len(keys) == 2
235
+ assert {specialization(dict(key.inputs)["second"]) for key in keys} == {7, 8}
236
+
237
+
238
+def test_triton_kernel_decorator_propagates_dispatch_assertions(
239
+ monkeypatch: pytest.MonkeyPatch,
240
+) -> None:
241
+ kernel = _FakeTritonKernel()
242
+
243
+ def warmup_inputs() -> dict[str, Any]:
244
+ return dict(first="warmup", second=WarmupChoices(1, 2))
245
+
246
+ @triton_kernel_dispatcher_with_warmup(kernel=kernel, warmup_inputs=warmup_inputs)
247
+ def dispatch(first: str, second: int) -> LaunchSpec:
248
+ assert second != 2, "broken dispatch"
249
+ return (1,), dict(CONST=second)
250
+
251
+ _patch_key_deriver(
252
+ monkeypatch,
253
+ lambda kernel, kwargs: {TritonJitKey(id(kernel), "fake", 0, kwargs["CONST"])},
254
+ )
255
+
256
+ with pytest.raises(AssertionError, match="broken dispatch"):
257
+ dispatch.get_warmup_keys()
258
+
259
+
260
+def test_triton_kernel_dispatch_uses_cuda_fake_tensors(
261
+ monkeypatch: pytest.MonkeyPatch,
262
+) -> None:
263
+ kernel = _FakeTritonKernel()
264
+
265
+ def warmup_inputs() -> dict[str, Any]:
266
+ return dict(
267
+ first=TritonWarmupTensor(torch.float32, shape=(2, 3)),
268
+ second=1,
269
+ )
270
+
271
+ @triton_kernel_dispatcher_with_warmup(kernel=kernel, warmup_inputs=warmup_inputs)
272
+ def dispatch(first: torch.Tensor, second: int) -> LaunchSpec:
273
+ assert isinstance(first, torch.Tensor)
274
+ assert first.is_cuda
275
+ assert first[0].is_contiguous()
276
+ return (first.shape[0],), dict(CONST=first[0].numel())
277
+
278
+ _patch_key_deriver(
279
+ monkeypatch,
280
+ lambda kernel, kwargs: {TritonJitKey(id(kernel), "fake", 0, kwargs["CONST"])},
281
+ )
282
+
283
+ keys = dispatch.get_warmup_keys()
284
+ assert len(keys) == 1
285
+ assert dict(keys[0].inputs)["first"].device.type == "cuda"
286
+
287
+
288
+def test_triton_kernel_decorates_native_launchers(
289
+ monkeypatch: pytest.MonkeyPatch,
290
+) -> None:
291
+ kernel = _FakeTritonKernel()
292
+
293
+ def warmup_inputs(kernel: Any = kernel) -> dict[str, Any]:
294
+ return triton_warmup_inputs(
295
+ kernel,
296
+ "warmup",
297
+ WarmupChoices(1, 2),
298
+ grid=(2,),
299
+ CONST=7,
300
+ )
301
+
302
+ launcher = triton_kernel_dispatcher_with_warmup(warmup_inputs=warmup_inputs)(kernel)
303
+ launcher[(3,)]("runtime", 4, CONST=8)
304
+
305
+ assert kernel.runtime_calls == [((3,), ("runtime", 4), {"CONST": 8})]
306
+
307
+ _patch_key_deriver(
308
+ monkeypatch,
309
+ lambda kernel, kwargs: {
310
+ TritonJitKey(id(kernel), "fake", 0, (kwargs["second"], kwargs["CONST"]))
311
+ },
312
+ )
313
+
314
+ keys = launcher.get_warmup_keys()
315
+ assert len(keys) == 2
316
+ launcher.compile(keys[0])
317
+ assert kernel.warmup_calls == [
318
+ {"grid": (1,), "first": "warmup", "second": 1, "CONST": 7}
319
+ ]
320
+
321
+
322
+def test_triton_warmup_inputs_expands_explicit_pointer_dtypes() -> None:
323
+ inputs = triton_warmup_inputs(
324
+ _pointer_group_test_kernel,
325
+ grid=(1,),
326
+ pointer_dtypes={torch.float32: ("x_ptr",)},
327
+ value=2,
328
+ BLOCK=16,
329
+ )
330
+
331
+ assert inputs["x_ptr"] == TritonWarmupTensor(torch.float32)
332
+ assert inputs["value"] == 2
333
+
334
+ with pytest.raises(ValueError, match="not a pointer: value"):
335
+ triton_warmup_inputs(
336
+ _pointer_group_test_kernel,
337
+ grid=(1,),
338
+ pointer_dtypes={torch.float32: ("value",)},
339
+ x_ptr=TritonWarmupTensor(torch.float32),
340
+ BLOCK=16,
341
+ )
342
+ with pytest.raises(ValueError, match="Missing Triton pointer inputs: x_ptr"):
343
+ triton_warmup_inputs(
344
+ _pointer_group_test_kernel,
345
+ grid=(1,),
346
+ pointer_dtypes={},
347
+ value=2,
348
+ BLOCK=16,
349
+ )
350
+
351
+
352
+def test_triton_key_derivation_applies_wrappers_and_runtime_options(
353
+ monkeypatch: pytest.MonkeyPatch,
354
+) -> None:
355
+ knobs = triton.knobs
356
+ driver = triton.runtime.driver
357
+
358
+ device = "test-device"
359
+ monkeypatch.setattr(driver.active, "get_current_device", lambda: device)
360
+ calls: list[dict[str, Any]] = []
361
+
362
+ def binder(x: Any, value: int, BLOCK: int, **options: Any) -> Any:
363
+ calls.append(dict(BLOCK=BLOCK, **options))
364
+ specialization = [("pointer", x.dtype), ("i32", value), ("constexpr", BLOCK)]
365
+ return {}, specialization, options
366
+
367
+ cache: dict[Any, Any] = {}
368
+ _binder_test_kernel.device_caches[device] = ({}, cache, None, None, binder)
369
+ heuristic_kernel = triton.heuristics(
370
+ {"BLOCK": lambda args: 16 if args["value"] <= 16 else 32}
371
+ )(_binder_test_kernel)
372
+
373
+ keys = jit_warmup_triton_helper._triton_key_deriver(heuristic_kernel)(
374
+ {"x": TritonWarmupTensor(tl.float32), "value": 2}
375
+ )
376
+
377
+ assert len(keys) == 1
378
+ expected_options = {
379
+ "debug": knobs.runtime.debug,
380
+ "instrumentation_mode": knobs.compilation.instrumentation_mode,
381
+ }
382
+ if hasattr(knobs.compilation, "fpsan_homomorphic_casts"):
383
+ expected_options["fpsan_homomorphic_casts"] = (
384
+ knobs.compilation.fpsan_homomorphic_casts
385
+ )
386
+ assert calls == [{"BLOCK": 16, **expected_options}]
387
+
388
+
389
+def test_triton_key_derivation_covers_autotune_configs_and_jit_identity(
390
+ monkeypatch: pytest.MonkeyPatch,
391
+) -> None:
392
+ driver = triton.runtime.driver
393
+
394
+ device = "test-device"
395
+ monkeypatch.setattr(driver.active, "get_current_device", lambda: device)
396
+
397
+ def binder(x: Any, value: int, BLOCK: int, **options: Any) -> Any:
398
+ specialization = [("pointer", x.dtype), ("i32", value), ("constexpr", BLOCK)]
399
+ return {}, specialization, options
400
+
401
+ for kernel in (_binder_test_kernel, _second_binder_test_kernel):
402
+ kernel.device_caches[device] = ({}, {}, None, None, binder)
403
+ autotuned = triton.autotune(
404
+ configs=[
405
+ triton.Config({"BLOCK": 16}, num_warps=2),
406
+ triton.Config({"BLOCK": 32}, num_warps=4),
407
+ ],
408
+ key=["value"],
409
+ )(_binder_test_kernel)
410
+ inputs = {"x": TritonWarmupTensor(tl.float32), "value": 2}
411
+
412
+ autotune_keys = jit_warmup_triton_helper._triton_key_deriver(autotuned)(inputs)
413
+ other_kernel_keys = jit_warmup_triton_helper._triton_key_deriver(
414
+ _second_binder_test_kernel
415
+ )(inputs | {"BLOCK": 16})
416
+
417
+ assert len(autotune_keys) == 2
418
+ assert {key.jit_function_key for key in autotune_keys} == {
419
+ _binder_test_kernel.cache_key
420
+ }
421
+ assert {key.jit_function_id for key in autotune_keys} == {id(_binder_test_kernel)}
422
+ assert autotune_keys.isdisjoint(other_kernel_keys)
423
+
424
+
425
def test_compute_slot_mapping_uses_named_launcher_inputs(monkeypatch) -> None:
426
from vllm.v1.attention.backends.utils import PAD_SLOT_ID
427
from vllm.v1.worker.block_table import ComputeSlotMappingKernel
tests/model_executor/test_spec_decode_rejection_warmup.py
deleted
-121
@@ -1,121 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-from types import SimpleNamespace
4
-
5
-import pytest
6
-import torch
7
-
8
-from vllm.config.watermarking import WatermarkConfig, derive_watermark_key
9
-from vllm.model_executor.warmup.spec_decode_rejection_warmup import (
10
- spec_decode_rejection_warmup,
11
-)
12
-
13
-MASTER_KEY = 20250910
14
-
15
-
16
-def _worker(
17
- watermark_config: WatermarkConfig | None,
18
- draft_sample_method: str = "greedy",
19
-) -> SimpleNamespace:
20
- return SimpleNamespace(
21
- device=torch.device("cpu"),
22
- vllm_config=SimpleNamespace(
23
- speculative_config=SimpleNamespace(
24
- num_speculative_tokens=2,
25
- rejection_sample_method="standard",
26
- draft_sample_method=draft_sample_method,
27
- ),
28
- model_config=SimpleNamespace(
29
- get_vocab_size=lambda: 128, dtype=torch.bfloat16
30
- ),
31
- watermark_config=watermark_config,
32
- ),
33
- )
34
-
35
-
36
-def _capture_calls(monkeypatch: pytest.MonkeyPatch) -> list[dict]:
37
- calls: list[dict] = []
38
-
39
- def fake_rejection_sample(**kwargs):
40
- calls.append(kwargs)
41
- return None, None
42
-
43
- monkeypatch.setattr(
44
- "vllm.v1.worker.gpu.spec_decode.rejection_sampler_utils.rejection_sample",
45
- fake_rejection_sample,
46
- )
47
- return calls
48
-
49
-
50
-def test_warmup_token_id_dtypes_match_the_runtime_buffers(
51
- monkeypatch: pytest.MonkeyPatch,
52
-) -> None:
53
- calls = _capture_calls(monkeypatch)
54
- spec_decode_rejection_warmup(_worker(None))
55
-
56
- assert calls
57
- for kwargs in calls:
58
- assert kwargs["draft_sampled"].dtype == torch.int32
59
- assert kwargs["pos"].dtype == torch.int64
60
-
61
-
62
-def test_warmup_index_mapping_dtypes_match_the_runtime_buffers(
63
- monkeypatch: pytest.MonkeyPatch,
64
-) -> None:
65
- calls = _capture_calls(monkeypatch)
66
- spec_decode_rejection_warmup(_worker(None))
67
-
68
- assert calls
69
- for kwargs in calls:
70
- assert kwargs["idx_mapping"].dtype == torch.int64
71
- assert kwargs["expanded_idx_mapping"].dtype == torch.int64
72
- assert kwargs["expanded_local_pos"].dtype == torch.int32
73
-
74
-
75
-def test_warmup_watermark_args_match_the_runtime_kernel_arguments(
76
- monkeypatch: pytest.MonkeyPatch,
77
-) -> None:
78
- watermark_config = WatermarkConfig(
79
- key=MASTER_KEY, algorithm="dual_key_gumbel", context_width=3
80
- )
81
- calls = _capture_calls(monkeypatch)
82
- spec_decode_rejection_warmup(
83
- _worker(watermark_config, draft_sample_method="probabilistic")
84
- )
85
-
86
- assert calls
87
- key_b = derive_watermark_key(MASTER_KEY, b"key_b")
88
- for kwargs in calls:
89
- contexts = kwargs["contexts"]
90
- assert contexts.dtype == torch.int32
91
- assert contexts.shape == (3, 3) and contexts.stride() == (3, 1)
92
- assert kwargs["watermarking"].dtype == torch.bool
93
- assert kwargs["watermark_key"] == key_b
94
- assert key_b & 0xFFFFFFFF >= 2**31 and key_b >> 32 >= 2**31
95
-
96
-
97
-def test_warmup_is_skipped_when_the_recovery_key_cannot_be_derived(
98
- monkeypatch: pytest.MonkeyPatch,
99
-) -> None:
100
- calls = _capture_calls(monkeypatch)
101
- monkeypatch.setattr(
102
- "vllm.v1.watermarking.spec_decode.speculative_target_watermark_key",
103
- lambda _: (_ for _ in ()).throw(NotImplementedError("no philox")),
104
- )
105
- spec_decode_rejection_warmup(_worker(WatermarkConfig(key=MASTER_KEY)))
106
-
107
- assert calls == []
108
-
109
-
110
-def test_warmup_draft_logits_match_the_runtime_draft_sample_method(
111
- monkeypatch: pytest.MonkeyPatch,
112
-) -> None:
113
- calls = _capture_calls(monkeypatch)
114
- spec_decode_rejection_warmup(_worker(None))
115
- assert calls
116
- assert all(kwargs["draft_logits"] is None for kwargs in calls)
117
-
118
- calls = _capture_calls(monkeypatch)
119
- spec_decode_rejection_warmup(_worker(None, draft_sample_method="probabilistic"))
120
- assert calls
121
- assert all(kwargs["draft_logits"] is not None for kwargs in calls)
tests/v1/sample/test_topk_topp_sampler.py
+4
@@ -79,6 +79,10 @@ def test_rocm_aiter_sampler_defers_import_when_generators_force_native(
79
def is_cuda():
80
return False
81
82
+ @staticmethod
83
+ def is_cuda_alike():
84
+ return True
85
+
86
@staticmethod
87
def is_cpu():
88
return False
tests/v1/shutdown/test_delete.py
+13
-3
@@ -11,6 +11,7 @@ from tests.v1.shutdown.utils import (
11
SHUTDOWN_TEST_TIMEOUT_SEC,
12
)
13
from vllm import LLM, SamplingParams
14
+from vllm.config import KernelConfig
15
from vllm.engine.arg_utils import AsyncEngineArgs
16
from vllm.platforms import current_platform
17
from vllm.sampling_params import RequestOutputKind
@@ -39,7 +40,10 @@ async def test_async_llm_delete(
40
pytest.skip(reason="Not enough CUDA devices")
41
42
engine_args = AsyncEngineArgs(
42
- model=model, enforce_eager=True, tensor_parallel_size=tensor_parallel_size
43
+ model=model,
44
+ enforce_eager=True,
45
+ tensor_parallel_size=tensor_parallel_size,
46
+ kernel_config=KernelConfig(enable_jit_warmup=False),
47
)
48
49
# Instantiate AsyncLLM; make request to complete any deferred
@@ -94,7 +98,10 @@ def test_llm_delete(
98
# Instantiate LLM; make request to complete any deferred
99
# initialization; then delete instance
100
llm = LLM(
97
- model=model, enforce_eager=True, tensor_parallel_size=tensor_parallel_size
101
+ model=model,
102
+ enforce_eager=True,
103
+ tensor_parallel_size=tensor_parallel_size,
104
+ kernel_config=KernelConfig(enable_jit_warmup=False),
105
)
106
if send_one_request:
107
llm.generate(
@@ -122,7 +129,10 @@ def test_llm_delete_inprocess(
129
with monkeypatch.context() as m:
130
m.setenv("VLLM_ENABLE_V1_MULTIPROCESSING", "0")
131
125
- with VllmRunner(model) as vllm_model:
132
+ with VllmRunner(
133
+ model,
134
+ kernel_config=KernelConfig(enable_jit_warmup=False),
135
+ ) as vllm_model:
136
if send_one_request:
137
vllm_model.generate(
138
["Hello my name is"],
tests/v1/spec_decode/test_dflash_prepare_inputs.py
+1
-1
@@ -43,7 +43,7 @@ def _run_prepare(
43
num_scheduled_tokens=np.array([4], dtype=np.int32),
44
positions=torch.tensor(target_positions, dtype=torch.int64, device=device),
45
query_start_loc=torch.tensor([0, 4], dtype=torch.int32, device=device),
46
- idx_mapping=torch.tensor([2], dtype=torch.int32, device=device),
46
+ idx_mapping=torch.tensor([2], dtype=torch.int64, device=device),
47
)
48
query_slot_mapping = torch.full(
49
(max_num_tokens,), -2, dtype=torch.int64, device=device
tests/v1/worker/test_gpu_autoregressive_speculator.py
+1
@@ -157,6 +157,7 @@ def test_mm_support_configured_after_model_load(monkeypatch):
157
speculator.device = device
158
speculator.max_num_tokens = 4
159
speculator.max_num_reqs = 2
160
+ speculator.num_speculative_steps = 1
161
speculator.hidden_size = 3
162
speculator.dtype = torch.float32
163
speculator.draft_model_config = draft_model_config
vllm/model_executor/layers/mamba/ops/scatter_states.py
+41
-8
@@ -1,8 +1,16 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
+from typing import Any
5
+
6
import torch
7
8
+from vllm.model_executor.warmup.jit_warmup import WarmupIntRange
9
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
10
+ DispatchSpec,
11
+ TritonWarmupTensor,
12
+ triton_kernel_dispatcher_with_warmup,
13
+)
14
from vllm.platforms import current_platform
15
from vllm.triton_utils import tl, triton
16
@@ -33,11 +41,39 @@ def _scatter_states_kernel(
41
tl.store(state_ptr + state_idx * stride_state_batch + offsets, values, mask=mask)
42
43
44
+def _scatter_states_warmup_inputs(
45
+ *,
46
+ state_shape: tuple[int, ...],
47
+ state_dtype: torch.dtype,
48
+ indices_dtype: torch.dtype,
49
+ max_num_tokens: int,
50
+) -> dict[str, object]:
51
+ num_tokens: Any = WarmupIntRange(1, max_num_tokens + 1)
52
+ return dict(
53
+ state=TritonWarmupTensor(
54
+ state_dtype,
55
+ shape=(1,) + state_shape,
56
+ ),
57
+ src=TritonWarmupTensor(
58
+ state_dtype,
59
+ shape=(num_tokens,) + state_shape,
60
+ ),
61
+ indices=TritonWarmupTensor(
62
+ indices_dtype,
63
+ shape=(num_tokens,),
64
+ ),
65
+ )
66
+
67
+
68
+@triton_kernel_dispatcher_with_warmup(
69
+ kernel=_scatter_states_kernel,
70
+ warmup_inputs=_scatter_states_warmup_inputs,
71
+)
72
def scatter_states(
73
state: torch.Tensor,
74
src: torch.Tensor,
75
indices: torch.Tensor,
40
-) -> None:
76
+) -> DispatchSpec:
77
"""Scatter ``src`` rows into ``state`` at ``indices`` (in place).
78
79
Equivalent to ``state[indices] = src`` but non-atomic and bandwidth-bound,
@@ -58,13 +94,10 @@ def scatter_states(
94
assert src[0].is_contiguous()
95
block_size = min(triton.next_power_of_2(row_size), 1024)
96
grid = (triton.cdiv(row_size, block_size), indices.numel())
61
- _scatter_states_kernel[grid](
62
- state,
63
- src,
64
- indices,
65
- state.stride(0),
66
- src.stride(0),
67
- indices.stride(0),
97
+ return grid, dict(
98
+ stride_state_batch=state.stride(0),
99
+ stride_src_batch=src.stride(0),
100
+ stride_indices=indices.stride(0),
101
row_size=row_size,
102
BLOCK_SIZE=block_size,
103
num_warps=8,
vllm/model_executor/warmup/jit_warmup.py
+309
-30
@@ -21,6 +21,7 @@ from typing import Any, Generic, TypeVar, cast
21
__all__ = [
22
"JitWarmupRegistry",
23
"VllmJitKernel",
24
+ "WarmupChoices",
25
"WarmupIntRange",
26
"get_ast_full_name",
27
"get_function_source_node",
@@ -62,6 +63,21 @@ class WarmupIntRange:
63
advance: Callable[[int], int] | None = None
64
65
66
+@dataclass(frozen=True)
67
+class WarmupChoices:
68
+ """Expand an explicit finite set of values in a traced warmup method."""
69
+
70
+ values: tuple[Any, ...]
71
+
72
+ def __init__(self, *values: Any) -> None:
73
+ object.__setattr__(self, "values", values)
74
+
75
+
76
+def _when(condition: bool) -> None:
77
+ """Filter symbolic cases in an AST-traced warmup-cases method."""
78
+ raise RuntimeError("_when() is only valid in an AST-traced warmup method")
79
+
80
+
81
WarmupValues = Any
82
CompileKeyDispatchFn = Callable[..., CompileKeyT]
83
WarmupPredicateFn = Callable[..., bool]
@@ -87,6 +103,8 @@ class _WarmupInputRows:
103
104
105
def _expand_warmup_values(values: WarmupValues) -> tuple[Any, ...]:
106
+ if isinstance(values, WarmupChoices):
107
+ return values.values
108
if isinstance(values, WarmupIntRange):
109
if values.advance is None:
110
return tuple(range(values.start, values.stop, values.step))
@@ -278,6 +296,27 @@ class _DispatchExprEvaluator(ast.NodeVisitor):
296
def visit_Constant(self, node: ast.Constant) -> Any:
297
return node.value
298
299
+ def visit_Lambda(self, node: ast.Lambda) -> Callable[..., Any]:
300
+ arguments = node.args
301
+ if (
302
+ arguments.posonlyargs
303
+ or arguments.vararg is not None
304
+ or arguments.kwonlyargs
305
+ or arguments.kwarg is not None
306
+ or arguments.defaults
307
+ ):
308
+ raise _dispatch_expr_error(node, "Traced lambdas require positional args")
309
+ names = tuple(argument.arg for argument in arguments.args)
310
+
311
+ def evaluate(*values: Any) -> Any:
312
+ if len(values) != len(names):
313
+ raise TypeError(f"Expected {len(names)} lambda arguments")
314
+ return type(self)(
315
+ dict(self.values) | dict(zip(names, values)), self.globals
316
+ ).eval(node.body)
317
+
318
+ return evaluate
319
+
320
def visit_IfExp(self, node: ast.IfExp) -> Any:
321
return self.visit(node.body if self.visit(node.test) else node.orelse)
322
@@ -333,7 +372,12 @@ class _DispatchExprEvaluator(ast.NodeVisitor):
372
return op(self.visit(node.left), self.visit(node.right))
373
374
def visit_Call(self, node: ast.Call) -> Any:
336
- args = [self.visit(arg) for arg in node.args]
375
+ args: list[Any] = []
376
+ for arg in node.args:
377
+ if isinstance(arg, ast.Starred):
378
+ args.extend(self.visit(arg.value))
379
+ else:
380
+ args.append(self.visit(arg))
381
fn = self.visit(node.func)
382
call_kwargs: dict[str, Any] = {}
383
for keyword in node.keywords:
@@ -388,6 +432,45 @@ def _eval_dispatch_expr(
432
return _DispatchExprEvaluator(kwargs, globals_).eval(node)
433
434
435
+def _validate_dispatch_expr(node: ast.AST) -> None:
436
+ """Enforce the dispatch AST subset before compiling traced code."""
437
+ allowed_nodes = (
438
+ ast.Name,
439
+ ast.Constant,
440
+ ast.Lambda,
441
+ ast.arguments,
442
+ ast.arg,
443
+ ast.IfExp,
444
+ ast.Tuple,
445
+ ast.List,
446
+ ast.BoolOp,
447
+ ast.And,
448
+ ast.Or,
449
+ ast.Compare,
450
+ *tuple(_CMP_OPS),
451
+ ast.UnaryOp,
452
+ ast.Not,
453
+ ast.USub,
454
+ ast.BinOp,
455
+ *tuple(_BIN_OPS),
456
+ ast.Call,
457
+ ast.keyword,
458
+ ast.Starred,
459
+ ast.Attribute,
460
+ ast.Subscript,
461
+ ast.Load,
462
+ )
463
+ for child in ast.walk(node):
464
+ if not isinstance(child, allowed_nodes):
465
+ raise _dispatch_expr_error(child, "Unsupported dispatch expression")
466
+ if isinstance(child, ast.Call) and any(
467
+ keyword.arg is None for keyword in child.keywords
468
+ ):
469
+ raise _dispatch_expr_error(
470
+ child, "Dispatch helper calls cannot use **kwargs"
471
+ )
472
+
473
+
474
def _collect_input_names(
475
node: ast.AST,
476
candidate_names: set[str],
@@ -406,6 +489,23 @@ def _collect_input_names(
489
}
490
491
492
+def _named_assignment(
493
+ statement: ast.stmt,
494
+ subject: str,
495
+) -> tuple[str, ast.AST] | None:
496
+ match statement:
497
+ case ast.Assign(targets=[ast.Name(id=name)], value=value):
498
+ return name, value
499
+ case ast.AnnAssign(target=ast.Name(id=name), value=value) if value is not None:
500
+ return name, value
501
+ case ast.Assign() | ast.AnnAssign():
502
+ raise _dispatch_expr_error(
503
+ statement, f"{subject} assignments require one name"
504
+ )
505
+ case _:
506
+ return None
507
+
508
+
509
def _collect_expression_body(
510
fn: Callable[..., Any],
511
function_def: ast.FunctionDef | ast.Lambda,
@@ -422,38 +522,19 @@ def _collect_expression_body(
522
):
523
continue
524
425
- if isinstance(statement, ast.Assign):
426
- if len(statement.targets) != 1 or not isinstance(
427
- statement.targets[0], ast.Name
428
- ):
429
- raise _dispatch_expr_error(
430
- statement,
431
- "Traced warmup helper assignments must target one local name",
432
- )
433
- local_exprs.append((statement.targets[0].id, statement.value))
525
+ if (
526
+ assignment := _named_assignment(statement, "Traced warmup helper")
527
+ ) is not None:
528
+ local_exprs.append(assignment)
529
continue
530
436
- if isinstance(statement, ast.AnnAssign):
531
+ if isinstance(statement, ast.Return):
532
if statement.value is None:
533
raise _dispatch_expr_error(
439
- statement, "Traced warmup helper annotations must assign a value"
534
+ statement, "Traced warmup helper must return an expression"
535
)
441
- if not isinstance(statement.target, ast.Name):
442
- raise _dispatch_expr_error(
443
- statement,
444
- "Traced warmup helper assignments must target one local name",
445
- )
446
- local_exprs.append((statement.target.id, statement.value))
447
- continue
448
-
449
- if isinstance(statement, ast.Return) and statement.value is not None:
536
return local_exprs, statement.value
537
452
- if isinstance(statement, ast.Return):
453
- raise _dispatch_expr_error(
454
- statement, "Traced warmup helper must return an expression"
455
- )
456
-
538
raise _dispatch_expr_error(
539
statement,
540
"Traced warmup helper may only contain local assignments before return",
@@ -574,10 +655,17 @@ class VllmJitKernel(Generic[CompileKeyT], ABC):
655
bind_launch_inputs = True
656
657
def __init__(self) -> None:
577
- self._dispatch_trace = _trace_compile_key_dispatch(self.dispatch)
658
+ dispatch = type(self).dispatch
659
+ self._dispatch_trace = (
660
+ None
661
+ if dispatch is VllmJitKernel.dispatch
662
+ else _trace_compile_key_dispatch(self.dispatch)
663
+ )
664
self._compiled_cache: dict[Any, Any] = {}
665
666
def compile_key(self, kwargs: Mapping[str, Any]) -> CompileKeyT:
667
+ if self._dispatch_trace is None:
668
+ raise TypeError(f"{type(self).__name__} does not define dispatch()")
669
return self._dispatch_trace.compile_key(self.CompileKey, kwargs)
670
671
def _get_or_compile(
@@ -683,7 +771,184 @@ class VllmJitKernel(Generic[CompileKeyT], ABC):
771
772
return traced
773
686
- @abstractmethod
774
+ def _expand_warmup_cases(
775
+ self,
776
+ cases_fn: Callable[..., Any],
777
+ *args: Any,
778
+ _value_expander: Callable[[WarmupValues], tuple[Any, ...]] = (
779
+ _expand_warmup_values
780
+ ),
781
+ **kwargs: Any,
782
+ ) -> Iterator[Mapping[str, Any]]:
783
+ """Expand symbolic domains declared inside a warmup-cases method."""
784
+ function_def = get_function_source_node(cases_fn)
785
+ if isinstance(function_def, ast.Lambda):
786
+ raise _dispatch_expr_error(
787
+ function_def, "Warmup cases must be a function definition"
788
+ )
789
+ signature = inspect.signature(cases_fn)
790
+ bound = signature.bind(*args, **kwargs)
791
+ bound.apply_defaults()
792
+ static_values = dict(bound.arguments)
793
+ bound_self = getattr(cases_fn, "__self__", None)
794
+ if bound_self is not None:
795
+ static_values["self"] = bound_self
796
+ globals_ = getattr(cases_fn, "__func__", cases_fn).__globals__
797
+
798
+ domains: list[tuple[str, tuple[Any, ...]]] = []
799
+ local_exprs: list[tuple[str, ast.AST]] = []
800
+ predicates: list[ast.AST] = []
801
+ return_expr: ast.AST | None = None
802
+ for statement in function_def.body:
803
+ if (
804
+ isinstance(statement, ast.Expr)
805
+ and isinstance(statement.value, ast.Constant)
806
+ and isinstance(statement.value.value, str)
807
+ ):
808
+ continue
809
+ assignment = _named_assignment(statement, "Warmup case")
810
+ if assignment is not None:
811
+ name, value_expr = assignment
812
+ call_name = (
813
+ (get_ast_full_name(value_expr.func) or "").split(".")[-1]
814
+ if isinstance(value_expr, ast.Call)
815
+ else ""
816
+ )
817
+ if call_name in {"WarmupIntRange", "WarmupChoices"}:
818
+ value = _eval_dispatch_expr(value_expr, static_values, globals_)
819
+ domains.append((name, _value_expander(value)))
820
+ else:
821
+ local_exprs.append((name, value_expr))
822
+ continue
823
+ if (
824
+ isinstance(statement, ast.Expr)
825
+ and isinstance(statement.value, ast.Call)
826
+ and (get_ast_full_name(statement.value.func) or "").endswith("_when")
827
+ ):
828
+ if len(statement.value.args) != 1 or statement.value.keywords:
829
+ raise _dispatch_expr_error(
830
+ statement, "_when requires exactly one positional expression"
831
+ )
832
+ predicates.append(statement.value.args[0])
833
+ continue
834
+ if isinstance(statement, ast.Return):
835
+ if return_expr is not None or statement.value is None:
836
+ raise _dispatch_expr_error(
837
+ statement, "Warmup cases require one final return expression"
838
+ )
839
+ return_expr = statement.value
840
+ continue
841
+ raise _dispatch_expr_error(
842
+ statement,
843
+ "AST-traced warmup cases support assignments, _when, and return",
844
+ )
845
+
846
+ if return_expr is None or not isinstance(return_expr, ast.Call):
847
+ raise ValueError("AST-traced warmup cases must return one case(...) call")
848
+ if any(keyword.arg is None for keyword in return_expr.keywords):
849
+ raise _dispatch_expr_error(
850
+ return_expr, "Warmup case expressions do not support **kwargs"
851
+ )
852
+
853
+ class _InlineDomainRewriter(ast.NodeTransformer):
854
+ def visit_Call(self, node: ast.Call) -> ast.AST:
855
+ call_name = (get_ast_full_name(node.func) or "").split(".")[-1]
856
+ if call_name not in {"WarmupIntRange", "WarmupChoices"}:
857
+ return self.generic_visit(node)
858
+ name = f"__warmup_domain_{len(domains)}"
859
+ domain = _eval_dispatch_expr(node, static_values, globals_)
860
+ domains.append((name, _value_expander(domain)))
861
+ return ast.copy_location(ast.Name(id=name, ctx=ast.Load()), node)
862
+
863
+ return_expr = cast(ast.Call, _InlineDomainRewriter().visit(return_expr))
864
+
865
+ domain_names = tuple(name for name, _ in domains)
866
+ dynamic_names = set(domain_names)
867
+ dynamic_local_exprs: list[tuple[str, ast.AST]] = []
868
+ for name, expr in local_exprs:
869
+ referenced_names = {
870
+ node.id for node in ast.walk(expr) if isinstance(node, ast.Name)
871
+ }
872
+ if referenced_names & dynamic_names:
873
+ dynamic_local_exprs.append((name, expr))
874
+ dynamic_names.add(name)
875
+ else:
876
+ static_values[name] = _eval_dispatch_expr(expr, static_values, globals_)
877
+
878
+ for _, expr in dynamic_local_exprs:
879
+ _validate_dispatch_expr(expr)
880
+ for predicate in predicates:
881
+ _validate_dispatch_expr(predicate)
882
+ _validate_dispatch_expr(return_expr)
883
+
884
+ case_body: list[ast.stmt] = [
885
+ ast.Assign(
886
+ targets=[ast.Name(id=name, ctx=ast.Store())],
887
+ value=cast(ast.expr, expr),
888
+ )
889
+ for name, expr in dynamic_local_exprs
890
+ ]
891
+ if predicates:
892
+ predicate = (
893
+ predicates[0]
894
+ if len(predicates) == 1
895
+ else ast.BoolOp(
896
+ op=ast.And(),
897
+ values=[cast(ast.expr, value) for value in predicates],
898
+ )
899
+ )
900
+ case_body.append(
901
+ ast.If(
902
+ test=ast.UnaryOp(op=ast.Not(), operand=cast(ast.expr, predicate)),
903
+ body=[ast.Return(value=ast.Constant(value=None))],
904
+ orelse=[],
905
+ )
906
+ )
907
+ case_body.append(ast.Return(value=cast(ast.expr, return_expr)))
908
+ # FunctionDef fields vary across the supported Python versions, so no
909
+ # single constructor overload matches every mypy target.
910
+ case_function = ast.FunctionDef( # type: ignore[call-overload]
911
+ name="__vllm_warmup_case",
912
+ args=ast.arguments(
913
+ posonlyargs=[],
914
+ args=[ast.arg(arg=name) for name in domain_names],
915
+ kwonlyargs=[],
916
+ kw_defaults=[],
917
+ defaults=[],
918
+ ),
919
+ body=case_body,
920
+ decorator_list=[],
921
+ returns=None,
922
+ type_comment=None,
923
+ )
924
+ case_globals = dict(globals_)
925
+ case_globals.update(static_values)
926
+ exec(
927
+ compile(
928
+ ast.fix_missing_locations(
929
+ ast.Module(body=[case_function], type_ignores=[])
930
+ ),
931
+ "<jit-warmup>",
932
+ "exec",
933
+ ),
934
+ case_globals,
935
+ )
936
+ evaluate_case = cast(Callable[..., Any], case_globals[case_function.name])
937
+
938
+ domain_values = tuple(values for _, values in domains)
939
+ for values in itertools.product(*domain_values):
940
+ case = evaluate_case(*values)
941
+ if case is None:
942
+ continue
943
+ if not isinstance(case, Mapping):
944
+ raise TypeError("AST-traced warmup cases must return a mapping")
945
+ if any(
946
+ isinstance(value, WarmupChoices | WarmupIntRange)
947
+ for value in case.values()
948
+ ):
949
+ raise TypeError("Warmup domains must be direct expressions")
950
+ yield case
951
+
952
def dispatch(self, **kwargs: Any) -> CompileKeyT:
953
"""Build one compile key from one concrete dispatch point."""
954
raise NotImplementedError
@@ -751,6 +1016,19 @@ class JitWarmupRegistry:
1016
list[tuple[tuple[Any, ...], dict[str, Any]]],
1017
] = {}
1018
1019
+ @classmethod
1020
+ def capture(cls, init_fn: Callable[..., None]) -> Callable[..., None]:
1021
+ """Collect warmup registrations made while the decorated callable runs."""
1022
+
1023
+ @wraps(init_fn)
1024
+ def wrapped(instance: Any, vllm_config: Any, *args: Any, **kwargs: Any) -> None:
1025
+ registry = cls(vllm_config)
1026
+ instance.jit_warmup_registry = registry
1027
+ with registry.activate():
1028
+ init_fn(instance, vllm_config, *args, **kwargs)
1029
+
1030
+ return wrapped
1031
+
1032
@contextmanager
1033
def activate(self) -> Iterator[None]:
1034
"""Collect registrations made in this context."""
@@ -808,9 +1086,10 @@ class JitWarmupRegistry:
1086
if (
1087
not args
1088
and not kwargs
811
- and inspect.signature(kernel.get_warmup_keys).parameters
1089
+ and "vllm_config"
1090
+ in inspect.signature(kernel.get_warmup_keys).parameters
1091
):
813
- args = (self.vllm_config,)
1092
+ kwargs = {"vllm_config": self.vllm_config}
1093
for compile_key in kernel.get_warmup_keys(*args, **kwargs):
1094
compile_keys[compile_key] = None
1095
if compile_keys:
vllm/model_executor/warmup/jit_warmup_triton_helper.py
+507
-13
@@ -3,25 +3,130 @@
3
import ast
4
import inspect
5
from abc import abstractmethod
6
-from collections.abc import Callable, Mapping
7
-from dataclasses import dataclass
8
-from functools import cached_property
9
-from typing import Any, ClassVar, Generic, TypeVar, cast
6
+from collections.abc import Callable, Hashable, Iterable, Mapping
7
+from contextlib import nullcontext, suppress
8
+from dataclasses import dataclass, field
9
+from functools import cache, cached_property, update_wrapper
10
+from typing import Any, Generic, ParamSpec, Protocol, TypeVar, cast, overload
11
12
from vllm.model_executor.warmup.jit_warmup import (
13
VllmJitKernel,
14
get_ast_full_name,
15
get_function_source_node,
16
)
17
+from vllm.platforms import current_platform
18
+from vllm.triton_utils import triton
19
20
CompileKeyT = TypeVar("CompileKeyT")
18
-# ``(grid, launch_kwargs)`` or ``(grid, launch_kwargs, outputs)`` for
19
-# self-allocating kernels. ``grid=None`` skips the launch (e.g. empty batch)
20
-# but still returns the declared outputs.
21
+P = ParamSpec("P")
22
+# ``kernel_launcher`` also supports a ``None`` grid when no launch is needed.
23
LaunchSpec = (
24
tuple[tuple[int, ...] | None, dict[str, Any]]
25
| tuple[tuple[int, ...] | None, dict[str, Any], Any]
26
)
27
+# Dispatchers select a concrete launch configuration for a Triton kernel.
28
+DispatchSpec = (
29
+ tuple[tuple[int, ...], dict[str, Any]] | tuple[tuple[int, ...], dict[str, Any], Any]
30
+)
31
+
32
+
33
+@dataclass(frozen=True)
34
+class _LaunchBindingPlan:
35
+ input_targets: tuple[tuple[int, str], ...]
36
+ stride_targets: tuple[tuple[str, str, int], ...]
37
+
38
+
39
+@cache
40
+def _launch_binding_plan(
41
+ arg_names: tuple[str, ...],
42
+ input_names: tuple[str, ...],
43
+ launch_names: tuple[str, ...],
44
+) -> _LaunchBindingPlan:
45
+ arg_name_set = set(arg_names)
46
+ launch_name_set = set(launch_names)
47
+
48
+ def kernel_arg(input_name: str) -> str | None:
49
+ pointer_name = f"{input_name}_ptr"
50
+ candidates = (
51
+ input_name,
52
+ pointer_name,
53
+ input_name.upper(),
54
+ pointer_name.upper(),
55
+ )
56
+ return next(
57
+ (candidate for candidate in candidates if candidate in arg_name_set),
58
+ None,
59
+ )
60
+
61
+ input_targets = tuple(
62
+ (index, target)
63
+ for index, name in enumerate(input_names)
64
+ if (target := kernel_arg(name)) is not None and target not in launch_name_set
65
+ )
66
+ bound_names = launch_name_set | {target for _, target in input_targets}
67
+ stride_targets = []
68
+ for target in arg_names:
69
+ if target in bound_names:
70
+ continue
71
+ name, separator, dim = target.rpartition("_stride")
72
+ if not separator:
73
+ name, separator, dim = target.rpartition("_STRIDE")
74
+ dim = dim.removeprefix("_")
75
+ if separator and (not dim or dim.isdigit()):
76
+ pointer = f"{name}_PTR" if separator == "_STRIDE" else f"{name}_ptr"
77
+ source = name if name in bound_names else pointer
78
+ if source in bound_names:
79
+ stride_targets.append((target, source, int(dim or 0)))
80
+ return _LaunchBindingPlan(input_targets, tuple(stride_targets))
81
+
82
+
83
+def triton_warmup_inputs(
84
+ kernel: Any,
85
+ *args: Any,
86
+ grid: tuple[int, ...],
87
+ pointer_dtypes: (
88
+ Mapping[Any, Iterable[str]] | Iterable[tuple[Any, Iterable[str]]] | None
89
+ ) = None,
90
+ **kwargs: Any,
91
+) -> dict[str, Any]:
92
+ """Build launcher inputs from Triton's native positional argument order."""
93
+ arg_names = tuple(kernel.arg_names)
94
+ if len(args) > len(arg_names):
95
+ raise ValueError(
96
+ f"Received {len(args)} positional inputs for {len(arg_names)} "
97
+ "Triton kernel arguments"
98
+ )
99
+ inputs = dict(zip(arg_names, args))
100
+ duplicate_names = inputs.keys() & kwargs.keys()
101
+ if duplicate_names:
102
+ raise ValueError(
103
+ f"Triton inputs passed twice: {', '.join(sorted(duplicate_names))}"
104
+ )
105
+ inputs.update(kwargs)
106
+ if pointer_dtypes is not None:
107
+ function_def = get_function_source_node(kernel)
108
+ if not isinstance(function_def, ast.FunctionDef):
109
+ raise ValueError("Expected Triton kernel to be defined as a function")
110
+ pointer_names = _pointer_arg_names(function_def, arg_names)
111
+ groups = (
112
+ pointer_dtypes.items()
113
+ if isinstance(pointer_dtypes, Mapping)
114
+ else pointer_dtypes
115
+ )
116
+ for dtype, names in groups:
117
+ for name in names:
118
+ if name not in arg_names:
119
+ raise ValueError(f"Unknown Triton pointer argument: {name}")
120
+ if name not in pointer_names:
121
+ raise ValueError(f"Triton argument is not a pointer: {name}")
122
+ if name in inputs:
123
+ raise ValueError(f"Triton input passed twice: {name}")
124
+ inputs[name] = TritonWarmupTensor(dtype)
125
+ missing_pointers = pointer_names - inputs.keys()
126
+ if missing_pointers:
127
+ names = ", ".join(sorted(missing_pointers))
128
+ raise ValueError(f"Missing Triton pointer inputs: {names}")
129
+ return {"grid": grid, **inputs}
130
131
132
def triton_scalar_specialization_rep(value: int) -> int:
@@ -72,6 +177,7 @@ class TritonWarmupTensor:
177
aligned: bool = True
178
shape: tuple[int, ...] = (1,)
179
strides: tuple[int, ...] | None = None
180
+ init: Any = 0
181
182
def data_ptr(self) -> int:
183
return 0 if self.aligned else 1
@@ -79,6 +185,12 @@ class TritonWarmupTensor:
185
def ptr_range(self) -> int:
186
return 0
187
188
+ @property
189
+ def device(self) -> Any:
190
+ import torch
191
+
192
+ return torch.device(current_platform.device_type)
193
+
194
def stride(self, dim: int | None = None) -> int | tuple[int, ...]:
195
if self.strides is None:
196
strides: list[int] = []
@@ -95,9 +207,10 @@ class TritonWarmupTensor:
207
class VllmTritonJitKernel(VllmJitKernel[CompileKeyT], Generic[CompileKeyT]):
208
"""Triton owner whose runtime launch specification is reused for warmup."""
209
98
- kernel: ClassVar[Any]
210
+ kernel: Any
211
_warming = False
212
_warming_compile_key: CompileKeyT | None = None
213
+ _run_autotune = False
214
215
@abstractmethod
216
def warmup_inputs(self, compile_key: CompileKeyT) -> dict[str, Any]:
@@ -126,6 +239,21 @@ class VllmTritonJitKernel(VllmJitKernel[CompileKeyT], Generic[CompileKeyT]):
239
f"Cannot inspect kernel parameters for {type(self.kernel).__name__}"
240
)
241
242
+ def _prepare_launch_kwargs(
243
+ self,
244
+ input_names: tuple[str, ...],
245
+ input_values: tuple[Any, ...],
246
+ launch_kwargs: dict[str, Any],
247
+ ) -> dict[str, Any]:
248
+ plan = _launch_binding_plan(
249
+ self._kernel_arg_names, input_names, tuple(launch_kwargs)
250
+ )
251
+ for index, target in plan.input_targets:
252
+ launch_kwargs[target] = input_values[index]
253
+ for target, source, dim in plan.stride_targets:
254
+ launch_kwargs[target] = launch_kwargs[source].stride(dim)
255
+ return launch_kwargs
256
+
257
def launch(
258
self,
259
launch_spec: LaunchSpec,
@@ -136,15 +264,20 @@ class VllmTritonJitKernel(VllmJitKernel[CompileKeyT], Generic[CompileKeyT]):
264
else:
265
grid, launch_kwargs = launch_spec
266
outputs = None
139
- kwargs = dict(launch_kwargs)
267
+ kwargs = self._prepare_launch_kwargs(
268
+ tuple(inputs), tuple(inputs.values()), dict(launch_kwargs)
269
+ )
270
kernel = kwargs.pop("kernel", None) or self.kernel
271
runtime_launcher = kwargs.pop("_runtime_launcher", None)
272
runtime_launcher_arg_count = kwargs.pop("_runtime_launcher_arg_count", 0)
143
- for name, value in inputs.items():
144
- target = name if name in self._kernel_arg_names else f"{name}_ptr"
145
- if target in self._kernel_arg_names and target not in kwargs:
146
- kwargs[target] = value
273
if self._warming:
274
+ if self._run_autotune:
275
+ assert grid is not None
276
+ kernel[grid](**kwargs)
277
+ return outputs
278
+ kwargs = {
279
+ name: _triton_metadata_arg(value) for name, value in kwargs.items()
280
+ }
281
if (
282
self._warming_compile_key is not None
283
and "launch_pdl" in kwargs
@@ -168,6 +301,367 @@ class VllmTritonJitKernel(VllmJitKernel[CompileKeyT], Generic[CompileKeyT]):
301
return outputs
302
303
304
+@dataclass(frozen=True)
305
+class TritonJitKey:
306
+ """Process-local identity of one Triton JIT specialization."""
307
+
308
+ jit_function_id: int
309
+ jit_function_key: Hashable
310
+ device: Hashable
311
+ cache_key: Hashable
312
+
313
+
314
+@dataclass(frozen=True)
315
+class TritonCompileKey:
316
+ """Triton-derived compile key with one non-comparing replay input."""
317
+
318
+ jit_keys: frozenset[TritonJitKey]
319
+ inputs: tuple[tuple[str, Any], ...] = field(compare=False, hash=False, repr=False)
320
+
321
+
322
+def _triton_key_deriver(
323
+ kernel: Any,
324
+) -> Callable[[Mapping[str, Any]], set[TritonJitKey]]:
325
+ """Prepare Triton's key derivation once for one kernel and device."""
326
+ knobs = triton.knobs
327
+ Autotuner = triton.runtime.autotuner.Autotuner
328
+ Heuristics = triton.runtime.autotuner.Heuristics
329
+ driver = triton.runtime.driver
330
+ JITFunction = triton.runtime.jit.JITFunction
331
+ compute_cache_key = triton.runtime.jit.compute_cache_key
332
+
333
+ if isinstance(kernel, Heuristics):
334
+ derive_inner = _triton_key_deriver(kernel.fn)
335
+
336
+ def derive_heuristic(kwargs: Mapping[str, Any]) -> set[TritonJitKey]:
337
+ heuristic_kwargs = dict(kwargs)
338
+ for name, heuristic in kernel.values.items():
339
+ heuristic_kwargs[name] = heuristic(heuristic_kwargs)
340
+ return derive_inner(heuristic_kwargs)
341
+
342
+ return derive_heuristic
343
+
344
+ if isinstance(kernel, Autotuner):
345
+ derive_inner = _triton_key_deriver(kernel.fn)
346
+
347
+ def derive_autotuned(kwargs: Mapping[str, Any]) -> set[TritonJitKey]:
348
+ previous_nargs = getattr(kernel, "nargs", None)
349
+ kernel.nargs = {}
350
+ try:
351
+ configs = kernel.prune_configs(dict(kwargs))
352
+ finally:
353
+ kernel.nargs = previous_nargs
354
+ keys: set[TritonJitKey] = set()
355
+ for config in configs:
356
+ conflicts = kwargs.keys() & config.kwargs.keys()
357
+ if conflicts:
358
+ names = ", ".join(sorted(conflicts))
359
+ raise ValueError(f"Conflicting autotune parameters: {names}")
360
+ keys.update(derive_inner(dict(kwargs) | config.all_kwargs()))
361
+ return keys
362
+
363
+ return derive_autotuned
364
+
365
+ if not isinstance(kernel, JITFunction):
366
+ raise TypeError(f"Unsupported Triton kernel wrapper: {type(kernel).__name__}")
367
+
368
+ device = cast(Hashable, driver.active.get_current_device())
369
+ _, kernel_key_cache, _, _, binder = kernel.device_caches[device]
370
+ jit_function_id = id(kernel)
371
+ jit_function_key = cast(Hashable, kernel.cache_key)
372
+ debug = kernel.debug or knobs.runtime.debug
373
+ instrumentation_mode = knobs.compilation.instrumentation_mode
374
+ fpsan_homomorphic_casts = getattr(
375
+ knobs.compilation, "fpsan_homomorphic_casts", None
376
+ )
377
+ inspection_hook = knobs.runtime.add_stages_inspection_hook
378
+
379
+ def derive_jit(kwargs: Mapping[str, Any]) -> set[TritonJitKey]:
380
+ binder_kwargs = dict(kwargs)
381
+ binder_kwargs["debug"] = binder_kwargs.get("debug", debug) or debug
382
+ binder_kwargs["instrumentation_mode"] = instrumentation_mode
383
+ if fpsan_homomorphic_casts is not None:
384
+ binder_kwargs["fpsan_homomorphic_casts"] = fpsan_homomorphic_casts
385
+ _, specialization, options = binder(**binder_kwargs)
386
+ if inspection_hook is not None:
387
+ _, inspection_hash = inspection_hook()
388
+ specialization.append(f'("custom_pipeline", {inspection_hash})')
389
+ cache_key = cast(
390
+ Hashable,
391
+ compute_cache_key(kernel_key_cache, specialization, options),
392
+ )
393
+ return {
394
+ TritonJitKey(
395
+ jit_function_id,
396
+ jit_function_key,
397
+ device,
398
+ cache_key,
399
+ )
400
+ }
401
+
402
+ return derive_jit
403
+
404
+
405
+WarmupCases = Mapping[str, Any] | Iterable[Mapping[str, Any]]
406
+
407
+
408
+class _AutomaticTritonJitKernel(VllmTritonJitKernel[TritonCompileKey]):
409
+ def warmup_inputs(self, compile_key: TritonCompileKey) -> dict[str, Any]:
410
+ return dict(compile_key.inputs)
411
+
412
+ def _provider_cases(
413
+ self,
414
+ provider: Callable[..., WarmupCases],
415
+ *args: Any,
416
+ **kwargs: Any,
417
+ ) -> Iterable[Mapping[str, Any]]:
418
+ cases_node = get_function_source_node(provider)
419
+ if any(
420
+ isinstance(node, ast.Call)
421
+ and (get_ast_full_name(node.func) or "").split(".")[-1]
422
+ in {"WarmupIntRange", "WarmupChoices", "_when"}
423
+ for node in ast.walk(cases_node)
424
+ ):
425
+ return self._expand_warmup_cases(provider, *args, **kwargs)
426
+ cases = provider(*args, **kwargs)
427
+ return (cases,) if isinstance(cases, Mapping) else cases
428
+
429
+
430
+def _is_autotuned(kernel: Any) -> bool:
431
+ Autotuner = triton.runtime.autotuner.Autotuner
432
+
433
+ while kernel is not None:
434
+ if isinstance(kernel, Autotuner):
435
+ return True
436
+ kernel = getattr(kernel, "fn", None)
437
+ return False
438
+
439
+
440
+def _triton_metadata_arg(value: Any) -> Any:
441
+ from torch._subclasses.fake_tensor import FakeTensor
442
+
443
+ if not isinstance(value, FakeTensor):
444
+ return value
445
+ return TritonWarmupTensor(
446
+ value.dtype,
447
+ aligned=getattr(value, "_vllm_warmup_aligned", True),
448
+ shape=tuple(value.shape),
449
+ strides=tuple(value.stride()),
450
+ )
451
+
452
+
453
+def _materialize_warmup_case(
454
+ case: Mapping[str, Any],
455
+ *,
456
+ real: bool,
457
+ mode: Any,
458
+ cache: dict[TritonWarmupTensor, Any],
459
+) -> dict[str, Any]:
460
+ import torch
461
+
462
+ def materialize(value: Any) -> Any:
463
+ if not isinstance(value, TritonWarmupTensor):
464
+ return value
465
+ if not real:
466
+ try:
467
+ return cache[value]
468
+ except KeyError:
469
+ pass
470
+ except TypeError:
471
+ # Unhashable initializers are uncommon and safe to materialize directly.
472
+ pass
473
+
474
+ strides = cast(tuple[int, ...], value.stride())
475
+ with mode:
476
+ tensor = torch.empty_strided(
477
+ value.shape,
478
+ strides,
479
+ dtype=value.dtype,
480
+ device=value.device,
481
+ )
482
+ if real:
483
+ if callable(value.init):
484
+ value.init(tensor)
485
+ else:
486
+ tensor.fill_(value.init)
487
+ else:
488
+ tensor._vllm_warmup_aligned = value.aligned
489
+ with suppress(TypeError):
490
+ cache[value] = tensor
491
+ return tensor
492
+
493
+ return {name: materialize(value) for name, value in case.items()}
494
+
495
+
496
+class _DecoratedTritonJitKernel(_AutomaticTritonJitKernel):
497
+ def __init__(
498
+ self,
499
+ kernel: Any,
500
+ warmup_inputs: Callable[..., WarmupCases],
501
+ dispatch: Callable[..., DispatchSpec] | None,
502
+ ) -> None:
503
+ self.kernel = kernel
504
+ self._run_autotune = _is_autotuned(kernel)
505
+ self._warmup_inputs_fn = warmup_inputs
506
+ self._dispatch_fn = dispatch
507
+ self._dispatch_arg_names: tuple[str, ...] = ()
508
+ self._dispatch_defaults: dict[str, Any] = {}
509
+ if dispatch is not None:
510
+ parameters = tuple(inspect.signature(dispatch).parameters.values())
511
+ if any(
512
+ parameter.kind
513
+ in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
514
+ for parameter in parameters
515
+ ):
516
+ raise TypeError("Triton dispatchers require explicit parameters")
517
+ self._dispatch_arg_names = tuple(parameter.name for parameter in parameters)
518
+ self._dispatch_defaults = {
519
+ parameter.name: parameter.default
520
+ for parameter in parameters
521
+ if parameter.default is not inspect.Parameter.empty
522
+ }
523
+ super().__init__()
524
+
525
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
526
+ if self._dispatch_fn is None:
527
+ grid = kwargs.pop("grid") if not args else args[0]
528
+ kernel_args = () if not args else args[1:]
529
+ inputs = triton_warmup_inputs(
530
+ self.kernel, *kernel_args, grid=grid, **kwargs
531
+ )
532
+ inputs.pop("grid")
533
+ return self.launch((grid, inputs), {})
534
+ spec = self._dispatch_fn(*args, **kwargs)
535
+ input_values = args
536
+ if len(args) != len(self._dispatch_arg_names):
537
+ input_values += tuple(
538
+ kwargs[name] if name in kwargs else self._dispatch_defaults[name]
539
+ for name in self._dispatch_arg_names[len(args) :]
540
+ )
541
+ return self.launch(
542
+ spec,
543
+ dict(zip(self._dispatch_arg_names, input_values)),
544
+ )
545
+
546
+ def __getitem__(self, grid: tuple[int, ...]) -> Callable[..., Any]:
547
+ return cast(Callable[..., Any], self.kernel[grid])
548
+
549
+ def __getattr__(self, name: str) -> Any:
550
+ return getattr(self.kernel, name)
551
+
552
+ def get_warmup_keys(
553
+ self, *, vllm_config: Any | None = None, **kwargs: Any
554
+ ) -> list[TritonCompileKey]:
555
+ keys: list[TritonCompileKey] = []
556
+ seen_jit_keys: set[frozenset[TritonJitKey]] = set()
557
+ if "vllm_config" in inspect.signature(self._warmup_inputs_fn).parameters:
558
+ kwargs.setdefault("vllm_config", vllm_config)
559
+ cases = self._provider_cases(self._warmup_inputs_fn, **kwargs)
560
+ derive_keys = _triton_key_deriver(self.kernel)
561
+ materialize = self._run_autotune
562
+ mode: Any = nullcontext() if self._run_autotune else None
563
+ materialized: dict[TritonWarmupTensor, Any] = {}
564
+ for case in cases:
565
+ inputs = (
566
+ _materialize_warmup_case(
567
+ case,
568
+ real=self._run_autotune,
569
+ mode=mode,
570
+ cache=materialized,
571
+ )
572
+ if materialize
573
+ else case
574
+ )
575
+ if self._dispatch_fn is None:
576
+ prepared = dict(inputs)
577
+ prepared.pop("grid")
578
+ else:
579
+ dispatch_inputs = {
580
+ name: inputs[name]
581
+ for name in self._dispatch_arg_names
582
+ if name in inputs
583
+ }
584
+ try:
585
+ _, launch_kwargs = self._dispatch_fn(**dispatch_inputs)[:2]
586
+ except (AssertionError, AttributeError, TypeError):
587
+ if materialize:
588
+ raise
589
+ from torch._subclasses.fake_tensor import FakeTensorMode
590
+
591
+ materialize = True
592
+ mode = FakeTensorMode()
593
+ inputs = _materialize_warmup_case(
594
+ case,
595
+ real=False,
596
+ mode=mode,
597
+ cache=materialized,
598
+ )
599
+ dispatch_inputs = {
600
+ name: inputs[name]
601
+ for name in self._dispatch_arg_names
602
+ if name in inputs
603
+ }
604
+ _, launch_kwargs = self._dispatch_fn(**dispatch_inputs)[:2]
605
+ prepared = self._prepare_launch_kwargs(
606
+ tuple(inputs),
607
+ tuple(inputs.values()),
608
+ dict(launch_kwargs),
609
+ )
610
+ if materialize:
611
+ prepared = {
612
+ name: _triton_metadata_arg(value)
613
+ for name, value in prepared.items()
614
+ }
615
+ jit_keys = frozenset(derive_keys(prepared))
616
+ if jit_keys and jit_keys not in seen_jit_keys:
617
+ seen_jit_keys.add(jit_keys)
618
+ keys.append(TritonCompileKey(jit_keys, tuple(inputs.items())))
619
+ return keys
620
+
621
+
622
+class TritonKernelDispatcher(Protocol[P]):
623
+ """Callable Triton launcher with compile-time warmup support."""
624
+
625
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Any: ...
626
+
627
+ def register_warmup(self, *args: Any, **kwargs: Any) -> None: ...
628
+
629
+
630
+@overload
631
+def triton_kernel_dispatcher_with_warmup(
632
+ *,
633
+ warmup_inputs: Callable[..., WarmupCases],
634
+ kernel: None = None,
635
+) -> Callable[[Any], Any]: ...
636
+
637
+
638
+@overload
639
+def triton_kernel_dispatcher_with_warmup(
640
+ *,
641
+ warmup_inputs: Callable[..., WarmupCases],
642
+ kernel: Any,
643
+) -> Callable[[Callable[P, DispatchSpec]], TritonKernelDispatcher[P]]: ...
644
+
645
+
646
+def triton_kernel_dispatcher_with_warmup(
647
+ *,
648
+ warmup_inputs: Callable[..., WarmupCases],
649
+ kernel: Any | None = None,
650
+) -> Callable[[Any], Any]:
651
+ """Decorate a dispatch function or a native Triton kernel for warmup."""
652
+
653
+ def decorate(dispatch: Any) -> Any:
654
+ owner = _DecoratedTritonJitKernel(
655
+ dispatch if kernel is None else kernel,
656
+ warmup_inputs,
657
+ None if kernel is None else dispatch,
658
+ )
659
+ update_wrapper(owner, dispatch, updated=())
660
+ return owner
661
+
662
+ return decorate
663
+
664
+
665
@dataclass(frozen=True)
666
class TritonPointerInputVariant:
667
# Named pointer-alignment variant for compile-only Triton warmup.
vllm/model_executor/warmup/kernel_warmup.py
+4
-5
@@ -41,9 +41,6 @@ from vllm.model_executor.warmup.qwen_vl_triton_warmup import qwen_vl_triton_warm
41
from vllm.model_executor.warmup.replayssm_warmup import (
42
replayssm_autotune_warmup,
43
)
44
-from vllm.model_executor.warmup.spec_decode_rejection_warmup import (
45
- spec_decode_rejection_warmup,
46
-)
44
from vllm.model_executor.warmup.watermark_sample_warmup import (
45
watermark_sample_warmup,
46
)
@@ -178,7 +175,10 @@ def kernel_warmup(worker: "Worker", *, process_local_only: bool = False):
175
logger.info("JIT kernel warmup starting.")
176
jit_warmup_start = time.perf_counter()
177
try:
181
- worker.model_runner.jit_warmup_registry.warmup()
178
+ registry = (
179
+ worker.model_runner.jit_warmup_registry # type: ignore[attr-defined]
180
+ )
181
+ registry.warmup()
182
except Exception:
183
logger.exception(
184
"JIT kernel warmup failed after %.2fs.",
@@ -209,7 +209,6 @@ def kernel_warmup(worker: "Worker", *, process_local_only: bool = False):
209
# Run next so input-prep kernels JIT against pristine runner state.
210
if enable_jit_warmup:
211
kimi_k3_triton_warmup(worker)
212
- spec_decode_rejection_warmup(worker)
212
watermark_sample_warmup(worker)
213
qwen4_exp_qsa_triton_warmup(worker)
214
vllm/model_executor/warmup/spec_decode_rejection_warmup.py
deleted
-139
@@ -1,139 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-"""Warm up speculative-decoding rejection kernels."""
4
-
5
-from __future__ import annotations
6
-
7
-from typing import TYPE_CHECKING, Any
8
-
9
-import torch
10
-
11
-from vllm.logger import init_logger
12
-
13
-if TYPE_CHECKING:
14
- from vllm.v1.worker.gpu_worker import Worker
15
-
16
-logger = init_logger(__name__)
17
-
18
-
19
-@torch.inference_mode()
20
-def spec_decode_rejection_warmup(worker: Worker) -> None:
21
- spec_config = worker.vllm_config.speculative_config
22
- if spec_config is None:
23
- return
24
-
25
- from vllm.v1.worker.gpu.spec_decode.rejection_sampler_utils import (
26
- rejection_sample,
27
- )
28
-
29
- model_config = worker.vllm_config.model_config
30
- vocab_size = model_config.get_vocab_size()
31
- num_spec = spec_config.num_speculative_tokens
32
- if num_spec <= 0 or vocab_size <= 0:
33
- return
34
-
35
- rejection_method = getattr(spec_config, "rejection_sample_method", None)
36
- use_block_verification = rejection_method == "block"
37
- use_synthetic = rejection_method == "synthetic"
38
- # HAS_DRAFT_LOGITS is a constexpr, so the warmup must match the runtime.
39
- use_draft_logits = (
40
- getattr(spec_config, "draft_sample_method", "greedy") == "probabilistic"
41
- )
42
-
43
- device = worker.device
44
- num_reqs = 1
45
- tokens_per_req = num_spec + 1
46
- num_logits = num_reqs * tokens_per_req
47
-
48
- watermark_config = getattr(worker.vllm_config, "watermark_config", None)
49
- watermark_kwargs: dict[str, Any] = {}
50
- if watermark_config is not None:
51
- from vllm.v1.watermarking.spec_decode import speculative_target_watermark_key
52
-
53
- try:
54
- watermark_key = speculative_target_watermark_key(watermark_config)
55
- except Exception:
56
- logger.warning(
57
- "Skipping spec-decode rejection sampler warmup: could not derive "
58
- "the watermark recovery key.",
59
- exc_info=True,
60
- )
61
- return
62
- # Tensor dtypes and integer widths affect Triton specialization.
63
- watermark_kwargs = {
64
- "contexts": torch.zeros(
65
- (num_logits, watermark_config.context_width),
66
- dtype=torch.int32,
67
- device=device,
68
- ),
69
- "watermarking": torch.zeros(num_reqs, dtype=torch.bool, device=device),
70
- "watermark_key": watermark_key,
71
- }
72
-
73
- # Sampling-parameter processing may promote either distribution to fp32.
74
- model_dtype = model_config.dtype
75
- warmup_dtype_pairs: set[tuple[torch.dtype, torch.dtype | None]]
76
- if use_draft_logits:
77
- warmup_dtype_pairs = {
78
- (model_dtype, model_dtype),
79
- (torch.float32, torch.float32),
80
- (torch.float32, model_dtype),
81
- (model_dtype, torch.float32),
82
- }
83
- else:
84
- warmup_dtype_pairs = {(model_dtype, None), (torch.float32, None)}
85
-
86
- logger.info(
87
- "Warming up spec-decode rejection sampler kernels "
88
- "(vocab=%d, num_spec=%d, dtype_pairs=%s, block_verify=%s, watermark=%s).",
89
- vocab_size,
90
- num_spec,
91
- [(str(t), str(d)) for t, d in warmup_dtype_pairs],
92
- use_block_verification,
93
- watermark_config is not None,
94
- )
95
- for tgt_dtype, draft_dtype in warmup_dtype_pairs:
96
- target_logits = torch.zeros(
97
- (num_logits, vocab_size), dtype=tgt_dtype, device=device
98
- )
99
- draft_logits = (
100
- torch.zeros(
101
- (num_reqs, num_spec, vocab_size), dtype=draft_dtype, device=device
102
- )
103
- if draft_dtype is not None
104
- else None
105
- )
106
- synthetic_rates = (
107
- torch.full((num_spec,), 0.5, dtype=torch.float32, device=device)
108
- if use_synthetic
109
- else None
110
- )
111
- try:
112
- rejection_sample(
113
- target_logits=target_logits,
114
- draft_logits=draft_logits,
115
- draft_sampled=torch.zeros(num_logits, dtype=torch.int32, device=device),
116
- cu_num_logits=torch.tensor(
117
- [0, num_logits], dtype=torch.int32, device=device
118
- ),
119
- pos=torch.zeros(num_logits, dtype=torch.int64, device=device),
120
- idx_mapping=torch.zeros(num_reqs, dtype=torch.int64, device=device),
121
- expanded_idx_mapping=torch.zeros(
122
- num_logits, dtype=torch.int64, device=device
123
- ),
124
- expanded_local_pos=torch.arange(
125
- num_logits, dtype=torch.int32, device=device
126
- ),
127
- temperature=torch.zeros(num_reqs, dtype=torch.float32, device=device),
128
- seed=torch.full((num_reqs,), 42, dtype=torch.int64, device=device),
129
- num_speculative_steps=num_spec,
130
- synthetic_conditional_rates=synthetic_rates,
131
- use_fp64=False,
132
- use_block_verification=use_block_verification,
133
- **watermark_kwargs,
134
- )
135
- except Exception:
136
- logger.warning(
137
- "Skipping spec-decode rejection sampler warmup.", exc_info=True
138
- )
139
- return
vllm/models/glm5next/nvidia/kda.py
+9
@@ -175,6 +175,15 @@ class Glm5NextLinearAttention(GatedDeltaNetAttention):
175
projection_size = self.head_dim * self.num_heads
176
self.local_projection_size = divide(projection_size, self.tp_size)
177
178
+ _, recurrent_state_shape = self.get_state_shape()
179
+ _, recurrent_state_dtype = self.get_state_dtype()
180
+ scatter_states.register_warmup(
181
+ state_shape=recurrent_state_shape,
182
+ state_dtype=recurrent_state_dtype,
183
+ indices_dtype=torch.int32,
184
+ max_num_tokens=vllm_config.scheduler_config.max_num_seqs,
185
+ )
186
+
187
# Merge q, k, v, b, f_a, g_a projections into one GEMM (6→1 launches).
188
# Order matches checkpoint's fused_qkvbfg_a_proj convention.
189
# Shards 4 (f_a) and 5 (g_a) are replicated across TP ranks.
vllm/v1/sample/ops/topk_topp_sampler.py
+20
-1
@@ -13,11 +13,27 @@ from vllm.platforms import CpuArchEnum, current_platform
13
from vllm.triton_utils import HAS_TRITON
14
15
if HAS_TRITON:
16
- from vllm.v1.sample.ops.topk_topp_triton import apply_top_k_top_p_triton
16
+ from vllm.v1.sample.ops.topk_topp_triton import (
17
+ _topk_topp,
18
+ _topp_split_mask,
19
+ _topp_split_stats,
20
+ _topp_split_step,
21
+ apply_top_k_top_p_triton,
22
+ )
23
24
logger = init_logger(__name__)
25
26
27
+def register_top_k_top_p_warmups() -> None:
28
+ """Register every native accelerator sampling kernel used at runtime."""
29
+ if HAS_TRITON and not current_platform.is_cpu():
30
+ _topk_topp.register_warmup()
31
+ if current_platform.is_cuda_alike():
32
+ _topp_split_stats.register_warmup()
33
+ _topp_split_step.register_warmup()
34
+ _topp_split_mask.register_warmup()
35
+
36
+
37
def _skip_aiter_sampler_on_gfx1250() -> bool:
38
# Lazy ROCm-only import; keeps arch detection out of import time on CUDA/CPU.
39
from vllm.platforms.rocm import on_gfx1250
@@ -128,6 +144,9 @@ class TopKTopPSampler(nn.Module):
144
else:
145
self.forward = self.forward_native
146
147
+ # Every accelerator backend can fall back to native sampling at runtime.
148
+ register_top_k_top_p_warmups()
149
+
150
def forward_native(
151
self,
152
logits: torch.Tensor,
vllm/v1/sample/ops/topk_topp_triton.py
+267
-66
@@ -9,8 +9,21 @@ using Pivot-based Truncation and Selection" By Park et al.
9
10
"""
11
12
+from typing import Any
13
+
14
import torch
15
16
+from vllm.model_executor.warmup.jit_warmup import (
17
+ WarmupChoices,
18
+ WarmupIntRange,
19
+ _when,
20
+)
21
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
22
+ DispatchSpec,
23
+ TritonWarmupTensor,
24
+ triton_kernel_dispatcher_with_warmup,
25
+)
26
+from vllm.platforms import current_platform
27
from vllm.triton_utils import tl, triton
28
from vllm.utils.gpu_sync_debug import gpu_sync_allowed
29
from vllm.utils.math_utils import next_power_of_2
@@ -863,6 +876,90 @@ def _topk_topp_kernel(
876
tl.store(LOGITS_ROW + offs_n, logits_blk, mask=mask_n)
877
878
879
+def _max_sampler_batch_size(vllm_config: Any) -> int:
880
+ return vllm_config.scheduler_config.max_num_seqs * (
881
+ vllm_config.num_speculative_tokens + 1
882
+ )
883
+
884
+
885
+def _topk_topp_warmup_inputs(vllm_config: Any) -> dict[str, Any]:
886
+ vocab_size = vllm_config.model_config.get_vocab_size()
887
+ split_enabled = current_platform.is_cuda_alike()
888
+ mode: Any = WarmupChoices((True, False), (True, True), (False, True))
889
+ logits_stride: Any = WarmupChoices(16, 2)
890
+ batch_size: Any = WarmupIntRange(1, _max_sampler_batch_size(vllm_config) + 1)
891
+ topk_enabled = mode[0]
892
+ topp_enabled = mode[1]
893
+ _when(topk_enabled or not (split_enabled and batch_size <= _SPLIT_MAX_BATCH))
894
+ logits = TritonWarmupTensor(
895
+ torch.float32,
896
+ shape=(batch_size, vocab_size),
897
+ strides=(logits_stride, 1),
898
+ )
899
+ return dict(
900
+ logits=logits,
901
+ buffer=TritonWarmupTensor(torch.float32),
902
+ percentile_to_std_table=TritonWarmupTensor(torch.float32),
903
+ normal_cdf_to_sigma_table=TritonWarmupTensor(torch.float32),
904
+ k=TritonWarmupTensor(torch.int32) if topk_enabled else None,
905
+ p=TritonWarmupTensor(torch.float32) if topp_enabled else None,
906
+ mask_value=float("-inf"),
907
+ num_sm=num_compute_units(),
908
+ )
909
+
910
+
911
+@triton_kernel_dispatcher_with_warmup(
912
+ kernel=_topk_topp_kernel,
913
+ warmup_inputs=_topk_topp_warmup_inputs,
914
+)
915
+def _topk_topp(
916
+ logits: torch.Tensor,
917
+ buffer: torch.Tensor,
918
+ percentile_to_std_table: torch.Tensor,
919
+ normal_cdf_to_sigma_table: torch.Tensor,
920
+ k: torch.Tensor | None,
921
+ p: torch.Tensor | None,
922
+ mask_value: float,
923
+ num_sm: int,
924
+) -> DispatchSpec:
925
+ batch_size, vocab_size = logits.shape
926
+ topk_enabled = k is not None
927
+ topp_enabled = p is not None
928
+ k_ptr = k if k is not None else logits
929
+ p_ptr = p if p is not None else logits
930
+ num_programs = min(num_sm, batch_size)
931
+ # Smaller tiles compile and run faster on CPU; GPU benefits from larger tiles.
932
+ # On XPU, large BLOCK_SIZE causes precision loss in the single-pass pivot
933
+ # approximation; use smaller tiles for accurate top-p results.
934
+ if logits.device.type == "cpu":
935
+ block_size, block_size_trunc, num_warps = 256, 128, 4
936
+ elif logits.device.type == "xpu":
937
+ block_size, block_size_trunc, num_warps = 4096, 2048, 4
938
+ else:
939
+ block_size, block_size_trunc, num_warps = 8192, 4096, 8
940
+ # Each program serially sweeps the vocab row in BLOCK_SIZE tiles, so
941
+ # per-tile latency bounds kernel latency, and Triton's default of 4
942
+ # warps leaves an 8192-wide tile at 16 elements per lane. 8 warps is
943
+ # faster on every arch measured (SM90, SM100, SM120, gfx950); 16 is not.
944
+ split_covers_ponly = (
945
+ current_platform.is_cuda_alike()
946
+ and topp_enabled
947
+ and batch_size <= _SPLIT_MAX_BATCH
948
+ )
949
+ return (num_programs,), dict(
950
+ K=k_ptr,
951
+ P=p_ptr,
952
+ BATCH_SIZE=batch_size,
953
+ VOCAB_SIZE=vocab_size,
954
+ BLOCK_SIZE=block_size,
955
+ BLOCK_SIZE_TRUNC=block_size_trunc,
956
+ TOPK_ENABLED=topk_enabled,
957
+ TOPP_ENABLED=topp_enabled,
958
+ SPLIT_COVERS_PONLY=split_covers_ponly,
959
+ num_warps=num_warps,
960
+ )
961
+
962
+
963
# ---------------------------------------------------------------------------
964
# Split-row pipeline for small batches.
965
#
@@ -892,6 +989,13 @@ _SPLIT_ROUNDS = 5
989
_TRITON_SPLIT_CACHE: dict[torch.device, dict[str, torch.Tensor]] = {}
990
991
992
+def _topp_split_count(batch_size: int, num_sm: int) -> int:
993
+ splits = 1
994
+ while splits < _SPLIT_MAX_SPLITS and splits * 2 * batch_size <= num_sm:
995
+ splits *= 2
996
+ return splits
997
+
998
+
999
@triton.jit
1000
def _topp_sb_stats_kernel(
1001
LOGITS,
@@ -939,6 +1043,51 @@ def _topp_sb_stats_kernel(
1043
tl.store(STATS + base + 3, cnt)
1044
1045
1046
+def _topp_split_stats_warmup_inputs(vllm_config: Any) -> dict[str, Any]:
1047
+ vocab_size = vllm_config.model_config.get_vocab_size()
1048
+ num_sm = num_compute_units()
1049
+ logits_stride: Any = WarmupChoices(16, 2)
1050
+ batch_size: Any = WarmupIntRange(
1051
+ 1, min(_max_sampler_batch_size(vllm_config), _SPLIT_MAX_BATCH) + 1
1052
+ )
1053
+ has_k: Any = WarmupChoices(False, True)
1054
+ logits = TritonWarmupTensor(
1055
+ torch.float32,
1056
+ shape=(batch_size, vocab_size),
1057
+ strides=(logits_stride, 1),
1058
+ )
1059
+ return dict(
1060
+ logits=logits,
1061
+ stats=TritonWarmupTensor(torch.float32),
1062
+ k=TritonWarmupTensor(torch.int32) if has_k else None,
1063
+ p=TritonWarmupTensor(torch.float32),
1064
+ num_sm=num_sm,
1065
+ )
1066
+
1067
+
1068
+@triton_kernel_dispatcher_with_warmup(
1069
+ kernel=_topp_sb_stats_kernel,
1070
+ warmup_inputs=_topp_split_stats_warmup_inputs,
1071
+)
1072
+def _topp_split_stats(
1073
+ logits: torch.Tensor,
1074
+ stats: torch.Tensor,
1075
+ k: torch.Tensor | None,
1076
+ p: torch.Tensor,
1077
+ num_sm: int,
1078
+) -> DispatchSpec:
1079
+ batch_size, vocab_size = logits.shape
1080
+ splits = _topp_split_count(batch_size, num_sm)
1081
+ return (logits.shape[0] * splits,), dict(
1082
+ K=k if k is not None else logits,
1083
+ HAS_K=k is not None,
1084
+ VOCAB_SIZE=vocab_size,
1085
+ S=splits,
1086
+ BLOCK=8192,
1087
+ num_warps=8,
1088
+ )
1089
+
1090
+
1091
@triton.jit
1092
def _topp_sb_row_stats(STATS, row, S: tl.constexpr):
1093
"""Combine per-slice partials into row (max, sum_exp, min, n_finite)."""
@@ -1183,6 +1332,58 @@ def _topp_sb_step_kernel(
1332
tl.store(pbase + fidx * 3 + 2, nmin)
1333
1334
1335
+def _topp_split_step_warmup_inputs(vllm_config: Any) -> dict[str, Any]:
1336
+ vocab_size = vllm_config.model_config.get_vocab_size()
1337
+ num_sm = num_compute_units()
1338
+ logits_stride: Any = WarmupChoices(16, 2)
1339
+ batch_size: Any = WarmupIntRange(
1340
+ 1, min(_max_sampler_batch_size(vllm_config), _SPLIT_MAX_BATCH) + 1
1341
+ )
1342
+ round: Any = WarmupIntRange(0, _SPLIT_ROUNDS)
1343
+ has_k: Any = WarmupChoices(False, True)
1344
+ logits = TritonWarmupTensor(
1345
+ torch.float32,
1346
+ shape=(batch_size, vocab_size),
1347
+ strides=(logits_stride, 1),
1348
+ )
1349
+ return dict(
1350
+ logits=logits,
1351
+ stats=TritonWarmupTensor(torch.float32),
1352
+ parts=TritonWarmupTensor(torch.float32),
1353
+ k=TritonWarmupTensor(torch.int32) if has_k else None,
1354
+ p=TritonWarmupTensor(torch.float32),
1355
+ round=round,
1356
+ num_sm=num_sm,
1357
+ )
1358
+
1359
+
1360
+@triton_kernel_dispatcher_with_warmup(
1361
+ kernel=_topp_sb_step_kernel,
1362
+ warmup_inputs=_topp_split_step_warmup_inputs,
1363
+)
1364
+def _topp_split_step(
1365
+ logits: torch.Tensor,
1366
+ stats: torch.Tensor,
1367
+ parts: torch.Tensor,
1368
+ k: torch.Tensor | None,
1369
+ p: torch.Tensor,
1370
+ round: int,
1371
+ num_sm: int,
1372
+) -> DispatchSpec:
1373
+ batch_size, vocab_size = logits.shape
1374
+ splits = _topp_split_count(batch_size, num_sm)
1375
+ return (logits.shape[0] * splits,), dict(
1376
+ K=k if k is not None else logits,
1377
+ HAS_K=k is not None,
1378
+ S=splits,
1379
+ F=_SPLIT_FANOUT,
1380
+ NUM_ROUNDS=_SPLIT_ROUNDS,
1381
+ VOCAB_SIZE=vocab_size,
1382
+ BLOCK=2048,
1383
+ num_warps=8,
1384
+ )
1385
+
1386
+
1387
@triton.jit
1388
def _topp_sb_mask_kernel(
1389
LOGITS,
@@ -1313,6 +1514,57 @@ def _topp_sb_mask_kernel(
1514
tl.store(ROW + i + offs, tl.where(keep, x, MASK_VALUE), mask=mask_n)
1515
1516
1517
+def _topp_split_mask_warmup_inputs(vllm_config: Any) -> dict[str, Any]:
1518
+ vocab_size = vllm_config.model_config.get_vocab_size()
1519
+ num_sm = num_compute_units()
1520
+ logits_stride: Any = WarmupChoices(16, 2)
1521
+ batch_size: Any = WarmupIntRange(
1522
+ 1, min(_max_sampler_batch_size(vllm_config), _SPLIT_MAX_BATCH) + 1
1523
+ )
1524
+ has_k: Any = WarmupChoices(False, True)
1525
+ logits = TritonWarmupTensor(
1526
+ torch.float32,
1527
+ shape=(batch_size, vocab_size),
1528
+ strides=(logits_stride, 1),
1529
+ )
1530
+ return dict(
1531
+ logits=logits,
1532
+ stats=TritonWarmupTensor(torch.float32),
1533
+ parts=TritonWarmupTensor(torch.float32),
1534
+ k=TritonWarmupTensor(torch.int32) if has_k else None,
1535
+ p=TritonWarmupTensor(torch.float32),
1536
+ mask_value=float("-inf"),
1537
+ num_sm=num_sm,
1538
+ )
1539
+
1540
+
1541
+@triton_kernel_dispatcher_with_warmup(
1542
+ kernel=_topp_sb_mask_kernel,
1543
+ warmup_inputs=_topp_split_mask_warmup_inputs,
1544
+)
1545
+def _topp_split_mask(
1546
+ logits: torch.Tensor,
1547
+ stats: torch.Tensor,
1548
+ parts: torch.Tensor,
1549
+ k: torch.Tensor | None,
1550
+ p: torch.Tensor,
1551
+ mask_value: float,
1552
+ num_sm: int,
1553
+) -> DispatchSpec:
1554
+ batch_size, vocab_size = logits.shape
1555
+ splits = _topp_split_count(batch_size, num_sm)
1556
+ return (logits.shape[0] * splits,), dict(
1557
+ K=k if k is not None else logits,
1558
+ HAS_K=k is not None,
1559
+ S=splits,
1560
+ F=_SPLIT_FANOUT,
1561
+ NUM_ROUNDS=_SPLIT_ROUNDS,
1562
+ VOCAB_SIZE=vocab_size,
1563
+ BLOCK=8192,
1564
+ num_warps=8,
1565
+ )
1566
+
1567
+
1568
def _apply_topp_split(
1569
logits: torch.Tensor,
1570
k: torch.Tensor | None,
@@ -1321,11 +1573,6 @@ def _apply_topp_split(
1573
num_sm: int,
1574
) -> None:
1575
"""Split-row top-p pipeline for small batches; masks logits in place."""
1324
- batch_size, vocab_size = logits.shape
1325
- splits = 1
1326
- while splits < _SPLIT_MAX_SPLITS and splits * 2 * batch_size <= num_sm:
1327
- splits *= 2
1328
-
1576
ws = _TRITON_SPLIT_CACHE.get(logits.device)
1577
if ws is None:
1578
ws = {
@@ -1336,53 +1583,31 @@ def _apply_topp_split(
1583
}
1584
_TRITON_SPLIT_CACHE[logits.device] = ws
1585
1339
- k_ptr = k if k is not None else logits # dummy pointer when HAS_K=False
1340
- has_k = k is not None
1341
-
1342
- _topp_sb_stats_kernel[(batch_size * splits,)](
1586
+ _topp_split_stats(
1587
logits,
1344
- logits.stride(0),
1588
ws["stats"],
1346
- k_ptr,
1589
+ k,
1590
p,
1348
- HAS_K=has_k,
1349
- VOCAB_SIZE=vocab_size,
1350
- S=splits,
1351
- BLOCK=8192,
1352
- num_warps=8,
1591
+ num_sm,
1592
)
1593
for round_i in range(_SPLIT_ROUNDS):
1355
- _topp_sb_step_kernel[(batch_size * splits,)](
1594
+ _topp_split_step(
1595
logits,
1357
- logits.stride(0),
1596
ws["stats"],
1597
ws["parts"],
1360
- k_ptr,
1598
+ k,
1599
p,
1600
round_i,
1363
- HAS_K=has_k,
1364
- S=splits,
1365
- F=_SPLIT_FANOUT,
1366
- NUM_ROUNDS=_SPLIT_ROUNDS,
1367
- VOCAB_SIZE=vocab_size,
1368
- BLOCK=2048,
1369
- num_warps=8,
1601
+ num_sm,
1602
)
1371
- _topp_sb_mask_kernel[(batch_size * splits,)](
1603
+ _topp_split_mask(
1604
logits,
1373
- logits.stride(0),
1605
ws["stats"],
1606
ws["parts"],
1376
- k_ptr,
1607
+ k,
1608
p,
1378
- HAS_K=has_k,
1379
- MASK_VALUE=mask_value,
1380
- S=splits,
1381
- F=_SPLIT_FANOUT,
1382
- NUM_ROUNDS=_SPLIT_ROUNDS,
1383
- VOCAB_SIZE=vocab_size,
1384
- BLOCK=8192,
1385
- num_warps=8,
1609
+ mask_value,
1610
+ num_sm,
1611
)
1612
1613
@@ -1477,39 +1702,15 @@ def apply_top_k_top_p_triton(
1702
else:
1703
normal_cdf_to_sigma_table, percentile_to_std_table = tables
1704
1480
- # Smaller tiles compile and run faster on CPU; GPU benefits from larger tiles.
1481
- # On XPU, large BLOCK_SIZE causes precision loss in the single-pass pivot
1482
- # approximation; use smaller tiles for accurate top-p results.
1483
- launch_kwargs = {}
1484
- if logits.device.type == "cpu":
1485
- block_size, block_size_trunc = 256, 128
1486
- elif logits.device.type == "xpu":
1487
- block_size, block_size_trunc = 4096, 2048
1488
- else:
1489
- block_size, block_size_trunc = 8192, 4096
1490
- # Each program serially sweeps the vocab row in BLOCK_SIZE tiles, so
1491
- # per-tile latency bounds kernel latency, and Triton's default of 4
1492
- # warps leaves an 8192-wide tile at 16 elements per lane. 8 warps is
1493
- # faster on every arch measured (SM90, SM100, SM120, gfx950); 16 is not.
1494
- launch_kwargs["num_warps"] = 8
1495
-
1496
- _topk_topp_kernel[(NUM_PROGRAMS,)](
1705
+ _topk_topp(
1706
logits,
1498
- logits.stride(0),
1707
buffer,
1708
percentile_to_std_table,
1709
normal_cdf_to_sigma_table,
1502
- k_ptr,
1503
- p_ptr,
1504
- BATCH_SIZE=batch_size,
1505
- MASK_VALUE=mask_value,
1506
- VOCAB_SIZE=vocab_size,
1507
- BLOCK_SIZE=block_size,
1508
- BLOCK_SIZE_TRUNC=block_size_trunc,
1509
- TOPK_ENABLED=topk_enabled,
1510
- TOPP_ENABLED=topp_enabled,
1511
- SPLIT_COVERS_PONLY=use_split,
1512
- **launch_kwargs,
1710
+ k_ptr if topk_enabled else None,
1711
+ p_ptr if topp_enabled else None,
1712
+ mask_value,
1713
+ num_sm,
1714
)
1715
if use_split:
1716
_apply_topp_split(logits, k_ptr, p_ptr, mask_value, num_sm)
vllm/v1/sample/rejection_sampler.py
+186
-15
@@ -5,13 +5,19 @@ from __future__ import annotations
5
6
from collections.abc import Sequence
7
from dataclasses import replace
8
-from typing import TYPE_CHECKING
8
+from typing import TYPE_CHECKING, Any
9
10
import torch
11
import torch.nn as nn
12
13
from vllm.config.model import PROCESSED_LOGPROBS_MODES
14
from vllm.logger import init_logger
15
+from vllm.model_executor.warmup.jit_warmup import WarmupChoices
16
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
17
+ DispatchSpec,
18
+ TritonWarmupTensor,
19
+ triton_kernel_dispatcher_with_warmup,
20
+)
21
from vllm.triton_utils import tl, triton
22
from vllm.v1.outputs import LogprobsLists, LogprobsTensors, SamplerOutput
23
from vllm.v1.sample.logits_processor.builtin import MinTokensLogitsProcessor
@@ -88,6 +94,14 @@ class RejectionSampler(nn.Module):
94
device=device,
95
)
96
self.synthetic_mode = self.synthetic_conditional_rates is not None
97
+ _rejection_greedy_sample.register_warmup(synthetic_mode=self.synthetic_mode)
98
+ _rejection_random_sample.register_warmup(
99
+ synthetic_mode=self.synthetic_mode,
100
+ )
101
+ _expand.register_warmup()
102
+ _sample_recovered_tokens.register_warmup(
103
+ use_fp64_gumbel=self.use_fp64_gumbel,
104
+ )
105
106
def forward(
107
self,
@@ -453,7 +467,7 @@ def rejection_sample(
467
if not sampling_metadata.all_random:
468
# Rejection sampling for greedy sampling requests.
469
target_argmax = target_logits.argmax(dim=-1)
456
- rejection_greedy_sample_kernel[(batch_size,)](
470
+ _rejection_greedy_sample(
471
output_token_ids,
472
cu_num_draft_tokens,
473
draft_token_ids,
@@ -463,7 +477,6 @@ def rejection_sample(
477
max_spec_len,
478
uniform_probs,
479
synthetic_conditional_rates,
466
- SYNTHETIC_MODE=synthetic_mode,
480
)
481
if sampling_metadata.all_greedy:
482
return output_token_ids
@@ -488,7 +501,7 @@ def rejection_sample(
501
502
# Rejection sampling for random sampling requests.
503
assert uniform_probs is not None
491
- rejection_random_sample_kernel[(batch_size,)](
504
+ _rejection_random_sample(
505
output_token_ids,
506
cu_num_draft_tokens,
507
draft_token_ids,
@@ -499,10 +512,7 @@ def rejection_sample(
512
uniform_probs,
513
is_greedy,
514
max_spec_len,
502
- vocab_size,
515
synthetic_conditional_rates,
504
- NO_DRAFT_PROBS=draft_probs is None,
505
- SYNTHETIC_MODE=synthetic_mode,
516
)
517
return output_token_ids
518
@@ -594,13 +604,12 @@ def expand_batch_to_tokens(
604
batch_size = x.shape[0]
605
assert cu_num_tokens.shape[0] == batch_size
606
expanded_x = x.new_empty(num_tokens)
597
- expand_kernel[(batch_size,)](
607
+ _expand(
608
expanded_x,
609
x,
610
cu_num_tokens,
611
replace_from,
612
replace_to,
603
- MAX_NUM_TOKENS=MAX_SPEC_LEN, # To avoid recompilation.
613
)
614
return expanded_x
615
@@ -694,18 +703,14 @@ def sample_recovered_tokens(
703
inv_q = q.reciprocal()
704
705
recovered_token_ids = torch.empty_like(draft_token_ids)
697
- BLOCK_SIZE = 8192
698
- sample_recovered_tokens_kernel[(batch_size, max_spec_len)](
706
+ _sample_recovered_tokens(
707
recovered_token_ids,
708
cu_num_draft_tokens,
709
draft_token_ids,
710
draft_probs,
711
target_probs,
712
inv_q,
705
- vocab_size,
706
- BLOCK_SIZE,
707
- NO_DRAFT_PROBS=draft_probs is None,
708
- USE_FP64_GUMBEL=use_fp64_gumbel,
713
+ max_spec_len=max_spec_len,
714
)
715
return recovered_token_ids
716
@@ -769,6 +774,50 @@ def rejection_greedy_sample_kernel(
774
)
775
776
777
+def _rejection_greedy_sample_warmup_inputs(
778
+ *, synthetic_mode: bool
779
+) -> dict[str, object]:
780
+ all_greedy = WarmupChoices(False, True)
781
+ int32 = TritonWarmupTensor(torch.int32)
782
+ return dict(
783
+ output_token_ids=int32,
784
+ cu_num_draft_tokens=int32,
785
+ draft_token_ids=int32,
786
+ target_argmax=TritonWarmupTensor(torch.int64),
787
+ bonus_token_ids=TritonWarmupTensor(torch.int64),
788
+ is_greedy=None if all_greedy else TritonWarmupTensor(torch.bool),
789
+ max_spec_len=5,
790
+ uniform_probs=(
791
+ None
792
+ if all_greedy and not synthetic_mode
793
+ else TritonWarmupTensor(torch.float64)
794
+ ),
795
+ synthetic_conditional_rates=(
796
+ TritonWarmupTensor(torch.float32) if synthetic_mode else None
797
+ ),
798
+ )
799
+
800
+
801
+@triton_kernel_dispatcher_with_warmup(
802
+ kernel=rejection_greedy_sample_kernel,
803
+ warmup_inputs=_rejection_greedy_sample_warmup_inputs,
804
+)
805
+def _rejection_greedy_sample(
806
+ output_token_ids: torch.Tensor,
807
+ cu_num_draft_tokens: torch.Tensor,
808
+ draft_token_ids: torch.Tensor,
809
+ target_argmax: torch.Tensor,
810
+ bonus_token_ids: torch.Tensor,
811
+ is_greedy: torch.Tensor | None,
812
+ max_spec_len: int,
813
+ uniform_probs: torch.Tensor | None,
814
+ synthetic_conditional_rates: torch.Tensor | None,
815
+) -> DispatchSpec:
816
+ return (output_token_ids.shape[0],), dict(
817
+ SYNTHETIC_MODE=synthetic_conditional_rates is not None,
818
+ )
819
+
820
+
821
# NOTE(woosuk): Avoid specialization to prevent unnecessary recompilation.
822
@triton.jit(do_not_specialize=["max_spec_len"])
823
def rejection_random_sample_kernel(
@@ -845,6 +894,57 @@ def rejection_random_sample_kernel(
894
)
895
896
897
+def _rejection_random_sample_warmup_inputs(
898
+ *, synthetic_mode: bool
899
+) -> dict[str, object]:
900
+ vocab_size: Any = WarmupChoices(2, 16)
901
+ no_draft_probs = WarmupChoices(False, True)
902
+ int32 = TritonWarmupTensor(torch.int32)
903
+ probabilities = TritonWarmupTensor(
904
+ torch.float32,
905
+ shape=(1, vocab_size),
906
+ )
907
+ return dict(
908
+ output_token_ids=int32,
909
+ cu_num_draft_tokens=int32,
910
+ draft_token_ids=int32,
911
+ draft_probs=None if no_draft_probs else probabilities,
912
+ target_probs=probabilities,
913
+ bonus_token_ids=TritonWarmupTensor(torch.int64),
914
+ recovered_token_ids=int32,
915
+ uniform_probs=TritonWarmupTensor(torch.float64),
916
+ is_greedy=TritonWarmupTensor(torch.bool),
917
+ max_spec_len=5,
918
+ synthetic_conditional_rates=(
919
+ TritonWarmupTensor(torch.float32) if synthetic_mode else None
920
+ ),
921
+ )
922
+
923
+
924
+@triton_kernel_dispatcher_with_warmup(
925
+ kernel=rejection_random_sample_kernel,
926
+ warmup_inputs=_rejection_random_sample_warmup_inputs,
927
+)
928
+def _rejection_random_sample(
929
+ output_token_ids: torch.Tensor,
930
+ cu_num_draft_tokens: torch.Tensor,
931
+ draft_token_ids: torch.Tensor,
932
+ draft_probs: torch.Tensor | None,
933
+ target_probs: torch.Tensor,
934
+ bonus_token_ids: torch.Tensor,
935
+ recovered_token_ids: torch.Tensor,
936
+ uniform_probs: torch.Tensor,
937
+ is_greedy: torch.Tensor,
938
+ max_spec_len: int,
939
+ synthetic_conditional_rates: torch.Tensor | None,
940
+) -> DispatchSpec:
941
+ return (output_token_ids.shape[0],), dict(
942
+ vocab_size=target_probs.shape[1],
943
+ NO_DRAFT_PROBS=draft_probs is None,
944
+ SYNTHETIC_MODE=synthetic_conditional_rates is not None,
945
+ )
946
+
947
+
948
# NOTE(woosuk): Avoid specialization to prevent unnecessary recompilation.
949
@triton.jit(do_not_specialize=["replace_from", "replace_to"])
950
def expand_kernel(
@@ -869,6 +969,31 @@ def expand_kernel(
969
tl.store(output_ptr + start_idx + offset, src_val, mask=offset < num_tokens)
970
971
972
+def _expand_warmup_inputs() -> dict[str, object]:
973
+ dtype = WarmupChoices(torch.float32, torch.int32, torch.int64)
974
+ data = TritonWarmupTensor(dtype)
975
+ return dict(
976
+ output=data,
977
+ input=data,
978
+ cu_num_tokens=TritonWarmupTensor(torch.int32),
979
+ replace_from=0,
980
+ replace_to=0,
981
+ )
982
+
983
+
984
+@triton_kernel_dispatcher_with_warmup(
985
+ kernel=expand_kernel, warmup_inputs=_expand_warmup_inputs
986
+)
987
+def _expand(
988
+ output: torch.Tensor,
989
+ input: torch.Tensor,
990
+ cu_num_tokens: torch.Tensor,
991
+ replace_from: int,
992
+ replace_to: int,
993
+) -> DispatchSpec:
994
+ return (input.shape[0],), dict(MAX_NUM_TOKENS=MAX_SPEC_LEN)
995
+
996
+
997
@triton.jit
998
def sample_recovered_tokens_kernel(
999
output_token_ids_ptr, # [num_tokens]
@@ -951,3 +1076,49 @@ def sample_recovered_tokens_kernel(
1076
1077
recovered_id = tl.minimum(recovered_id, vocab_size - 1)
1078
tl.store(output_token_ids_ptr + token_idx, recovered_id)
1079
+
1080
+
1081
+def _sample_recovered_tokens_warmup_inputs(
1082
+ *, use_fp64_gumbel: bool
1083
+) -> dict[str, object]:
1084
+ vocab_size: Any = WarmupChoices(2, 16)
1085
+ no_draft_probs = WarmupChoices(False, True)
1086
+ int32 = TritonWarmupTensor(torch.int32)
1087
+ probabilities = TritonWarmupTensor(
1088
+ torch.float32,
1089
+ shape=(1, vocab_size),
1090
+ )
1091
+ return dict(
1092
+ output_token_ids=int32,
1093
+ cu_num_draft_tokens=int32,
1094
+ draft_token_ids=int32,
1095
+ draft_probs=None if no_draft_probs else probabilities,
1096
+ target_probs=probabilities,
1097
+ inv_q=TritonWarmupTensor(
1098
+ torch.float64 if use_fp64_gumbel else torch.float32,
1099
+ shape=(1, vocab_size),
1100
+ ),
1101
+ max_spec_len=5,
1102
+ )
1103
+
1104
+
1105
+@triton_kernel_dispatcher_with_warmup(
1106
+ kernel=sample_recovered_tokens_kernel,
1107
+ warmup_inputs=_sample_recovered_tokens_warmup_inputs,
1108
+)
1109
+def _sample_recovered_tokens(
1110
+ output_token_ids: torch.Tensor,
1111
+ cu_num_draft_tokens: torch.Tensor,
1112
+ draft_token_ids: torch.Tensor,
1113
+ draft_probs: torch.Tensor | None,
1114
+ target_probs: torch.Tensor,
1115
+ inv_q: torch.Tensor,
1116
+ *,
1117
+ max_spec_len: int,
1118
+) -> DispatchSpec:
1119
+ return (cu_num_draft_tokens.shape[0], max_spec_len), dict(
1120
+ vocab_size=target_probs.shape[1],
1121
+ BLOCK_SIZE=8192,
1122
+ NO_DRAFT_PROBS=draft_probs is None,
1123
+ USE_FP64_GUMBEL=inv_q.dtype == torch.float64,
1124
+ )
vllm/v1/spec_decode/dflash.py
+12
-6
@@ -13,7 +13,7 @@ from vllm.logger import init_logger
13
from vllm.v1.attention.backend import CommonAttentionMetadata
14
from vllm.v1.spec_decode.llm_base_proposer import SpecDecodeBaseProposer
15
from vllm.v1.spec_decode.utils import (
16
- copy_and_expand_dflash_inputs_kernel,
16
+ _copy_and_expand_dflash_inputs,
17
next_power_of_2,
18
)
19
@@ -79,6 +79,13 @@ class DFlashProposer(SpecDecodeBaseProposer):
79
self.dflash_causal = not dflash_has_any_non_causal(
80
self.draft_model_config.hf_config
81
)
82
+ _copy_and_expand_dflash_inputs.register_warmup(
83
+ block_table_stride=(self.max_model_len + self.block_size - 1)
84
+ // self.block_size,
85
+ parallel_drafting_token_id=self.parallel_drafting_token_id,
86
+ block_size=self.block_size,
87
+ num_speculative_tokens=self.num_speculative_tokens,
88
+ )
89
90
@override
91
def _create_draft_vllm_config(self) -> VllmConfig:
@@ -137,11 +144,9 @@ class DFlashProposer(SpecDecodeBaseProposer):
144
max_ctx_per_req = cad.max_query_len
145
max_tokens_per_req = max_ctx_per_req + num_query_per_req
146
BLOCK_SIZE = min(256, next_power_of_2(max_tokens_per_req))
140
- num_blocks = (max_tokens_per_req + BLOCK_SIZE - 1) // BLOCK_SIZE
141
- grid = (batch_size, num_blocks)
147
148
has_num_rejected = num_rejected_tokens_gpu is not None
144
- copy_and_expand_dflash_inputs_kernel[grid](
149
+ _copy_and_expand_dflash_inputs(
150
# Inputs
151
next_token_ids_ptr=next_token_ids,
152
target_positions_ptr=target_positions,
@@ -166,8 +171,9 @@ class DFlashProposer(SpecDecodeBaseProposer):
171
num_query_per_req=num_query_per_req,
172
num_speculative_tokens=self.num_speculative_tokens,
173
total_input_tokens=num_context,
169
- BLOCK_SIZE=BLOCK_SIZE,
170
- HAS_NUM_REJECTED=has_num_rejected,
174
+ max_tokens_per_req=max_tokens_per_req,
175
+ triton_block_size=BLOCK_SIZE,
176
+ has_num_rejected=has_num_rejected,
177
)
178
179
query_slot_mapping = self._slot_mapping_buffer[:num_query_total]
vllm/v1/spec_decode/llm_base_proposer.py
+26
-9
@@ -52,10 +52,11 @@ from vllm.v1.sample.sampler import _SAMPLING_EPS
52
from vllm.v1.spec_decode.metadata import SpecDecodeMetadata
53
from vllm.v1.spec_decode.utils import (
54
PADDING_SLOT_ID,
55
+ _copy_and_expand_eagle_inputs,
56
+ _eagle_prepare_inputs_padded,
57
+ _eagle_prepare_next_token_padded,
58
+ _eagle_step_slot_mapping_metadata,
59
compute_new_slot_mapping,
56
- copy_and_expand_eagle_inputs_kernel,
57
- eagle_prepare_inputs_padded_kernel,
58
- eagle_prepare_next_token_padded_kernel,
60
eagle_step_update_slot_mapping_and_metadata,
61
extend_all_queries_by_N,
62
next_power_of_2,
@@ -140,6 +141,23 @@ class SpecDecodeBaseProposer:
141
142
self.max_batch_size = vllm_config.scheduler_config.max_num_seqs
143
self.max_num_tokens = vllm_config.scheduler_config.max_num_batched_tokens
144
+ _eagle_step_slot_mapping_metadata.register_warmup(
145
+ max_model_len=self.max_model_len,
146
+ max_batch_size=self.max_batch_size,
147
+ )
148
+ _eagle_prepare_inputs_padded.register_warmup(max_batch_size=self.max_batch_size)
149
+ _eagle_prepare_next_token_padded.register_warmup(
150
+ vocab_size=vllm_config.model_config.get_vocab_size(),
151
+ num_sampled_tokens_per_req=self.num_speculative_tokens + 1,
152
+ max_batch_size=self.max_batch_size,
153
+ )
154
+ if self.needs_extra_input_slots:
155
+ _copy_and_expand_eagle_inputs.register_warmup(
156
+ parallel_drafting_token_id=self.parallel_drafting_token_id,
157
+ num_padding_slots_per_request=self.extra_slots_per_request,
158
+ shift_input_ids=self.pass_hidden_states_to_model,
159
+ max_num_tokens=self.max_num_tokens,
160
+ )
161
self.token_arange_np = np.arange(self.max_num_tokens, dtype=np.int32)
162
163
# Can be specialized by methods like DFlash to reduce the limit
@@ -872,13 +890,12 @@ class SpecDecodeBaseProposer:
890
)
891
892
# Kernel grid: one program per request (row)
875
- grid = (batch_size, num_blocks)
893
query_start_loc = cad.query_start_loc
894
query_end_loc = cad.query_start_loc[1:] - 1
895
if num_rejected_tokens_gpu is not None:
896
query_end_loc = query_end_loc - num_rejected_tokens_gpu
897
881
- copy_and_expand_eagle_inputs_kernel[grid](
898
+ _copy_and_expand_eagle_inputs(
899
# (Padded) Inputs from the target model
900
target_token_ids_ptr=target_token_ids,
901
target_positions_ptr=target_positions,
@@ -901,6 +918,8 @@ class SpecDecodeBaseProposer:
918
num_padding_slots_per_request=self.extra_slots_per_request,
919
shift_input_ids=self.pass_hidden_states_to_model,
920
BLOCK_SIZE_TOKENS=BLOCK_SIZE_TOKENS,
921
+ batch_size=batch_size,
922
+ num_blocks=num_blocks,
923
)
924
if self.pass_hidden_states_to_model:
925
assert self.parallel_drafting_hidden_state_tensor is not None
@@ -1065,11 +1084,10 @@ class SpecDecodeBaseProposer:
1084
valid_sampled_tokens_count = next_token_ids.new_empty(batch_size)
1085
1086
# Kernel grid: one program per request (row)
1068
- grid = (batch_size,)
1087
1088
# Find the next power of 2 for block sizes
1089
BLOCK_SIZE_TOKENS = next_power_of_2(num_tokens)
1072
- eagle_prepare_next_token_padded_kernel[grid](
1090
+ _eagle_prepare_next_token_padded(
1091
sampled_token_ids,
1092
discard_request_mask,
1093
backup_tokens_gpu,
@@ -1108,8 +1126,7 @@ class SpecDecodeBaseProposer:
1126
(num_reqs,), dtype=torch.int32, device=device
1127
)
1128
1111
- grid = (num_reqs,)
1112
- eagle_prepare_inputs_padded_kernel[grid](
1129
+ _eagle_prepare_inputs_padded(
1130
spec_decode_metadata.cu_num_draft_tokens,
1131
valid_sampled_tokens_count,
1132
common_attn_metadata.query_start_loc,
vllm/v1/spec_decode/utils.py
+257
-1
@@ -1,7 +1,18 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from typing import Any
4
+
5
import torch
6
7
+from vllm.model_executor.warmup.jit_warmup import (
8
+ WarmupChoices,
9
+ WarmupIntRange,
10
+)
11
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
12
+ DispatchSpec,
13
+ TritonWarmupTensor,
14
+ triton_kernel_dispatcher_with_warmup,
15
+)
16
from vllm.platforms import current_platform
17
from vllm.triton_utils import tl, triton
18
from vllm.v1.attention.backends.utils import (
@@ -85,6 +96,49 @@ def eagle_step_slot_mapping_metadata_kernel(
96
tl.store(seq_lens_ptr + req_idx, new_seq_len)
97
98
99
+def _eagle_step_warmup_inputs(*, max_model_len: int, max_batch_size: int):
100
+ block_size = WarmupIntRange(1, 257, advance=lambda value: value * 2)
101
+ n_blocks_per_req = triton.cdiv(max_model_len, block_size)
102
+ return dict(
103
+ positions=TritonWarmupTensor(torch.int64),
104
+ block_table=TritonWarmupTensor(
105
+ torch.int32, shape=(1, n_blocks_per_req), strides=(n_blocks_per_req, 1)
106
+ ),
107
+ block_table_stride=n_blocks_per_req,
108
+ seq_lens=TritonWarmupTensor(torch.int32),
109
+ out_clamped_positions=TritonWarmupTensor(torch.int64),
110
+ out_slot_mapping=TritonWarmupTensor(torch.int64),
111
+ block_size=block_size,
112
+ max_model_len=max_model_len,
113
+ n_blocks_per_req=n_blocks_per_req,
114
+ PAD_ID=PADDING_SLOT_ID,
115
+ batch_size=WarmupIntRange(1, max_batch_size + 1),
116
+ input_batch_size=1,
117
+ )
118
+
119
+
120
+@triton_kernel_dispatcher_with_warmup(
121
+ kernel=eagle_step_slot_mapping_metadata_kernel,
122
+ warmup_inputs=_eagle_step_warmup_inputs,
123
+)
124
+def _eagle_step_slot_mapping_metadata(
125
+ positions: torch.Tensor,
126
+ block_table: torch.Tensor,
127
+ block_table_stride: int,
128
+ seq_lens: torch.Tensor,
129
+ out_clamped_positions: torch.Tensor,
130
+ out_slot_mapping: torch.Tensor,
131
+ *,
132
+ block_size: int,
133
+ max_model_len: int,
134
+ n_blocks_per_req: int,
135
+ PAD_ID: int,
136
+ batch_size: int,
137
+ input_batch_size: int,
138
+) -> DispatchSpec:
139
+ return (input_batch_size,), {}
140
+
141
+
142
def eagle_step_update_slot_mapping_and_metadata(
143
positions_1d: torch.Tensor,
144
block_table_tensor: torch.Tensor,
@@ -118,7 +172,7 @@ def eagle_step_update_slot_mapping_and_metadata(
172
input_batch_size = batch_size
173
174
n_blocks_per_req = block_table_tensor.shape[1]
121
- eagle_step_slot_mapping_metadata_kernel[(input_batch_size,)](
175
+ _eagle_step_slot_mapping_metadata(
176
positions_1d,
177
block_table_tensor,
178
block_table_tensor.stride(0),
@@ -130,6 +184,7 @@ def eagle_step_update_slot_mapping_and_metadata(
184
n_blocks_per_req=n_blocks_per_req,
185
PAD_ID=PADDING_SLOT_ID,
186
batch_size=batch_size,
187
+ input_batch_size=input_batch_size,
188
)
189
190
@@ -175,6 +230,33 @@ def eagle_prepare_inputs_padded_kernel(
230
tl.store(num_rejected_tokens_gpu_ptr + req_idx, num_rejected_tokens)
231
232
233
+def _eagle_prepare_inputs_warmup_inputs(*, max_batch_size: int):
234
+ int32 = TritonWarmupTensor(torch.int32)
235
+ return dict(
236
+ cu_num_draft_tokens=int32,
237
+ valid_sampled_tokens_count=int32,
238
+ query_start_loc_gpu=int32,
239
+ token_indices_to_sample=int32,
240
+ num_rejected_tokens_gpu=int32,
241
+ num_reqs=WarmupIntRange(1, max_batch_size + 1),
242
+ )
243
+
244
+
245
+@triton_kernel_dispatcher_with_warmup(
246
+ kernel=eagle_prepare_inputs_padded_kernel,
247
+ warmup_inputs=_eagle_prepare_inputs_warmup_inputs,
248
+)
249
+def _eagle_prepare_inputs_padded(
250
+ cu_num_draft_tokens: torch.Tensor,
251
+ valid_sampled_tokens_count: torch.Tensor,
252
+ query_start_loc_gpu: torch.Tensor,
253
+ token_indices_to_sample: torch.Tensor,
254
+ num_rejected_tokens_gpu: torch.Tensor,
255
+ num_reqs: int,
256
+) -> DispatchSpec:
257
+ return (num_reqs,), {}
258
+
259
+
260
@triton.jit
261
def eagle_prepare_next_token_padded_kernel(
262
sampled_token_ids_ptr, # [num_reqs, num_sampled_tokens_per_req]
@@ -238,6 +320,49 @@ def eagle_prepare_next_token_padded_kernel(
320
tl.store(valid_sampled_tokens_count_ptr + req_idx, valid_count)
321
322
323
+def _eagle_prepare_next_token_warmup_inputs(
324
+ *, vocab_size: int, num_sampled_tokens_per_req: int, max_batch_size: int
325
+):
326
+ int32 = TritonWarmupTensor(torch.int32)
327
+ sampled_width: Any = WarmupIntRange(1, num_sampled_tokens_per_req + 1)
328
+ return dict(
329
+ sampled_token_ids=TritonWarmupTensor(
330
+ torch.int32,
331
+ shape=(1, sampled_width),
332
+ strides=(sampled_width, 1),
333
+ ),
334
+ discard_request_mask=TritonWarmupTensor(torch.bool),
335
+ backup_next_token_ids=int32,
336
+ next_token_ids=int32,
337
+ valid_sampled_tokens_count=int32,
338
+ vocab_size=vocab_size,
339
+ num_sampled_tokens_per_req=sampled_width,
340
+ num_reqs=WarmupIntRange(1, max_batch_size + 1),
341
+ stride_sampled_token_ids=sampled_width,
342
+ BLOCK_SIZE_TOKENS=next_power_of_2(sampled_width),
343
+ )
344
+
345
+
346
+@triton_kernel_dispatcher_with_warmup(
347
+ kernel=eagle_prepare_next_token_padded_kernel,
348
+ warmup_inputs=_eagle_prepare_next_token_warmup_inputs,
349
+)
350
+def _eagle_prepare_next_token_padded(
351
+ sampled_token_ids: torch.Tensor,
352
+ discard_request_mask: torch.Tensor,
353
+ backup_next_token_ids: torch.Tensor,
354
+ next_token_ids: torch.Tensor,
355
+ valid_sampled_tokens_count: torch.Tensor,
356
+ vocab_size: int,
357
+ num_sampled_tokens_per_req: int,
358
+ num_reqs: int,
359
+ stride_sampled_token_ids: int,
360
+ *,
361
+ BLOCK_SIZE_TOKENS: int,
362
+) -> DispatchSpec:
363
+ return (num_reqs,), {}
364
+
365
+
366
def compute_new_slot_mapping(
367
cad: CommonAttentionMetadata,
368
new_positions: torch.Tensor,
@@ -454,6 +579,68 @@ def copy_and_expand_eagle_inputs_kernel(
579
)
580
581
582
+def _copy_and_expand_eagle_warmup_inputs(
583
+ *,
584
+ parallel_drafting_token_id: int,
585
+ num_padding_slots_per_request: int,
586
+ shift_input_ids: bool,
587
+ max_num_tokens: int,
588
+):
589
+ int32 = TritonWarmupTensor(torch.int32)
590
+ int64 = TritonWarmupTensor(torch.int64)
591
+ boolean = TritonWarmupTensor(torch.bool)
592
+ return dict(
593
+ target_token_ids_ptr=int32,
594
+ target_positions_ptr=int64,
595
+ next_token_ids_ptr=int32,
596
+ out_input_ids_ptr=int32,
597
+ out_positions_ptr=int64,
598
+ out_is_rejected_token_mask_ptr=boolean,
599
+ out_is_masked_token_mask_ptr=boolean,
600
+ out_new_token_indices_ptr=int32,
601
+ out_hidden_state_mapping_ptr=int32,
602
+ query_start_loc_ptr=int32,
603
+ query_end_loc_ptr=int32,
604
+ padding_token_id=0,
605
+ parallel_drafting_token_id=parallel_drafting_token_id,
606
+ total_input_tokens=WarmupIntRange(1, max_num_tokens + 1),
607
+ num_padding_slots_per_request=num_padding_slots_per_request,
608
+ shift_input_ids=shift_input_ids,
609
+ BLOCK_SIZE_TOKENS=WarmupIntRange(1, 257, advance=lambda value: value * 2),
610
+ batch_size=1,
611
+ num_blocks=1,
612
+ )
613
+
614
+
615
+@triton_kernel_dispatcher_with_warmup(
616
+ kernel=copy_and_expand_eagle_inputs_kernel,
617
+ warmup_inputs=_copy_and_expand_eagle_warmup_inputs,
618
+)
619
+def _copy_and_expand_eagle_inputs(
620
+ target_token_ids_ptr: torch.Tensor,
621
+ target_positions_ptr: torch.Tensor,
622
+ next_token_ids_ptr: torch.Tensor,
623
+ out_input_ids_ptr: torch.Tensor,
624
+ out_positions_ptr: torch.Tensor,
625
+ out_is_rejected_token_mask_ptr: torch.Tensor,
626
+ out_is_masked_token_mask_ptr: torch.Tensor,
627
+ out_new_token_indices_ptr: torch.Tensor,
628
+ out_hidden_state_mapping_ptr: torch.Tensor,
629
+ query_start_loc_ptr: torch.Tensor,
630
+ query_end_loc_ptr: torch.Tensor,
631
+ padding_token_id: int,
632
+ parallel_drafting_token_id: int,
633
+ total_input_tokens: int,
634
+ num_padding_slots_per_request: int,
635
+ shift_input_ids: bool,
636
+ *,
637
+ BLOCK_SIZE_TOKENS: int,
638
+ batch_size: int,
639
+ num_blocks: int,
640
+) -> DispatchSpec:
641
+ return (batch_size, num_blocks), {}
642
+
643
+
644
@triton.jit
645
def copy_and_expand_dflash_inputs_kernel(
646
# Inputs
@@ -562,6 +749,75 @@ def copy_and_expand_dflash_inputs_kernel(
749
)
750
751
752
+def _copy_and_expand_dflash_warmup_inputs(
753
+ *,
754
+ block_table_stride: int,
755
+ parallel_drafting_token_id: int,
756
+ block_size: int,
757
+ num_speculative_tokens: int,
758
+):
759
+ int32 = TritonWarmupTensor(torch.int32)
760
+ int64 = TritonWarmupTensor(torch.int64)
761
+ triton_block_size = WarmupIntRange(1, 257, advance=lambda value: value * 2)
762
+ has_num_rejected = WarmupChoices(False, True)
763
+ return dict(
764
+ next_token_ids_ptr=int32,
765
+ target_positions_ptr=int64,
766
+ out_input_ids_ptr=int32,
767
+ out_context_positions_ptr=int64,
768
+ out_query_positions_ptr=int64,
769
+ out_context_slot_mapping_ptr=int64,
770
+ out_query_slot_mapping_ptr=int64,
771
+ out_token_indices_ptr=int32,
772
+ block_table_ptr=int32,
773
+ query_start_loc_ptr=int32,
774
+ num_rejected_tokens_ptr=(int32 if has_num_rejected else 0),
775
+ block_table_stride=block_table_stride,
776
+ parallel_drafting_token_id=parallel_drafting_token_id,
777
+ block_size=block_size,
778
+ num_query_per_req=1 + num_speculative_tokens,
779
+ num_speculative_tokens=num_speculative_tokens,
780
+ total_input_tokens=WarmupChoices(1, 2, 16),
781
+ max_tokens_per_req=triton_block_size,
782
+ triton_block_size=triton_block_size,
783
+ has_num_rejected=has_num_rejected,
784
+ )
785
+
786
+
787
+@triton_kernel_dispatcher_with_warmup(
788
+ kernel=copy_and_expand_dflash_inputs_kernel,
789
+ warmup_inputs=_copy_and_expand_dflash_warmup_inputs,
790
+)
791
+def _copy_and_expand_dflash_inputs(
792
+ *,
793
+ next_token_ids_ptr: torch.Tensor,
794
+ target_positions_ptr: torch.Tensor,
795
+ out_input_ids_ptr: torch.Tensor,
796
+ out_context_positions_ptr: torch.Tensor,
797
+ out_query_positions_ptr: torch.Tensor,
798
+ out_context_slot_mapping_ptr: torch.Tensor,
799
+ out_query_slot_mapping_ptr: torch.Tensor,
800
+ out_token_indices_ptr: torch.Tensor,
801
+ block_table_ptr: torch.Tensor,
802
+ query_start_loc_ptr: torch.Tensor,
803
+ num_rejected_tokens_ptr: torch.Tensor | int,
804
+ block_table_stride: int,
805
+ parallel_drafting_token_id: int,
806
+ block_size: int,
807
+ num_query_per_req: int,
808
+ num_speculative_tokens: int,
809
+ total_input_tokens: int,
810
+ max_tokens_per_req: int,
811
+ triton_block_size: int,
812
+ has_num_rejected: bool,
813
+) -> DispatchSpec:
814
+ num_reqs = query_start_loc_ptr.shape[0] - 1
815
+ num_blocks = triton.cdiv(max_tokens_per_req, triton_block_size)
816
+ return (num_reqs, num_blocks), dict(
817
+ BLOCK_SIZE=triton_block_size,
818
+ )
819
+
820
+
821
@torch.compile(dynamic=True, backend=current_platform.simple_compile_backend)
822
def update_num_computed_tokens_for_batch_change(
823
num_computed_tokens: torch.Tensor,
vllm/v1/worker/cpu_model_runner.py
+3
-4
@@ -80,16 +80,15 @@ class CPUModelRunner(GPUModelRunner):
80
81
# Speculative decoding fallbacks
82
import vllm.v1.sample.rejection_sampler
83
- import vllm.v1.spec_decode.llm_base_proposer
83
import vllm.v1.spec_decode.utils as spec_decode_utils
84
86
- vllm.v1.spec_decode.llm_base_proposer.eagle_prepare_inputs_padded_kernel = (
85
+ spec_decode_utils._eagle_prepare_inputs_padded.kernel = (
86
cpu_tl.eagle_prepare_inputs_padded_kernel
87
)
89
- vllm.v1.spec_decode.llm_base_proposer.eagle_prepare_next_token_padded_kernel = (
88
+ spec_decode_utils._eagle_prepare_next_token_padded.kernel = (
89
cpu_tl.eagle_prepare_next_token_padded_kernel
90
)
92
- vllm.v1.spec_decode.llm_base_proposer.copy_and_expand_eagle_inputs_kernel = (
91
+ spec_decode_utils._copy_and_expand_eagle_inputs.kernel = (
92
cpu_tl.copy_and_expand_eagle_inputs_kernel
93
)
94
spec_decode_utils.copy_and_expand_dflash_inputs_kernel = (
vllm/v1/worker/gpu/model_runner.py
+53
-44
@@ -188,6 +188,7 @@ logger = init_logger(__name__)
188
189
190
class GPUModelRunner(LoRAModelRunnerMixin):
191
+ @JitWarmupRegistry.capture
192
def __init__(self, vllm_config: VllmConfig, device: torch.device):
193
self.vllm_config = vllm_config
194
self.model_config = vllm_config.model_config
@@ -202,7 +203,6 @@ class GPUModelRunner(LoRAModelRunnerMixin):
203
self.speculative_config is not None and self.speculative_config.use_dspark()
204
)
205
self.observability_config = vllm_config.observability_config
205
- self.jit_warmup_registry = JitWarmupRegistry(vllm_config)
206
207
self.device = device
208
self.dtype = self.model_config.dtype
@@ -399,7 +399,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
399
logger.info_once("Loading model from scratch...")
400
401
# Capture warmup providers selected while constructing the model.
402
- with self.jit_warmup_registry.activate():
402
+ with self.jit_warmup_registry.activate(): # type: ignore[attr-defined]
403
self.model = model_loader.load_model(
404
vllm_config=self.vllm_config,
405
model_config=self.vllm_config.model_config,
@@ -461,47 +461,50 @@ class GPUModelRunner(LoRAModelRunnerMixin):
461
462
# Initialize samplers. Model states may override via custom_sampler().
463
if self.is_last_pp_rank and not self.is_pooling_model:
464
- sampler_kwargs: dict[str, Any] = {
465
- "max_num_reqs": self.max_num_reqs,
466
- "vocab_size": self.vocab_size,
467
- "device": self.device,
468
- "req_states": self.req_states,
469
- "logprobs_mode": self.model_config.logprobs_mode,
470
- "num_speculative_tokens": self.decode_query_len,
471
- "use_fp64_gumbel": self.model_config.use_fp64_gumbel,
472
- "enable_trace_replay": self.model_config.enable_trace_replay,
473
- "reasoning_config": self.vllm_config.reasoning_config,
474
- "return_sampling_mask": self.model_config.return_sampling_mask,
475
- }
476
- if self.vllm_config.watermark_config is None:
477
- self.sampler = Sampler(**sampler_kwargs)
478
- else:
479
- wm_config = self.vllm_config.watermark_config
480
- watermarker = create_watermarker(wm_config)
481
- if self.speculative_config is not None:
482
- watermarker = create_speculative_target_watermarker(watermarker)
483
- self.sampler = GPUWatermarkSampler(
484
- watermarker,
485
- deduplicate_contexts=wm_config.deduplicate_contexts,
486
- deduplicate_contexts_max_history=(
487
- wm_config.deduplicate_contexts_max_history
488
- ),
489
- **sampler_kwargs,
490
- )
491
- custom = self.model_state.custom_sampler(self.sampler)
492
-
493
- if custom:
494
- self.vllm_config._check_watermarking_unsupported(custom_sampler=True)
495
- self.sampler, self.rejection_sampler = custom
496
- elif self.speculative_config is not None:
497
- self.rejection_sampler = RejectionSampler(
498
- self.sampler,
499
- self.speculative_config,
500
- self.device,
501
- watermark_key=speculative_target_watermark_key(
502
- self.vllm_config.watermark_config
503
- ),
504
- )
464
+ with self.jit_warmup_registry.activate(): # type: ignore[attr-defined]
465
+ sampler_kwargs: dict[str, Any] = {
466
+ "max_num_reqs": self.max_num_reqs,
467
+ "vocab_size": self.vocab_size,
468
+ "device": self.device,
469
+ "req_states": self.req_states,
470
+ "logprobs_mode": self.model_config.logprobs_mode,
471
+ "num_speculative_tokens": self.decode_query_len,
472
+ "use_fp64_gumbel": self.model_config.use_fp64_gumbel,
473
+ "enable_trace_replay": self.model_config.enable_trace_replay,
474
+ "reasoning_config": self.vllm_config.reasoning_config,
475
+ "return_sampling_mask": self.model_config.return_sampling_mask,
476
+ }
477
+ if self.vllm_config.watermark_config is None:
478
+ self.sampler = Sampler(**sampler_kwargs)
479
+ else:
480
+ wm_config = self.vllm_config.watermark_config
481
+ watermarker = create_watermarker(wm_config)
482
+ if self.speculative_config is not None:
483
+ watermarker = create_speculative_target_watermarker(watermarker)
484
+ self.sampler = GPUWatermarkSampler(
485
+ watermarker,
486
+ deduplicate_contexts=wm_config.deduplicate_contexts,
487
+ deduplicate_contexts_max_history=(
488
+ wm_config.deduplicate_contexts_max_history
489
+ ),
490
+ **sampler_kwargs,
491
+ )
492
+ custom = self.model_state.custom_sampler(self.sampler)
493
+
494
+ if custom:
495
+ self.vllm_config._check_watermarking_unsupported(
496
+ custom_sampler=True
497
+ )
498
+ self.sampler, self.rejection_sampler = custom
499
+ elif self.speculative_config is not None:
500
+ self.rejection_sampler = RejectionSampler(
501
+ self.sampler,
502
+ self.speculative_config,
503
+ self.device,
504
+ watermark_key=speculative_target_watermark_key(
505
+ self.vllm_config.watermark_config
506
+ ),
507
+ )
508
self.prompt_logprobs_worker = PromptLogprobsWorker(
509
self.max_num_reqs,
510
logprobs_mode=self.model_config.logprobs_mode,
@@ -738,7 +741,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
741
self.speculator.init_cudagraph_manager(cudagraph_mode)
742
743
# Capture warmup providers that depend on allocated KV-cache strides.
741
- with self.jit_warmup_registry.activate():
744
+ with self.jit_warmup_registry.activate(): # type: ignore[attr-defined]
745
kv_caches_dict = init_kv_cache(
746
self.compilation_config.static_forward_context,
747
self.kv_cache_config,
@@ -924,6 +927,12 @@ class GPUModelRunner(LoRAModelRunnerMixin):
927
# during actual execution.
928
assert self.sampler is not None
929
self.sampler(logits, dummy_input_batch)
930
+ if self.rejection_sampler is not None:
931
+ # Warm head-dtype logits; scheduler warmup covers processed FP32 logits.
932
+ assert self.speculator is not None
933
+ self.rejection_sampler(
934
+ logits, dummy_input_batch, self.speculator.draft_logits
935
+ )
936
937
@torch.inference_mode()
938
def _dummy_pooler_run(self, hidden_states: torch.Tensor) -> None:
vllm/v1/worker/gpu/sample/states.py
+5
-1
@@ -4,7 +4,10 @@ import numpy as np
4
import torch
5
6
from vllm.sampling_params import SamplingParams
7
-from vllm.v1.sample.ops.topk_topp_sampler import apply_top_k_top_p
7
+from vllm.v1.sample.ops.topk_topp_sampler import (
8
+ apply_top_k_top_p,
9
+ register_top_k_top_p_warmups,
10
+)
11
from vllm.v1.worker.gpu.buffer_utils import UvaBackedTensor
12
from vllm.v1.worker.gpu.sample.gumbel import apply_temperature
13
from vllm.v1.worker.gpu.sample.min_p import apply_min_p
@@ -18,6 +21,7 @@ class SamplingStates:
21
def __init__(self, max_num_reqs: int, vocab_size: int):
22
self.max_num_reqs = max_num_reqs
23
self.vocab_size = vocab_size
24
+ register_top_k_top_p_warmups()
25
26
self.temperature = UvaBackedTensor(max_num_reqs, dtype=torch.float32)
27
self.top_k = UvaBackedTensor(max_num_reqs, dtype=torch.int32)
vllm/v1/worker/gpu/spec_decode/dflash/speculator.py
+92
-39
@@ -1,8 +1,10 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import copy
4
+from types import SimpleNamespace
5
from typing import Any
6
7
+import numpy as np
8
import torch
9
import torch.nn as nn
10
@@ -10,6 +12,15 @@ from vllm.config import VllmConfig, replace
12
from vllm.config.compilation import CUDAGraphMode
13
from vllm.forward_context import BatchDescriptor, set_forward_context
14
from vllm.logger import init_logger
15
+from vllm.model_executor.warmup.jit_warmup import (
16
+ WarmupChoices,
17
+ WarmupIntRange,
18
+)
19
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
20
+ DispatchSpec,
21
+ TritonWarmupTensor,
22
+ triton_kernel_dispatcher_with_warmup,
23
+)
24
from vllm.triton_utils import tl, triton
25
from vllm.v1.attention.backend import AttentionCGSupport
26
from vllm.v1.attention.backends.utils import PAD_SLOT_ID
@@ -108,6 +119,7 @@ class DFlashSpeculator(DraftModelSpeculator):
119
120
self.query_cudagraph_manager: DFlashCudaGraphManager | None = None
121
self.draft_kv_cache_group_id: int = -1
122
+ prepare_dflash_inputs.register_warmup(speculator=self)
123
124
@property
125
def attn_vllm_config(self) -> VllmConfig:
@@ -682,6 +694,64 @@ def _prepare_dflash_inputs_kernel(
694
tl.store(out_query_slot_mapping_ptr + block, PAD_SLOT_ID, mask=mask)
695
696
697
+def _prepare_dflash_warmup_inputs(*, speculator: DFlashSpeculator) -> dict[str, Any]:
698
+ int32 = TritonWarmupTensor(torch.int32)
699
+ int64 = TritonWarmupTensor(torch.int64)
700
+ float32 = TritonWarmupTensor(torch.float32)
701
+ input_buffers = SimpleNamespace(
702
+ input_ids=int32, positions=int64, query_start_loc=int32, seq_lens=int32
703
+ )
704
+ gid: Any = WarmupChoices(*speculator.draft_kv_cache_group_ids)
705
+ max_target_query_len: Any = WarmupIntRange(1, 257, advance=lambda value: value * 2)
706
+ block_table_stride = int(speculator.block_tables.input_block_tables[gid].stride(0))
707
+ block_table = TritonWarmupTensor(
708
+ torch.int32,
709
+ shape=(1, block_table_stride),
710
+ strides=(block_table_stride, 1),
711
+ )
712
+ input_batch = SimpleNamespace(
713
+ num_reqs=1,
714
+ num_scheduled_tokens=np.array([max_target_query_len]),
715
+ positions=int64,
716
+ query_start_loc=int32,
717
+ idx_mapping=int64,
718
+ )
719
+ return dict(
720
+ input_buffers=input_buffers,
721
+ query_slot_mapping=int64,
722
+ context_positions=int64,
723
+ context_slot_mapping=int64,
724
+ sample_indices=int64,
725
+ sample_pos=int64,
726
+ sample_idx_mapping=int32,
727
+ temperature=float32,
728
+ seeds=int64,
729
+ input_batch=input_batch,
730
+ num_sampled=int32,
731
+ num_rejected=int32,
732
+ last_sampled=int64,
733
+ next_prefill_tokens=int32,
734
+ input_temperature=float32,
735
+ input_seeds=int64,
736
+ block_table=block_table,
737
+ block_size=speculator.block_tables.kernel_block_sizes[gid],
738
+ cp_rank=speculator.block_tables.cp_rank,
739
+ cp_size=speculator.block_tables.cp_size,
740
+ cp_interleave=speculator.block_tables.cp_interleave,
741
+ parallel_drafting_token_id=speculator.parallel_drafting_token_id,
742
+ num_query_per_req=speculator.num_query_per_req,
743
+ num_speculative_steps=speculator.num_speculative_steps,
744
+ max_num_reqs=speculator.max_num_reqs,
745
+ max_num_tokens=speculator.max_num_tokens,
746
+ max_model_len=speculator.max_model_len,
747
+ sample_from_anchor=speculator.sample_from_anchor,
748
+ )
749
+
750
+
751
+@triton_kernel_dispatcher_with_warmup(
752
+ kernel=_prepare_dflash_inputs_kernel,
753
+ warmup_inputs=_prepare_dflash_warmup_inputs,
754
+)
755
def prepare_dflash_inputs(
756
input_buffers: InputBuffers,
757
query_slot_mapping: torch.Tensor,
@@ -718,50 +788,33 @@ def prepare_dflash_inputs(
788
max_num_tokens: int,
789
max_model_len: int,
790
sample_from_anchor: bool = False,
721
-) -> None:
791
+) -> DispatchSpec:
792
num_reqs = input_batch.num_reqs
793
assert num_reqs > 0
794
# Cover the longest possible per-request span (ctx + query). Use the max
795
# per-request query length, not the total token count across the batch.
796
max_target_query_len = int(input_batch.num_scheduled_tokens.max())
797
max_tokens_per_req = max_target_query_len + num_query_per_req
728
- BLOCK_SIZE = min(256, triton.next_power_of_2(max(1, max_tokens_per_req)))
729
- num_blocks = triton.cdiv(max_tokens_per_req, BLOCK_SIZE)
730
- _prepare_dflash_inputs_kernel[(num_reqs, num_blocks)](
731
- input_buffers.input_ids,
732
- input_buffers.positions,
733
- input_buffers.query_start_loc,
734
- input_buffers.seq_lens,
735
- query_slot_mapping,
736
- context_positions,
737
- context_slot_mapping,
738
- sample_indices,
739
- sample_pos,
740
- sample_idx_mapping,
741
- temperature,
742
- seeds,
743
- input_batch.positions,
744
- input_batch.query_start_loc,
745
- input_batch.idx_mapping,
746
- last_sampled,
747
- next_prefill_tokens,
748
- num_sampled,
749
- num_rejected,
750
- input_temperature,
751
- input_seeds,
752
- block_table,
753
- block_table.stride(0),
754
- parallel_drafting_token_id,
755
- block_size,
756
- num_query_per_req,
757
- num_speculative_steps,
758
- max_num_reqs,
759
- max_num_tokens,
760
- max_model_len,
761
- cp_rank,
762
- SAMPLE_FROM_ANCHOR=sample_from_anchor,
798
+ block = min(256, triton.next_power_of_2(max(1, max_tokens_per_req)))
799
+ num_blocks = triton.cdiv(max_tokens_per_req, block)
800
+ return (num_reqs, num_blocks), dict(
801
+ out_input_ids_ptr=input_buffers.input_ids,
802
+ out_query_positions_ptr=input_buffers.positions,
803
+ out_query_start_loc_ptr=input_buffers.query_start_loc,
804
+ out_seq_lens_ptr=input_buffers.seq_lens,
805
+ out_query_slot_mapping_ptr=query_slot_mapping,
806
+ out_context_positions_ptr=context_positions,
807
+ out_context_slot_mapping_ptr=context_slot_mapping,
808
+ out_sample_indices_ptr=sample_indices,
809
+ out_sample_pos_ptr=sample_pos,
810
+ out_sample_idx_mapping_ptr=sample_idx_mapping,
811
+ out_temperature_ptr=temperature,
812
+ out_seeds_ptr=seeds,
813
+ target_positions_ptr=input_batch.positions,
814
+ target_query_start_loc_ptr=input_batch.query_start_loc,
815
+ idx_mapping_ptr=input_batch.idx_mapping,
816
+ temperature_ptr=input_temperature,
817
+ seeds_ptr=input_seeds,
818
PAD_SLOT_ID=PAD_SLOT_ID,
764
- CP_SIZE=cp_size,
765
- CP_INTERLEAVE=cp_interleave,
766
- BLOCK_SIZE=BLOCK_SIZE,
819
+ BLOCK_SIZE=block,
820
)
vllm/v1/worker/gpu/spec_decode/multi_module_mtp/speculator.py
+76
-12
@@ -9,6 +9,11 @@ from vllm.config import VllmConfig
9
from vllm.config.compilation import CUDAGraphMode
10
from vllm.forward_context import BatchDescriptor, set_forward_context
11
from vllm.logger import init_logger
12
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
13
+ DispatchSpec,
14
+ TritonWarmupTensor,
15
+ triton_kernel_dispatcher_with_warmup,
16
+)
17
from vllm.triton_utils import tl, triton
18
from vllm.v1.attention.backends.utils import PAD_SLOT_ID
19
from vllm.v1.worker.gpu.attn_utils import build_slot_mappings_by_layer
@@ -69,6 +74,9 @@ class MultiModuleMTPSpeculator(DraftModelSpeculator):
74
75
self.cudagraph_manager: SpeculatorCudaGraphManager | None = None
76
77
+ _shift_input_ids.register_warmup()
78
+ _shift_input_embeds.register_warmup(speculator=self)
79
+
80
def load_draft_model(
81
self,
82
target_model: nn.Module,
@@ -1008,6 +1016,32 @@ def _shift_input_ids_kernel(
1016
tl.store(input_ids_ptr + last_token_index, draft_token)
1017
1018
1019
+def _shift_input_ids_warmup_inputs() -> dict[str, Any]:
1020
+ return dict(
1021
+ input_ids=TritonWarmupTensor(torch.int32),
1022
+ idx_mapping=TritonWarmupTensor(torch.int32),
1023
+ query_start_loc=TritonWarmupTensor(torch.int32),
1024
+ last_token_indices=TritonWarmupTensor(torch.int64),
1025
+ draft_tokens=TritonWarmupTensor(torch.int64),
1026
+ num_reqs=1,
1027
+ )
1028
+
1029
+
1030
+@triton_kernel_dispatcher_with_warmup(
1031
+ kernel=_shift_input_ids_kernel,
1032
+ warmup_inputs=_shift_input_ids_warmup_inputs,
1033
+)
1034
+def _shift_input_ids(
1035
+ input_ids: torch.Tensor,
1036
+ idx_mapping: torch.Tensor,
1037
+ query_start_loc: torch.Tensor,
1038
+ last_token_indices: torch.Tensor,
1039
+ draft_tokens: torch.Tensor,
1040
+ num_reqs: int,
1041
+) -> DispatchSpec:
1042
+ return (num_reqs,), dict(BLOCK_SIZE=1024)
1043
+
1044
+
1045
@triton.jit
1046
def _shift_input_embeds_kernel(
1047
input_embeds_ptr,
@@ -1065,6 +1099,44 @@ def _shift_input_embeds_kernel(
1099
)
1100
1101
1102
+def _shift_input_embeds_warmup_inputs(
1103
+ *, speculator: MultiModuleMTPSpeculator
1104
+) -> dict[str, Any]:
1105
+ hidden = TritonWarmupTensor(
1106
+ speculator.dtype,
1107
+ shape=(1, speculator.hidden_size),
1108
+ strides=(speculator.hidden_size, 1),
1109
+ )
1110
+ return dict(
1111
+ input_embeds=hidden,
1112
+ draft_embeds=hidden,
1113
+ idx_mapping=TritonWarmupTensor(torch.int32),
1114
+ query_start_loc=TritonWarmupTensor(torch.int32),
1115
+ last_token_indices=TritonWarmupTensor(torch.int64),
1116
+ num_reqs=1,
1117
+ )
1118
+
1119
+
1120
+@triton_kernel_dispatcher_with_warmup(
1121
+ kernel=_shift_input_embeds_kernel,
1122
+ warmup_inputs=_shift_input_embeds_warmup_inputs,
1123
+)
1124
+def _shift_input_embeds(
1125
+ input_embeds: torch.Tensor,
1126
+ draft_embeds: torch.Tensor,
1127
+ idx_mapping: torch.Tensor,
1128
+ query_start_loc: torch.Tensor,
1129
+ last_token_indices: torch.Tensor,
1130
+ num_reqs: int,
1131
+) -> DispatchSpec:
1132
+ hidden_size = input_embeds.shape[-1]
1133
+ return (num_reqs, triton.cdiv(hidden_size, 256)), dict(
1134
+ hidden_size=hidden_size,
1135
+ BLOCK_SIZE_Q=16,
1136
+ BLOCK_SIZE_H=256,
1137
+ )
1138
+
1139
+
1140
def update_draft_inputs(
1141
draft_tokens: torch.Tensor,
1142
draft_embeds: torch.Tensor | None,
@@ -1074,29 +1146,21 @@ def update_draft_inputs(
1146
idx_mapping: torch.Tensor,
1147
num_reqs: int,
1148
) -> None:
1077
- _shift_input_ids_kernel[(num_reqs,)](
1149
+ _shift_input_ids(
1150
input_buffers.input_ids,
1151
idx_mapping,
1152
input_buffers.query_start_loc,
1153
last_token_indices,
1154
draft_tokens,
1083
- BLOCK_SIZE=1024,
1155
+ num_reqs=num_reqs,
1156
)
1157
if input_embeds is not None:
1158
assert draft_embeds is not None
1087
- hidden_size = input_embeds.shape[-1]
1088
- hidden_block_size = 256
1089
- _shift_input_embeds_kernel[
1090
- (num_reqs, triton.cdiv(hidden_size, hidden_block_size))
1091
- ](
1159
+ _shift_input_embeds(
1160
input_embeds,
1093
- input_embeds.stride(0),
1161
draft_embeds,
1095
- draft_embeds.stride(0),
1162
idx_mapping,
1163
input_buffers.query_start_loc,
1164
last_token_indices,
1099
- hidden_size,
1100
- BLOCK_SIZE_Q=16,
1101
- BLOCK_SIZE_H=hidden_block_size,
1165
+ num_reqs=num_reqs,
1166
)
vllm/v1/worker/gpu_model_runner.py
+33
-34
@@ -498,6 +498,7 @@ class ExecuteModelState(NamedTuple):
498
class GPUModelRunner(
499
LoRAModelRunnerMixin, KVConnectorModelRunnerMixin, ECConnectorModelRunnerMixin
500
):
501
+ @JitWarmupRegistry.capture
502
def __init__(
503
self,
504
vllm_config: VllmConfig,
@@ -514,7 +515,6 @@ class GPUModelRunner(
515
self.scheduler_config = vllm_config.scheduler_config
516
self.speculative_config = vllm_config.speculative_config
517
self.observability_config = vllm_config.observability_config
517
- self.jit_warmup_registry = JitWarmupRegistry(vllm_config)
518
519
model_config = self.model_config
520
cache_config = self.cache_config
@@ -750,36 +750,35 @@ class GPUModelRunner(
750
self.parallel_config.cp_kv_cache_interleave_size
751
)
752
# Capture warmup providers registered by the initial placeholder InputBatch
753
- with self.jit_warmup_registry.activate():
754
- self.input_batch = InputBatch(
755
- max_num_reqs=self.max_num_reqs,
756
- # We need to use the encoder length for encoder-decoder
757
- # because of KV cache for cross-attention.
758
- max_model_len=max(self.max_model_len, self.max_encoder_len),
759
- max_num_batched_tokens=self.max_num_tokens,
760
- device=self.device,
761
- vocab_size=self.model_config.get_vocab_size(),
762
- block_sizes=[placeholder_block_size],
763
- kernel_block_sizes=[placeholder_block_size],
764
- max_num_blocks_per_req=[placeholder_max_num_blocks],
765
- num_spec_tokens=self.num_spec_tokens,
766
- logitsprocs=build_logitsprocs(
767
- self.vllm_config,
768
- self.device,
769
- PIN_MEMORY,
770
- self.is_pooling_model,
771
- custom_logitsprocs,
772
- ),
773
- # We currently don't know whether a particular custom logits processor
774
- # uses output token ids so we set this conservatively. Thinking-budget
775
- # tracking is requested dynamically when a budgeted request is in the
776
- # batch.
777
- logitsprocs_need_output_token_ids=bool(custom_logitsprocs),
778
- is_pooling_model=self.is_pooling_model,
779
- cp_kv_cache_interleave_size=self.parallel_config.cp_kv_cache_interleave_size,
780
- reasoning_config=self.vllm_config.reasoning_config,
781
- use_replayssm=self.cache_config.use_replayssm,
782
- )
753
+ self.input_batch = InputBatch(
754
+ max_num_reqs=self.max_num_reqs,
755
+ # We need to use the encoder length for encoder-decoder
756
+ # because of KV cache for cross-attention.
757
+ max_model_len=max(self.max_model_len, self.max_encoder_len),
758
+ max_num_batched_tokens=self.max_num_tokens,
759
+ device=self.device,
760
+ vocab_size=self.model_config.get_vocab_size(),
761
+ block_sizes=[placeholder_block_size],
762
+ kernel_block_sizes=[placeholder_block_size],
763
+ max_num_blocks_per_req=[placeholder_max_num_blocks],
764
+ num_spec_tokens=self.num_spec_tokens,
765
+ logitsprocs=build_logitsprocs(
766
+ self.vllm_config,
767
+ self.device,
768
+ PIN_MEMORY,
769
+ self.is_pooling_model,
770
+ custom_logitsprocs,
771
+ ),
772
+ # We currently don't know whether a particular custom logits processor
773
+ # uses output token ids so we set this conservatively. Thinking-budget
774
+ # tracking is requested dynamically when a budgeted request is in the
775
+ # batch.
776
+ logitsprocs_need_output_token_ids=bool(custom_logitsprocs),
777
+ is_pooling_model=self.is_pooling_model,
778
+ cp_kv_cache_interleave_size=self.parallel_config.cp_kv_cache_interleave_size,
779
+ reasoning_config=self.vllm_config.reasoning_config,
780
+ use_replayssm=self.cache_config.use_replayssm,
781
+ )
782
783
# Separate cuda stream for overlapping transfer of sampled token ids from
784
# GPU to CPU when async scheduling is enabled.
@@ -5332,7 +5331,7 @@ class GPUModelRunner(
5331
self.load_config.load_format = "dummy"
5332
model_loader = get_model_loader(self.load_config)
5333
# Capture warmup providers selected while constructing the model.
5335
- with self.jit_warmup_registry.activate():
5334
+ with self.jit_warmup_registry.activate(): # type: ignore[attr-defined]
5335
self.model = model_loader.load_model(
5336
vllm_config=self.vllm_config, model_config=self.model_config
5337
)
@@ -7304,7 +7303,7 @@ class GPUModelRunner(
7303
self.parallel_config.cp_kv_cache_interleave_size
7304
)
7305
# Capture warmup providers registered after final KV-cache geometry is known
7307
- with self.jit_warmup_registry.activate():
7306
+ with self.jit_warmup_registry.activate(): # type: ignore[attr-defined]
7307
self.input_batch = InputBatch(
7308
max_num_reqs=self.max_num_reqs,
7309
max_model_len=max_model_len,
@@ -7454,7 +7453,7 @@ class GPUModelRunner(
7453
# Reinitialize need to after initialize_attn_backend
7454
self.may_reinitialize_input_batch(kv_cache_config, kernel_block_sizes)
7455
# Capture warmup providers that depend on allocated KV-cache strides.
7457
- with self.jit_warmup_registry.activate():
7456
+ with self.jit_warmup_registry.activate(): # type: ignore[attr-defined]
7457
kv_caches = self.initialize_kv_cache_tensors(
7458
kv_cache_config,
7459
kernel_block_sizes,