A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 2, 17:01 UTC
[ROCm][DSV4] Enable Tilelang MHC replacing torch/triton mhc (#43679)
Signed-off-by: tjtanaa <tunjian.tan@embeddedllm.com>
TJian committed
May 28, 2026 at 15:05 UTC
0ba46d4b11d2b224e39f71af443335dbecf3a878
13 files changed
+716
-99
requirements/build/rocm.txt
+1
@@ -16,3 +16,4 @@ wheel
16
jinja2>=3.1.6
17
amdsmi==7.0.2
18
timm>=1.0.17
19
+tilelang==0.1.10
requirements/rocm.txt
+1
@@ -22,3 +22,4 @@ timm>=1.0.17
22
# amd-quark: required for Quark quantization on ROCm
23
# To be consistent with test_quark.py
24
amd-quark>=0.8.99
25
+tilelang==0.1.10
requirements/test/rocm.in
+1
@@ -43,6 +43,7 @@ schemathesis>=3.39.15 # Required for openai schema test
43
# quantization
44
bitsandbytes==0.49.2
45
buildkite-test-collector==0.1.9
46
+tilelang==0.1.10
47
48
genai_perf>=0.0.8
49
tritonclient>=2.51.0
requirements/test/rocm.txt
+21
-2
@@ -43,7 +43,9 @@ anyio==4.13.0
43
# starlette
44
# watchfiles
45
apache-tvm-ffi==0.1.10
46
- # via xgrammar
46
+ # via
47
+ # tilelang
48
+ # xgrammar
49
arctic-inference==0.1.1
50
# via -r requirements/test/rocm.in
51
argcomplete==3.6.3
@@ -129,7 +131,9 @@ click==8.3.1
131
# typer
132
# uvicorn
133
cloudpickle==3.1.2
132
- # via -r requirements/test/../common.txt
134
+ # via
135
+ # -r requirements/test/../common.txt
136
+ # tilelang
137
colorama==0.4.6
138
# via
139
# perceptron
@@ -511,6 +515,8 @@ mistral-common==1.11.2
515
# -c requirements/common.txt
516
# -r requirements/test/../common.txt
517
# -r requirements/test/rocm.in
518
+ml-dtypes==0.5.4
519
+ # via tilelang
520
model-hosting-container-standards==0.1.14
521
# via
522
# -c requirements/common.txt
@@ -587,6 +593,7 @@ numpy==2.2.6
593
# lm-eval
594
# matplotlib
595
# mistral-common
596
+ # ml-dtypes
597
# mteb
598
# numba
599
# opencv-python-headless
@@ -610,6 +617,7 @@ numpy==2.2.6
617
# statsmodels
618
# tensorizer
619
# tifffile
620
+ # tilelang
621
# torchvision
622
# transformers
623
# tritonclient
@@ -811,6 +819,7 @@ psutil==7.2.2
819
# accelerate
820
# peft
821
# tensorizer
822
+ # tilelang
823
py==1.11.0
824
# via pytest-forked
825
py-cpuinfo==9.0.0
@@ -1192,6 +1201,10 @@ tiktoken==0.12.0
1201
# gpt-oss
1202
# lm-eval
1203
# mistral-common
1204
+tilelang==0.1.10
1205
+ # via
1206
+ # -c requirements/rocm.txt
1207
+ # -r requirements/test/rocm.in
1208
timm==1.0.17
1209
# via
1210
# -c requirements/rocm.txt
@@ -1208,6 +1221,8 @@ tomli==2.4.0
1221
# via schemathesis
1222
tomli-w==1.2.0
1223
# via schemathesis
1224
+torch-c-dlpack-ext==0.1.5
1225
+ # via tilelang
1226
tqdm==4.67.3
1227
# via
1228
# -r requirements/test/../common.txt
@@ -1225,6 +1240,7 @@ tqdm==4.67.3
1240
# pqdm
1241
# segmentation-models-pytorch
1242
# sentence-transformers
1243
+ # tilelang
1244
# transformers
1245
transformers==5.5.3
1246
# via
@@ -1293,6 +1309,7 @@ typing-extensions==4.15.0
1309
# sentence-transformers
1310
# sqlalchemy
1311
# starlette
1312
+ # tilelang
1313
# torch
1314
# typeguard
1315
# typing-inspection
@@ -1359,6 +1376,8 @@ yarl==1.23.0
1376
# via
1377
# aiohttp
1378
# schemathesis
1379
+z3-solver==4.15.4.0
1380
+ # via tilelang
1381
zipp==3.23.0
1382
# via importlib-metadata
1383
tests/kernels/test_mhc_kernels.py
+166
-2
@@ -4,7 +4,12 @@ import pytest
4
import torch
5
6
import vllm.model_executor.kernels.mhc # noqa: F401
7
+from vllm.model_executor.kernels.mhc.tilelang import (
8
+ _tilelang_hc_prenorm_gemm,
9
+ _torch_hc_prenorm_gemm,
10
+)
11
from vllm.platforms import current_platform
12
+from vllm.utils.import_utils import has_tilelang
13
from vllm.utils.torch_utils import set_random_seed
14
15
DEVICE = current_platform.device_type
@@ -92,8 +97,128 @@ def hc_head_ref(
97
98
99
@pytest.mark.skipif(
95
- not current_platform.is_cuda(),
96
- reason="CUDA required",
100
+ not (current_platform.is_cuda_alike() and has_tilelang()),
101
+ reason="CUDA or ROCm and tilelang required",
102
+)
103
+@pytest.mark.parametrize("num_tokens", [1, 4, 8, 128])
104
+@pytest.mark.parametrize("hidden_size", [4096, 7168])
105
+@pytest.mark.parametrize("hc_mult", [4])
106
+def test_mhc_pre_tilelang(num_tokens, hidden_size, hc_mult):
107
+ torch.set_default_device(DEVICE)
108
+ set_random_seed(0)
109
+
110
+ residual = torch.randn((num_tokens, hc_mult, hidden_size), dtype=torch.bfloat16)
111
+ hc_mult2 = hc_mult * hc_mult
112
+ hc_mult3 = 2 * hc_mult + hc_mult2
113
+ fn = (
114
+ torch.randn((hc_mult3, hc_mult, hidden_size), dtype=torch.float)
115
+ * 1e-4
116
+ * (1 + torch.arange(hc_mult).mul(0.01).view(1, -1, 1))
117
+ ).flatten(1, 2)
118
+ hc_scale = torch.randn((3,), dtype=torch.float) * 0.1
119
+ hc_base = torch.randn((hc_mult3,), dtype=torch.float) * 0.1
120
+
121
+ hc_sinkhorn_eps = hc_pre_eps = rms_eps = 1e-6
122
+ sinkhorn_repeat = 20
123
+ hc_post_alpha = 1.0
124
+
125
+ ref = mhc_pre_ref(
126
+ residual,
127
+ fn,
128
+ hc_scale,
129
+ hc_base,
130
+ rms_eps,
131
+ hc_pre_eps,
132
+ hc_sinkhorn_eps,
133
+ hc_post_alpha,
134
+ sinkhorn_repeat,
135
+ )
136
+ out = torch.ops.vllm.mhc_pre_tilelang(
137
+ residual,
138
+ fn,
139
+ hc_scale,
140
+ hc_base,
141
+ rms_eps,
142
+ hc_pre_eps,
143
+ hc_sinkhorn_eps,
144
+ hc_post_alpha,
145
+ sinkhorn_repeat,
146
+ )
147
+
148
+ for actual, expected in zip(out, ref, strict=True):
149
+ torch.testing.assert_close(actual, expected, atol=5e-2, rtol=1e-2)
150
+
151
+
152
+@pytest.mark.skipif(
153
+ not (current_platform.is_cuda_alike() and has_tilelang()),
154
+ reason="CUDA or ROCm and tilelang required",
155
+)
156
+@pytest.mark.parametrize(
157
+ ("num_tokens", "hidden_size"),
158
+ [
159
+ (1, 1280),
160
+ (512, 1280),
161
+ (2048, 1280),
162
+ (1, 4096),
163
+ (64, 4096),
164
+ (512, 4096),
165
+ (2048, 4096),
166
+ (1, 7168),
167
+ (64, 7168),
168
+ (512, 7168),
169
+ (2048, 7168),
170
+ ],
171
+)
172
+def test_hc_prenorm_gemm_tilelang(num_tokens, hidden_size):
173
+ torch.set_default_device(DEVICE)
174
+ set_random_seed(0)
175
+
176
+ hc_mult = 4
177
+ hc_mult3 = 2 * hc_mult + hc_mult * hc_mult
178
+ x = torch.randn((num_tokens, hc_mult * hidden_size), dtype=torch.bfloat16)
179
+ fn = torch.randn((hc_mult3, hc_mult * hidden_size), dtype=torch.float32) * 1e-4
180
+ out_ref = torch.empty((1, num_tokens, hc_mult3), dtype=torch.float32)
181
+ sqrsum_ref = torch.empty((1, num_tokens), dtype=torch.float32)
182
+ out = torch.empty_like(out_ref)
183
+ sqrsum = torch.empty_like(sqrsum_ref)
184
+
185
+ _torch_hc_prenorm_gemm(x, fn, out_ref, sqrsum_ref)
186
+ _tilelang_hc_prenorm_gemm(x, fn, out, sqrsum, hidden_size, hc_mult)
187
+
188
+ torch.testing.assert_close(out, out_ref, atol=1e-5, rtol=1e-4)
189
+ torch.testing.assert_close(sqrsum, sqrsum_ref, atol=8.0, rtol=5e-4)
190
+
191
+
192
+@pytest.mark.skipif(
193
+ not (current_platform.is_cuda_alike() and has_tilelang()),
194
+ reason="CUDA or ROCm and tilelang required",
195
+)
196
+@pytest.mark.parametrize("num_tokens", [1, 4, 8, 128])
197
+@pytest.mark.parametrize("hidden_size", [4096, 7168])
198
+@pytest.mark.parametrize("hc_mult", [4])
199
+def test_mhc_post_tilelang(num_tokens, hidden_size, hc_mult):
200
+ torch.set_default_device(DEVICE)
201
+ set_random_seed(0)
202
+
203
+ x = torch.randn((num_tokens, hidden_size), dtype=torch.bfloat16)
204
+ residual = torch.randn((num_tokens, hc_mult, hidden_size), dtype=torch.bfloat16)
205
+ post_layer_mix = torch.randn((num_tokens, hc_mult, 1), dtype=torch.float32)
206
+ comb_res_mix = torch.randn((num_tokens, hc_mult, hc_mult), dtype=torch.float32)
207
+
208
+ ref = mhc_post_ref(x, residual, post_layer_mix, comb_res_mix)
209
+ out = torch.ops.vllm.mhc_post_tilelang(
210
+ x,
211
+ residual,
212
+ post_layer_mix,
213
+ comb_res_mix,
214
+ )
215
+
216
+ torch.testing.assert_close(out, ref, atol=5e-2, rtol=1e-2)
217
+
218
+
219
+@pytest.mark.skipif(
220
+ not (current_platform.is_cuda_alike() and has_tilelang()),
221
+ reason="CUDA or ROCm and tilelang required",
222
)
223
@pytest.mark.parametrize("num_tokens", [1, 4, 8, 128])
224
@pytest.mark.parametrize("hidden_size", [4096, 7168])
@@ -196,3 +321,42 @@ def test_hc_head_triton(num_tokens, hidden_size, hc_mult):
321
322
out_ref = hc_head_ref(residual, fn, hc_scale, hc_base, rms_eps, hc_eps)
323
torch.testing.assert_close(out, out_ref, atol=5e-2, rtol=1e-2)
324
+
325
+
326
+@pytest.mark.skipif(
327
+ not (current_platform.is_cuda_alike() and has_tilelang()),
328
+ reason="CUDA or ROCm and tilelang required",
329
+)
330
+@pytest.mark.parametrize("num_tokens", [1, 4, 8, 128])
331
+@pytest.mark.parametrize("hidden_size", [4096, 7168])
332
+@pytest.mark.parametrize("hc_mult", [4])
333
+def test_hc_head_tilelang(num_tokens, hidden_size, hc_mult):
334
+ torch.set_default_device(DEVICE)
335
+ set_random_seed(0)
336
+
337
+ residual = torch.randn((num_tokens, hc_mult, hidden_size), dtype=torch.bfloat16)
338
+ fn = torch.randn((hc_mult, hc_mult * hidden_size), dtype=torch.float32) * 1e-4
339
+ hc_scale = torch.randn((1,), dtype=torch.float32) * 0.1
340
+ hc_base = torch.randn((hc_mult,), dtype=torch.float32) * 0.1
341
+ rms_eps = hc_eps = 1e-6
342
+
343
+ out = torch.empty((num_tokens, hidden_size), dtype=torch.bfloat16)
344
+ out.fill_(float("nan"))
345
+
346
+ result = torch.ops.vllm.hc_head_fused_kernel_tilelang(
347
+ residual,
348
+ fn,
349
+ hc_scale,
350
+ hc_base,
351
+ out,
352
+ hidden_size,
353
+ rms_eps,
354
+ hc_eps,
355
+ hc_mult,
356
+ )
357
+
358
+ assert result is None
359
+ assert not torch.isnan(out).any()
360
+
361
+ out_ref = hc_head_ref(residual, fn, hc_scale, hc_base, rms_eps, hc_eps)
362
+ torch.testing.assert_close(out, out_ref, atol=5e-2, rtol=1e-2)
vllm/_tilelang_ops.py
+224
-40
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import math
4
from functools import cache
5
-from typing import TYPE_CHECKING
5
+from typing import TYPE_CHECKING, Any
6
7
import torch
8
@@ -10,8 +10,9 @@ from vllm.platforms import current_platform
10
from vllm.utils.import_utils import has_tilelang
11
from vllm.utils.math_utils import cdiv
12
13
-# tilelang is only available on CUDA platforms
14
-if TYPE_CHECKING or current_platform.is_cuda():
13
+# TileLang is used for MHC on CUDA and ROCm. Keep non-GPU imports cheap so
14
+# registering the Python wrapper modules does not require TileLang everywhere.
15
+if TYPE_CHECKING or current_platform.is_cuda_alike():
16
if not has_tilelang():
17
raise ImportError(
18
"tilelang is required for mhc but is not installed. Install it with "
@@ -23,6 +24,8 @@ else:
24
tilelang = None # type: ignore[assignment]
25
T = None # type: ignore[assignment]
26
27
+ENABLE_PDL = current_platform.is_arch_support_pdl() and current_platform.is_cuda()
28
+
29
30
@cache
31
def compute_num_split(block_k: int, k: int | None, grid_size: int) -> int:
@@ -37,12 +40,17 @@ def compute_num_split(block_k: int, k: int | None, grid_size: int) -> int:
40
return split_k
41
42
43
+pass_configs: dict[tilelang.PassConfigKey, Any] = {
44
+ tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True,
45
+ tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
46
+}
47
+
48
+if current_platform.is_cuda():
49
+ pass_configs[tilelang.PassConfigKey.TL_PTXAS_REGISTER_USAGE_LEVEL] = 10
50
+
51
+
52
@tilelang.jit(
41
- pass_configs={
42
- tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True,
43
- tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
44
- tilelang.PassConfigKey.TL_PTXAS_REGISTER_USAGE_LEVEL: 10,
45
- },
53
+ pass_configs=pass_configs,
54
)
55
def mhc_pre_big_fuse_tilelang(
56
gemm_out_mul,
@@ -78,7 +86,8 @@ def mhc_pre_big_fuse_tilelang(
86
layer_input: T.Tensor[[num_tokens, hidden_size], T.bfloat16] # type: ignore[no-redef, valid-type]
87
88
with T.Kernel(num_tokens, threads=96) as i:
81
- T.pdl_sync()
89
+ if ENABLE_PDL:
90
+ T.pdl_sync()
91
##################################################################
92
# _pre_norm_fn_fwd_norm
93
rms = T.alloc_fragment(1, T.float32)
@@ -174,18 +183,16 @@ def mhc_pre_big_fuse_tilelang(
183
ol[i1_h] += pre * xl[i_hc, i1_h]
184
185
T.copy(ol, layer_input[i, i0_h * hidden_block])
177
- T.pdl_trigger()
186
+
187
+ if ENABLE_PDL:
188
+ T.pdl_trigger()
189
190
191
# Copied from https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/layers/mhc.py#L478
192
193
194
@tilelang.jit(
184
- pass_configs={
185
- tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True,
186
- tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
187
- tilelang.PassConfigKey.TL_PTXAS_REGISTER_USAGE_LEVEL: 10,
188
- },
195
+ pass_configs=pass_configs,
196
)
197
def mhc_pre_big_fuse_with_norm_tilelang(
198
gemm_out_mul,
@@ -230,7 +237,8 @@ def mhc_pre_big_fuse_with_norm_tilelang(
237
T.clear(mixes)
238
rms[0] = 0
239
233
- T.pdl_sync()
240
+ if ENABLE_PDL:
241
+ T.pdl_sync()
242
243
for i_split in T.serial(n_splits):
244
rms[0] += gemm_out_sqrsum[i_split, i]
@@ -341,15 +349,12 @@ def mhc_pre_big_fuse_with_norm_tilelang(
349
350
T.copy(ol, layer_input[i, i0_h * hidden_block])
351
344
- T.pdl_trigger()
352
+ if ENABLE_PDL:
353
+ T.pdl_trigger()
354
355
356
@tilelang.jit(
348
- pass_configs={
349
- tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True,
350
- tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
351
- tilelang.PassConfigKey.TL_PTXAS_REGISTER_USAGE_LEVEL: 10,
352
- },
357
+ pass_configs=pass_configs,
358
)
359
def mhc_fused_tilelang(
360
comb_mix,
@@ -390,8 +395,8 @@ def mhc_fused_tilelang(
395
396
with T.Kernel(m, n_tiles, split_k, threads=n_thr) as (i_n, i_nt, i_ks):
397
tid = T.get_thread_binding()
393
- warp_id = T.get_warp_idx()
394
- lane = T.get_lane_idx()
398
+ warp_id = tid // 32
399
+ lane = tid % 32
400
401
s_warp = T.alloc_shared((num_warps, tile_n + 1), T.float32)
402
s_post = T.alloc_shared((hc,), T.float32)
@@ -407,7 +412,8 @@ def mhc_fused_tilelang(
412
T.clear(sqr)
413
h_split_start = i_ks * h_per_split
414
410
- T.pdl_sync()
415
+ if ENABLE_PDL:
416
+ T.pdl_sync()
417
418
T.copy(post_mix[i_n, 0], s_post)
419
T.copy(comb_mix[i_n, 0, 0], s_comb)
@@ -466,15 +472,12 @@ def mhc_fused_tilelang(
472
v2 += s_warp[w, tile_n]
473
rp_out[i_ks, i_n] = v2
474
469
- T.pdl_trigger()
475
+ if ENABLE_PDL:
476
+ T.pdl_trigger()
477
478
479
@tilelang.jit(
473
- pass_configs={
474
- tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True,
475
- tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
476
- tilelang.PassConfigKey.TL_PTXAS_REGISTER_USAGE_LEVEL: 10,
477
- },
480
+ pass_configs=pass_configs,
481
)
482
def mhc_post_tilelang(
483
a,
@@ -507,7 +510,8 @@ def mhc_post_tilelang(
510
511
a_local = T.alloc_fragment((hc, hc), T.float32)
512
c_local = T.alloc_fragment(hc, T.float32)
510
- T.pdl_sync()
513
+ if ENABLE_PDL:
514
+ T.pdl_sync()
515
T.copy(a[i_n, 0, 0], a_local)
516
T.copy(c[i_n, 0], c_local)
517
@@ -523,15 +527,193 @@ def mhc_post_tilelang(
527
x_local[i_hco, i1_h] += a_local[i_hci, i_hco] * b_local[i_hci, i1_h]
528
529
T.copy(x_local, x[i_n, 0, i0_h * h_blk])
526
- T.pdl_trigger()
530
+ if ENABLE_PDL:
531
+ T.pdl_trigger()
532
+
533
+
534
+@tilelang.jit(
535
+ pass_configs=pass_configs,
536
+)
537
+def hc_prenorm_gemm_tilelang(
538
+ x,
539
+ fn,
540
+ out,
541
+ sqrsum,
542
+ hidden_size: int,
543
+ hc_mult: int = 4,
544
+ n_out: int = 24,
545
+ n_thr: int = 512,
546
+ tile_n: int = 12,
547
+ n_splits: int = 1,
548
+) -> tilelang.JITKernel:
549
+ num_tokens = T.dynamic("num_tokens")
550
+ hc_hidden_size = hc_mult * hidden_size
551
+ k_per_split = hc_hidden_size // n_splits
552
+ k_iters = k_per_split // n_thr
553
+ n_tiles = T.ceildiv(n_out, tile_n)
554
+
555
+ x: T.Tensor((num_tokens, hc_hidden_size), T.bfloat16) # type: ignore[no-redef, valid-type]
556
+ fn: T.Tensor((n_out, hc_hidden_size), T.float32) # type: ignore[no-redef, valid-type]
557
+ out: T.Tensor((n_splits, num_tokens, n_out), T.float32) # type: ignore[no-redef, valid-type]
558
+ sqrsum: T.Tensor((n_splits, num_tokens), T.float32) # type: ignore[no-redef, valid-type]
559
+
560
+ with T.Kernel(num_tokens, n_tiles, n_splits, threads=n_thr) as (
561
+ i_n,
562
+ i_t,
563
+ i_s,
564
+ ):
565
+ tid = T.get_thread_binding()
566
+ acc = T.alloc_local((tile_n,), T.float32)
567
+ sqr = T.alloc_local((1,), T.float32)
568
+ T.clear(acc)
569
+ T.clear(sqr)
570
+
571
+ if ENABLE_PDL:
572
+ T.pdl_sync()
573
+
574
+ for it in T.serial(k_iters):
575
+ i_k = i_s * k_per_split + it * n_thr + tid
576
+ x_val = x[i_n, i_k]
577
+ for i_o in T.unroll(tile_n):
578
+ out_idx = i_t * tile_n + i_o
579
+ if out_idx < n_out:
580
+ acc[i_o] += x_val * fn[out_idx, i_k]
581
+ if i_t == 0:
582
+ sqr[0] += x_val * x_val
583
+
584
+ for i_o in T.unroll(tile_n):
585
+ acc[i_o] = T.warp_reduce_sum(acc[i_o])
586
+ if i_t == 0:
587
+ sqr[0] = T.warp_reduce_sum(sqr[0])
588
+
589
+ lane = tid % 32
590
+ warp_id = tid // 32
591
+ num_warps = n_thr // 32
592
+ warp_acc = T.alloc_shared((num_warps, tile_n), T.float32)
593
+ warp_sqr = T.alloc_shared(num_warps, T.float32)
594
+
595
+ if lane == 0:
596
+ for i_o in T.unroll(tile_n):
597
+ warp_acc[warp_id, i_o] = acc[i_o]
598
+ if i_t == 0:
599
+ warp_sqr[warp_id] = sqr[0]
600
+ T.sync_threads()
601
+
602
+ if warp_id == 0:
603
+ if lane < tile_n:
604
+ reduced_acc = T.alloc_var(T.float32, init=0.0)
605
+ for i_w in T.unroll(num_warps):
606
+ reduced_acc += warp_acc[i_w, lane]
607
+ out_idx = i_t * tile_n + lane
608
+ if out_idx < n_out:
609
+ out[i_s, i_n, out_idx] = reduced_acc
610
+ if lane == 0 and i_t == 0:
611
+ reduced_sqr = T.alloc_var(T.float32, init=0.0)
612
+ for i_w in T.unroll(num_warps):
613
+ reduced_sqr += warp_sqr[i_w]
614
+ sqrsum[i_s, i_n] = reduced_sqr
615
+
616
+ if ENABLE_PDL:
617
+ T.pdl_trigger()
618
+
619
+
620
+@tilelang.jit(
621
+ pass_configs=pass_configs,
622
+)
623
+def hc_prenorm_gemm_block_m_tilelang(
624
+ x,
625
+ fn,
626
+ out,
627
+ sqrsum,
628
+ hidden_size: int,
629
+ hc_mult: int = 4,
630
+ n_out: int = 24,
631
+ n_thr: int = 512,
632
+ tile_n: int = 12,
633
+ block_m: int = 2,
634
+) -> tilelang.JITKernel:
635
+ num_tokens = T.dynamic("num_tokens")
636
+ hc_hidden_size = hc_mult * hidden_size
637
+ k_iters = hc_hidden_size // n_thr
638
+ n_tiles = T.ceildiv(n_out, tile_n)
639
+ m_tiles = T.ceildiv(num_tokens, block_m)
640
+
641
+ x: T.Tensor((num_tokens, hc_hidden_size), T.bfloat16) # type: ignore[no-redef, valid-type]
642
+ fn: T.Tensor((n_out, hc_hidden_size), T.float32) # type: ignore[no-redef, valid-type]
643
+ out: T.Tensor((1, num_tokens, n_out), T.float32) # type: ignore[no-redef, valid-type]
644
+ sqrsum: T.Tensor((1, num_tokens), T.float32) # type: ignore[no-redef, valid-type]
645
+
646
+ with T.Kernel(m_tiles, n_tiles, threads=n_thr) as (i_mt, i_t):
647
+ tid = T.get_thread_binding()
648
+ acc = T.alloc_local((block_m, tile_n), T.float32)
649
+ sqr = T.alloc_local((block_m,), T.float32)
650
+ T.clear(acc)
651
+ T.clear(sqr)
652
+
653
+ if ENABLE_PDL:
654
+ T.pdl_sync()
655
+
656
+ for it in T.serial(k_iters):
657
+ i_k = it * n_thr + tid
658
+ fn_val = T.alloc_local((tile_n,), T.float32)
659
+ for i_o in T.unroll(tile_n):
660
+ out_idx = i_t * tile_n + i_o
661
+ if out_idx < n_out:
662
+ fn_val[i_o] = fn[out_idx, i_k]
663
+ else:
664
+ fn_val[i_o] = 0.0
665
+ for i_m in T.unroll(block_m):
666
+ token_idx = i_mt * block_m + i_m
667
+ if token_idx < num_tokens:
668
+ x_val = x[token_idx, i_k]
669
+ for i_o in T.unroll(tile_n):
670
+ acc[i_m, i_o] += x_val * fn_val[i_o]
671
+ if i_t == 0:
672
+ sqr[i_m] += x_val * x_val
673
+
674
+ for i_m in T.unroll(block_m):
675
+ for i_o in T.unroll(tile_n):
676
+ acc[i_m, i_o] = T.warp_reduce_sum(acc[i_m, i_o])
677
+ if i_t == 0:
678
+ sqr[i_m] = T.warp_reduce_sum(sqr[i_m])
679
+
680
+ lane = tid % 32
681
+ warp_id = tid // 32
682
+ num_warps = n_thr // 32
683
+ warp_acc = T.alloc_shared((num_warps, block_m, tile_n), T.float32)
684
+ warp_sqr = T.alloc_shared((num_warps, block_m), T.float32)
685
+
686
+ if lane == 0:
687
+ for i_m in T.unroll(block_m):
688
+ for i_o in T.unroll(tile_n):
689
+ warp_acc[warp_id, i_m, i_o] = acc[i_m, i_o]
690
+ if i_t == 0:
691
+ warp_sqr[warp_id, i_m] = sqr[i_m]
692
+ T.sync_threads()
693
+
694
+ if warp_id == 0:
695
+ for i_m in T.unroll(block_m):
696
+ token_idx = i_mt * block_m + i_m
697
+ if token_idx < num_tokens:
698
+ if lane < tile_n:
699
+ reduced_acc = T.alloc_var(T.float32, init=0.0)
700
+ for i_w in T.unroll(num_warps):
701
+ reduced_acc += warp_acc[i_w, i_m, lane]
702
+ out_idx = i_t * tile_n + lane
703
+ if out_idx < n_out:
704
+ out[0, token_idx, out_idx] = reduced_acc
705
+ if lane == 0 and i_t == 0:
706
+ reduced_sqr = T.alloc_var(T.float32, init=0.0)
707
+ for i_w in T.unroll(num_warps):
708
+ reduced_sqr += warp_sqr[i_w, i_m]
709
+ sqrsum[0, token_idx] = reduced_sqr
710
+
711
+ if ENABLE_PDL:
712
+ T.pdl_trigger()
713
714
715
@tilelang.jit(
530
- pass_configs={
531
- tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True,
532
- tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
533
- tilelang.PassConfigKey.TL_PTXAS_REGISTER_USAGE_LEVEL: 10,
534
- },
716
+ pass_configs=pass_configs,
717
)
718
def hc_head_fuse_tilelang(
719
residual,
@@ -566,7 +748,8 @@ def hc_head_fuse_tilelang(
748
out: T.Tensor[[num_tokens, hidden_size], T.bfloat16] # type: ignore[no-redef,valid-type]
749
750
with T.Kernel(num_tokens, threads=n_thr) as i:
569
- T.pdl_sync()
751
+ if ENABLE_PDL:
752
+ T.pdl_sync()
753
754
# ------------------------------------------------------------------
755
# Pass 1 – for each residual channel m_c and h_block:
@@ -624,4 +807,5 @@ def hc_head_fuse_tilelang(
807
808
T.copy(ol, out[i, i0_h * h_block], disable_tma=True)
809
627
- T.pdl_trigger()
810
+ if ENABLE_PDL:
811
+ T.pdl_trigger()
vllm/model_executor/kernels/mhc/tilelang.py
+145
-27
@@ -5,6 +5,88 @@ import torch
5
from vllm.utils.torch_utils import direct_register_custom_op
6
7
8
+def _torch_hc_prenorm_gemm(
9
+ x: torch.Tensor,
10
+ fn: torch.Tensor,
11
+ out: torch.Tensor,
12
+ sqrsum: torch.Tensor,
13
+) -> None:
14
+ assert out.shape[0] == 1
15
+ assert sqrsum.shape[0] == 1
16
+ x_float = x.float()
17
+ out[0].copy_(x_float @ fn.t())
18
+ sqrsum[0].copy_(x_float.square().sum(dim=-1))
19
+
20
+
21
+def _tilelang_hc_prenorm_gemm(
22
+ x: torch.Tensor,
23
+ fn: torch.Tensor,
24
+ out: torch.Tensor,
25
+ sqrsum: torch.Tensor,
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:
32
+ from vllm._tilelang_ops 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,
55
+ )
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(
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,
74
+ )
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
+ )
88
+
89
+
90
def mhc_pre_tilelang(
91
residual: torch.Tensor,
92
fn: torch.Tensor,
@@ -80,10 +162,16 @@ def mhc_pre_tilelang(
162
residual_flat = residual.view(-1, hc_mult, hidden_size)
163
num_tokens = residual_flat.shape[0]
164
83
- # these numbers are from deepgemm kernel impl
84
- block_k = 64
85
- block_m = 64
86
- n_splits = compute_num_split(block_k, hc_hidden_size, cdiv(num_tokens, block_m))
165
+ from vllm.utils.deep_gemm import is_deep_gemm_supported
166
+
167
+ use_deep_gemm = is_deep_gemm_supported()
168
+ if use_deep_gemm:
169
+ # these numbers are from deepgemm kernel impl
170
+ block_k = 64
171
+ block_m = 64
172
+ n_splits = compute_num_split(block_k, hc_hidden_size, cdiv(num_tokens, block_m))
173
+ else:
174
+ n_splits = 1
175
176
post_mix = torch.empty(
177
num_tokens, hc_mult, dtype=torch.float32, device=residual.device
@@ -102,13 +190,24 @@ def mhc_pre_tilelang(
190
n_splits, num_tokens, dtype=torch.float32, device=residual.device
191
)
192
105
- tf32_hc_prenorm_gemm(
106
- residual_flat.view(num_tokens, hc_mult * hidden_size),
107
- fn,
108
- gemm_out_mul,
109
- gemm_out_sqrsum,
110
- n_splits,
111
- )
193
+ residual_2d = residual_flat.view(num_tokens, hc_mult * hidden_size)
194
+ if use_deep_gemm:
195
+ tf32_hc_prenorm_gemm(
196
+ residual_2d,
197
+ fn,
198
+ gemm_out_mul,
199
+ gemm_out_sqrsum,
200
+ n_splits,
201
+ )
202
+ else:
203
+ _tilelang_hc_prenorm_gemm(
204
+ residual_2d,
205
+ fn,
206
+ gemm_out_mul,
207
+ gemm_out_sqrsum,
208
+ hidden_size,
209
+ hc_mult,
210
+ )
211
212
if norm_weight is None:
213
mhc_pre_big_fuse_tilelang(
@@ -304,16 +403,24 @@ def mhc_fused_post_pre_tilelang(
403
post_layer_mix_flat = post_layer_mix.view(num_tokens, hc_mult)
404
comb_res_mix_flat = comb_res_mix.view(num_tokens, hc_mult, hc_mult)
405
307
- fma_token_threshold = 16
308
- if num_tokens <= fma_token_threshold:
406
+ from vllm.utils.deep_gemm import is_deep_gemm_supported
407
+
408
+ use_deep_gemm = is_deep_gemm_supported()
409
+ use_small_fma = num_tokens <= 16
410
+ if use_small_fma:
411
# TODO(gnovack): investigate autotuning these heuristics
412
tile_n = 2 if num_tokens < 8 else 3
413
n_splits = 8 if (num_tokens < 8 and hidden_size <= 4096) else 4
414
else:
313
- # these number are from deepgemm kernel impl
314
- block_k = 64
315
- block_m = 64
316
- n_splits = compute_num_split(block_k, hc_hidden_size, cdiv(num_tokens, block_m))
415
+ if use_deep_gemm:
416
+ # these number are from deepgemm kernel impl
417
+ block_k = 64
418
+ block_m = 64
419
+ n_splits = compute_num_split(
420
+ block_k, hc_hidden_size, cdiv(num_tokens, block_m)
421
+ )
422
+ else:
423
+ n_splits = 1
424
425
gemm_out_mul = torch.empty(
426
n_splits,
@@ -348,7 +455,7 @@ def mhc_fused_post_pre_tilelang(
455
device=residual.device,
456
)
457
351
- if num_tokens <= fma_token_threshold:
458
+ if use_small_fma:
459
mhc_fused_tilelang(
460
comb_res_mix_flat,
461
residual_flat,
@@ -375,15 +482,26 @@ def mhc_fused_post_pre_tilelang(
482
residual.shape[-1],
483
)
484
378
- from vllm.utils.deep_gemm import tf32_hc_prenorm_gemm
379
-
380
- tf32_hc_prenorm_gemm(
381
- residual_cur.view(num_tokens, hc_mult * hidden_size),
382
- fn,
383
- gemm_out_mul,
384
- gemm_out_sqrsum,
385
- n_splits,
386
- )
485
+ residual_cur_2d = residual_cur.view(num_tokens, hc_mult * hidden_size)
486
+ if use_deep_gemm:
487
+ from vllm.utils.deep_gemm import tf32_hc_prenorm_gemm
488
+
489
+ tf32_hc_prenorm_gemm(
490
+ residual_cur_2d,
491
+ fn,
492
+ gemm_out_mul,
493
+ gemm_out_sqrsum,
494
+ n_splits,
495
+ )
496
+ else:
497
+ _tilelang_hc_prenorm_gemm(
498
+ residual_cur_2d,
499
+ fn,
500
+ gemm_out_mul,
501
+ gemm_out_sqrsum,
502
+ hidden_size,
503
+ hc_mult,
504
+ )
505
506
if norm_weight is None:
507
mhc_pre_big_fuse_tilelang(
vllm/model_executor/layers/mhc.py
+126
-20
@@ -3,8 +3,12 @@
3
import torch
4
5
# this import will also register the custom ops
6
+# import vllm.model_executor.kernels.mhc # noqa: F401
7
import vllm.model_executor.kernels.mhc as mhc_kernels
8
from vllm.model_executor.custom_op import CustomOp
9
+from vllm.utils.import_utils import has_tilelang
10
+
11
+HAS_TILELANG = has_tilelang()
12
13
14
# --8<-- [start:mhc_pre]
@@ -85,6 +89,52 @@ class MHCPreOp(CustomOp):
89
# sinkhorn_repeat,
90
# )
91
# else:
92
+ if HAS_TILELANG:
93
+ return torch.ops.vllm.mhc_pre_tilelang(
94
+ residual,
95
+ fn,
96
+ hc_scale,
97
+ hc_base,
98
+ rms_eps,
99
+ hc_pre_eps,
100
+ hc_sinkhorn_eps,
101
+ hc_post_mult_value,
102
+ sinkhorn_repeat,
103
+ n_splits,
104
+ norm_weight,
105
+ norm_eps,
106
+ )
107
+ else:
108
+ return self.forward_native(
109
+ residual,
110
+ fn,
111
+ hc_scale,
112
+ hc_base,
113
+ rms_eps,
114
+ hc_pre_eps,
115
+ hc_sinkhorn_eps,
116
+ hc_post_mult_value,
117
+ sinkhorn_repeat,
118
+ n_splits,
119
+ norm_weight,
120
+ norm_eps,
121
+ )
122
+
123
+ def forward_native(
124
+ self,
125
+ residual: torch.Tensor,
126
+ fn: torch.Tensor,
127
+ hc_scale: torch.Tensor,
128
+ hc_base: torch.Tensor,
129
+ rms_eps: float,
130
+ hc_pre_eps: float,
131
+ hc_sinkhorn_eps: float,
132
+ hc_post_mult_value: float,
133
+ sinkhorn_repeat: int,
134
+ n_splits: int = 1,
135
+ norm_weight: torch.Tensor | None = None,
136
+ norm_eps: float = 0.0,
137
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
138
return mhc_kernels.mhc_pre_torch(
139
residual,
140
fn,
@@ -97,9 +147,6 @@ class MHCPreOp(CustomOp):
147
sinkhorn_repeat,
148
)
149
100
- def forward_native(self, *args, **kwargs):
101
- raise NotImplementedError("Native implementation of mhc_pre is not available")
102
-
150
151
# --8<-- [start:mhc_post]
152
@CustomOp.register("mhc_post")
@@ -147,6 +194,20 @@ class MHCPostOp(CustomOp):
194
# comb_res_mix,
195
# )
196
# else:
197
+ if HAS_TILELANG:
198
+ return torch.ops.vllm.mhc_post_tilelang(
199
+ x, residual, post_layer_mix, comb_res_mix
200
+ )
201
+ else:
202
+ return self.forward_native(x, residual, post_layer_mix, comb_res_mix)
203
+
204
+ def forward_native(
205
+ self,
206
+ x: torch.Tensor,
207
+ residual: torch.Tensor,
208
+ post_layer_mix: torch.Tensor,
209
+ comb_res_mix: torch.Tensor,
210
+ ) -> torch.Tensor:
211
return mhc_kernels.mhc_post_torch(
212
x,
213
residual,
@@ -154,9 +215,6 @@ class MHCPostOp(CustomOp):
215
comb_res_mix,
216
)
217
157
- def forward_native(self, *args, **kwargs):
158
- raise NotImplementedError("Native implementation of mhc_post is not available")
159
-
218
219
# --8<-- [start:hc_head]
220
@CustomOp.register("hc_head")
@@ -220,17 +278,32 @@ class HCHeadOp(CustomOp):
278
out = torch.empty(
279
num_tokens, hidden_size, dtype=torch.bfloat16, device=hidden_states.device
280
)
223
- torch.ops.vllm.hc_head_triton(
224
- hs_flat,
225
- hc_fn,
226
- hc_scale,
227
- hc_base,
228
- out,
229
- hidden_size,
230
- rms_norm_eps,
231
- hc_eps,
232
- hc_mult,
233
- )
281
+
282
+ if HAS_TILELANG:
283
+ torch.ops.vllm.hc_head_fused_kernel_tilelang(
284
+ hs_flat,
285
+ hc_fn,
286
+ hc_scale,
287
+ hc_base,
288
+ out,
289
+ hidden_size,
290
+ rms_norm_eps,
291
+ hc_eps,
292
+ hc_mult,
293
+ )
294
+ else:
295
+ torch.ops.vllm.hc_head_triton(
296
+ hs_flat,
297
+ hc_fn,
298
+ hc_scale,
299
+ hc_base,
300
+ out,
301
+ hidden_size,
302
+ rms_norm_eps,
303
+ hc_eps,
304
+ hc_mult,
305
+ )
306
+
307
return out.view(*outer_shape, hidden_size)
308
309
def forward_native(self, *args, **kwargs):
@@ -290,9 +363,42 @@ class MHCFusedPostPreOp(CustomOp):
363
norm_eps,
364
)
365
293
- def forward_hip(self, *args, **kwargs):
294
- raise NotImplementedError(
295
- "Hip implementation of mhc_fused_post_pre is not available"
366
+ def forward_hip(
367
+ self,
368
+ x: torch.Tensor,
369
+ residual: torch.Tensor,
370
+ post_layer_mix: torch.Tensor,
371
+ comb_res_mix: torch.Tensor,
372
+ fn: torch.Tensor,
373
+ hc_scale: torch.Tensor,
374
+ hc_base: torch.Tensor,
375
+ rms_eps: float,
376
+ hc_pre_eps: float,
377
+ hc_sinkhorn_eps: float,
378
+ hc_post_mult_value: float,
379
+ sinkhorn_repeat: int,
380
+ n_splits: int = 1,
381
+ tile_n: int = 1,
382
+ norm_weight: torch.Tensor | None = None,
383
+ norm_eps: float = 0.0,
384
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
385
+ return torch.ops.vllm.mhc_fused_post_pre_tilelang(
386
+ x,
387
+ residual,
388
+ post_layer_mix,
389
+ comb_res_mix,
390
+ fn,
391
+ hc_scale,
392
+ hc_base,
393
+ rms_eps,
394
+ hc_pre_eps,
395
+ hc_sinkhorn_eps,
396
+ hc_post_mult_value,
397
+ sinkhorn_repeat,
398
+ n_splits,
399
+ tile_n,
400
+ norm_weight,
401
+ norm_eps,
402
)
403
404
def forward_native(self, *args, **kwargs):
vllm/models/deepseek_v4/amd/model.py
+11
-7
@@ -54,6 +54,7 @@ from vllm.models.deepseek_v4.attention import (
54
)
55
from vllm.platforms import current_platform
56
from vllm.sequence import IntermediateTensors
57
+from vllm.utils.import_utils import has_tilelang
58
59
60
class DeepseekV4MLP(nn.Module):
@@ -473,6 +474,7 @@ class DeepseekV4DecoderLayer(nn.Module):
474
self.mhc_pre = MHCPreOp()
475
self.mhc_post = MHCPostOp()
476
self.mhc_fused_post_pre = MHCFusedPostPreOp()
477
+ self.has_tilelang = has_tilelang()
478
479
def hc_pre(
480
self,
@@ -503,7 +505,7 @@ class DeepseekV4DecoderLayer(nn.Module):
505
):
506
return self.mhc_post(x, residual, post, comb)
507
506
- def _forward_cuda(
508
+ def _forward_fused_post_pre(
509
self,
510
x: torch.Tensor,
511
positions: torch.Tensor,
@@ -555,7 +557,7 @@ class DeepseekV4DecoderLayer(nn.Module):
557
x = self.ffn(x, input_ids)
558
return x, residual, post_mix, res_mix
559
558
- def _forward_rocm(
560
+ def _forward_unfused_post_pre(
561
self,
562
x: torch.Tensor,
563
positions: torch.Tensor,
@@ -594,12 +596,13 @@ class DeepseekV4DecoderLayer(nn.Module):
596
) -> tuple[
597
torch.Tensor, torch.Tensor | None, torch.Tensor | None, torch.Tensor | None
598
]:
597
- if current_platform.is_rocm():
598
- return self._forward_rocm(
599
+ if not self.has_tilelang:
600
+ return self._forward_unfused_post_pre(
601
x, positions, input_ids, post_mix, res_mix, residual
602
)
601
-
602
- return self._forward_cuda(x, positions, input_ids, post_mix, res_mix, residual)
603
+ return self._forward_fused_post_pre(
604
+ x, positions, input_ids, post_mix, res_mix, residual
605
+ )
606
607
608
@support_torch_compile
@@ -682,6 +685,7 @@ class DeepseekV4Model(nn.Module):
685
requires_grad=False,
686
)
687
self.hc_head_op = HCHeadOp()
688
+ self.has_tilelang = has_tilelang()
689
# Pre-hc_head residual stream buffer for the MTP draft. Stable
690
# address (outside the cudagraph pool) so the copy_ in forward()
691
# refreshes it correctly across captured shapes.
@@ -748,7 +752,7 @@ class DeepseekV4Model(nn.Module):
752
res_mix,
753
residual,
754
)
751
- if layer is not None and current_platform.is_cuda():
755
+ if layer is not None and self.has_tilelang:
756
hidden_states = layer.hc_post(hidden_states, residual, post_mix, res_mix)
757
758
if not get_pp_group().is_last_rank:
vllm/models/deepseek_v4/amd/mtp.py
+3
-1
@@ -39,6 +39,7 @@ from vllm.model_executor.models.deepseek_v2 import get_spec_layer_idx_from_weigh
39
from vllm.model_executor.models.utils import maybe_prefix
40
from vllm.platforms import current_platform
41
from vllm.sequence import IntermediateTensors
42
+from vllm.utils.import_utils import has_tilelang
43
44
from .model import DeepseekV4DecoderLayer
45
@@ -118,6 +119,7 @@ class DeepSeekV4MultiTokenPredictorLayer(nn.Module):
119
)
120
121
self.hc_head_op = HCHeadOp()
122
+ self.has_tilelang = has_tilelang()
123
124
def forward(
125
self,
@@ -144,7 +146,7 @@ class DeepSeekV4MultiTokenPredictorLayer(nn.Module):
146
hidden_states, residual, post_mix, res_mix = self.mtp_block(
147
positions=positions, x=hidden_states, input_ids=None
148
)
147
- if current_platform.is_cuda():
149
+ if self.has_tilelang:
150
hidden_states = self.mtp_block.hc_post(
151
hidden_states, residual, post_mix, res_mix
152
)
vllm/platforms/cuda.py
+9
@@ -592,6 +592,15 @@ class CudaPlatformBase(Platform):
592
default, rms_norm=rms_norm, fused_add_rms_norm=rms_norm
593
)
594
595
+ @classmethod
596
+ def is_arch_support_pdl(cls) -> bool:
597
+ try:
598
+ device = torch.cuda.current_device()
599
+ major, _ = torch.cuda.get_device_capability(device)
600
+ except Exception:
601
+ return False
602
+ return major >= 9
603
+
604
605
# NVML utils
606
# Note that NVML is not affected by `CUDA_VISIBLE_DEVICES`,
vllm/platforms/interface.py
+7
@@ -1016,6 +1016,13 @@ class Platform:
1016
# Native always used by default. Platforms can override this behavior.
1017
return IrOpPriorityConfig.with_default(["native"])
1018
1019
+ @classmethod
1020
+ def is_arch_support_pdl(cls) -> bool:
1021
+ """
1022
+ Does the current platform support PDL (Programmatic Dependent Launch)?
1023
+ """
1024
+ return False
1025
+
1026
1027
class UnspecifiedPlatform(Platform):
1028
_enum = PlatformEnum.UNSPECIFIED
vllm/utils/import_utils.py
+1
@@ -430,6 +430,7 @@ def has_triton_kernels() -> bool:
430
return is_available
431
432
433
+@cache
434
def has_tilelang() -> bool:
435
"""Whether the optional `tilelang` package is available."""
436
return _has_module("tilelang")