[9/N][warmup][DSv4] Migrate MHC TileLang kernels (#50178)
Signed-off-by: LopezCastroRoberto <rocastro@redhat.com> Signed-off-by: Roberto L. Castro <38211239+LopezCastroRoberto@users.noreply.github.com> Co-authored-by: Codex <codex@openai.com>
Roberto L. Castro committed
Sep 13, 2026 at 02:25 UTC
e52be1a62d3879b1202f4f355d3c3472b560c6f2
9 files changed
+1454
-489
tests/kernels/test_mhc_jit_warmup.py
new
+296
@@ -0,0 +1,296 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Validate JIT dispatch against pre-contract behavior."""
4
+
5
+from typing import Any
6
+
7
+import pytest
8
+
9
+from vllm.platforms import current_platform
10
+
11
+if not current_platform.is_cuda_alike():
12
+ pytest.skip("NVIDIA dispatch tests require CUDA", allow_module_level=True)
13
+
14
+from vllm.model_executor.kernels.mhc.tilelang_kernels import (
15
+ HcHeadFusedTileLangKernel,
16
+ HcPrenormGemmTileLangKernel,
17
+ MhcFusedTileLangKernel,
18
+ MhcPostTileLangKernel,
19
+ MhcPreBigFuseTileLangKernel,
20
+)
21
+from vllm.model_executor.warmup import jit_warmup_tilelang_helper
22
+
23
+
24
+@pytest.mark.parametrize(
25
+ ("kwargs", "expected"),
26
+ [
27
+ (
28
+ dict(
29
+ num_tokens=64,
30
+ hc_hidden_size=4096,
31
+ hidden_size=2048,
32
+ hc_mult=2,
33
+ n_out=128,
34
+ ),
35
+ (2048, 2, 128, 1024, 4, 1, False, 1),
36
+ ),
37
+ (
38
+ dict(
39
+ num_tokens=1024,
40
+ hc_hidden_size=4096,
41
+ hidden_size=2048,
42
+ hc_mult=2,
43
+ n_out=128,
44
+ ),
45
+ (2048, 2, 128, 512, 12, 1, True, 2),
46
+ ),
47
+ (
48
+ dict(
49
+ num_tokens=64,
50
+ hc_hidden_size=4096,
51
+ hidden_size=2048,
52
+ hc_mult=2,
53
+ n_out=128,
54
+ n_thr=256,
55
+ tile_n=8,
56
+ n_splits=4,
57
+ ),
58
+ (2048, 2, 128, 256, 8, 4, False, 1),
59
+ ),
60
+ ],
61
+)
62
+def test_hc_prenorm_gemm_dispatch_matches_legacy_runtime_config(
63
+ kwargs: dict[str, Any],
64
+ expected: tuple[int, int, int, int, int, int, bool, int],
65
+) -> None:
66
+ kernel = HcPrenormGemmTileLangKernel()
67
+
68
+ assert kernel.dispatch(**kwargs) == kernel.CompileKey(*expected)
69
+
70
+
71
+@pytest.mark.parametrize(
72
+ ("is_broadcast", "use_norm_weight", "expected_use_norm", "expected_eps"),
73
+ [
74
+ (False, False, False, 0.0),
75
+ (False, True, True, 1.0e-5),
76
+ (True, False, True, 2.0e-5),
77
+ ],
78
+)
79
+def test_mhc_pre_big_fuse_dispatch_matches_legacy_runtime_config(
80
+ is_broadcast: bool,
81
+ use_norm_weight: bool,
82
+ expected_use_norm: bool,
83
+ expected_eps: float,
84
+) -> None:
85
+ kernel = MhcPreBigFuseTileLangKernel()
86
+
87
+ assert kernel.dispatch(
88
+ hidden_size=4096,
89
+ hc_mult=4,
90
+ n_splits=2,
91
+ is_broadcast=is_broadcast,
92
+ use_norm_weight=use_norm_weight,
93
+ rms_eps=1.0e-6,
94
+ hc_pre_eps=2.0e-6,
95
+ hc_sinkhorn_eps=3.0e-6,
96
+ hc_post_mult_value=0.5,
97
+ sinkhorn_repeat=3,
98
+ norm_eps=1.0e-5,
99
+ broadcast_norm_eps=2.0e-5,
100
+ ) == kernel.CompileKey(
101
+ hidden_size=4096,
102
+ hc_mult=4,
103
+ n_splits=2,
104
+ use_norm_weight=expected_use_norm,
105
+ is_broadcast=is_broadcast,
106
+ rms_eps=1.0e-6,
107
+ hc_pre_eps=2.0e-6,
108
+ hc_sinkhorn_eps=3.0e-6,
109
+ hc_post_mult_value=0.5,
110
+ sinkhorn_repeat=3,
111
+ norm_eps=expected_eps,
112
+ )
113
+
114
+
115
+@pytest.mark.parametrize(
116
+ ("num_tokens", "hidden_size", "expected_n_splits", "expected_tile_n"),
117
+ [(4, 4096, 8, 2), (4, 8192, 4, 2), (8, 4096, 4, 3)],
118
+)
119
+def test_mhc_fused_dispatch_matches_legacy_runtime_config(
120
+ num_tokens: int,
121
+ hidden_size: int,
122
+ expected_n_splits: int,
123
+ expected_tile_n: int,
124
+) -> None:
125
+ kernel = MhcFusedTileLangKernel()
126
+
127
+ assert kernel.dispatch(
128
+ num_tokens=num_tokens,
129
+ hidden_size=hidden_size,
130
+ hc_mult=4,
131
+ ) == kernel.CompileKey(
132
+ hidden_size=hidden_size,
133
+ hc_mult=4,
134
+ n_splits=expected_n_splits,
135
+ tile_n=expected_tile_n,
136
+ )
137
+
138
+
139
+@pytest.mark.parametrize(
140
+ ("kernel", "compile_key"),
141
+ [
142
+ (
143
+ HcPrenormGemmTileLangKernel(),
144
+ HcPrenormGemmTileLangKernel.CompileKey(
145
+ hidden_size=2048,
146
+ hc_mult=2,
147
+ n_out=128,
148
+ n_thr=1024,
149
+ tile_n=4,
150
+ n_splits=1,
151
+ use_block_m=False,
152
+ block_m=1,
153
+ ),
154
+ ),
155
+ (
156
+ HcPrenormGemmTileLangKernel(),
157
+ HcPrenormGemmTileLangKernel.CompileKey(
158
+ hidden_size=2048,
159
+ hc_mult=2,
160
+ n_out=128,
161
+ n_thr=512,
162
+ tile_n=12,
163
+ n_splits=1,
164
+ use_block_m=False,
165
+ block_m=1,
166
+ ),
167
+ ),
168
+ (
169
+ HcPrenormGemmTileLangKernel(),
170
+ HcPrenormGemmTileLangKernel.CompileKey(
171
+ hidden_size=2048,
172
+ hc_mult=2,
173
+ n_out=128,
174
+ n_thr=512,
175
+ tile_n=12,
176
+ n_splits=1,
177
+ use_block_m=True,
178
+ block_m=2,
179
+ ),
180
+ ),
181
+ (
182
+ MhcPreBigFuseTileLangKernel(),
183
+ MhcPreBigFuseTileLangKernel.CompileKey(
184
+ hidden_size=4096,
185
+ hc_mult=4,
186
+ n_splits=2,
187
+ use_norm_weight=True,
188
+ is_broadcast=False,
189
+ rms_eps=1.0e-6,
190
+ hc_pre_eps=2.0e-6,
191
+ hc_sinkhorn_eps=3.0e-6,
192
+ hc_post_mult_value=0.5,
193
+ sinkhorn_repeat=3,
194
+ norm_eps=1.0e-5,
195
+ ),
196
+ ),
197
+ (
198
+ MhcPreBigFuseTileLangKernel(),
199
+ MhcPreBigFuseTileLangKernel.CompileKey(
200
+ hidden_size=4096,
201
+ hc_mult=4,
202
+ n_splits=1,
203
+ use_norm_weight=False,
204
+ is_broadcast=False,
205
+ rms_eps=1.0e-6,
206
+ hc_pre_eps=2.0e-6,
207
+ hc_sinkhorn_eps=3.0e-6,
208
+ hc_post_mult_value=0.5,
209
+ sinkhorn_repeat=3,
210
+ norm_eps=0.0,
211
+ ),
212
+ ),
213
+ (
214
+ MhcPreBigFuseTileLangKernel(),
215
+ MhcPreBigFuseTileLangKernel.CompileKey(
216
+ hidden_size=4096,
217
+ hc_mult=4,
218
+ n_splits=2,
219
+ use_norm_weight=True,
220
+ is_broadcast=True,
221
+ rms_eps=1.0e-6,
222
+ hc_pre_eps=2.0e-6,
223
+ hc_sinkhorn_eps=3.0e-6,
224
+ hc_post_mult_value=0.5,
225
+ sinkhorn_repeat=3,
226
+ norm_eps=1.0e-5,
227
+ ),
228
+ ),
229
+ (
230
+ MhcPostTileLangKernel(),
231
+ MhcPostTileLangKernel.CompileKey(hidden_size=4096, hc_mult=4),
232
+ ),
233
+ (
234
+ MhcFusedTileLangKernel(),
235
+ MhcFusedTileLangKernel.CompileKey(
236
+ hidden_size=4096,
237
+ hc_mult=4,
238
+ n_splits=4,
239
+ tile_n=3,
240
+ ),
241
+ ),
242
+ (
243
+ MhcFusedTileLangKernel(),
244
+ MhcFusedTileLangKernel.CompileKey(
245
+ hidden_size=4096,
246
+ hc_mult=4,
247
+ n_splits=8,
248
+ tile_n=2,
249
+ ),
250
+ ),
251
+ (
252
+ MhcFusedTileLangKernel(),
253
+ MhcFusedTileLangKernel.CompileKey(
254
+ hidden_size=8192,
255
+ hc_mult=4,
256
+ n_splits=4,
257
+ tile_n=2,
258
+ ),
259
+ ),
260
+ (
261
+ HcHeadFusedTileLangKernel(),
262
+ HcHeadFusedTileLangKernel.CompileKey(
263
+ hidden_size=4096,
264
+ hc_mult=4,
265
+ rms_eps=1.0e-6,
266
+ hc_eps=2.0e-6,
267
+ ),
268
+ ),
269
+ ],
270
+)
271
+def test_tilelang_warmup_inputs_reproduce_compile_key(
272
+ monkeypatch: pytest.MonkeyPatch,
273
+ kernel: Any,
274
+ compile_key: Any,
275
+) -> None:
276
+ compiled: list[Any] = []
277
+ single_kernel = isinstance(
278
+ kernel,
279
+ (MhcPostTileLangKernel, MhcFusedTileLangKernel, HcHeadFusedTileLangKernel),
280
+ )
281
+ expected_kernel = kernel.kernel() if single_kernel else kernel.kernel(compile_key)
282
+ if single_kernel:
283
+ monkeypatch.setattr(
284
+ kernel,
285
+ "dispatch",
286
+ lambda **kwargs: pytest.fail("single-kernel launch must not dispatch"),
287
+ )
288
+ monkeypatch.setattr(
289
+ jit_warmup_tilelang_helper,
290
+ "compile_tilelang",
291
+ lambda jit_impl, *args, **kwargs: compiled.append(jit_impl),
292
+ )
293
+
294
+ kernel.compile(compile_key)
295
+
296
+ assert compiled == [expected_kernel]
tests/kernels/test_mhc_kernels.py
+11
-2
@@ -10,10 +10,12 @@ import torch.nn.functional as F
10
import vllm.model_executor.kernels.mhc # noqa: F401
11
import vllm.model_executor.layers.mhc as mhc_layers
12
from vllm.model_executor.kernels.mhc.tilelang import (
13
- _tilelang_hc_prenorm_gemm,
13
_torch_hc_prenorm_gemm,
14
mhc_pre_delayed_tilelang,
15
)
16
+from vllm.model_executor.kernels.mhc.tilelang_kernels import (
17
+ _HC_PRENORM_GEMM_TILELANG_KERNEL,
18
+)
19
from vllm.model_executor.kernels.mhc.torch import mhc_pre_delayed_torch
20
from vllm.model_executor.kernels.mhc.triton import hc_collapse_triton
21
from vllm.model_executor.layers.mhc import (
@@ -485,7 +487,14 @@ def test_hc_prenorm_gemm_tilelang(num_tokens, hidden_size):
487
sqrsum = torch.empty_like(sqrsum_ref)
488
489
_torch_hc_prenorm_gemm(x, fn, out_ref, sqrsum_ref)
488
- _tilelang_hc_prenorm_gemm(x, fn, out, sqrsum, hidden_size, hc_mult)
490
+ _HC_PRENORM_GEMM_TILELANG_KERNEL(
491
+ x,
492
+ fn,
493
+ out,
494
+ sqrsum,
495
+ hidden_size,
496
+ hc_mult,
497
+ )
498
499
torch.testing.assert_close(out, out_ref, atol=1e-5, rtol=1e-4)
500
torch.testing.assert_close(sqrsum, sqrsum_ref, atol=1e-2, rtol=1e-6)
vllm/model_executor/kernels/mhc/tilelang.py
+117
-264
@@ -2,6 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import torch
4
5
+from vllm.utils.math_utils import cdiv
6
from vllm.utils.torch_utils import direct_register_custom_op
7
8
@@ -18,73 +19,56 @@ def _torch_hc_prenorm_gemm(
19
sqrsum[0].copy_(x_float.square().sum(dim=-1))
20
21
21
-def _tilelang_hc_prenorm_gemm(
22
+def _hc_prenorm_gemm_outputs(
23
x: torch.Tensor,
24
fn: torch.Tensor,
24
- out: torch.Tensor,
25
- sqrsum: torch.Tensor,
25
+ *,
26
hidden_size: int,
27
hc_mult: int,
28
- tile_n: int = 12,
29
- n_thr: int = 512,
30
- n_splits: int = 1,
31
-) -> None:
28
+ use_tilelang_fallback: bool = True,
29
+) -> tuple[torch.Tensor, torch.Tensor]:
30
from vllm.model_executor.kernels.mhc.tilelang_kernels import (
33
- hc_prenorm_gemm_block_m_tilelang,
34
- hc_prenorm_gemm_tilelang,
35
- )
36
-
37
- assert out.shape[0] == n_splits
38
- assert sqrsum.shape[0] == n_splits
39
- assert x.shape[1] == hc_mult * hidden_size
40
- assert x.shape[1] % n_splits == 0
41
- assert (x.shape[1] // n_splits) % n_thr == 0
42
- use_default_config = tile_n == 12 and n_thr == 512
43
- if n_splits == 1 and use_default_config and x.shape[0] >= 1024:
44
- hc_prenorm_gemm_block_m_tilelang(
45
- x,
46
- fn,
47
- out,
48
- sqrsum,
49
- hidden_size,
50
- hc_mult,
51
- fn.shape[0],
52
- n_thr,
53
- tile_n,
54
- 2,
31
+ compute_num_split,
32
+ )
33
+ from vllm.utils.deep_gemm import (
34
+ is_deep_gemm_supported,
35
+ tf32_hc_prenorm_gemm,
36
+ )
37
+
38
+ use_deep_gemm = is_deep_gemm_supported() or not use_tilelang_fallback
39
+ num_tokens = x.shape[0]
40
+ n_splits = (
41
+ compute_num_split(64, x.shape[1], cdiv(num_tokens, 64)) if use_deep_gemm else 1
42
+ )
43
+ out = torch.empty(
44
+ n_splits,
45
+ num_tokens,
46
+ fn.shape[0],
47
+ dtype=torch.float32,
48
+ device=x.device,
49
+ )
50
+ sqrsum = torch.empty(
51
+ n_splits,
52
+ num_tokens,
53
+ dtype=torch.float32,
54
+ device=x.device,
55
+ )
56
+ if use_deep_gemm:
57
+ tf32_hc_prenorm_gemm(x, fn, out, sqrsum, n_splits)
58
+ else:
59
+ from vllm.model_executor.kernels.mhc.tilelang_kernels import (
60
+ _HC_PRENORM_GEMM_TILELANG_KERNEL,
61
)
56
- return
57
- if (
58
- n_splits == 1
59
- and use_default_config
60
- and x.shape[0] < 128
61
- and x.shape[1] % 1024 == 0
62
- ):
63
- hc_prenorm_gemm_tilelang(
62
+
63
+ _HC_PRENORM_GEMM_TILELANG_KERNEL(
64
x,
65
fn,
66
out,
67
sqrsum,
68
hidden_size,
69
hc_mult,
70
- fn.shape[0],
71
- 1024,
72
- 4,
73
- n_splits,
70
)
75
- return
76
- hc_prenorm_gemm_tilelang(
77
- x,
78
- fn,
79
- out,
80
- sqrsum,
81
- hidden_size,
82
- hc_mult,
83
- fn.shape[0],
84
- n_thr,
85
- tile_n,
86
- n_splits,
87
- )
71
+ return out, sqrsum
72
73
74
def mhc_pre_delayed_tilelang(
@@ -127,6 +111,7 @@ def mhc_pre_delayed_tilelang(
111
(tokens, hc_mult, hc_mult), (tokens, hidden_size), and (tokens, hc_mult).
112
"""
113
from vllm.model_executor.kernels.mhc.tilelang_kernels import (
114
+ _HC_PRENORM_GEMM_TILELANG_KERNEL,
115
mhc_pre_big_fuse_tilelang,
116
)
117
from vllm.model_executor.kernels.mhc.warmup import (
@@ -186,7 +171,14 @@ def mhc_pre_delayed_tilelang(
171
if use_deep_gemm:
172
tf32_hc_prenorm_gemm(x, fn, mixes, sqrsum, n_splits)
173
else:
189
- _tilelang_hc_prenorm_gemm(x, fn, mixes, sqrsum, input_size, 1)
174
+ _HC_PRENORM_GEMM_TILELANG_KERNEL(
175
+ x,
176
+ fn,
177
+ mixes,
178
+ sqrsum,
179
+ input_size,
180
+ 1,
181
+ )
182
if norm_weight is not None:
183
assert norm_weight.shape == (hidden_size,)
184
assert norm_weight.dtype == torch.bfloat16 and norm_weight.is_contiguous()
@@ -298,7 +290,8 @@ def mhc_pre_tilelang(
290
hc_sinkhorn_eps: sinkhorn epsilon
291
hc_post_mult_value: post-mix multiplier value
292
sinkhorn_repeat: number of sinkhorn iterations
301
- n_splits: split-k factor;
293
+ n_splits: retained for the shared MHC operator API; the active GEMM
294
+ backend selects its split factor internally.
295
norm_weight: optional RMSNorm weight, shape (hidden_size,), dtype
296
torch.bfloat16. When provided, RMSNorm is fused into the
297
layer_input write path of the big_fuse kernel.
@@ -311,12 +304,8 @@ def mhc_pre_tilelang(
304
layer_input: shape (..., hidden_size), dtype torch.bfloat16
305
"""
306
from vllm.model_executor.kernels.mhc.tilelang_kernels import (
314
- compute_num_split,
315
- mhc_pre_big_fuse_tilelang,
316
- mhc_pre_big_fuse_with_norm_tilelang,
307
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL,
308
)
318
- from vllm.utils.deep_gemm import tf32_hc_prenorm_gemm
319
- from vllm.utils.math_utils import cdiv
309
310
assert residual.dtype == torch.bfloat16
311
assert fn.dtype == torch.float32
@@ -346,17 +335,6 @@ def mhc_pre_tilelang(
335
residual_flat = residual.view(-1, hc_mult, hidden_size)
336
num_tokens = residual_flat.shape[0]
337
349
- from vllm.utils.deep_gemm import is_deep_gemm_supported
350
-
351
- use_deep_gemm = is_deep_gemm_supported()
352
- if use_deep_gemm:
353
- # these numbers are from deepgemm kernel impl
354
- block_k = 64
355
- block_m = 64
356
- n_splits = compute_num_split(block_k, hc_hidden_size, cdiv(num_tokens, block_m))
357
- else:
358
- n_splits = 1
359
-
338
post_mix = torch.empty(
339
num_tokens, hc_mult, dtype=torch.float32, device=residual.device
340
)
@@ -367,78 +345,30 @@ def mhc_pre_tilelang(
345
num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
346
)
347
370
- gemm_out_mul = torch.empty(
371
- n_splits, num_tokens, hc_mult3, dtype=torch.float32, device=residual.device
348
+ gemm_out_mul, gemm_out_sqrsum = _hc_prenorm_gemm_outputs(
349
+ residual_flat.view(num_tokens, hc_mult * hidden_size),
350
+ fn,
351
+ hidden_size=hidden_size,
352
+ hc_mult=hc_mult,
353
)
373
- gemm_out_sqrsum = torch.empty(
374
- n_splits, num_tokens, dtype=torch.float32, device=residual.device
354
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL(
355
+ gemm_out_mul,
356
+ gemm_out_sqrsum,
357
+ hc_scale,
358
+ hc_base,
359
+ residual_flat,
360
+ post_mix,
361
+ comb_mix,
362
+ layer_input,
363
+ rms_eps,
364
+ hc_pre_eps,
365
+ hc_sinkhorn_eps,
366
+ hc_post_mult_value,
367
+ sinkhorn_repeat,
368
+ norm_weight=norm_weight,
369
+ norm_eps=norm_eps,
370
)
371
377
- residual_2d = residual_flat.view(num_tokens, hc_mult * hidden_size)
378
- if use_deep_gemm:
379
- tf32_hc_prenorm_gemm(
380
- residual_2d,
381
- fn,
382
- gemm_out_mul,
383
- gemm_out_sqrsum,
384
- n_splits,
385
- )
386
- else:
387
- _tilelang_hc_prenorm_gemm(
388
- residual_2d,
389
- fn,
390
- gemm_out_mul,
391
- gemm_out_sqrsum,
392
- hidden_size,
393
- hc_mult,
394
- )
395
-
396
- if norm_weight is None:
397
- mhc_pre_big_fuse_tilelang(
398
- gemm_out_mul,
399
- gemm_out_sqrsum,
400
- hc_scale,
401
- hc_base,
402
- residual_flat,
403
- post_mix,
404
- comb_mix,
405
- layer_input,
406
- # Pre-mix buffers are unused in V4 mode.
407
- post_mix,
408
- post_mix,
409
- hidden_size,
410
- rms_eps,
411
- hc_pre_eps,
412
- hc_sinkhorn_eps,
413
- hc_post_mult_value,
414
- sinkhorn_repeat,
415
- n_splits,
416
- hc_mult,
417
- )
418
- else:
419
- mhc_pre_big_fuse_with_norm_tilelang(
420
- gemm_out_mul,
421
- gemm_out_sqrsum,
422
- hc_scale,
423
- hc_base,
424
- residual_flat,
425
- post_mix,
426
- comb_mix,
427
- layer_input,
428
- norm_weight,
429
- post_mix,
430
- post_mix,
431
- hidden_size,
432
- rms_eps,
433
- hc_pre_eps,
434
- hc_sinkhorn_eps,
435
- hc_post_mult_value,
436
- sinkhorn_repeat,
437
- norm_eps,
438
- n_splits,
439
- hc_mult,
440
- )
441
-
372
return (
373
post_mix.view(*outer_shape, hc_mult, 1),
374
comb_mix.view(*outer_shape, hc_mult, hc_mult),
@@ -505,11 +435,10 @@ def mhc_pre_broadcast_tilelang(
435
fn_broadcast: torch.Tensor | None = None,
436
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
437
"""First-layer mHC pre for a residual broadcast from ``(T, H)``."""
438
+ # n_splits is retained for the shared API; split selection is internal.
439
from vllm.model_executor.kernels.mhc.tilelang_kernels import (
509
- compute_num_split,
510
- mhc_pre_big_fuse_broadcast_with_norm_tilelang,
440
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL,
441
)
512
- from vllm.utils.math_utils import cdiv
442
443
assert norm_weight is not None, "broadcast mHC pre currently requires fused RMSNorm"
444
assert residual.dtype == torch.bfloat16
@@ -537,8 +466,6 @@ def mhc_pre_broadcast_tilelang(
466
residual_flat = residual
467
num_tokens = residual.shape[0]
468
540
- n_splits = compute_num_split(64, hidden_size, cdiv(num_tokens, 64))
541
-
469
residual_out = torch.empty(
470
num_tokens, hc_mult, hidden_size, dtype=torch.bfloat16, device=residual.device
471
)
@@ -551,42 +478,31 @@ def mhc_pre_broadcast_tilelang(
478
layer_input = torch.empty(
479
num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
480
)
554
- gemm_out_mul = torch.empty(
555
- n_splits, num_tokens, hc_mult3, dtype=torch.float32, device=residual.device
556
- )
557
- gemm_out_sqrsum = torch.empty(
558
- n_splits, num_tokens, dtype=torch.float32, device=residual.device
559
- )
481
561
- from vllm.utils.deep_gemm import tf32_hc_prenorm_gemm
562
-
563
- tf32_hc_prenorm_gemm(
482
+ gemm_out_mul, gemm_out_sqrsum = _hc_prenorm_gemm_outputs(
483
residual_flat,
484
fn_broadcast,
566
- gemm_out_mul,
567
- gemm_out_sqrsum,
568
- n_splits,
485
+ hidden_size=hidden_size,
486
+ hc_mult=hc_mult,
487
+ use_tilelang_fallback=False,
488
)
570
- mhc_pre_big_fuse_broadcast_with_norm_tilelang(
489
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL(
490
gemm_out_mul,
491
gemm_out_sqrsum,
492
hc_scale,
493
hc_base,
494
residual_flat,
576
- residual_out,
495
post_mix,
496
comb_mix,
497
layer_input,
580
- norm_weight,
581
- hidden_size,
498
rms_eps,
499
hc_pre_eps,
500
hc_sinkhorn_eps,
501
hc_post_mult_value,
502
sinkhorn_repeat,
587
- norm_eps,
588
- n_splits,
589
- hc_mult,
503
+ residual_out=residual_out,
504
+ norm_weight=norm_weight,
505
+ norm_eps=norm_eps,
506
)
507
return (
508
residual_out,
@@ -603,11 +519,11 @@ def mhc_post_tilelang(
519
comb_res_mix: torch.Tensor,
520
) -> torch.Tensor:
521
from vllm.model_executor.kernels.mhc.tilelang_kernels import (
606
- mhc_post_tilelang as _mhc_post_kernel,
522
+ _MHC_POST_TILELANG_KERNEL,
523
)
524
525
out = torch.empty_like(residual)
610
- _mhc_post_kernel(
526
+ _MHC_POST_TILELANG_KERNEL(
527
comb_res_mix,
528
residual,
529
post_layer_mix.squeeze(-1),
@@ -644,6 +560,9 @@ def mhc_fused_post_pre_tilelang(
560
RMSNorm'd activation (fused into the kernel); otherwise it is the
561
raw pre-norm activation as before.
562
563
+ ``n_splits`` and ``tile_n`` are retained for the shared MHC operator API.
564
+ The TileLang path selects both values internally from the runtime shape.
565
+
566
Returns:
567
residual_cur: post-mapped residual, shape (..., hc_mult, hidden_size)
568
post_mix_cur: shape (..., hc_mult, 1)
@@ -652,13 +571,10 @@ def mhc_fused_post_pre_tilelang(
571
"""
572
573
from vllm.model_executor.kernels.mhc.tilelang_kernels import (
655
- compute_num_split,
656
- mhc_fused_tilelang,
657
- mhc_post_tilelang,
658
- mhc_pre_big_fuse_tilelang,
659
- mhc_pre_big_fuse_with_norm_tilelang,
574
+ _MHC_FUSED_TILELANG_KERNEL,
575
+ _MHC_POST_TILELANG_KERNEL,
576
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL,
577
)
661
- from vllm.utils.math_utils import cdiv
578
579
assert residual.dtype == torch.bfloat16
580
assert x.dtype == torch.bfloat16
@@ -692,48 +608,14 @@ def mhc_fused_post_pre_tilelang(
608
if not norm_weight.is_contiguous():
609
norm_weight = norm_weight.contiguous()
610
695
- assert n_splits in (1, 2, 4, 8)
696
- assert hidden_size % n_splits == 0
697
-
611
residual_flat = residual.view(-1, hc_mult, hidden_size)
612
num_tokens = residual_flat.shape[0]
613
x_flat = x.view(num_tokens, hidden_size)
614
post_layer_mix_flat = post_layer_mix.view(num_tokens, hc_mult)
615
comb_res_mix_flat = comb_res_mix.view(num_tokens, hc_mult, hc_mult)
616
704
- from vllm.utils.deep_gemm import is_deep_gemm_supported
705
-
706
- use_deep_gemm = is_deep_gemm_supported()
617
use_small_fma = num_tokens <= 16
708
- if use_small_fma:
709
- # TODO(gnovack): investigate autotuning these heuristics
710
- tile_n = 2 if num_tokens < 8 else 3
711
- n_splits = 8 if (num_tokens < 8 and hidden_size <= 4096) else 4
712
- else:
713
- if use_deep_gemm:
714
- # these number are from deepgemm kernel impl
715
- block_k = 64
716
- block_m = 64
717
- n_splits = compute_num_split(
718
- block_k, hc_hidden_size, cdiv(num_tokens, block_m)
719
- )
720
- else:
721
- n_splits = 1
722
-
723
- gemm_out_mul = torch.empty(
724
- n_splits,
725
- num_tokens,
726
- hc_mult3,
727
- dtype=torch.float32,
728
- device=residual.device,
729
- )
730
- gemm_out_sqrsum = torch.empty(
731
- n_splits,
732
- num_tokens,
733
- dtype=torch.float32,
734
- device=residual.device,
735
- )
736
- residual_cur = torch.empty_like(residual_flat)
618
+
619
post_mix_cur = torch.empty(
620
num_tokens,
621
hc_mult,
@@ -754,55 +636,17 @@ def mhc_fused_post_pre_tilelang(
636
)
637
638
if use_small_fma:
757
- mhc_fused_tilelang(
639
+ gemm_out_mul, gemm_out_sqrsum, residual_cur = _MHC_FUSED_TILELANG_KERNEL(
640
comb_res_mix_flat,
641
residual_flat,
642
post_layer_mix_flat,
643
x_flat,
644
fn.view(hc_mult3, hc_mult, hidden_size),
763
- gemm_out_mul,
764
- gemm_out_sqrsum,
765
- residual_cur,
645
hc_mult,
646
hidden_size,
647
hc_mult3,
769
- tile_n=tile_n,
770
- n_splits=n_splits,
648
)
772
- else:
773
- mhc_post_tilelang(
774
- comb_res_mix_flat,
775
- residual_flat,
776
- post_layer_mix_flat,
777
- x_flat,
778
- residual_cur,
779
- residual.shape[-2],
780
- residual.shape[-1],
781
- )
782
-
783
- residual_cur_2d = residual_cur.view(num_tokens, hc_mult * hidden_size)
784
- if use_deep_gemm:
785
- from vllm.utils.deep_gemm import tf32_hc_prenorm_gemm
786
-
787
- tf32_hc_prenorm_gemm(
788
- residual_cur_2d,
789
- fn,
790
- gemm_out_mul,
791
- gemm_out_sqrsum,
792
- n_splits,
793
- )
794
- else:
795
- _tilelang_hc_prenorm_gemm(
796
- residual_cur_2d,
797
- fn,
798
- gemm_out_mul,
799
- gemm_out_sqrsum,
800
- hidden_size,
801
- hc_mult,
802
- )
803
-
804
- if norm_weight is None:
805
- mhc_pre_big_fuse_tilelang(
649
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL(
650
gemm_out_mul,
651
gemm_out_sqrsum,
652
hc_scale,
@@ -811,20 +655,32 @@ def mhc_fused_post_pre_tilelang(
655
post_mix_cur,
656
comb_mix_cur,
657
layer_input_cur,
814
- # Pre-mix buffers are unused in V4 mode.
815
- post_mix_cur,
816
- post_mix_cur,
817
- hidden_size,
658
rms_eps,
659
hc_pre_eps,
660
hc_sinkhorn_eps,
661
hc_post_mult_value,
662
sinkhorn_repeat,
823
- n_splits,
824
- hc_mult,
663
+ norm_weight=norm_weight,
664
+ norm_eps=norm_eps,
665
)
666
else:
827
- mhc_pre_big_fuse_with_norm_tilelang(
667
+ residual_cur = torch.empty_like(residual_flat)
668
+ _MHC_POST_TILELANG_KERNEL(
669
+ comb_res_mix_flat,
670
+ residual_flat,
671
+ post_layer_mix_flat,
672
+ x_flat,
673
+ residual_cur,
674
+ residual.shape[-2],
675
+ residual.shape[-1],
676
+ )
677
+ gemm_out_mul, gemm_out_sqrsum = _hc_prenorm_gemm_outputs(
678
+ residual_cur.view(num_tokens, hc_mult * hidden_size),
679
+ fn,
680
+ hidden_size=hidden_size,
681
+ hc_mult=hc_mult,
682
+ )
683
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL(
684
gemm_out_mul,
685
gemm_out_sqrsum,
686
hc_scale,
@@ -833,18 +689,13 @@ def mhc_fused_post_pre_tilelang(
689
post_mix_cur,
690
comb_mix_cur,
691
layer_input_cur,
836
- norm_weight,
837
- post_mix_cur,
838
- post_mix_cur,
839
- hidden_size,
692
rms_eps,
693
hc_pre_eps,
694
hc_sinkhorn_eps,
695
hc_post_mult_value,
696
sinkhorn_repeat,
845
- norm_eps,
846
- n_splits,
847
- hc_mult,
697
+ norm_weight=norm_weight,
698
+ norm_eps=norm_eps,
699
)
700
701
return (
@@ -926,9 +777,11 @@ def hc_head_fused_kernel_tilelang(
777
)
778
if num_tokens == 0:
779
return out
929
- from vllm.model_executor.kernels.mhc.tilelang_kernels import hc_head_fuse_tilelang
780
+ from vllm.model_executor.kernels.mhc.tilelang_kernels import (
781
+ _HC_HEAD_FUSED_TILELANG_KERNEL,
782
+ )
783
931
- hc_head_fuse_tilelang(
784
+ _HC_HEAD_FUSED_TILELANG_KERNEL(
785
hs_flat,
786
fn,
787
hc_scale,
vllm/model_executor/kernels/mhc/tilelang_kernels.py
+755
@@ -4,10 +4,19 @@
4
from __future__ import annotations
5
6
import math
7
+from dataclasses import dataclass
8
from functools import cache
9
+from typing import Any
10
11
import torch
12
13
+from vllm.model_executor.warmup.jit_warmup import WarmupIntRange, zip_inputs
14
+from vllm.model_executor.warmup.jit_warmup_tilelang_helper import (
15
+ TileLangLaunchSpec,
16
+ VllmTileLangJitKernel,
17
+ kernel_launcher,
18
+ make_tilelang_warmup_tensor,
19
+)
20
from vllm.platforms import current_platform
21
from vllm.tilelang_utils import T, tilelang, tilelang_jit
22
from vllm.utils.math_utils import cdiv
@@ -28,6 +37,10 @@ def compute_num_split(block_k: int, k: int | None, grid_size: int) -> int:
37
return split_k
38
39
40
+def _mhc_fused_n_splits(num_tokens: int, hidden_size: int) -> int:
41
+ return 8 if num_tokens < 8 and hidden_size <= 4096 else 4
42
+
43
+
44
@tilelang_jit
45
def mhc_pre_big_fuse_tilelang(
46
gemm_out_mul,
@@ -987,3 +1000,745 @@ def hc_head_fuse_tilelang(
1000
1001
if ENABLE_PDL:
1002
T.pdl_trigger()
1003
+
1004
+
1005
+class HcPrenormGemmTileLangKernel(
1006
+ VllmTileLangJitKernel["HcPrenormGemmTileLangKernel.CompileKey"]
1007
+):
1008
+ @dataclass(frozen=True)
1009
+ class CompileKey:
1010
+ hidden_size: int
1011
+ hc_mult: int
1012
+ n_out: int
1013
+ n_thr: int
1014
+ tile_n: int
1015
+ n_splits: int
1016
+ use_block_m: bool
1017
+ block_m: int
1018
+
1019
+ @staticmethod
1020
+ def kernel(compile_key: CompileKey) -> Any:
1021
+ if compile_key.use_block_m:
1022
+ return hc_prenorm_gemm_block_m_tilelang
1023
+ return hc_prenorm_gemm_tilelang
1024
+
1025
+ def dispatch( # type: ignore[override]
1026
+ self,
1027
+ *,
1028
+ num_tokens: int,
1029
+ hc_hidden_size: int,
1030
+ n_thr: int = 512,
1031
+ tile_n: int = 12,
1032
+ n_splits: int = 1,
1033
+ **compile_key_fields: int,
1034
+ ) -> CompileKey:
1035
+ use_default_config = n_splits == 1 and tile_n == 12 and n_thr == 512
1036
+ use_block_m = use_default_config and num_tokens >= 1024
1037
+ use_small_tile = (
1038
+ use_default_config
1039
+ and not use_block_m
1040
+ and num_tokens < 128
1041
+ and hc_hidden_size % 1024 == 0
1042
+ )
1043
+ effective_n_thr = 1024 if use_small_tile else n_thr
1044
+ effective_tile_n = 4 if use_small_tile else tile_n
1045
+ return self.CompileKey(
1046
+ **compile_key_fields,
1047
+ n_thr=effective_n_thr,
1048
+ tile_n=effective_tile_n,
1049
+ n_splits=n_splits,
1050
+ use_block_m=use_block_m,
1051
+ block_m=2 if use_block_m else 1,
1052
+ )
1053
+
1054
+ def get_warmup_keys(
1055
+ self,
1056
+ vllm_config: Any,
1057
+ *,
1058
+ hidden_size: int,
1059
+ hc_mult: int,
1060
+ n_out: int,
1061
+ ) -> list[CompileKey]:
1062
+ max_tokens = vllm_config.scheduler_config.max_num_batched_tokens
1063
+ if max_tokens <= 0:
1064
+ return []
1065
+ return self._trace_dispatch(self.dispatch)(
1066
+ num_tokens=WarmupIntRange(
1067
+ 1,
1068
+ max_tokens + 1,
1069
+ advance=lambda value: (
1070
+ 128 if value < 128 else 1024 if value < 1024 else max_tokens + 1
1071
+ ),
1072
+ ),
1073
+ hc_hidden_size=hidden_size * hc_mult,
1074
+ hidden_size=hidden_size,
1075
+ hc_mult=hc_mult,
1076
+ n_out=n_out,
1077
+ )
1078
+
1079
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
1080
+ num_tokens = 128
1081
+ if compile_key.use_block_m:
1082
+ num_tokens = 1024
1083
+ elif compile_key.n_thr == 1024:
1084
+ num_tokens = 1
1085
+ hc_hidden_size = compile_key.hidden_size * compile_key.hc_mult
1086
+ return dict(
1087
+ x=make_tilelang_warmup_tensor(torch.bfloat16, num_tokens, hc_hidden_size),
1088
+ fn=make_tilelang_warmup_tensor(
1089
+ torch.float32, compile_key.n_out, hc_hidden_size
1090
+ ),
1091
+ out=make_tilelang_warmup_tensor(
1092
+ torch.float32,
1093
+ compile_key.n_splits,
1094
+ num_tokens,
1095
+ compile_key.n_out,
1096
+ ),
1097
+ sqrsum=make_tilelang_warmup_tensor(
1098
+ torch.float32, compile_key.n_splits, num_tokens
1099
+ ),
1100
+ hidden_size=compile_key.hidden_size,
1101
+ hc_mult=compile_key.hc_mult,
1102
+ tile_n=compile_key.tile_n,
1103
+ n_thr=compile_key.n_thr,
1104
+ n_splits=compile_key.n_splits,
1105
+ )
1106
+
1107
+ @kernel_launcher
1108
+ def __call__(
1109
+ self,
1110
+ x: Any,
1111
+ fn: Any,
1112
+ out: Any,
1113
+ sqrsum: Any,
1114
+ hidden_size: int,
1115
+ hc_mult: int,
1116
+ tile_n: int = 12,
1117
+ n_thr: int = 512,
1118
+ n_splits: int = 1,
1119
+ ) -> TileLangLaunchSpec:
1120
+ assert out.shape[0] == n_splits
1121
+ assert sqrsum.shape[0] == n_splits
1122
+ assert x.shape[1] == hc_mult * hidden_size
1123
+ assert x.shape[1] % n_splits == 0
1124
+ assert (x.shape[1] // n_splits) % n_thr == 0
1125
+ compile_key = self.dispatch(
1126
+ num_tokens=x.shape[0],
1127
+ hc_hidden_size=x.shape[1],
1128
+ hidden_size=hidden_size,
1129
+ hc_mult=hc_mult,
1130
+ n_out=fn.shape[0],
1131
+ n_thr=n_thr,
1132
+ tile_n=tile_n,
1133
+ n_splits=n_splits,
1134
+ )
1135
+ kernel_arg = (
1136
+ compile_key.block_m if compile_key.use_block_m else compile_key.n_splits
1137
+ )
1138
+ return (compile_key,), (
1139
+ x,
1140
+ fn,
1141
+ out,
1142
+ sqrsum,
1143
+ hidden_size,
1144
+ hc_mult,
1145
+ fn.shape[0],
1146
+ compile_key.n_thr,
1147
+ compile_key.tile_n,
1148
+ kernel_arg,
1149
+ )
1150
+
1151
+
1152
+class MhcPreBigFuseTileLangKernel(
1153
+ VllmTileLangJitKernel["MhcPreBigFuseTileLangKernel.CompileKey"]
1154
+):
1155
+ @dataclass(frozen=True)
1156
+ class CompileKey:
1157
+ hidden_size: int
1158
+ hc_mult: int
1159
+ n_splits: int
1160
+ use_norm_weight: bool
1161
+ is_broadcast: bool
1162
+ rms_eps: float
1163
+ hc_pre_eps: float
1164
+ hc_sinkhorn_eps: float
1165
+ hc_post_mult_value: float
1166
+ sinkhorn_repeat: int
1167
+ norm_eps: float
1168
+
1169
+ @staticmethod
1170
+ def kernel(compile_key: CompileKey) -> Any:
1171
+ if compile_key.is_broadcast:
1172
+ return mhc_pre_big_fuse_broadcast_with_norm_tilelang
1173
+ if compile_key.use_norm_weight:
1174
+ return mhc_pre_big_fuse_with_norm_tilelang
1175
+ return mhc_pre_big_fuse_tilelang
1176
+
1177
+ @staticmethod
1178
+ def _kernel_args(
1179
+ compile_key: CompileKey,
1180
+ *,
1181
+ gemm_out_mul: Any,
1182
+ gemm_out_sqrsum: Any,
1183
+ hc_scale: Any,
1184
+ hc_base: Any,
1185
+ residual: Any,
1186
+ residual_out: Any | None,
1187
+ post_mix: Any,
1188
+ comb_mix: Any,
1189
+ layer_input: Any,
1190
+ norm_weight: Any | None,
1191
+ ) -> tuple[Any, ...]:
1192
+ common_args = (
1193
+ gemm_out_mul,
1194
+ gemm_out_sqrsum,
1195
+ hc_scale,
1196
+ hc_base,
1197
+ residual,
1198
+ )
1199
+ output_args = (post_mix, comb_mix, layer_input)
1200
+ eps_args = (
1201
+ compile_key.rms_eps,
1202
+ compile_key.hc_pre_eps,
1203
+ compile_key.hc_sinkhorn_eps,
1204
+ compile_key.hc_post_mult_value,
1205
+ compile_key.sinkhorn_repeat,
1206
+ )
1207
+ middle_args: tuple[Any, ...]
1208
+ if compile_key.is_broadcast:
1209
+ assert residual_out is not None
1210
+ assert norm_weight is not None
1211
+ middle_args = (residual_out, *output_args, norm_weight)
1212
+ elif compile_key.use_norm_weight:
1213
+ assert norm_weight is not None
1214
+ middle_args = (*output_args, norm_weight, post_mix, post_mix)
1215
+ else:
1216
+ middle_args = (*output_args, post_mix, post_mix)
1217
+ norm_eps_args = (compile_key.norm_eps,) if compile_key.use_norm_weight else ()
1218
+ return (
1219
+ *common_args,
1220
+ *middle_args,
1221
+ compile_key.hidden_size,
1222
+ *eps_args,
1223
+ *norm_eps_args,
1224
+ compile_key.n_splits,
1225
+ compile_key.hc_mult,
1226
+ )
1227
+
1228
+ def dispatch( # type: ignore[override]
1229
+ self,
1230
+ *,
1231
+ hidden_size: int,
1232
+ hc_mult: int,
1233
+ n_splits: int,
1234
+ is_broadcast: bool,
1235
+ use_norm_weight: bool,
1236
+ use_pre_gemm_splits: bool = False,
1237
+ pre_gemm_k: int = 0,
1238
+ sinkhorn_repeat: int,
1239
+ norm_eps: float,
1240
+ broadcast_norm_eps: float = 0.0,
1241
+ num_tokens: int = 1,
1242
+ use_fused_tilelang: bool = False,
1243
+ **compile_key_fields: float,
1244
+ ) -> CompileKey:
1245
+ pre_gemm_n_splits = (
1246
+ compute_num_split(64, pre_gemm_k, (num_tokens + 63) // 64)
1247
+ if use_pre_gemm_splits
1248
+ else n_splits
1249
+ )
1250
+ fused_n_splits = _mhc_fused_n_splits(num_tokens, hidden_size)
1251
+ actual_n_splits = fused_n_splits if use_fused_tilelang else pre_gemm_n_splits
1252
+ actual_norm_eps = broadcast_norm_eps if is_broadcast else norm_eps
1253
+ actual_use_norm_weight = use_norm_weight or is_broadcast
1254
+ return self.CompileKey(
1255
+ **compile_key_fields,
1256
+ hidden_size=hidden_size,
1257
+ hc_mult=hc_mult,
1258
+ n_splits=actual_n_splits,
1259
+ use_norm_weight=actual_use_norm_weight,
1260
+ is_broadcast=is_broadcast,
1261
+ sinkhorn_repeat=sinkhorn_repeat,
1262
+ norm_eps=actual_norm_eps if actual_use_norm_weight else 0.0,
1263
+ )
1264
+
1265
+ def get_warmup_keys(
1266
+ self,
1267
+ vllm_config: Any,
1268
+ *,
1269
+ hidden_size: int,
1270
+ hc_mult: int,
1271
+ use_norm_weight: bool,
1272
+ rms_eps: float,
1273
+ hc_pre_eps: float,
1274
+ hc_sinkhorn_eps: float,
1275
+ hc_post_mult_value: float,
1276
+ sinkhorn_repeat: int,
1277
+ norm_eps: float | tuple[float, ...],
1278
+ broadcast_norm_eps: float | tuple[float, ...] | None = None,
1279
+ include_pre_gemm_splits: bool = False,
1280
+ include_broadcast_splits: bool = False,
1281
+ ) -> list[CompileKey]:
1282
+ max_tokens = vllm_config.scheduler_config.max_num_batched_tokens
1283
+ if max_tokens <= 0:
1284
+ return []
1285
+
1286
+ warmup_cases = zip_inputs(
1287
+ dict(
1288
+ is_broadcast=False,
1289
+ use_pre_gemm_splits=False,
1290
+ pre_gemm_k=0,
1291
+ use_fused_tilelang=False,
1292
+ ),
1293
+ dict(
1294
+ is_broadcast=False,
1295
+ use_pre_gemm_splits=include_pre_gemm_splits,
1296
+ pre_gemm_k=hc_mult * hidden_size,
1297
+ use_fused_tilelang=False,
1298
+ ),
1299
+ dict(
1300
+ is_broadcast=include_broadcast_splits,
1301
+ use_pre_gemm_splits=include_broadcast_splits,
1302
+ pre_gemm_k=hidden_size,
1303
+ use_fused_tilelang=False,
1304
+ ),
1305
+ dict(
1306
+ is_broadcast=False,
1307
+ use_pre_gemm_splits=False,
1308
+ pre_gemm_k=0,
1309
+ use_fused_tilelang=True,
1310
+ ),
1311
+ )
1312
+ return self._trace_dispatch(self.dispatch)(
1313
+ warmup_cases,
1314
+ num_tokens=WarmupIntRange(
1315
+ 1,
1316
+ max_tokens + 1,
1317
+ advance=lambda value: 8 if value < 8 else cdiv(value, 64) * 64 + 1,
1318
+ ),
1319
+ n_splits=1,
1320
+ hidden_size=hidden_size,
1321
+ hc_mult=hc_mult,
1322
+ use_norm_weight=use_norm_weight,
1323
+ rms_eps=rms_eps,
1324
+ hc_pre_eps=hc_pre_eps,
1325
+ hc_sinkhorn_eps=hc_sinkhorn_eps,
1326
+ hc_post_mult_value=hc_post_mult_value,
1327
+ sinkhorn_repeat=sinkhorn_repeat,
1328
+ norm_eps=norm_eps,
1329
+ broadcast_norm_eps=broadcast_norm_eps
1330
+ if broadcast_norm_eps is not None
1331
+ else norm_eps,
1332
+ _when=lambda *, use_fused_tilelang, num_tokens: (
1333
+ not use_fused_tilelang or num_tokens <= 16
1334
+ ),
1335
+ )
1336
+
1337
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
1338
+ hidden_size = compile_key.hidden_size
1339
+ hc_mult = compile_key.hc_mult
1340
+ hc_mult3 = hc_mult * (2 + hc_mult)
1341
+ num_tokens = 1
1342
+
1343
+ gemm_out_mul = make_tilelang_warmup_tensor(
1344
+ torch.float32, compile_key.n_splits, num_tokens, hc_mult3
1345
+ )
1346
+ gemm_out_sqrsum = make_tilelang_warmup_tensor(
1347
+ torch.float32, compile_key.n_splits, num_tokens
1348
+ )
1349
+ hc_scale = make_tilelang_warmup_tensor(torch.float32, 3)
1350
+ hc_base = make_tilelang_warmup_tensor(torch.float32, hc_mult3)
1351
+ post_mix = make_tilelang_warmup_tensor(torch.float32, num_tokens, hc_mult)
1352
+ comb_mix = make_tilelang_warmup_tensor(
1353
+ torch.float32, num_tokens, hc_mult * hc_mult
1354
+ )
1355
+ layer_input = make_tilelang_warmup_tensor(
1356
+ torch.bfloat16, num_tokens, hidden_size
1357
+ )
1358
+ residual_shape = (
1359
+ (num_tokens, hidden_size)
1360
+ if compile_key.is_broadcast
1361
+ else (num_tokens, hc_mult, hidden_size)
1362
+ )
1363
+ residual = make_tilelang_warmup_tensor(torch.bfloat16, *residual_shape)
1364
+ residual_out = (
1365
+ make_tilelang_warmup_tensor(
1366
+ torch.bfloat16, num_tokens, hc_mult, hidden_size
1367
+ )
1368
+ if compile_key.is_broadcast
1369
+ else None
1370
+ )
1371
+ norm_weight = (
1372
+ make_tilelang_warmup_tensor(torch.bfloat16, hidden_size)
1373
+ if compile_key.is_broadcast or compile_key.use_norm_weight
1374
+ else None
1375
+ )
1376
+ return dict(
1377
+ gemm_out_mul=gemm_out_mul,
1378
+ gemm_out_sqrsum=gemm_out_sqrsum,
1379
+ hc_scale=hc_scale,
1380
+ hc_base=hc_base,
1381
+ residual=residual,
1382
+ post_mix=post_mix,
1383
+ comb_mix=comb_mix,
1384
+ layer_input=layer_input,
1385
+ rms_eps=compile_key.rms_eps,
1386
+ hc_pre_eps=compile_key.hc_pre_eps,
1387
+ hc_sinkhorn_eps=compile_key.hc_sinkhorn_eps,
1388
+ hc_post_mult_value=compile_key.hc_post_mult_value,
1389
+ sinkhorn_repeat=compile_key.sinkhorn_repeat,
1390
+ residual_out=residual_out,
1391
+ norm_weight=norm_weight,
1392
+ norm_eps=compile_key.norm_eps,
1393
+ )
1394
+
1395
+ @kernel_launcher
1396
+ def __call__(
1397
+ self,
1398
+ gemm_out_mul: Any,
1399
+ gemm_out_sqrsum: Any,
1400
+ hc_scale: Any,
1401
+ hc_base: Any,
1402
+ residual: Any,
1403
+ post_mix: Any,
1404
+ comb_mix: Any,
1405
+ layer_input: Any,
1406
+ rms_eps: float,
1407
+ hc_pre_eps: float,
1408
+ hc_sinkhorn_eps: float,
1409
+ hc_post_mult_value: float,
1410
+ sinkhorn_repeat: int,
1411
+ *,
1412
+ residual_out: Any | None = None,
1413
+ norm_weight: Any | None = None,
1414
+ norm_eps: float = 0.0,
1415
+ ) -> TileLangLaunchSpec:
1416
+ is_broadcast = residual_out is not None
1417
+ use_norm_weight = norm_weight is not None
1418
+ n_splits = gemm_out_mul.shape[0]
1419
+ if is_broadcast:
1420
+ assert residual_out is not None
1421
+ hidden_size = residual.shape[-1]
1422
+ hc_mult = residual_out.shape[-2]
1423
+ else:
1424
+ hc_mult = residual.shape[-2]
1425
+ hidden_size = residual.shape[-1]
1426
+ compile_key = self.dispatch(
1427
+ hidden_size=hidden_size,
1428
+ hc_mult=hc_mult,
1429
+ n_splits=n_splits,
1430
+ is_broadcast=is_broadcast,
1431
+ use_norm_weight=use_norm_weight,
1432
+ rms_eps=rms_eps,
1433
+ hc_pre_eps=hc_pre_eps,
1434
+ hc_sinkhorn_eps=hc_sinkhorn_eps,
1435
+ hc_post_mult_value=hc_post_mult_value,
1436
+ sinkhorn_repeat=sinkhorn_repeat,
1437
+ norm_eps=norm_eps,
1438
+ broadcast_norm_eps=norm_eps,
1439
+ )
1440
+ return (
1441
+ (compile_key,),
1442
+ self._kernel_args(
1443
+ compile_key,
1444
+ gemm_out_mul=gemm_out_mul,
1445
+ gemm_out_sqrsum=gemm_out_sqrsum,
1446
+ hc_scale=hc_scale,
1447
+ hc_base=hc_base,
1448
+ residual=residual,
1449
+ residual_out=residual_out,
1450
+ post_mix=post_mix,
1451
+ comb_mix=comb_mix,
1452
+ layer_input=layer_input,
1453
+ norm_weight=norm_weight,
1454
+ ),
1455
+ )
1456
+
1457
+
1458
+class MhcPostTileLangKernel(VllmTileLangJitKernel["MhcPostTileLangKernel.CompileKey"]):
1459
+ @dataclass(frozen=True)
1460
+ class CompileKey:
1461
+ hidden_size: int
1462
+ hc_mult: int
1463
+
1464
+ @staticmethod
1465
+ def kernel() -> Any:
1466
+ return mhc_post_tilelang
1467
+
1468
+ def dispatch( # type: ignore[override]
1469
+ self,
1470
+ **compile_key_fields: int,
1471
+ ) -> CompileKey:
1472
+ return self.CompileKey(**compile_key_fields)
1473
+
1474
+ def get_warmup_keys(
1475
+ self,
1476
+ *,
1477
+ hidden_size: int,
1478
+ hc_mult: int,
1479
+ ) -> list[CompileKey]:
1480
+ return self._trace_dispatch(self.dispatch)(
1481
+ hidden_size=hidden_size,
1482
+ hc_mult=hc_mult,
1483
+ )
1484
+
1485
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
1486
+ num_tokens = 1
1487
+ hidden_size = compile_key.hidden_size
1488
+ hc_mult = compile_key.hc_mult
1489
+ comb_mix = make_tilelang_warmup_tensor(
1490
+ torch.float32, num_tokens, hc_mult, hc_mult
1491
+ )
1492
+ residual = make_tilelang_warmup_tensor(
1493
+ torch.bfloat16, num_tokens, hc_mult, hidden_size
1494
+ )
1495
+ post_mix = make_tilelang_warmup_tensor(torch.float32, num_tokens, hc_mult)
1496
+ layer_input = make_tilelang_warmup_tensor(
1497
+ torch.bfloat16, num_tokens, hidden_size
1498
+ )
1499
+ out = make_tilelang_warmup_tensor(
1500
+ torch.bfloat16, num_tokens, hc_mult, hidden_size
1501
+ )
1502
+ return dict(
1503
+ comb_mix=comb_mix,
1504
+ residual=residual,
1505
+ post_mix=post_mix,
1506
+ layer_input=layer_input,
1507
+ out=out,
1508
+ hc_mult=hc_mult,
1509
+ hidden_size=hidden_size,
1510
+ )
1511
+
1512
+ @kernel_launcher
1513
+ def __call__(
1514
+ self,
1515
+ comb_mix: Any,
1516
+ residual: Any,
1517
+ post_mix: Any,
1518
+ layer_input: Any,
1519
+ out: Any,
1520
+ hc_mult: int,
1521
+ hidden_size: int,
1522
+ ) -> TileLangLaunchSpec:
1523
+ return (), (
1524
+ comb_mix,
1525
+ residual,
1526
+ post_mix,
1527
+ layer_input,
1528
+ out,
1529
+ hc_mult,
1530
+ hidden_size,
1531
+ )
1532
+
1533
+
1534
+class MhcFusedTileLangKernel(
1535
+ VllmTileLangJitKernel["MhcFusedTileLangKernel.CompileKey"]
1536
+):
1537
+ @dataclass(frozen=True)
1538
+ class CompileKey:
1539
+ hidden_size: int
1540
+ hc_mult: int
1541
+ n_splits: int
1542
+ tile_n: int
1543
+
1544
+ @staticmethod
1545
+ def kernel() -> Any:
1546
+ return mhc_fused_tilelang
1547
+
1548
+ def dispatch( # type: ignore[override]
1549
+ self,
1550
+ *,
1551
+ num_tokens: int,
1552
+ hidden_size: int,
1553
+ hc_mult: int,
1554
+ ) -> CompileKey:
1555
+ # TODO(gnovack): investigate autotuning these heuristics
1556
+ tile_n = 2 if num_tokens < 8 else 3
1557
+ n_splits = _mhc_fused_n_splits(num_tokens, hidden_size)
1558
+ return self.CompileKey(
1559
+ hidden_size=hidden_size,
1560
+ hc_mult=hc_mult,
1561
+ n_splits=n_splits,
1562
+ tile_n=tile_n,
1563
+ )
1564
+
1565
+ def get_warmup_keys(
1566
+ self,
1567
+ vllm_config: Any,
1568
+ *,
1569
+ hidden_size: int,
1570
+ hc_mult: int,
1571
+ ) -> list[CompileKey]:
1572
+ max_tokens = vllm_config.scheduler_config.max_num_batched_tokens
1573
+ return self._trace_dispatch(self.dispatch)(
1574
+ num_tokens=WarmupIntRange(1, max_tokens + 1),
1575
+ hidden_size=hidden_size,
1576
+ hc_mult=hc_mult,
1577
+ _when=lambda *, num_tokens: num_tokens <= 16,
1578
+ )
1579
+
1580
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
1581
+ num_tokens = 1 if compile_key.tile_n == 2 else 8
1582
+ hidden_size = compile_key.hidden_size
1583
+ hc_mult = compile_key.hc_mult
1584
+ hc_mult3 = hc_mult * (2 + hc_mult)
1585
+ comb_mix = make_tilelang_warmup_tensor(
1586
+ torch.float32, num_tokens, hc_mult, hc_mult
1587
+ )
1588
+ residual_in = make_tilelang_warmup_tensor(
1589
+ torch.bfloat16, num_tokens, hc_mult, hidden_size
1590
+ )
1591
+ post_mix = make_tilelang_warmup_tensor(torch.float32, num_tokens, hc_mult)
1592
+ x_in = make_tilelang_warmup_tensor(torch.bfloat16, num_tokens, hidden_size)
1593
+ weight_t = make_tilelang_warmup_tensor(
1594
+ torch.float32, hc_mult3, hc_mult, hidden_size
1595
+ )
1596
+ return dict(
1597
+ comb_mix=comb_mix,
1598
+ residual_in=residual_in,
1599
+ post_mix=post_mix,
1600
+ x_in=x_in,
1601
+ weight_t=weight_t,
1602
+ hc_mult=hc_mult,
1603
+ hidden_size=hidden_size,
1604
+ hc_mult3=hc_mult3,
1605
+ )
1606
+
1607
+ @kernel_launcher
1608
+ def __call__(
1609
+ self,
1610
+ comb_mix: Any,
1611
+ residual_in: Any,
1612
+ post_mix: Any,
1613
+ x_in: Any,
1614
+ weight_t: Any,
1615
+ hc_mult: int,
1616
+ hidden_size: int,
1617
+ hc_mult3: int,
1618
+ ) -> TileLangLaunchSpec:
1619
+ num_tokens = residual_in.shape[0]
1620
+ tile_n = 2 if num_tokens < 8 else 3
1621
+ n_splits = _mhc_fused_n_splits(num_tokens, hidden_size)
1622
+ yp_out = residual_in.new_empty(
1623
+ (n_splits, num_tokens, hc_mult3), dtype=torch.float32
1624
+ )
1625
+ rp_out = residual_in.new_empty((n_splits, num_tokens), dtype=torch.float32)
1626
+ residual_out = residual_in.new_empty(residual_in.shape)
1627
+ return (
1628
+ (),
1629
+ (
1630
+ comb_mix,
1631
+ residual_in,
1632
+ post_mix,
1633
+ x_in,
1634
+ weight_t,
1635
+ yp_out,
1636
+ rp_out,
1637
+ residual_out,
1638
+ hc_mult,
1639
+ hidden_size,
1640
+ hc_mult3,
1641
+ ),
1642
+ dict(tile_n=tile_n, split_k=n_splits),
1643
+ (yp_out, rp_out, residual_out),
1644
+ )
1645
+
1646
+
1647
+class HcHeadFusedTileLangKernel(
1648
+ VllmTileLangJitKernel["HcHeadFusedTileLangKernel.CompileKey"]
1649
+):
1650
+ @dataclass(frozen=True)
1651
+ class CompileKey:
1652
+ hidden_size: int
1653
+ hc_mult: int
1654
+ rms_eps: float
1655
+ hc_eps: float
1656
+
1657
+ @staticmethod
1658
+ def kernel() -> Any:
1659
+ return hc_head_fuse_tilelang
1660
+
1661
+ def dispatch( # type: ignore[override]
1662
+ self,
1663
+ *,
1664
+ hidden_size: int,
1665
+ hc_mult: int,
1666
+ rms_eps: float,
1667
+ hc_eps: float,
1668
+ ) -> CompileKey:
1669
+ return self.CompileKey(
1670
+ hidden_size=hidden_size,
1671
+ hc_mult=hc_mult,
1672
+ rms_eps=rms_eps,
1673
+ hc_eps=hc_eps,
1674
+ )
1675
+
1676
+ def get_warmup_keys(
1677
+ self,
1678
+ *,
1679
+ hidden_size: int,
1680
+ hc_mult: int,
1681
+ rms_eps: float,
1682
+ hc_eps: float,
1683
+ ) -> list[CompileKey]:
1684
+ return self._trace_dispatch(self.dispatch)(
1685
+ hidden_size=hidden_size,
1686
+ hc_mult=hc_mult,
1687
+ rms_eps=rms_eps,
1688
+ hc_eps=hc_eps,
1689
+ )
1690
+
1691
+ def warmup_inputs(self, compile_key: CompileKey) -> dict[str, Any]:
1692
+ num_tokens = 1
1693
+ hidden_size = compile_key.hidden_size
1694
+ hc_mult = compile_key.hc_mult
1695
+ residual = make_tilelang_warmup_tensor(
1696
+ torch.bfloat16, num_tokens, hc_mult, hidden_size
1697
+ )
1698
+ fn = make_tilelang_warmup_tensor(torch.float32, hc_mult, hc_mult * hidden_size)
1699
+ hc_scale = make_tilelang_warmup_tensor(torch.float32, 1)
1700
+ hc_base = make_tilelang_warmup_tensor(torch.float32, hc_mult)
1701
+ out = make_tilelang_warmup_tensor(torch.bfloat16, num_tokens, hidden_size)
1702
+ return dict(
1703
+ residual=residual,
1704
+ fn=fn,
1705
+ hc_scale=hc_scale,
1706
+ hc_base=hc_base,
1707
+ out=out,
1708
+ hidden_size=hidden_size,
1709
+ rms_eps=compile_key.rms_eps,
1710
+ hc_eps=compile_key.hc_eps,
1711
+ hc_mult=hc_mult,
1712
+ )
1713
+
1714
+ @kernel_launcher
1715
+ def __call__(
1716
+ self,
1717
+ residual: Any,
1718
+ fn: Any,
1719
+ hc_scale: Any,
1720
+ hc_base: Any,
1721
+ out: Any,
1722
+ hidden_size: int,
1723
+ rms_eps: float,
1724
+ hc_eps: float,
1725
+ hc_mult: int,
1726
+ ) -> TileLangLaunchSpec:
1727
+ return (), (
1728
+ residual,
1729
+ fn,
1730
+ hc_scale,
1731
+ hc_base,
1732
+ out,
1733
+ hidden_size,
1734
+ rms_eps,
1735
+ hc_eps,
1736
+ hc_mult,
1737
+ )
1738
+
1739
+
1740
+_HC_PRENORM_GEMM_TILELANG_KERNEL = HcPrenormGemmTileLangKernel()
1741
+_MHC_PRE_BIG_FUSE_TILELANG_KERNEL = MhcPreBigFuseTileLangKernel()
1742
+_MHC_POST_TILELANG_KERNEL = MhcPostTileLangKernel()
1743
+_MHC_FUSED_TILELANG_KERNEL = MhcFusedTileLangKernel()
1744
+_HC_HEAD_FUSED_TILELANG_KERNEL = HcHeadFusedTileLangKernel()
vllm/model_executor/warmup/deepseek_v4_mhc_warmup.py
deleted
-211
@@ -1,211 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-"""Warm up DeepSeek V4 mHC TileLang kernels before serving requests.
4
-
5
-Ported from lucifer1004/vllm-jasl with the two env-var knobs removed
6
-(`VLLM_ENABLE_DEEPSEEK_V4_MHC_WARMUP`, `VLLM_DEEPSEEK_V4_MHC_WARMUP_TOKEN_SIZES`).
7
-Gating is intrinsic: non-DSv4 models and layers without hc_* attributes
8
-return early, so the warmup is a no-op except where it's needed.
9
-"""
10
-
11
-import time
12
-from collections.abc import Iterable
13
-
14
-import torch
15
-
16
-from vllm.logger import init_logger
17
-from vllm.tracing import instrument
18
-
19
-logger = init_logger(__name__)
20
-
21
-_AUTO_WARMUP_MAX_TOKENS = 16_384
22
-_DEFAULT_TOKEN_SIZE_CANDIDATES = (
23
- 1,
24
- 2,
25
- 4,
26
- 8,
27
- 16,
28
- 32,
29
- 64,
30
- 128,
31
- 256,
32
- 512,
33
- 1024,
34
- 2048,
35
- 4096,
36
- 8192,
37
- 16_384,
38
-)
39
-
40
-
41
-def _normalize_token_sizes(
42
- token_sizes: Iterable[int],
43
- *,
44
- max_tokens: int,
45
-) -> list[int]:
46
- return sorted({size for size in token_sizes if 1 <= size <= max_tokens})
47
-
48
-
49
-def _select_mhc_warmup_token_sizes(
50
- *,
51
- max_tokens: int,
52
- cudagraph_capture_sizes: list[int],
53
-) -> list[int]:
54
- if max_tokens <= 0:
55
- return []
56
-
57
- max_auto_tokens = min(max_tokens, _AUTO_WARMUP_MAX_TOKENS)
58
- candidates = list(_DEFAULT_TOKEN_SIZE_CANDIDATES)
59
- candidates.extend(cudagraph_capture_sizes)
60
- candidates.append(max_auto_tokens)
61
- return _normalize_token_sizes(candidates, max_tokens=max_auto_tokens)
62
-
63
-
64
-def _find_first_mhc_layer(model: torch.nn.Module) -> torch.nn.Module | None:
65
- for module in model.modules():
66
- if module.__class__.__name__ != "DeepseekV4DecoderLayer":
67
- continue
68
- if all(
69
- hasattr(module, attr)
70
- for attr in (
71
- "hc_pre",
72
- "hc_post",
73
- "hc_attn_fn",
74
- "hc_attn_scale",
75
- "hc_attn_base",
76
- "hc_ffn_fn",
77
- "hc_ffn_scale",
78
- "hc_ffn_base",
79
- )
80
- ):
81
- return module
82
- return None
83
-
84
-
85
-def _find_deepseek_v4_model(model: torch.nn.Module) -> torch.nn.Module | None:
86
- for module in model.modules():
87
- if module.__class__.__name__ != "DeepseekV4Model":
88
- continue
89
- if all(
90
- hasattr(module, attr)
91
- for attr in ("hc_head_fn", "hc_head_scale", "hc_head_base")
92
- ):
93
- return module
94
- return None
95
-
96
-
97
-def _warmup_layer_mhc(
98
- layer: torch.nn.Module,
99
- token_sizes: list[int],
100
-) -> None:
101
- max_tokens = max(token_sizes)
102
- hidden_size = int(layer.hidden_size)
103
- hc_mult = int(layer.hc_mult)
104
- device = layer.hc_attn_fn.device
105
- residual = torch.zeros(
106
- max_tokens,
107
- hc_mult,
108
- hidden_size,
109
- dtype=torch.bfloat16,
110
- device=device,
111
- )
112
-
113
- for size in token_sizes:
114
- residual_slice = residual[:size]
115
- for fn, scale, base in (
116
- (layer.hc_attn_fn, layer.hc_attn_scale, layer.hc_attn_base),
117
- (layer.hc_ffn_fn, layer.hc_ffn_scale, layer.hc_ffn_base),
118
- ):
119
- layer_input, post_mix, comb_mix = layer.hc_pre(
120
- residual_slice,
121
- fn,
122
- scale,
123
- base,
124
- )
125
- layer.hc_post(layer_input, residual_slice, post_mix, comb_mix)
126
-
127
-
128
-def _warmup_hc_head(
129
- model: torch.nn.Module,
130
- token_sizes: list[int],
131
-) -> None:
132
- # Upstream a8887c208 ("[DSV4] aiter mhc support (ROCm)") refactored
133
- # ``hc_head`` from a free function into the ``HCHeadOp`` CustomOp
134
- # instance attached to the model as ``hc_head_op``. We call through
135
- # that instance so the warmup exercises the same dispatched
136
- # implementation as the inference path.
137
- hc_head_op = getattr(model, "hc_head_op", None)
138
- if hc_head_op is None:
139
- return
140
-
141
- max_tokens = max(token_sizes)
142
- hidden_size = int(model.config.hidden_size)
143
- hc_mult = int(model.hc_mult)
144
- device = model.hc_head_fn.device
145
- hidden_states = torch.zeros(
146
- max_tokens,
147
- hc_mult,
148
- hidden_size,
149
- dtype=torch.bfloat16,
150
- device=device,
151
- )
152
-
153
- for size in token_sizes:
154
- hc_head_op(
155
- hidden_states[:size],
156
- model.hc_head_fn,
157
- model.hc_head_scale,
158
- model.hc_head_base,
159
- model.rms_norm_eps,
160
- model.hc_eps,
161
- )
162
-
163
-
164
-@instrument(span_name="DeepSeek V4 mHC warmup")
165
-def deepseek_v4_mhc_warmup(
166
- model: torch.nn.Module,
167
- *,
168
- max_tokens: int,
169
- cudagraph_capture_sizes: list[int] | None = None,
170
-) -> None:
171
- # Cheap model-type gate before walking ``model.modules()``. The class
172
- # walk below is O(num_layers) and shows up in startup time on very
173
- # large checkpoints; bail out for any model that is not DeepSeek V4.
174
- config = getattr(model, "config", None)
175
- model_type = getattr(config, "model_type", None) if config is not None else None
176
- if model_type is not None and model_type not in (
177
- "deepseek_v4",
178
- "deepseek_v41",
179
- ):
180
- return
181
-
182
- layer = _find_first_mhc_layer(model)
183
- if layer is None:
184
- return
185
-
186
- device = layer.hc_attn_fn.device
187
- if device.type != "cuda":
188
- return
189
-
190
- deepseek_model = _find_deepseek_v4_model(model)
191
- token_sizes = _select_mhc_warmup_token_sizes(
192
- max_tokens=max_tokens,
193
- cudagraph_capture_sizes=cudagraph_capture_sizes or [],
194
- )
195
- if not token_sizes:
196
- return
197
-
198
- started = time.perf_counter()
199
- logger.info(
200
- "Warming up DeepSeek V4 mHC TileLang kernels for token sizes: %s",
201
- token_sizes,
202
- )
203
- with torch.inference_mode():
204
- _warmup_layer_mhc(layer, token_sizes)
205
- if deepseek_model is not None:
206
- _warmup_hc_head(deepseek_model, token_sizes)
207
- torch.accelerator.synchronize()
208
- logger.info(
209
- "DeepSeek V4 mHC TileLang warmup finished in %.2f seconds.",
210
- time.perf_counter() - started,
211
- )
vllm/model_executor/warmup/jit_warmup_tilelang_helper.py
new
+172
@@ -0,0 +1,172 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Compile-only helpers for TileLang JIT warmup."""
4
+
5
+from __future__ import annotations
6
+
7
+import logging
8
+from abc import abstractmethod
9
+from collections.abc import Callable, Iterator, Mapping, MutableMapping
10
+from contextlib import contextmanager
11
+from dataclasses import dataclass, field
12
+from functools import wraps
13
+from typing import Any, ClassVar, Generic, TypeAlias, TypeVar, cast
14
+
15
+import torch
16
+
17
+from vllm.model_executor.warmup.jit_warmup import VllmJitKernel
18
+
19
+CompileKeyT = TypeVar("CompileKeyT")
20
+# (kernel_args, launch_args), optionally followed by keywords and a result.
21
+TileLangLaunchSpec: TypeAlias = (
22
+ tuple[tuple[Any, ...], tuple[Any, ...]]
23
+ | tuple[tuple[Any, ...], tuple[Any, ...], Mapping[str, Any] | None]
24
+ | tuple[tuple[Any, ...], tuple[Any, ...], Mapping[str, Any] | None, Any]
25
+)
26
+
27
+
28
+@contextmanager
29
+def _quiet_tilelang_warmup_logs() -> Iterator[None]:
30
+ loggers = [
31
+ logging.getLogger("tilelang.cache.kernel_cache"),
32
+ logging.getLogger("tilelang.jit.kernel"),
33
+ ]
34
+ previous_levels = [logger.level for logger in loggers]
35
+ try:
36
+ for logger in loggers:
37
+ logger.setLevel(logging.ERROR)
38
+ yield
39
+ finally:
40
+ for logger, level in zip(loggers, previous_levels):
41
+ logger.setLevel(level)
42
+
43
+
44
+@dataclass(frozen=True)
45
+class TileLangWarmupTensor:
46
+ """Minimal tensor-like object accepted by TileLang compile().
47
+
48
+ TileLang builds its cache key and TIR from tensor dtype, shape and stride.
49
+ This object deliberately has no storage, so compile-only warmup does not
50
+ allocate GPU memory and does not launch the kernel.
51
+ """
52
+
53
+ dtype: torch.dtype
54
+ shape: tuple[int, ...] = (1,)
55
+ strides: tuple[int, ...] | None = field(default=None)
56
+
57
+ def stride(self) -> tuple[int, ...]:
58
+ if self.strides is not None:
59
+ return self.strides
60
+
61
+ strides: list[int] = []
62
+ stride = 1
63
+ for size in reversed(self.shape):
64
+ strides.append(stride)
65
+ stride *= size
66
+ return tuple(reversed(strides))
67
+
68
+ def new_empty(
69
+ self,
70
+ shape: tuple[int, ...],
71
+ *,
72
+ dtype: torch.dtype | None = None,
73
+ device: Any = None,
74
+ ) -> TileLangWarmupTensor:
75
+ return TileLangWarmupTensor(dtype or self.dtype, shape)
76
+
77
+
78
+def make_tilelang_warmup_tensor(
79
+ dtype: torch.dtype,
80
+ *shape: int,
81
+ strides: tuple[int, ...] | None = None,
82
+) -> TileLangWarmupTensor:
83
+ return TileLangWarmupTensor(dtype=dtype, shape=tuple(shape), strides=strides)
84
+
85
+
86
+def _tilelang_call_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]:
87
+ call_kwargs = dict(kwargs)
88
+ tune_params = call_kwargs.pop("__tune_params", {})
89
+ if isinstance(tune_params, dict):
90
+ call_kwargs.update(tune_params)
91
+ return call_kwargs
92
+
93
+
94
+def compile_tilelang(jit_impl: Any, *args: Any, **kwargs: Any) -> None:
95
+ """Compile one TileLang specialization and populate its call cache.
96
+
97
+ TileLang's ``compile()`` materializes the kernel without launching it.
98
+ We also store the compiled kernel in ``_kernel_cache`` using the same
99
+ parsed key as ``__call__`` so runtime does not report a cache miss for an
100
+ already materialized specialization.
101
+ """
102
+
103
+ with _quiet_tilelang_warmup_logs():
104
+ compiled = jit_impl.compile(*args, **kwargs)
105
+ func = getattr(jit_impl, "func", None)
106
+ parse_args = getattr(func, "parse_args", None)
107
+ cache = getattr(jit_impl, "_kernel_cache", None)
108
+ if not callable(parse_args) or not isinstance(cache, MutableMapping):
109
+ return
110
+
111
+ key, _ = parse_args(*args, **_tilelang_call_kwargs(kwargs))
112
+ cache[key] = compiled
113
+
114
+
115
+class VllmTileLangJitKernel(VllmJitKernel[CompileKeyT], Generic[CompileKeyT]):
116
+ """TileLang owner whose runtime launch specification is reused for warmup."""
117
+
118
+ kernel: ClassVar[Any]
119
+ _warming_key: CompileKeyT | None = None
120
+
121
+ @abstractmethod
122
+ def warmup_inputs(self, compile_key: CompileKeyT) -> dict[str, Any]:
123
+ """Return runtime-shaped inputs that reproduce one compile key."""
124
+ raise NotImplementedError
125
+
126
+ def compile(self, compile_key: CompileKeyT) -> None:
127
+ self._warming_key = compile_key
128
+ try:
129
+ cast(Callable[..., Any], self)(**self.warmup_inputs(compile_key))
130
+ finally:
131
+ self._warming_key = None
132
+
133
+ def launch(
134
+ self,
135
+ jit_impl: Any,
136
+ args: tuple[Any, ...],
137
+ kwargs: Mapping[str, Any] | None = None,
138
+ ) -> Any:
139
+ call_kwargs = dict(kwargs or {})
140
+ if self._warming_key is not None:
141
+ return compile_tilelang(jit_impl, *args, **call_kwargs)
142
+ return jit_impl(*args, **call_kwargs)
143
+
144
+
145
+def kernel_launcher(
146
+ call_fn: Callable[..., TileLangLaunchSpec],
147
+) -> Callable[..., Any]:
148
+ """Launch TileLang from declarative kernel and runtime argument tuples."""
149
+
150
+ @wraps(call_fn)
151
+ def wrapper(
152
+ self: VllmTileLangJitKernel[Any],
153
+ *args: Any,
154
+ **kwargs: Any,
155
+ ) -> Any:
156
+ launch_spec = call_fn(self, *args, **kwargs)
157
+ kernel_args, launch_args = launch_spec[:2]
158
+ if self._warming_key is not None and kernel_args not in (
159
+ (),
160
+ (self._warming_key,),
161
+ ):
162
+ raise RuntimeError(
163
+ "TileLang warmup inputs produced a different compile key: "
164
+ f"expected {self._warming_key!r}, got kernel arguments "
165
+ f"{kernel_args!r}"
166
+ )
167
+ launch_kwargs = launch_spec[2] if len(launch_spec) > 2 else None
168
+ output = launch_spec[3] if len(launch_spec) > 3 else None
169
+ result = self.launch(self.kernel(*kernel_args), launch_args, launch_kwargs)
170
+ return output if len(launch_spec) > 3 else result
171
+
172
+ return wrapper
vllm/model_executor/warmup/kernel_warmup.py
-12
@@ -17,9 +17,6 @@ from vllm.logger import init_logger
17
from vllm.model_executor.warmup.b12x_warmup import b12x_warmup
18
from vllm.model_executor.warmup.cutedsl_warmup import cutedsl_warmup
19
from vllm.model_executor.warmup.deep_gemm_warmup import deep_gemm_warmup
20
-from vllm.model_executor.warmup.deepseek_v4_mhc_warmup import (
21
- deepseek_v4_mhc_warmup,
22
-)
20
from vllm.model_executor.warmup.flashinfer_autotune_cache import (
21
resolve_flashinfer_autotune_file,
22
write_flashinfer_autotune_cache,
@@ -197,15 +194,6 @@ def kernel_warmup(worker: "Worker", *, process_local_only: bool = False):
194
compilation_config = worker.vllm_config.compilation_config
195
cudagraph_capture_sizes = list(compilation_config.cudagraph_capture_sizes or [])
196
200
- # DSv4 mHC TileLang kernels (hc_pre/hc_post/hc_head_op) run every decoder
201
- # layer per token; warm them across token sizes first so the first real
202
- # request doesn't pay JIT cost. No-op for non-DSv4 models (gated inside).
203
- deepseek_v4_mhc_warmup(
204
- worker.get_model(),
205
- max_tokens=worker.scheduler_config.max_num_batched_tokens,
206
- cudagraph_capture_sizes=cudagraph_capture_sizes,
207
- )
208
-
197
# Run next so input-prep kernels JIT against pristine runner state.
198
if enable_jit_warmup:
199
kimi_k3_triton_warmup(worker)
vllm/models/deepseek_v4/nvidia/model.py
+59
@@ -1210,6 +1210,53 @@ class DeepseekV4DecoderLayer(nn.Module):
1210
requires_grad=False,
1211
)
1212
1213
+ if vllm_config.kernel_config.enable_jit_warmup:
1214
+ from vllm.model_executor.kernels.mhc.tilelang_kernels import (
1215
+ _HC_PRENORM_GEMM_TILELANG_KERNEL,
1216
+ _MHC_FUSED_TILELANG_KERNEL,
1217
+ _MHC_POST_TILELANG_KERNEL,
1218
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL,
1219
+ )
1220
+ from vllm.utils.deep_gemm import is_deep_gemm_supported
1221
+
1222
+ include_pre_gemm_splits = is_deep_gemm_supported()
1223
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL.register_warmup(
1224
+ vllm_config,
1225
+ hidden_size=self.hidden_size,
1226
+ hc_mult=self.hc_mult,
1227
+ use_norm_weight=True,
1228
+ include_pre_gemm_splits=include_pre_gemm_splits,
1229
+ include_broadcast_splits=(
1230
+ get_pp_group().is_first_rank and extract_layer_index(prefix) == 0
1231
+ ),
1232
+ rms_eps=self.rms_norm_eps,
1233
+ hc_pre_eps=self.hc_eps,
1234
+ hc_sinkhorn_eps=self.hc_eps,
1235
+ hc_post_mult_value=self.hc_post_alpha,
1236
+ sinkhorn_repeat=self.hc_sinkhorn_iters,
1237
+ norm_eps=(
1238
+ self.attn_norm.variance_epsilon,
1239
+ self.ffn_norm.variance_epsilon,
1240
+ ),
1241
+ broadcast_norm_eps=self.attn_norm.variance_epsilon,
1242
+ )
1243
+ if not include_pre_gemm_splits:
1244
+ _HC_PRENORM_GEMM_TILELANG_KERNEL.register_warmup(
1245
+ vllm_config,
1246
+ hidden_size=self.hidden_size,
1247
+ hc_mult=self.hc_mult,
1248
+ n_out=self.hc_mult * (2 + self.hc_mult),
1249
+ )
1250
+ _MHC_POST_TILELANG_KERNEL.register_warmup(
1251
+ hidden_size=self.hidden_size,
1252
+ hc_mult=self.hc_mult,
1253
+ )
1254
+ _MHC_FUSED_TILELANG_KERNEL.register_warmup(
1255
+ vllm_config,
1256
+ hidden_size=self.hidden_size,
1257
+ hc_mult=self.hc_mult,
1258
+ )
1259
+
1260
def forward(
1261
self,
1262
x: torch.Tensor,
@@ -1402,6 +1449,18 @@ class DeepseekV4Model(nn.Module, EagleModelMixin):
1449
else:
1450
self._mtp_hidden_buffer = None
1451
1452
+ if vllm_config.kernel_config.enable_jit_warmup and get_pp_group().is_last_rank:
1453
+ from vllm.model_executor.kernels.mhc.tilelang_kernels import (
1454
+ _HC_HEAD_FUSED_TILELANG_KERNEL,
1455
+ )
1456
+
1457
+ _HC_HEAD_FUSED_TILELANG_KERNEL.register_warmup(
1458
+ hidden_size=config.hidden_size,
1459
+ hc_mult=self.hc_mult,
1460
+ rms_eps=self.rms_norm_eps,
1461
+ hc_eps=self.hc_eps,
1462
+ )
1463
+
1464
def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1465
return self.embed_tokens(input_ids)
1466
vllm/models/glm5next/nvidia/model.py
+44
@@ -403,6 +403,50 @@ class Glm5NextDecoderLayer(nn.Module):
403
self.mhc_post_op = MHCPostOp()
404
self.mhc_fused_post_pre_op = MHCFusedPostPreOp()
405
406
+ if vllm_config.kernel_config.enable_jit_warmup:
407
+ from vllm.model_executor.kernels.mhc.tilelang_kernels import (
408
+ _HC_PRENORM_GEMM_TILELANG_KERNEL,
409
+ _MHC_FUSED_TILELANG_KERNEL,
410
+ _MHC_POST_TILELANG_KERNEL,
411
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL,
412
+ )
413
+ from vllm.utils.deep_gemm import is_deep_gemm_supported
414
+
415
+ include_pre_gemm_splits = is_deep_gemm_supported()
416
+ _MHC_PRE_BIG_FUSE_TILELANG_KERNEL.register_warmup(
417
+ vllm_config,
418
+ hidden_size=self.hidden_size,
419
+ hc_mult=self.n,
420
+ use_norm_weight=True,
421
+ include_pre_gemm_splits=include_pre_gemm_splits,
422
+ include_broadcast_splits=False,
423
+ rms_eps=self.rms_norm_eps,
424
+ hc_pre_eps=self.hc_eps,
425
+ hc_sinkhorn_eps=self.hc_eps,
426
+ hc_post_mult_value=self.mhc_post_mult_value,
427
+ sinkhorn_repeat=self.mhc_sinkhorn_iterations,
428
+ norm_eps=(
429
+ self.input_layernorm.variance_epsilon,
430
+ self.post_attention_layernorm.variance_epsilon,
431
+ ),
432
+ )
433
+ if not include_pre_gemm_splits:
434
+ _HC_PRENORM_GEMM_TILELANG_KERNEL.register_warmup(
435
+ vllm_config,
436
+ hidden_size=self.hidden_size,
437
+ hc_mult=self.n,
438
+ n_out=self.n * (2 + self.n),
439
+ )
440
+ _MHC_POST_TILELANG_KERNEL.register_warmup(
441
+ hidden_size=self.hidden_size,
442
+ hc_mult=self.n,
443
+ )
444
+ _MHC_FUSED_TILELANG_KERNEL.register_warmup(
445
+ vllm_config,
446
+ hidden_size=self.hidden_size,
447
+ hc_mult=self.n,
448
+ )
449
+
450
def forward(
451
self,
452
positions: torch.Tensor,