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 12, 18:30 UTC
Add TRTLLM MoE NVFP4 kernel to CompressedTensorsW4A4MoeMethod (#28892)
Signed-off-by: mingyuanm <mingyuanm@nvidia.com> Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: mgoin <mgoin64@gmail.com>
Mingyuan Ma committed
Nov 21, 2025 at 08:54 UTC
b4c8fbaae2592501f442817f86e32cfeb795d81f
3 files changed
+358
-210
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe.py
+122
-20
@@ -8,6 +8,7 @@ from enum import Enum
8
import torch
9
from compressed_tensors import CompressionFormat
10
from compressed_tensors.quantization import ActivationOrdering, QuantizationStrategy
11
+from torch.nn.parameter import Parameter
12
13
import vllm.envs as envs
14
import vllm.model_executor.layers.fused_moe.modular_kernel as mk
@@ -50,9 +51,15 @@ from vllm.model_executor.layers.quantization.compressed_tensors.utils import (
51
from vllm.model_executor.layers.quantization.utils import replace_parameter
52
from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
53
build_flashinfer_fp4_cutlass_moe_prepare_finalize,
54
+ flashinfer_trtllm_fp4_moe,
55
+ prepare_static_weights_for_trtllm_fp4_moe,
56
reorder_w1w3_to_w3w1,
57
select_nvfp4_gemm_impl,
58
)
59
+from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
60
+ FlashinferMoeBackend,
61
+ get_flashinfer_moe_backend,
62
+)
63
from vllm.model_executor.layers.quantization.utils.fp8_utils import (
64
expert_weight_is_col_major,
65
requant_weight_ue8m0_inplace,
@@ -193,6 +200,13 @@ class CompressedTensorsW4A4MoeMethod(CompressedTensorsMoEMethod):
200
self.allow_flashinfer = _nvfp4.allow_flashinfer
201
self.use_marlin = _nvfp4.use_marlin
202
self.group_size = 16
203
+ self.flashinfer_moe_backend = None
204
+ if self.allow_flashinfer:
205
+ self.flashinfer_moe_backend = get_flashinfer_moe_backend()
206
+ logger.info_once(
207
+ f"Using FlashInfer {self.flashinfer_moe_backend.value} kernels"
208
+ " for CompressedTensorsW4A4MoeMethod."
209
+ )
210
211
def create_weights(
212
self,
@@ -344,21 +358,20 @@ class CompressedTensorsW4A4MoeMethod(CompressedTensorsMoEMethod):
358
if self.use_marlin:
359
prepare_moe_fp4_layer_for_marlin(layer)
360
return
347
-
348
- # swizzle weight scales
349
- layer.w13_weight_scale = torch.nn.Parameter(
350
- swizzle_blockscale(layer.w13_weight_scale), requires_grad=False
351
- )
352
-
353
- layer.w2_weight_scale = torch.nn.Parameter(
354
- swizzle_blockscale(layer.w2_weight_scale), requires_grad=False
355
- )
356
-
361
# w13
358
- w13_input_global_scale = layer.w13_input_global_scale.max(dim=1).values.to(
359
- torch.float32
360
- )
361
-
362
+ if (
363
+ self.allow_flashinfer
364
+ and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
365
+ ):
366
+ w13_input_global_scale = (
367
+ layer.w13_input_global_scale.min()
368
+ .to(torch.float32)
369
+ .expand(layer.num_experts)
370
+ )
371
+ else:
372
+ w13_input_global_scale = layer.w13_input_global_scale.min(dim=1).values.to(
373
+ torch.float32
374
+ )
375
layer.g1_alphas = torch.nn.Parameter(
376
((1 / w13_input_global_scale) * layer.w13_weight_scale_2),
377
requires_grad=False,
@@ -369,22 +382,92 @@ class CompressedTensorsW4A4MoeMethod(CompressedTensorsMoEMethod):
382
)
383
384
# w2
385
+ if (
386
+ self.allow_flashinfer
387
+ and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
388
+ ):
389
+ w2_input_global_scale = (
390
+ layer.w2_input_global_scale.min()
391
+ .to(torch.float32)
392
+ .expand(layer.num_experts)
393
+ )
394
+ else:
395
+ w2_input_global_scale = layer.w2_input_global_scale
396
+
397
layer.g2_alphas = torch.nn.Parameter(
373
- ((1 / layer.w2_input_global_scale) * layer.w2_weight_scale_2).to(
374
- torch.float32
375
- ),
398
+ ((1 / w2_input_global_scale) * layer.w2_weight_scale_2).to(torch.float32),
399
requires_grad=False,
400
)
401
402
layer.w2_input_scale_quant = torch.nn.Parameter(
380
- (layer.w2_input_global_scale), requires_grad=False
403
+ (w2_input_global_scale), requires_grad=False
404
)
405
406
+ # TensorRT-LLM specific processing
407
+ if (
408
+ self.allow_flashinfer
409
+ and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
410
+ ):
411
+ # Prepare static weights for TRT-LLM kernel
412
+ # alternate: prepare_static_weight_layouts_for_trtllm_moe
413
+ (
414
+ gemm1_weights_fp4_shuffled,
415
+ gemm1_scales_fp4_shuffled,
416
+ gemm2_weights_fp4_shuffled,
417
+ gemm2_scales_fp4_shuffled,
418
+ ) = prepare_static_weights_for_trtllm_fp4_moe(
419
+ layer.w13_weight,
420
+ layer.w2_weight,
421
+ layer.w13_weight_scale,
422
+ layer.w2_weight_scale,
423
+ layer.w2_weight.size(-2), # hidden_size
424
+ layer.w13_weight.size(-2) // 2, # intermediate_size
425
+ layer.w13_weight.size(0), # num_experts
426
+ )
427
+ logger.debug_once("Finished shuffling weights for TRT-LLM MOE")
428
+
429
+ layer.gemm1_weights_fp4_shuffled = Parameter(
430
+ gemm1_weights_fp4_shuffled, requires_grad=False
431
+ )
432
+ layer.gemm2_weights_fp4_shuffled = Parameter(
433
+ gemm2_weights_fp4_shuffled, requires_grad=False
434
+ )
435
+ layer.gemm1_scales_fp4_shuffled = Parameter(
436
+ gemm1_scales_fp4_shuffled, requires_grad=False
437
+ )
438
+ layer.gemm2_scales_fp4_shuffled = Parameter(
439
+ gemm2_scales_fp4_shuffled, requires_grad=False
440
+ )
441
+
442
+ # Additional parameter needed for TRT-LLM
443
+ layer.g1_scale_c = Parameter(
444
+ (layer.w2_input_scale_quant * layer.g1_alphas).to(torch.float32),
445
+ requires_grad=False,
446
+ )
447
+
448
+ # Clean up weights that won't be used by TRT-LLM
449
+ del layer.w2_weight
450
+ del layer.w2_weight_scale
451
+ del layer.w13_weight
452
+ del layer.w13_weight_scale
453
+ else:
454
+ # swizzle weight scales
455
+ layer.w13_weight_scale = torch.nn.Parameter(
456
+ swizzle_blockscale(layer.w13_weight_scale), requires_grad=False
457
+ )
458
+
459
+ layer.w2_weight_scale = torch.nn.Parameter(
460
+ swizzle_blockscale(layer.w2_weight_scale), requires_grad=False
461
+ )
462
+
463
def maybe_make_prepare_finalize(
464
self,
465
routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
466
) -> mk.FusedMoEPrepareAndFinalize | None:
387
- if self.use_marlin:
467
+ if self.use_marlin or (
468
+ self.allow_flashinfer
469
+ and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
470
+ ):
471
return None
472
elif not self.allow_flashinfer:
473
return super().maybe_make_prepare_finalize(routing_tables)
@@ -411,7 +494,10 @@ class CompressedTensorsW4A4MoeMethod(CompressedTensorsMoEMethod):
494
def get_fused_moe_quant_config(
495
self, layer: torch.nn.Module
496
) -> FusedMoEQuantConfig | None:
414
- if self.use_marlin:
497
+ if (
498
+ self.use_marlin
499
+ or self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
500
+ ):
501
return None
502
503
return nvfp4_moe_quant_config(
@@ -452,6 +538,22 @@ class CompressedTensorsW4A4MoeMethod(CompressedTensorsMoEMethod):
538
)
539
assert activation == "silu", "Only SiLU activation is supported."
540
541
+ if (
542
+ self.allow_flashinfer
543
+ and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
544
+ ):
545
+ return flashinfer_trtllm_fp4_moe(
546
+ layer=layer,
547
+ x=x,
548
+ router_logits=router_logits,
549
+ top_k=top_k,
550
+ global_num_experts=global_num_experts,
551
+ num_expert_group=num_expert_group,
552
+ topk_group=topk_group,
553
+ custom_routing_function=custom_routing_function,
554
+ e_score_correction_bias=e_score_correction_bias,
555
+ )
556
+
557
topk_weights, topk_ids, _ = FusedMoE.select_experts(
558
hidden_states=x,
559
router_logits=router_logits,
vllm/model_executor/layers/quantization/modelopt.py
+15
-190
@@ -15,7 +15,6 @@ from vllm._custom_ops import cutlass_scaled_fp4_mm, scaled_fp4_quant
15
from vllm.logger import init_logger
16
from vllm.model_executor.layers.fused_moe.config import (
17
FusedMoEQuantConfig,
18
- RoutingMethodType,
18
fp8_w8a8_moe_quant_config,
19
nvfp4_moe_quant_config,
20
)
@@ -38,6 +37,8 @@ from vllm.model_executor.layers.quantization.base_config import (
37
from vllm.model_executor.layers.quantization.kv_cache import BaseKVCacheMethod
38
from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
39
build_flashinfer_fp4_cutlass_moe_prepare_finalize,
40
+ flashinfer_trtllm_fp4_moe,
41
+ prepare_static_weights_for_trtllm_fp4_moe,
42
reorder_w1w3_to_w3w1,
43
select_nvfp4_gemm_impl,
44
)
@@ -1136,7 +1137,6 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1137
self.allow_flashinfer = _nvfp4.allow_flashinfer
1138
self.use_marlin = _nvfp4.use_marlin
1139
self.flashinfer_moe_backend = None
1139
- self._cache_permute_indices: dict[torch.Size, torch.Tensor] = {}
1140
if self.allow_flashinfer:
1141
self.flashinfer_moe_backend = get_flashinfer_moe_backend()
1142
logger.info_once(
@@ -1303,138 +1303,14 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1303
)
1304
layer.register_parameter("w2_input_scale", w2_input_scale)
1305
1306
- def prepare_static_weights_for_trtllm_fp4_moe(
1307
- self,
1308
- # args_dequant,
1309
- # args,
1310
- gemm1_weights,
1311
- gemm2_weights,
1312
- gemm1_scales_linear_fp4_bytes,
1313
- gemm2_scales_linear_fp4_bytes,
1314
- hidden_size,
1315
- intermediate_size,
1316
- num_experts,
1317
- ):
1318
- from flashinfer import nvfp4_block_scale_interleave
1319
- from flashinfer.fused_moe.core import (
1320
- _maybe_get_cached_w3_w1_permute_indices,
1321
- get_w2_permute_indices_with_cache,
1322
- )
1323
-
1324
- """Prepare quantized weights for kernel (done offline with weights)."""
1325
- epilogue_tile_m = 128 # FIXME: this depends on the kernel internals
1326
-
1327
- # Convert quantized weights to proper formats
1328
- gemm1_weights_fp4 = gemm1_weights.view(torch.float8_e4m3fn).reshape(
1329
- num_experts, 2 * intermediate_size, hidden_size // 2
1330
- ) # packed fp4
1331
- gemm1_scales_linear_fp4 = gemm1_scales_linear_fp4_bytes.view(
1332
- torch.float8_e4m3fn
1333
- ).reshape(
1334
- num_experts, 2 * intermediate_size, hidden_size // 16
1335
- ) # fp8 scaling factors
1336
-
1337
- gemm2_weights_fp4 = gemm2_weights.view(torch.float8_e4m3fn).reshape(
1338
- num_experts, hidden_size, intermediate_size // 2
1339
- ) # packed fp4
1340
- gemm2_scales_linear_fp4 = gemm2_scales_linear_fp4_bytes.view(
1341
- torch.float8_e4m3fn
1342
- ).reshape(
1343
- num_experts, hidden_size, intermediate_size // 16
1344
- ) # fp8 scaling factors
1345
-
1346
- gemm1_weights_fp4_shuffled = []
1347
- gemm1_scales_fp4_shuffled = []
1348
- gemm2_weights_fp4_shuffled = []
1349
- gemm2_scales_fp4_shuffled = []
1350
- for i in range(num_experts):
1351
- # Calculate the permute indices for the following:
1352
- # 1. Reorder rows of W1 and scales for fused gated activation
1353
- # 2. Shuffle weights and scaling factors for transposed mma output
1354
- # for both w3_w1 and w2 weights and scale factors
1355
- permute_indices = _maybe_get_cached_w3_w1_permute_indices(
1356
- self._cache_permute_indices,
1357
- gemm1_weights_fp4[i].view(torch.uint8),
1358
- epilogue_tile_m,
1359
- )
1360
- gemm1_weights_fp4_shuffled.append(
1361
- gemm1_weights_fp4[i]
1362
- .view(torch.uint8)[permute_indices.to(gemm1_weights_fp4.device)]
1363
- .contiguous()
1364
- )
1365
-
1366
- permute_sf_indices = _maybe_get_cached_w3_w1_permute_indices(
1367
- self._cache_permute_indices,
1368
- gemm1_scales_linear_fp4[i].view(torch.uint8),
1369
- epilogue_tile_m,
1370
- num_elts_per_sf=16,
1371
- )
1372
- gemm1_scales_fp4_shuffled.append(
1373
- nvfp4_block_scale_interleave(
1374
- gemm1_scales_linear_fp4[i]
1375
- .view(torch.uint8)[
1376
- permute_sf_indices.to(gemm1_scales_linear_fp4.device)
1377
- ]
1378
- .contiguous()
1379
- )
1380
- )
1381
-
1382
- permute_indices = get_w2_permute_indices_with_cache(
1383
- self._cache_permute_indices,
1384
- gemm2_weights_fp4[i].view(torch.uint8),
1385
- epilogue_tile_m,
1386
- )
1387
- gemm2_weights_fp4_shuffled.append(
1388
- gemm2_weights_fp4[i]
1389
- .view(torch.uint8)[permute_indices.to(gemm2_weights_fp4.device)]
1390
- .contiguous()
1391
- )
1392
-
1393
- permute_sf_indices = get_w2_permute_indices_with_cache(
1394
- self._cache_permute_indices,
1395
- gemm2_scales_linear_fp4[i].view(torch.uint8),
1396
- epilogue_tile_m,
1397
- num_elts_per_sf=16,
1398
- )
1399
- gemm2_scales_fp4_shuffled.append(
1400
- nvfp4_block_scale_interleave(
1401
- gemm2_scales_linear_fp4[i]
1402
- .view(torch.uint8)[
1403
- permute_sf_indices.to(gemm2_scales_linear_fp4.device)
1404
- ]
1405
- .contiguous()
1406
- )
1407
- )
1408
-
1409
- # Stack weights for all experts
1410
- gemm1_weights_fp4_shuffled = torch.stack(gemm1_weights_fp4_shuffled)
1411
- gemm1_scales_fp4_shuffled = (
1412
- torch.stack(gemm1_scales_fp4_shuffled)
1413
- .view(torch.float8_e4m3fn)
1414
- .reshape(num_experts, 2 * intermediate_size, hidden_size // 16)
1415
- )
1416
-
1417
- gemm2_weights_fp4_shuffled = torch.stack(gemm2_weights_fp4_shuffled)
1418
- gemm2_scales_fp4_shuffled = (
1419
- torch.stack(gemm2_scales_fp4_shuffled)
1420
- .view(torch.float8_e4m3fn)
1421
- .reshape(num_experts, hidden_size, intermediate_size // 16)
1422
- )
1423
- return (
1424
- gemm1_weights_fp4_shuffled,
1425
- gemm1_scales_fp4_shuffled,
1426
- gemm2_weights_fp4_shuffled,
1427
- gemm2_scales_fp4_shuffled,
1428
- )
1429
-
1306
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
1307
# GEMM 1 processing
1308
gemm1_weight = layer.w13_weight.data
1309
gemm1_weight_scale = layer.w13_weight_scale.data
1310
1435
- if (
1436
- self.allow_flashinfer
1437
- and self.flashinfer_moe_backend == FlashinferMoeBackend.CUTLASS
1311
+ if self.allow_flashinfer and (
1312
+ self.flashinfer_moe_backend == FlashinferMoeBackend.CUTLASS
1313
+ or self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
1314
):
1315
gemm1_weight, gemm1_weight_scale = reorder_w1w3_to_w3w1(
1316
gemm1_weight, gemm1_weight_scale, dim=-2
@@ -1508,7 +1384,7 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1384
gemm1_scales_fp4_shuffled,
1385
gemm2_weights_fp4_shuffled,
1386
gemm2_scales_fp4_shuffled,
1511
- ) = self.prepare_static_weights_for_trtllm_fp4_moe(
1387
+ ) = prepare_static_weights_for_trtllm_fp4_moe(
1388
layer.w13_weight,
1389
layer.w2_weight,
1390
layer.w13_weight_scale,
@@ -1614,68 +1490,17 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1490
self.allow_flashinfer
1491
and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
1492
):
1617
- import flashinfer
1618
-
1619
- from vllm.model_executor.models.llama4 import Llama4MoE
1620
-
1621
- a1_gscale = layer.w13_input_scale_quant
1622
- (hidden_states_fp4, hidden_states_scale_linear_fp4) = (
1623
- flashinfer.fp4_quantize(
1624
- x,
1625
- a1_gscale,
1626
- is_sf_swizzled_layout=False,
1627
- )
1628
- )
1629
- use_llama4_routing = (
1630
- custom_routing_function is Llama4MoE.custom_routing_function
1631
- )
1632
- routing_method_type = layer.routing_method_type
1633
- if use_llama4_routing:
1634
- routing_method_type = RoutingMethodType.Llama4
1635
- router_logits = (
1636
- router_logits.to(torch.float32)
1637
- if routing_method_type == RoutingMethodType.DeepSeekV3
1638
- else router_logits
1639
- )
1640
- routing_bias = e_score_correction_bias
1641
- if routing_bias is not None:
1642
- routing_bias = routing_bias.to(torch.bfloat16)
1643
- out = flashinfer.fused_moe.trtllm_fp4_block_scale_moe(
1644
- routing_logits=router_logits,
1645
- routing_bias=routing_bias,
1646
- hidden_states=hidden_states_fp4,
1647
- hidden_states_scale=hidden_states_scale_linear_fp4.view(
1648
- torch.float8_e4m3fn
1649
- ).flatten(),
1650
- gemm1_weights=layer.gemm1_weights_fp4_shuffled.data,
1651
- gemm1_weights_scale=layer.gemm1_scales_fp4_shuffled.data.view(
1652
- torch.float8_e4m3fn
1653
- ),
1654
- gemm1_bias=None,
1655
- gemm1_alpha=None,
1656
- gemm1_beta=None,
1657
- gemm1_clamp_limit=None,
1658
- gemm2_weights=layer.gemm2_weights_fp4_shuffled.data,
1659
- gemm2_weights_scale=layer.gemm2_scales_fp4_shuffled.data.view(
1660
- torch.float8_e4m3fn
1661
- ),
1662
- gemm2_bias=None,
1663
- output1_scale_scalar=layer.g1_scale_c.data,
1664
- output1_scale_gate_scalar=layer.g1_alphas.data,
1665
- output2_scale_scalar=layer.g2_alphas.data,
1666
- num_experts=global_num_experts,
1493
+ return flashinfer_trtllm_fp4_moe(
1494
+ layer=layer,
1495
+ x=x,
1496
+ router_logits=router_logits,
1497
top_k=top_k,
1668
- n_group=num_expert_group,
1498
+ global_num_experts=global_num_experts,
1499
+ num_expert_group=num_expert_group,
1500
topk_group=topk_group,
1670
- intermediate_size=layer.intermediate_size_per_partition,
1671
- local_expert_offset=layer.ep_rank * layer.local_num_experts,
1672
- local_num_experts=layer.local_num_experts,
1673
- routed_scaling_factor=1.0,
1674
- tile_tokens_dim=None,
1675
- routing_method_type=routing_method_type,
1676
- do_finalize=True,
1677
- )[0]
1678
- return out
1501
+ custom_routing_function=custom_routing_function,
1502
+ e_score_correction_bias=e_score_correction_bias,
1503
+ )
1504
1505
topk_weights, topk_ids, _ = FusedMoE.select_experts(
1506
hidden_states=x,
vllm/model_executor/layers/quantization/utils/flashinfer_fp4_moe.py
+221
@@ -9,6 +9,7 @@ import vllm.model_executor.layers.fused_moe.modular_kernel as mk
9
from vllm.model_executor.layers.fused_moe.config import (
10
FusedMoEConfig,
11
FusedMoEQuantConfig,
12
+ RoutingMethodType,
13
)
14
from vllm.model_executor.layers.fused_moe.flashinfer_cutedsl_moe import (
15
FlashInferCuteDSLExperts,
@@ -110,3 +111,223 @@ def select_nvfp4_gemm_impl(
111
"CutlassExpertsFp4 doesn't support DP. Use flashinfer CUTLASS "
112
"Fused MoE backend instead (set VLLM_USE_FLASHINFER_MOE_FP4=1)"
113
)
114
+
115
+
116
+def prepare_static_weights_for_trtllm_fp4_moe(
117
+ # args_dequant,
118
+ # args,
119
+ gemm1_weights,
120
+ gemm2_weights,
121
+ gemm1_scales_linear_fp4_bytes,
122
+ gemm2_scales_linear_fp4_bytes,
123
+ hidden_size,
124
+ intermediate_size,
125
+ num_experts,
126
+):
127
+ from flashinfer import nvfp4_block_scale_interleave
128
+ from flashinfer.fused_moe.core import (
129
+ _maybe_get_cached_w3_w1_permute_indices,
130
+ get_w2_permute_indices_with_cache,
131
+ )
132
+
133
+ _cache_permute_indices: dict[torch.Size, torch.Tensor] = {}
134
+ """Prepare quantized weights for kernel (done offline with weights)."""
135
+ epilogue_tile_m = 128 # FIXME: this depends on the kernel internals
136
+
137
+ # Convert quantized weights to proper formats
138
+ gemm1_weights_fp4 = gemm1_weights.view(torch.float8_e4m3fn).reshape(
139
+ num_experts, 2 * intermediate_size, hidden_size // 2
140
+ ) # packed fp4
141
+ gemm1_scales_linear_fp4 = gemm1_scales_linear_fp4_bytes.view(
142
+ torch.float8_e4m3fn
143
+ ).reshape(
144
+ num_experts, 2 * intermediate_size, hidden_size // 16
145
+ ) # fp8 scaling factors
146
+
147
+ gemm2_weights_fp4 = gemm2_weights.view(torch.float8_e4m3fn).reshape(
148
+ num_experts, hidden_size, intermediate_size // 2
149
+ ) # packed fp4
150
+ gemm2_scales_linear_fp4 = gemm2_scales_linear_fp4_bytes.view(
151
+ torch.float8_e4m3fn
152
+ ).reshape(num_experts, hidden_size, intermediate_size // 16) # fp8 scaling factors
153
+
154
+ gemm1_weights_fp4_shuffled = []
155
+ gemm1_scales_fp4_shuffled = []
156
+ gemm2_weights_fp4_shuffled = []
157
+ gemm2_scales_fp4_shuffled = []
158
+ for i in range(num_experts):
159
+ # Calculate the permute indices for the following:
160
+ # 1. Reorder rows of W1 and scales for fused gated activation
161
+ # 2. Shuffle weights and scaling factors for transposed mma output
162
+ # for both w3_w1 and w2 weights and scale factors
163
+ permute_indices = _maybe_get_cached_w3_w1_permute_indices(
164
+ _cache_permute_indices,
165
+ gemm1_weights_fp4[i].view(torch.uint8),
166
+ epilogue_tile_m,
167
+ )
168
+ gemm1_weights_fp4_shuffled.append(
169
+ gemm1_weights_fp4[i]
170
+ .view(torch.uint8)[permute_indices.to(gemm1_weights_fp4.device)]
171
+ .contiguous()
172
+ )
173
+
174
+ permute_sf_indices = _maybe_get_cached_w3_w1_permute_indices(
175
+ _cache_permute_indices,
176
+ gemm1_scales_linear_fp4[i].view(torch.uint8),
177
+ epilogue_tile_m,
178
+ num_elts_per_sf=16,
179
+ )
180
+ gemm1_scales_fp4_shuffled.append(
181
+ nvfp4_block_scale_interleave(
182
+ gemm1_scales_linear_fp4[i]
183
+ .view(torch.uint8)[
184
+ permute_sf_indices.to(gemm1_scales_linear_fp4.device)
185
+ ]
186
+ .contiguous()
187
+ )
188
+ )
189
+
190
+ permute_indices = get_w2_permute_indices_with_cache(
191
+ _cache_permute_indices,
192
+ gemm2_weights_fp4[i].view(torch.uint8),
193
+ epilogue_tile_m,
194
+ )
195
+ gemm2_weights_fp4_shuffled.append(
196
+ gemm2_weights_fp4[i]
197
+ .view(torch.uint8)[permute_indices.to(gemm2_weights_fp4.device)]
198
+ .contiguous()
199
+ )
200
+
201
+ permute_sf_indices = get_w2_permute_indices_with_cache(
202
+ _cache_permute_indices,
203
+ gemm2_scales_linear_fp4[i].view(torch.uint8),
204
+ epilogue_tile_m,
205
+ num_elts_per_sf=16,
206
+ )
207
+ gemm2_scales_fp4_shuffled.append(
208
+ nvfp4_block_scale_interleave(
209
+ gemm2_scales_linear_fp4[i]
210
+ .view(torch.uint8)[
211
+ permute_sf_indices.to(gemm2_scales_linear_fp4.device)
212
+ ]
213
+ .contiguous()
214
+ )
215
+ )
216
+
217
+ # Stack weights for all experts
218
+ gemm1_weights_fp4_shuffled = torch.stack(gemm1_weights_fp4_shuffled)
219
+ gemm1_scales_fp4_shuffled = (
220
+ torch.stack(gemm1_scales_fp4_shuffled)
221
+ .view(torch.float8_e4m3fn)
222
+ .reshape(num_experts, 2 * intermediate_size, hidden_size // 16)
223
+ )
224
+
225
+ gemm2_weights_fp4_shuffled = torch.stack(gemm2_weights_fp4_shuffled)
226
+ gemm2_scales_fp4_shuffled = (
227
+ torch.stack(gemm2_scales_fp4_shuffled)
228
+ .view(torch.float8_e4m3fn)
229
+ .reshape(num_experts, hidden_size, intermediate_size // 16)
230
+ )
231
+ return (
232
+ gemm1_weights_fp4_shuffled,
233
+ gemm1_scales_fp4_shuffled,
234
+ gemm2_weights_fp4_shuffled,
235
+ gemm2_scales_fp4_shuffled,
236
+ )
237
+
238
+
239
+def flashinfer_trtllm_fp4_moe(
240
+ layer: torch.nn.Module,
241
+ x: torch.Tensor,
242
+ router_logits: torch.Tensor,
243
+ top_k: int,
244
+ global_num_experts: int,
245
+ num_expert_group: int | None,
246
+ topk_group: int | None,
247
+ custom_routing_function: object | None,
248
+ e_score_correction_bias: torch.Tensor | None,
249
+) -> torch.Tensor:
250
+ """
251
+ Apply FlashInfer TensorRT-LLM FP4 MoE kernel.
252
+
253
+ Args:
254
+ layer: The MoE layer with weights and scales
255
+ x: Input tensor
256
+ router_logits: Router logits for expert selection
257
+ top_k: Number of experts to select per token
258
+ global_num_experts: Total number of experts across all ranks
259
+ num_expert_group: Number of expert groups (for grouped routing)
260
+ topk_group: Top-k within each group
261
+ custom_routing_function: Custom routing function (e.g., Llama4)
262
+ e_score_correction_bias: Optional routing bias correction
263
+
264
+ Returns:
265
+ Output tensor from the MoE layer
266
+ """
267
+ import flashinfer
268
+
269
+ from vllm.model_executor.models.llama4 import Llama4MoE
270
+
271
+ # Quantize input to FP4
272
+ a1_gscale = layer.w13_input_scale_quant
273
+ (hidden_states_fp4, hidden_states_scale_linear_fp4) = flashinfer.fp4_quantize(
274
+ x,
275
+ a1_gscale,
276
+ is_sf_swizzled_layout=False,
277
+ )
278
+
279
+ # Determine routing method type
280
+ use_llama4_routing = custom_routing_function is Llama4MoE.custom_routing_function
281
+ routing_method_type = layer.routing_method_type
282
+ if use_llama4_routing:
283
+ routing_method_type = flashinfer.RoutingMethodType.Llama4
284
+
285
+ # Prepare routing bias
286
+ routing_bias = e_score_correction_bias
287
+ if routing_bias is not None:
288
+ routing_bias = routing_bias.to(torch.bfloat16)
289
+
290
+ router_logits = (
291
+ router_logits.to(torch.float32)
292
+ if routing_method_type == RoutingMethodType.DeepSeekV3
293
+ else router_logits
294
+ )
295
+
296
+ # Call TRT-LLM FP4 block-scale MoE kernel
297
+ out = flashinfer.fused_moe.trtllm_fp4_block_scale_moe(
298
+ routing_logits=router_logits,
299
+ routing_bias=routing_bias,
300
+ hidden_states=hidden_states_fp4,
301
+ hidden_states_scale=hidden_states_scale_linear_fp4.view(
302
+ torch.float8_e4m3fn
303
+ ).flatten(),
304
+ gemm1_weights=layer.gemm1_weights_fp4_shuffled.data,
305
+ gemm1_weights_scale=layer.gemm1_scales_fp4_shuffled.data.view(
306
+ torch.float8_e4m3fn
307
+ ),
308
+ gemm1_bias=None,
309
+ gemm1_alpha=None,
310
+ gemm1_beta=None,
311
+ gemm1_clamp_limit=None,
312
+ gemm2_weights=layer.gemm2_weights_fp4_shuffled.data,
313
+ gemm2_weights_scale=layer.gemm2_scales_fp4_shuffled.data.view(
314
+ torch.float8_e4m3fn
315
+ ),
316
+ gemm2_bias=None,
317
+ output1_scale_scalar=layer.g1_scale_c.data,
318
+ output1_scale_gate_scalar=layer.g1_alphas.data,
319
+ output2_scale_scalar=layer.g2_alphas.data,
320
+ num_experts=global_num_experts,
321
+ top_k=top_k,
322
+ n_group=num_expert_group if num_expert_group is not None else 0,
323
+ topk_group=topk_group if topk_group is not None else 0,
324
+ intermediate_size=layer.intermediate_size_per_partition,
325
+ local_expert_offset=layer.ep_rank * layer.local_num_experts,
326
+ local_num_experts=layer.local_num_experts,
327
+ routed_scaling_factor=None,
328
+ tile_tokens_dim=None,
329
+ routing_method_type=routing_method_type,
330
+ do_finalize=True,
331
+ )[0]
332
+
333
+ return out