[Kernel][MoE] Tune block-FP8 fused MoE for low-batch decode (#46642)
Signed-off-by: mgoin <mgoin64@gmail.com>
Michael Goin committed
Jun 25, 2026 at 13:38 UTC
e8e7b592d11d3ea5d5cdb78ee0f1ab4c5b440667
10 files changed
+110
-88
benchmarks/kernels/benchmark_moe.py
+10
-7
@@ -391,16 +391,19 @@ def get_configs_compute_bound(use_fp16, block_quant_shape) -> list[dict[str, int
391
config = dict(zip(keys, config_values))
392
configs.append(config)
393
394
- # Remove configs that are not compatible with fp8 block quantization
395
- # BLOCK_SIZE_K must be a multiple of block_k
396
- # BLOCK_SIZE_N must be a multiple of block_n
394
+ # Drop configs incompatible with fp8 block quantization. A tile must align
395
+ # to the quant-block scale grid, i.e. tile and block must divide one
396
+ # another. The kernel indexes scales per element (offs_bn // group_n,
397
+ # k_start // group_k), so a tile narrower than the block (e.g. N=64 with
398
+ # block_n=128) is valid -- and often faster at small batch. An exact
399
+ # multiple was required before, which dropped those smaller tiles entirely.
400
if block_quant_shape is not None and not use_fp16:
401
block_n, block_k = block_quant_shape[0], block_quant_shape[1]
402
for config in configs[:]:
400
- if (
401
- config["BLOCK_SIZE_K"] % block_k != 0
402
- or config["BLOCK_SIZE_N"] % block_n != 0
403
- ):
403
+ bn, bk = config["BLOCK_SIZE_N"], config["BLOCK_SIZE_K"]
404
+ n_aligned = bn % block_n == 0 or block_n % bn == 0
405
+ k_aligned = bk % block_k == 0 or block_k % bk == 0
406
+ if not (n_aligned and k_aligned):
407
configs.remove(config)
408
return configs
409
vllm/model_executor/layers/fused_moe/configs/E=128,N=512,device_name=NVIDIA_H200,dtype=fp8_w8a8,block_shape=[128,128].json
+10
-10
@@ -2,35 +2,35 @@
2
"triton_version": "3.5.0",
3
"1": {
4
"BLOCK_SIZE_M": 16,
5
- "BLOCK_SIZE_N": 128,
5
+ "BLOCK_SIZE_N": 64,
6
"BLOCK_SIZE_K": 128,
7
"GROUP_SIZE_M": 1,
8
"num_warps": 4,
9
"num_stages": 4
10
},
11
"2": {
12
- "BLOCK_SIZE_M": 16,
13
- "BLOCK_SIZE_N": 128,
12
+ "BLOCK_SIZE_M": 8,
13
+ "BLOCK_SIZE_N": 64,
14
"BLOCK_SIZE_K": 128,
15
"GROUP_SIZE_M": 1,
16
"num_warps": 4,
17
"num_stages": 4
18
},
19
"4": {
20
- "BLOCK_SIZE_M": 16,
21
- "BLOCK_SIZE_N": 128,
20
+ "BLOCK_SIZE_M": 8,
21
+ "BLOCK_SIZE_N": 64,
22
"BLOCK_SIZE_K": 128,
23
- "GROUP_SIZE_M": 64,
23
+ "GROUP_SIZE_M": 1,
24
"num_warps": 4,
25
- "num_stages": 4
25
+ "num_stages": 3
26
},
27
"8": {
28
"BLOCK_SIZE_M": 16,
29
- "BLOCK_SIZE_N": 128,
29
+ "BLOCK_SIZE_N": 64,
30
"BLOCK_SIZE_K": 128,
31
- "GROUP_SIZE_M": 64,
31
+ "GROUP_SIZE_M": 16,
32
"num_warps": 4,
33
- "num_stages": 4
33
+ "num_stages": 3
34
},
35
"16": {
36
"BLOCK_SIZE_M": 16,
vllm/model_executor/layers/fused_moe/configs/E=128,N=768,device_name=NVIDIA_H200,dtype=fp8_w8a8,block_shape=[128,128].json
+9
-9
@@ -1,17 +1,17 @@
1
{
2
"1": {
3
- "BLOCK_SIZE_M": 64,
3
+ "BLOCK_SIZE_M": 16,
4
"BLOCK_SIZE_N": 64,
5
"BLOCK_SIZE_K": 128,
6
"GROUP_SIZE_M": 1,
7
"num_warps": 4,
8
- "num_stages": 3
8
+ "num_stages": 5
9
},
10
"2": {
11
- "BLOCK_SIZE_M": 64,
12
- "BLOCK_SIZE_N": 128,
11
+ "BLOCK_SIZE_M": 16,
12
+ "BLOCK_SIZE_N": 64,
13
"BLOCK_SIZE_K": 128,
14
- "GROUP_SIZE_M": 32,
14
+ "GROUP_SIZE_M": 1,
15
"num_warps": 4,
16
"num_stages": 4
17
},
@@ -25,11 +25,11 @@
25
},
26
"8": {
27
"BLOCK_SIZE_M": 16,
28
- "BLOCK_SIZE_N": 128,
28
+ "BLOCK_SIZE_N": 64,
29
"BLOCK_SIZE_K": 128,
30
- "GROUP_SIZE_M": 16,
30
+ "GROUP_SIZE_M": 1,
31
"num_warps": 4,
32
- "num_stages": 5
32
+ "num_stages": 3
33
},
34
"16": {
35
"BLOCK_SIZE_M": 16,
@@ -143,4 +143,4 @@
143
"num_warps": 4,
144
"num_stages": 4
145
}
146
-}
\ No newline at end of file
146
+}
vllm/model_executor/layers/fused_moe/configs/E=160,N=640,device_name=NVIDIA_H100,dtype=fp8_w8a8,block_shape=[128,128].json
+7
-7
@@ -1,7 +1,7 @@
1
{
2
"1": {
3
"BLOCK_SIZE_M": 16,
4
- "BLOCK_SIZE_N": 128,
4
+ "BLOCK_SIZE_N": 64,
5
"BLOCK_SIZE_K": 128,
6
"GROUP_SIZE_M": 1,
7
"num_warps": 4,
@@ -9,11 +9,11 @@
9
},
10
"2": {
11
"BLOCK_SIZE_M": 16,
12
- "BLOCK_SIZE_N": 128,
12
+ "BLOCK_SIZE_N": 64,
13
"BLOCK_SIZE_K": 128,
14
"GROUP_SIZE_M": 1,
15
"num_warps": 4,
16
- "num_stages": 3
16
+ "num_stages": 4
17
},
18
"4": {
19
"BLOCK_SIZE_M": 16,
@@ -24,12 +24,12 @@
24
"num_stages": 3
25
},
26
"8": {
27
- "BLOCK_SIZE_M": 16,
28
- "BLOCK_SIZE_N": 128,
27
+ "BLOCK_SIZE_M": 32,
28
+ "BLOCK_SIZE_N": 64,
29
"BLOCK_SIZE_K": 128,
30
- "GROUP_SIZE_M": 16,
30
+ "GROUP_SIZE_M": 1,
31
"num_warps": 4,
32
- "num_stages": 5
32
+ "num_stages": 3
33
},
34
"16": {
35
"BLOCK_SIZE_M": 16,
vllm/model_executor/layers/fused_moe/configs/E=256,N=256,device_name=NVIDIA_H200,dtype=fp8_w8a8,block_shape=[128,128].json
+13
-13
@@ -1,35 +1,35 @@
1
{
2
"1": {
3
- "BLOCK_SIZE_M": 16,
4
- "BLOCK_SIZE_N": 128,
3
+ "BLOCK_SIZE_M": 8,
4
+ "BLOCK_SIZE_N": 32,
5
"BLOCK_SIZE_K": 128,
6
- "GROUP_SIZE_M": 16,
6
+ "GROUP_SIZE_M": 1,
7
"num_warps": 4,
8
"num_stages": 4
9
},
10
"2": {
11
- "BLOCK_SIZE_M": 16,
12
- "BLOCK_SIZE_N": 128,
11
+ "BLOCK_SIZE_M": 8,
12
+ "BLOCK_SIZE_N": 32,
13
"BLOCK_SIZE_K": 128,
14
- "GROUP_SIZE_M": 16,
14
+ "GROUP_SIZE_M": 1,
15
"num_warps": 4,
16
- "num_stages": 4
16
+ "num_stages": 5
17
},
18
"4": {
19
- "BLOCK_SIZE_M": 16,
20
- "BLOCK_SIZE_N": 128,
19
+ "BLOCK_SIZE_M": 8,
20
+ "BLOCK_SIZE_N": 64,
21
"BLOCK_SIZE_K": 128,
22
- "GROUP_SIZE_M": 64,
22
+ "GROUP_SIZE_M": 1,
23
"num_warps": 4,
24
"num_stages": 4
25
},
26
"8": {
27
"BLOCK_SIZE_M": 16,
28
- "BLOCK_SIZE_N": 128,
28
+ "BLOCK_SIZE_N": 64,
29
"BLOCK_SIZE_K": 128,
30
- "GROUP_SIZE_M": 16,
30
+ "GROUP_SIZE_M": 32,
31
"num_warps": 4,
32
- "num_stages": 4
32
+ "num_stages": 3
33
},
34
"16": {
35
"BLOCK_SIZE_M": 16,
vllm/model_executor/layers/fused_moe/configs/E=256,N=384,device_name=NVIDIA_H100_80GB_HBM3,dtype=fp8_w8a8,block_shape=[128,128].json
+7
-7
@@ -2,19 +2,19 @@
2
"triton_version": "3.5.0",
3
"1": {
4
"BLOCK_SIZE_M": 16,
5
- "BLOCK_SIZE_N": 128,
5
+ "BLOCK_SIZE_N": 64,
6
"BLOCK_SIZE_K": 128,
7
"GROUP_SIZE_M": 1,
8
"num_warps": 4,
9
- "num_stages": 4
9
+ "num_stages": 5
10
},
11
"2": {
12
- "BLOCK_SIZE_M": 16,
13
- "BLOCK_SIZE_N": 128,
12
+ "BLOCK_SIZE_M": 8,
13
+ "BLOCK_SIZE_N": 64,
14
"BLOCK_SIZE_K": 128,
15
"GROUP_SIZE_M": 1,
16
"num_warps": 4,
17
- "num_stages": 4
17
+ "num_stages": 5
18
},
19
"4": {
20
"BLOCK_SIZE_M": 16,
@@ -34,11 +34,11 @@
34
},
35
"16": {
36
"BLOCK_SIZE_M": 16,
37
- "BLOCK_SIZE_N": 128,
37
+ "BLOCK_SIZE_N": 64,
38
"BLOCK_SIZE_K": 128,
39
"GROUP_SIZE_M": 1,
40
"num_warps": 4,
41
- "num_stages": 5
41
+ "num_stages": 3
42
},
43
"24": {
44
"BLOCK_SIZE_M": 16,
vllm/model_executor/layers/fused_moe/configs/E=256,N=512,device_name=NVIDIA_H200,dtype=fp8_w8a8,block_shape=[128,128].json
+7
-7
@@ -1,20 +1,20 @@
1
{
2
"triton_version": "3.6.0",
3
"1": {
4
- "BLOCK_SIZE_M": 16,
5
- "BLOCK_SIZE_N": 128,
4
+ "BLOCK_SIZE_M": 8,
5
+ "BLOCK_SIZE_N": 64,
6
"BLOCK_SIZE_K": 128,
7
"GROUP_SIZE_M": 1,
8
"num_warps": 4,
9
- "num_stages": 3
9
+ "num_stages": 4
10
},
11
"2": {
12
"BLOCK_SIZE_M": 16,
13
- "BLOCK_SIZE_N": 128,
13
+ "BLOCK_SIZE_N": 64,
14
"BLOCK_SIZE_K": 128,
15
- "GROUP_SIZE_M": 16,
16
- "num_warps": 8,
17
- "num_stages": 4
15
+ "GROUP_SIZE_M": 1,
16
+ "num_warps": 4,
17
+ "num_stages": 3
18
},
19
"4": {
20
"BLOCK_SIZE_M": 16,
vllm/model_executor/layers/fused_moe/configs/E=384,N=128,device_name=NVIDIA_H200,dtype=fp8_w8a8,block_shape=[128,128].json
+12
-12
@@ -1,23 +1,23 @@
1
{
2
"1": {
3
"BLOCK_SIZE_M": 16,
4
- "BLOCK_SIZE_N": 128,
4
+ "BLOCK_SIZE_N": 64,
5
"BLOCK_SIZE_K": 128,
6
"GROUP_SIZE_M": 1,
7
"num_warps": 4,
8
"num_stages": 3
9
},
10
"2": {
11
- "BLOCK_SIZE_M": 16,
12
- "BLOCK_SIZE_N": 128,
11
+ "BLOCK_SIZE_M": 8,
12
+ "BLOCK_SIZE_N": 64,
13
"BLOCK_SIZE_K": 128,
14
"GROUP_SIZE_M": 1,
15
"num_warps": 4,
16
- "num_stages": 4
16
+ "num_stages": 5
17
},
18
"4": {
19
- "BLOCK_SIZE_M": 16,
20
- "BLOCK_SIZE_N": 128,
19
+ "BLOCK_SIZE_M": 8,
20
+ "BLOCK_SIZE_N": 64,
21
"BLOCK_SIZE_K": 128,
22
"GROUP_SIZE_M": 1,
23
"num_warps": 4,
@@ -25,19 +25,19 @@
25
},
26
"8": {
27
"BLOCK_SIZE_M": 16,
28
- "BLOCK_SIZE_N": 128,
28
+ "BLOCK_SIZE_N": 64,
29
"BLOCK_SIZE_K": 128,
30
- "GROUP_SIZE_M": 1,
30
+ "GROUP_SIZE_M": 32,
31
"num_warps": 4,
32
- "num_stages": 4
32
+ "num_stages": 3
33
},
34
"16": {
35
"BLOCK_SIZE_M": 16,
36
- "BLOCK_SIZE_N": 128,
36
+ "BLOCK_SIZE_N": 64,
37
"BLOCK_SIZE_K": 128,
38
- "GROUP_SIZE_M": 1,
38
+ "GROUP_SIZE_M": 16,
39
"num_warps": 4,
40
- "num_stages": 4
40
+ "num_stages": 3
41
},
42
"24": {
43
"BLOCK_SIZE_M": 16,
vllm/model_executor/layers/fused_moe/configs/E=512,N=256,device_name=NVIDIA_H100_80GB_HBM3,dtype=fp8_w8a8,block_shape=[128,128].json
+9
-9
@@ -2,27 +2,27 @@
2
"triton_version": "3.4.0",
3
"1": {
4
"BLOCK_SIZE_M": 16,
5
- "BLOCK_SIZE_N": 128,
5
+ "BLOCK_SIZE_N": 64,
6
"BLOCK_SIZE_K": 128,
7
"GROUP_SIZE_M": 1,
8
"num_warps": 4,
9
- "num_stages": 4
9
+ "num_stages": 5
10
},
11
"2": {
12
- "BLOCK_SIZE_M": 16,
13
- "BLOCK_SIZE_N": 128,
12
+ "BLOCK_SIZE_M": 8,
13
+ "BLOCK_SIZE_N": 64,
14
"BLOCK_SIZE_K": 128,
15
"GROUP_SIZE_M": 1,
16
"num_warps": 4,
17
"num_stages": 4
18
},
19
"4": {
20
- "BLOCK_SIZE_M": 16,
21
- "BLOCK_SIZE_N": 128,
20
+ "BLOCK_SIZE_M": 8,
21
+ "BLOCK_SIZE_N": 64,
22
"BLOCK_SIZE_K": 128,
23
"GROUP_SIZE_M": 1,
24
"num_warps": 4,
25
- "num_stages": 3
25
+ "num_stages": 4
26
},
27
"8": {
28
"BLOCK_SIZE_M": 16,
@@ -36,9 +36,9 @@
36
"BLOCK_SIZE_M": 16,
37
"BLOCK_SIZE_N": 128,
38
"BLOCK_SIZE_K": 128,
39
- "GROUP_SIZE_M": 1,
39
+ "GROUP_SIZE_M": 32,
40
"num_warps": 4,
41
- "num_stages": 4
41
+ "num_stages": 3
42
},
43
"24": {
44
"BLOCK_SIZE_M": 16,
vllm/model_executor/layers/fused_moe/fused_moe.py
+26
-7
@@ -1257,19 +1257,38 @@ def get_default_config(
1257
num_stages_rocm = 2
1258
1259
if dtype == "fp8_w8a8" and block_shape is not None:
1260
- # Block-wise quant: tile sizes are constrained by block_shape.
1261
- # Use a small M tile for decode-like batches where tokens are
1262
- # spread thin across experts. Larger batches benefit from
1263
- # GROUP_SIZE_M > 1 because the per-block scales add memory
1264
- # traffic that benefits from L2 tile reuse.
1260
+ # Block-wise quant. Use a small M tile for decode-like batches where
1261
+ # tokens are spread thin across experts. Larger batches benefit from
1262
+ # GROUP_SIZE_M > 1 because the per-block scales add memory traffic
1263
+ # that benefits from L2 tile reuse.
1264
+ #
1265
+ # BLOCK_SIZE_N need not equal block_shape[0]: the kernel indexes block
1266
+ # scales per N element (offs_bn // group_n), so any N tile dividing the
1267
+ # quant block is valid. At decode a 128-wide N tile leaves the gate-up
1268
+ # GEMM SM-bound; a 64-wide tile exposes ~2x the thread blocks, and the
1269
+ # swap-AB kernel keeps it efficient on Hopper down to the smallest
1270
+ # batches, so prefer N=64 through low batch sizes. CUDA only (validated
1271
+ # on NVIDIA); ROCm keeps its prior tile/pipeline sizes.
1272
+ if current_platform.is_rocm():
1273
+ block_n = block_shape[0]
1274
+ num_stages = num_stages_rocm
1275
+ elif M <= 8 and block_shape[0] % 64 == 0:
1276
+ block_n = 64
1277
+ # The smallest batches are memory-latency bound, so a deeper
1278
+ # pipeline hides the weight loads; by M=8 it turns occupancy/SMEM
1279
+ # bound and the extra stages hurt.
1280
+ num_stages = 4 if M <= 4 else 3
1281
+ else:
1282
+ block_n = block_shape[0]
1283
+ num_stages = 3
1284
config = {
1285
"BLOCK_SIZE_M": 16 if M <= 64 else 64,
1267
- "BLOCK_SIZE_N": block_shape[0],
1286
+ "BLOCK_SIZE_N": block_n,
1287
"BLOCK_SIZE_K": block_shape[1],
1288
"GROUP_SIZE_M": 1 if M <= 16 else 32,
1289
"SPLIT_K": 1,
1290
"num_warps": 4,
1272
- "num_stages": 3 if not current_platform.is_rocm() else num_stages_rocm,
1291
+ "num_stages": num_stages,
1292
}
1293
elif dtype in ["int4_w4a16", "int8_w8a16"] and block_shape is not None:
1294
# moe wna16 kernels