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 3, 10:30 UTC
Refactor AWQ Marlin MoE onto modular WNA16 oracle (#42483)
Signed-off-by: Siddharth Bedekar <bedeksid@gmail.com> Signed-off-by: Siddharth Bedekar <104613085+bedeks@users.noreply.github.com> Co-authored-by: Robert Shaw <robertgshaw2-redhat@users.noreply.github.com> Co-authored-by: OpenAI Codex <codex@openai.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Siddharth Bedekar committed
May 18, 2026 at 08:02 UTC
78e7a7b9b0b9c285bf6978c3fc09eeecea3ff230
6 files changed
+405
-206
tests/kernels/moe/test_moe.py
+79
-13
@@ -1338,36 +1338,96 @@ def test_cpu_fused_moe_basic(
1338
torch.testing.assert_close(out, ref, atol=atol, rtol=0)
1339
1340
1341
-@pytest.mark.parametrize("m", [16, 32, 64])
1342
-@pytest.mark.parametrize("n", [128])
1343
-@pytest.mark.parametrize("k", [128])
1344
-@pytest.mark.parametrize("e", [8, 12, 16, 32])
1345
-@pytest.mark.parametrize("topk", [2, 4])
1346
-@pytest.mark.parametrize("max_tokens_per_batch", [16, 32, 64])
1341
+def _batched_fused_marlin_moe_cases() -> list[Any]:
1342
+ cases = [
1343
+ pytest.param(
1344
+ m,
1345
+ 128,
1346
+ 128,
1347
+ e,
1348
+ topk,
1349
+ max_tokens_per_batch,
1350
+ torch.bfloat16,
1351
+ scalar_types.float4_e2m1f,
1352
+ None,
1353
+ 1e-3,
1354
+ id=(
1355
+ f"m{m}-n128-k128-e{e}-topk{topk}-max_tokens{max_tokens_per_batch}-mxfp4"
1356
+ ),
1357
+ )
1358
+ for m in [16, 32, 64]
1359
+ for e in [8, 12, 16, 32]
1360
+ for topk in [2, 4]
1361
+ for max_tokens_per_batch in [16, 32, 64]
1362
+ ]
1363
+ cases.append(
1364
+ pytest.param(
1365
+ 32,
1366
+ 128,
1367
+ 128,
1368
+ 8,
1369
+ 2,
1370
+ 64,
1371
+ torch.float16,
1372
+ scalar_types.uint4,
1373
+ scalar_types.int8,
1374
+ 4e-2,
1375
+ id="awq-int8-activation-metadata",
1376
+ )
1377
+ )
1378
+ return cases
1379
+
1380
+
1381
+@pytest.mark.parametrize(
1382
+ ("m,n,k,e,topk,max_tokens_per_batch,dtype,quant_dtype,input_type,atol"),
1383
+ _batched_fused_marlin_moe_cases(),
1384
+)
1385
@pytest.mark.skipif(current_platform.is_rocm(), reason="Skip for rocm")
1386
def test_batched_fused_marlin_moe(
1349
- m: int, n: int, k: int, e: int, topk: int, max_tokens_per_batch: int
1387
+ m: int,
1388
+ n: int,
1389
+ k: int,
1390
+ e: int,
1391
+ topk: int,
1392
+ max_tokens_per_batch: int,
1393
+ dtype: torch.dtype,
1394
+ quant_dtype: ScalarType,
1395
+ input_type: ScalarType | None,
1396
+ atol: float,
1397
):
1398
print(
1399
f"testing m={m}, n={n}, k={k}, e={e}, "
1400
f"topk={topk}, "
1354
- f"max_tokens_per_batch={max_tokens_per_batch}"
1401
+ f"max_tokens_per_batch={max_tokens_per_batch}, "
1402
+ f"dtype={dtype}, quant_dtype={quant_dtype}, input_type={input_type}"
1403
)
1404
set_random_seed(0)
1405
1358
- dtype = torch.bfloat16
1359
- quant_dtype = scalar_types.float4_e2m1f
1406
group_size = 32
1407
+ if input_type == scalar_types.int8:
1408
+ input_dtype = torch.int8
1409
+ elif input_type == scalar_types.float8_e4m3fn:
1410
+ input_dtype = torch.float8_e4m3fn
1411
+ else:
1412
+ input_dtype = None
1413
1414
a = torch.randn((m, k), device="cuda", dtype=dtype) / 10
1415
w1 = torch.randn((e, 2 * n, k), device="cuda", dtype=dtype) / 20
1416
w2 = torch.randn((e, k, n), device="cuda", dtype=dtype) / 20
1417
1418
w1_data = MarlinMoEWeightData.make(
1367
- w=w1, quant_type=quant_dtype, group_size=group_size, act_order=None
1419
+ w=w1,
1420
+ quant_type=quant_dtype,
1421
+ group_size=group_size,
1422
+ act_order=None,
1423
+ input_type=input_type,
1424
)
1425
w2_data = MarlinMoEWeightData.make(
1370
- w=w2, quant_type=quant_dtype, group_size=group_size, act_order=None
1426
+ w=w2,
1427
+ quant_type=quant_dtype,
1428
+ group_size=group_size,
1429
+ act_order=None,
1430
+ input_type=input_type,
1431
)
1432
1433
score = torch.randn((m, e), device="cuda", dtype=dtype)
@@ -1487,6 +1547,12 @@ def test_batched_fused_marlin_moe(
1547
"quant_type_id": quant_dtype.id,
1548
"is_k_full": True,
1549
}
1550
+ if input_dtype is not None:
1551
+ kwargs["input_dtype"] = input_dtype
1552
+ if w1_data.a_scales_factor is not None:
1553
+ kwargs["input_global_scale1"] = w1_data.a_scales_factor
1554
+ if w2_data.a_scales_factor is not None:
1555
+ kwargs["input_global_scale2"] = w2_data.a_scales_factor
1556
1557
# Reference
1558
fused_marlin_moe_kwargs = kwargs | {
@@ -1502,7 +1568,7 @@ def test_batched_fused_marlin_moe(
1568
pytest.skip("Cannot represent data in Batched Format.")
1569
marlin_output = br.run(a, kwargs)
1570
1505
- torch.testing.assert_close(marlin_output, ref_marlin_output, atol=1e-3, rtol=0)
1571
+ torch.testing.assert_close(marlin_output, ref_marlin_output, atol=atol, rtol=0)
1572
1573
1574
@pytest.mark.parametrize("m,n,k", [(32, 1024, 1024)])
vllm/model_executor/layers/fused_moe/config.py
+7
-2
@@ -957,9 +957,14 @@ def awq_marlin_moe_quant_config(
957
group_size: int,
958
w1_bias: torch.Tensor | None = None,
959
w2_bias: torch.Tensor | None = None,
960
+ a1_gscale: torch.Tensor | None = None,
961
+ a2_gscale: torch.Tensor | None = None,
962
) -> FusedMoEQuantConfig:
963
"""
964
Construct a quant config for awq marlin quantization.
965
+
966
+ a1_gscale / a2_gscale are optional global scales applied to activation
967
+ quantization scales when Marlin runs with 8-bit activations.
968
"""
969
from vllm.model_executor.layers.quantization.utils.quant_utils import GroupShape
970
@@ -977,8 +982,8 @@ def awq_marlin_moe_quant_config(
982
raise ValueError(f"Unsupported weight_bits: {weight_bits}")
983
984
return FusedMoEQuantConfig(
980
- _a1=FusedMoEQuantDesc(dtype=None, shape=a_shape),
981
- _a2=FusedMoEQuantDesc(dtype=None, shape=a_shape),
985
+ _a1=FusedMoEQuantDesc(dtype=None, shape=a_shape, alpha_or_gscale=a1_gscale),
986
+ _a2=FusedMoEQuantDesc(dtype=None, shape=a_shape, alpha_or_gscale=a2_gscale),
987
_w1=FusedMoEQuantDesc(weight_dtype, w_shape, w1_scale, None, w1_zp, w1_bias),
988
_w2=FusedMoEQuantDesc(weight_dtype, w_shape, w2_scale, None, w2_zp, w2_bias),
989
)
vllm/model_executor/layers/fused_moe/experts/marlin_moe.py
+29
@@ -401,6 +401,8 @@ def batched_fused_marlin_moe(
401
global_num_experts: int = -1,
402
activation: MoEActivation = MoEActivation.SILU,
403
expert_map: torch.Tensor | None = None,
404
+ input_global_scale1: torch.Tensor | None = None,
405
+ input_global_scale2: torch.Tensor | None = None,
406
global_scale1: torch.Tensor | None = None,
407
global_scale2: torch.Tensor | None = None,
408
g_idx1: torch.Tensor | None = None,
@@ -414,6 +416,7 @@ def batched_fused_marlin_moe(
416
intermediate_cache2: torch.Tensor | None = None,
417
is_k_full: bool = True,
418
output: torch.Tensor | None = None,
419
+ input_dtype: torch.dtype | None = None,
420
inplace: bool = False,
421
clamp_limit: float | None = None,
422
) -> torch.Tensor:
@@ -489,7 +492,15 @@ def batched_fused_marlin_moe(
492
topk = 1
493
494
# TODO(varun) : Choose a decent block size like in fused_marlin_moe
495
+ # Tune block_size_m based on expert capacity to reduce padding overhead.
496
block_size_m = 64
497
+ for b_m in [8, 16, 32, 48, 64]:
498
+ if BATCH_TOKENS_MAX / b_m < 0.9:
499
+ block_size_m = b_m
500
+ break
501
+
502
+ if input_dtype is not None and input_dtype.itemsize == 1:
503
+ block_size_m = max(block_size_m, 16)
504
505
sorted_token_ids, expert_ids, num_tokens_post_padded = batched_moe_align_block_size(
506
max_tokens_per_batch=BATCH_TOKENS_MAX,
@@ -525,6 +536,8 @@ def batched_fused_marlin_moe(
536
sorted_token_ids=sorted_token_ids,
537
expert_ids=expert_ids,
538
num_tokens_post_padded=num_tokens_post_padded,
539
+ input_global_scale1=input_global_scale1,
540
+ input_global_scale2=input_global_scale2,
541
global_scale1=global_scale1,
542
global_scale2=global_scale2,
543
g_idx1=g_idx1,
@@ -537,6 +550,7 @@ def batched_fused_marlin_moe(
550
intermediate_cache13=intermediate_cache13,
551
intermediate_cache2=intermediate_cache2,
552
output=output.view(-1, K) if output is not None else output,
553
+ input_dtype=input_dtype,
554
is_k_full=is_k_full,
555
clamp_limit=clamp_limit,
556
)
@@ -635,6 +649,8 @@ class MarlinExpertsBase(mk.FusedMoEExpertsModular):
649
@property
650
def quant_type_id(self) -> int:
651
if self.quant_config.use_int4_w4a16:
652
+ if self.w1_zp is not None or self.w2_zp is not None:
653
+ return scalar_types.uint4.id
654
return scalar_types.uint4b8.id
655
elif self.quant_config.use_mxfp4_w4a16 or self.quant_config.use_nvfp4_w4a16:
656
return scalar_types.float4_e2m1f.id
@@ -754,6 +770,10 @@ class MarlinExperts(LoRAExpertsMixin, MarlinExpertsBase):
770
topk_ids=topk_ids,
771
global_scale1=self.g1_alphas,
772
global_scale2=self.g2_alphas,
773
+ input_global_scale1=self.a1_gscale,
774
+ input_global_scale2=self.a2_gscale,
775
+ w1_zeros=self.w1_zp,
776
+ w2_zeros=self.w2_zp,
777
quant_type_id=self.quant_type_id,
778
apply_router_weight_on_input=apply_router_weight_on_input,
779
global_num_experts=global_num_experts,
@@ -853,6 +873,10 @@ class MarlinExperts(LoRAExpertsMixin, MarlinExpertsBase):
873
topk_ids=topk_ids,
874
global_scale1=self.g1_alphas,
875
global_scale2=self.g2_alphas,
876
+ input_global_scale1=self.a1_gscale,
877
+ input_global_scale2=self.a2_gscale,
878
+ w1_zeros=self.w1_zp,
879
+ w2_zeros=self.w2_zp,
880
quant_type_id=self.quant_type_id,
881
apply_router_weight_on_input=apply_router_weight_on_input,
882
global_num_experts=global_num_experts,
@@ -967,6 +991,8 @@ class BatchedMarlinExperts(MarlinExpertsBase):
991
global_num_experts=global_num_experts,
992
activation=activation,
993
expert_map=expert_map,
994
+ input_global_scale1=self.a1_gscale,
995
+ input_global_scale2=self.a2_gscale,
996
output=output,
997
intermediate_cache13=workspace13,
998
intermediate_cache2=workspace2,
@@ -974,6 +1000,9 @@ class BatchedMarlinExperts(MarlinExpertsBase):
1000
g_idx2=self.w2_g_idx,
1001
sort_indices1=self.w13_g_idx_sort_indices,
1002
sort_indices2=self.w2_g_idx_sort_indices,
1003
+ w1_zeros=self.w1_zp,
1004
+ w2_zeros=self.w2_zp,
1005
+ input_dtype=self.input_dtype,
1006
is_k_full=self.is_k_full,
1007
clamp_limit=self.gemm1_clamp_limit,
1008
)
vllm/model_executor/layers/fused_moe/oracle/int_wna16.py
+183
-4
@@ -21,6 +21,7 @@ from vllm.model_executor.layers.quantization.utils.marlin_utils import (
21
marlin_act_int8_process_scales,
22
marlin_moe_permute_scales,
23
marlin_permute_bias,
24
+ moe_awq_to_marlin_zero_points,
25
)
26
from vllm.model_executor.layers.quantization.utils.quant_utils import (
27
QuantKey,
@@ -28,6 +29,7 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import (
29
30
if TYPE_CHECKING:
31
from vllm.model_executor.layers.quantization.auto_gptq import AutoGPTQConfig
32
+ from vllm.model_executor.layers.quantization.awq_marlin import AWQMarlinConfig
33
34
logger = init_logger(__name__)
35
@@ -147,7 +149,6 @@ def make_wna16_moe_kernel(
149
moe_quant_config: FusedMoEQuantConfig,
150
moe_config: FusedMoEConfig,
151
experts_cls: type[mk.FusedMoEExperts] | None,
150
- layer: torch.nn.Module,
152
is_k_full: bool,
153
w13_g_idx: torch.Tensor | None,
154
w2_g_idx: torch.Tensor | None,
@@ -220,6 +221,8 @@ def _process_weights_marlin(
221
w2_scales: torch.Tensor,
222
w13_g_idx: torch.Tensor,
223
w2_g_idx: torch.Tensor,
224
+ w13_qzeros: torch.Tensor | None = None,
225
+ w2_qzeros: torch.Tensor | None = None,
226
w13_bias: torch.Tensor | None = None,
227
w2_bias: torch.Tensor | None = None,
228
) -> tuple[
@@ -231,6 +234,8 @@ def _process_weights_marlin(
234
torch.Tensor, # w2_g_idx
235
torch.Tensor, # w13_g_idx_sort_indices
236
torch.Tensor, # w2_g_idx_sort_indices
237
+ torch.Tensor | None, # w13_qzeros
238
+ torch.Tensor | None, # w2_qzeros
239
torch.Tensor | None, # w13_input_global_scale
240
torch.Tensor | None, # w2_input_global_scale
241
torch.Tensor | None, # w13_bias
@@ -368,6 +373,151 @@ def _process_weights_marlin(
373
w2_g_idx,
374
w13_g_idx_sort_indices,
375
w2_g_idx_sort_indices,
376
+ w13_qzeros,
377
+ w2_qzeros,
378
+ w13_input_global_scale,
379
+ w2_input_global_scale,
380
+ w13_bias_out,
381
+ w2_bias_out,
382
+ )
383
+
384
+
385
+def _process_awq_weights_marlin(
386
+ layer: torch.nn.Module,
387
+ quant_config: "AWQMarlinConfig",
388
+ input_dtype: torch.dtype | None,
389
+ w13_qweight: torch.Tensor,
390
+ w2_qweight: torch.Tensor,
391
+ w13_scales: torch.Tensor,
392
+ w2_scales: torch.Tensor,
393
+ w13_qzeros: torch.Tensor,
394
+ w2_qzeros: torch.Tensor,
395
+ w13_bias: torch.Tensor | None = None,
396
+ w2_bias: torch.Tensor | None = None,
397
+) -> tuple[
398
+ torch.Tensor, # w13_qweight
399
+ torch.Tensor, # w2_qweight
400
+ torch.Tensor, # w13_scales
401
+ torch.Tensor, # w2_scales
402
+ torch.Tensor | None, # w13_g_idx
403
+ torch.Tensor | None, # w2_g_idx
404
+ torch.Tensor | None, # w13_g_idx_sort_indices
405
+ torch.Tensor | None, # w2_g_idx_sort_indices
406
+ torch.Tensor | None, # w13_qzeros
407
+ torch.Tensor | None, # w2_qzeros
408
+ torch.Tensor | None, # w13_input_global_scale
409
+ torch.Tensor | None, # w2_input_global_scale
410
+ torch.Tensor | None, # w13_bias
411
+ torch.Tensor | None, # w2_bias
412
+]:
413
+ """AWQ-specific Marlin weight post-processing.
414
+
415
+ AWQ checkpoints use a different packing order than GPTQ, so they need
416
+ AWQ-specific weight repacking and zero-point conversion before Marlin runs.
417
+ """
418
+ num_experts = w13_qweight.shape[0]
419
+ device = w13_qweight.device
420
+ is_a_8bit = input_dtype is not None and input_dtype.itemsize == 1
421
+ w13_input_global_scale: torch.Tensor | None = None
422
+ w2_input_global_scale: torch.Tensor | None = None
423
+ w13_bias_out: torch.Tensor | None = None
424
+ w2_bias_out: torch.Tensor | None = None
425
+
426
+ if input_dtype == torch.float8_e4m3fn:
427
+ ops.marlin_int4_fp8_preprocess(
428
+ w13_qweight.view(-1, w13_qweight.size(2)),
429
+ w13_qzeros.view(-1, w13_qzeros.size(2)),
430
+ inplace=True,
431
+ )
432
+ ops.marlin_int4_fp8_preprocess(
433
+ w2_qweight.view(-1, w2_qweight.size(2)),
434
+ w2_qzeros.view(-1, w2_qzeros.size(2)),
435
+ inplace=True,
436
+ )
437
+ w13_scales = w13_scales.data * 512
438
+ w2_scales = w2_scales.data * 512
439
+
440
+ w13_g_idx_sort_indices = torch.nn.Parameter(
441
+ torch.empty((num_experts, 0), dtype=torch.int32, device=device),
442
+ requires_grad=False,
443
+ )
444
+ w2_g_idx_sort_indices = torch.nn.Parameter(
445
+ torch.empty((num_experts, 0), dtype=torch.int32, device=device),
446
+ requires_grad=False,
447
+ )
448
+
449
+ marlin_w13_qweight = ops.awq_marlin_moe_repack(
450
+ w13_qweight,
451
+ w13_g_idx_sort_indices,
452
+ size_k=w13_qweight.shape[1],
453
+ size_n=w13_qweight.shape[2] * quant_config.pack_factor,
454
+ num_bits=quant_config.weight_bits,
455
+ is_a_8bit=is_a_8bit,
456
+ )
457
+ marlin_w2_qweight = ops.awq_marlin_moe_repack(
458
+ w2_qweight,
459
+ w2_g_idx_sort_indices,
460
+ size_k=w2_qweight.shape[1],
461
+ size_n=w2_qweight.shape[2] * quant_config.pack_factor,
462
+ num_bits=quant_config.weight_bits,
463
+ is_a_8bit=is_a_8bit,
464
+ )
465
+
466
+ marlin_w13_scales = marlin_moe_permute_scales(
467
+ s=w13_scales,
468
+ size_k=layer.intermediate_size_per_partition,
469
+ size_n=w13_scales.shape[2],
470
+ group_size=quant_config.group_size,
471
+ is_a_8bit=is_a_8bit,
472
+ )
473
+ if input_dtype == torch.int8 and layer.num_groups_w13 > 1:
474
+ marlin_w13_scales, w13_input_global_scale = marlin_act_int8_process_scales(
475
+ marlin_w13_scales
476
+ )
477
+
478
+ marlin_w2_scales = marlin_moe_permute_scales(
479
+ s=w2_scales,
480
+ size_k=layer.intermediate_size_per_partition,
481
+ size_n=w2_scales.shape[2],
482
+ group_size=quant_config.group_size,
483
+ is_a_8bit=is_a_8bit,
484
+ )
485
+ if input_dtype == torch.int8 and layer.num_groups_w2 > 1:
486
+ marlin_w2_scales, w2_input_global_scale = marlin_act_int8_process_scales(
487
+ marlin_w2_scales
488
+ )
489
+
490
+ marlin_w13_qzeros = moe_awq_to_marlin_zero_points(
491
+ w13_qzeros,
492
+ size_k=w13_qzeros.shape[1],
493
+ size_n=w13_qzeros.shape[2] * quant_config.pack_factor,
494
+ num_bits=quant_config.weight_bits,
495
+ is_a_8bit=is_a_8bit,
496
+ )
497
+ marlin_w2_qzeros = moe_awq_to_marlin_zero_points(
498
+ w2_qzeros,
499
+ size_k=w2_qzeros.shape[1],
500
+ size_n=w2_qzeros.shape[2] * quant_config.pack_factor,
501
+ num_bits=quant_config.weight_bits,
502
+ is_a_8bit=is_a_8bit,
503
+ )
504
+
505
+ if w13_bias is not None:
506
+ w13_bias_out = marlin_permute_bias(w13_bias)
507
+ if w2_bias is not None:
508
+ w2_bias_out = marlin_permute_bias(w2_bias)
509
+
510
+ return (
511
+ marlin_w13_qweight,
512
+ marlin_w2_qweight,
513
+ marlin_w13_scales,
514
+ marlin_w2_scales,
515
+ None,
516
+ None,
517
+ w13_g_idx_sort_indices,
518
+ w2_g_idx_sort_indices,
519
+ marlin_w13_qzeros,
520
+ marlin_w2_qzeros,
521
w13_input_global_scale,
522
w2_input_global_scale,
523
w13_bias_out,
@@ -384,8 +534,10 @@ def convert_to_wna16_moe_kernel_format(
534
w2: torch.Tensor,
535
w13_scale: torch.Tensor,
536
w2_scale: torch.Tensor,
387
- w13_g_idx: torch.Tensor,
388
- w2_g_idx: torch.Tensor,
537
+ w13_g_idx: torch.Tensor | None = None,
538
+ w2_g_idx: torch.Tensor | None = None,
539
+ w13_qzeros: torch.Tensor | None = None,
540
+ w2_qzeros: torch.Tensor | None = None,
541
w13_bias: torch.Tensor | None = None,
542
w2_bias: torch.Tensor | None = None,
543
) -> tuple[
@@ -397,6 +549,8 @@ def convert_to_wna16_moe_kernel_format(
549
torch.Tensor | None, # w2_g_idx
550
torch.Tensor | None, # w13_g_idx_sort_indices
551
torch.Tensor | None, # w2_g_idx_sort_indices
552
+ torch.Tensor | None, # w13_qzeros
553
+ torch.Tensor | None, # w2_qzeros
554
torch.Tensor | None, # w13_input_global_scale
555
torch.Tensor | None, # w2_input_global_scale
556
torch.Tensor | None, # w13_bias
@@ -420,12 +574,35 @@ def convert_to_wna16_moe_kernel_format(
574
from vllm.model_executor.layers.quantization.auto_gptq import (
575
AutoGPTQConfig,
576
)
577
+ from vllm.model_executor.layers.quantization.awq_marlin import (
578
+ AWQMarlinConfig,
579
+ )
580
+
581
+ if isinstance(quant_config, AWQMarlinConfig):
582
+ if w13_qzeros is None or w2_qzeros is None:
583
+ raise ValueError("AWQ Marlin MoE requires zero-point tensors.")
584
+ return _process_awq_weights_marlin(
585
+ layer,
586
+ quant_config,
587
+ input_dtype,
588
+ w13,
589
+ w2,
590
+ w13_scale,
591
+ w2_scale,
592
+ w13_qzeros,
593
+ w2_qzeros,
594
+ w13_bias,
595
+ w2_bias,
596
+ )
597
598
if not isinstance(quant_config, AutoGPTQConfig):
599
raise TypeError(
426
- "Marlin WNA16 MoE backend requires AutoGPTQConfig, got "
600
+ "Marlin WNA16 MoE backend requires AutoGPTQConfig or "
601
+ "AWQMarlinConfig, got "
602
f"{type(quant_config).__name__}."
603
)
604
+ if w13_g_idx is None or w2_g_idx is None:
605
+ raise ValueError("GPTQ Marlin MoE requires g_idx tensors.")
606
return _process_weights_marlin(
607
layer,
608
quant_config,
@@ -436,6 +613,8 @@ def convert_to_wna16_moe_kernel_format(
613
w2_scale,
614
w13_g_idx,
615
w2_g_idx,
616
+ w13_qzeros,
617
+ w2_qzeros,
618
w13_bias,
619
w2_bias,
620
)
vllm/model_executor/layers/quantization/auto_gptq.py
+2
-1
@@ -659,6 +659,8 @@ class AutoGPTQMoEMethod(FusedMoEMethodBase):
659
w2_g_idx,
660
w13_g_idx_sort_indices,
661
w2_g_idx_sort_indices,
662
+ _w13_qzeros,
663
+ _w2_qzeros,
664
w13_input_global_scale,
665
w2_input_global_scale,
666
w13_bias,
@@ -729,7 +731,6 @@ class AutoGPTQMoEMethod(FusedMoEMethodBase):
731
moe_quant_config=self.moe_quant_config,
732
moe_config=self.moe,
733
experts_cls=self.experts_cls,
732
- layer=layer,
734
is_k_full=self.is_k_full,
735
w13_g_idx=layer.w13_g_idx,
736
w2_g_idx=layer.w2_g_idx,
vllm/model_executor/layers/quantization/awq_marlin.py
+105
-186
@@ -9,7 +9,6 @@ from torch.nn import Parameter
9
from transformers import PretrainedConfig
10
11
import vllm.model_executor.layers.fused_moe # noqa
12
-from vllm import _custom_ops as ops
12
from vllm import envs
13
from vllm.logger import init_logger
14
from vllm.model_executor.kernels.linear import (
@@ -27,7 +26,11 @@ from vllm.model_executor.layers.fused_moe.config import (
26
FusedMoEConfig,
27
FusedMoEQuantConfig,
28
)
30
-from vllm.model_executor.layers.fused_moe.experts.marlin_moe import fused_marlin_moe
29
+from vllm.model_executor.layers.fused_moe.oracle.int_wna16 import (
30
+ convert_to_wna16_moe_kernel_format,
31
+ make_wna16_moe_kernel,
32
+ select_wna16_moe_backend,
33
+)
34
from vllm.model_executor.layers.linear import (
35
LinearBase,
36
LinearMethodBase,
@@ -45,14 +48,13 @@ from vllm.model_executor.layers.quantization.utils.marlin_utils import (
48
check_marlin_supports_layer,
49
check_moe_marlin_supports_layer,
50
get_marlin_input_dtype,
48
- marlin_act_int8_process_scales,
51
marlin_make_workspace_new,
50
- marlin_moe_permute_scales,
51
- marlin_permute_bias,
52
- moe_awq_to_marlin_zero_points,
52
verify_marlin_supported,
53
)
55
-from vllm.model_executor.layers.quantization.utils.quant_utils import is_layer_skipped
54
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
55
+ is_layer_skipped,
56
+ kInt4Static,
57
+)
58
from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead
59
from vllm.model_executor.parameter import GroupQuantScaleParameter, PackedvLLMParameter
60
from vllm.platforms import current_platform
@@ -72,6 +74,19 @@ logger = init_logger(__name__)
74
_REVERSE_AWQ_PACK_ORDER = [0, 4, 1, 5, 2, 6, 3, 7]
75
76
77
+def _replace_or_register_parameter(
78
+ layer: torch.nn.Module,
79
+ name: str,
80
+ value: torch.Tensor | None,
81
+) -> None:
82
+ if value is None:
83
+ return
84
+ if hasattr(layer, name):
85
+ replace_parameter(layer, name, value)
86
+ else:
87
+ layer.register_parameter(name, Parameter(value, requires_grad=False))
88
+
89
+
90
def _convert_awq_to_standard_format(
91
layer: torch.nn.Module,
92
w_q_name: str,
@@ -505,6 +520,9 @@ class AWQMarlinMoEMethod(FusedMoEMethodBase):
520
self.quant_type = scalar_types.uint4
521
self.input_dtype = None
522
self.use_marlin = True
523
+ self.wna16_moe_backend, self.experts_cls = select_wna16_moe_backend(
524
+ moe, kInt4Static, quant_config.weight_bits
525
+ )
526
527
def create_weights(
528
self,
@@ -608,52 +626,38 @@ class AWQMarlinMoEMethod(FusedMoEMethodBase):
626
layer.workspace = marlin_make_workspace_new(device, 4)
627
628
def process_weights_after_loading(self, layer: RoutedExperts) -> None:
611
- num_experts = layer.w13_qweight.shape[0]
612
- device = layer.w13_qweight.device
613
- is_a_8bit = self.input_dtype is not None and self.input_dtype.itemsize == 1
614
-
615
- if self.input_dtype == torch.float8_e4m3fn:
616
- ops.marlin_int4_fp8_preprocess(
617
- layer.w13_qweight.view(-1, layer.w13_qweight.size(2)),
618
- layer.w13_qzeros.view(-1, layer.w13_qzeros.size(2)),
619
- inplace=True,
620
- )
621
- ops.marlin_int4_fp8_preprocess(
622
- layer.w2_qweight.view(-1, layer.w2_qweight.size(2)),
623
- layer.w2_qzeros.view(-1, layer.w2_qzeros.size(2)),
624
- inplace=True,
625
- )
626
- layer.w13_scales.data = layer.w13_scales.data * 512
627
- layer.w2_scales.data = layer.w2_scales.data * 512
628
-
629
- layer.w13_g_idx_sort_indices = torch.nn.Parameter(
630
- torch.empty((num_experts, 0), dtype=torch.int32, device=device),
631
- requires_grad=False,
632
- )
633
- layer.w2_g_idx_sort_indices = torch.nn.Parameter(
634
- torch.empty((num_experts, 0), dtype=torch.int32, device=device),
635
- requires_grad=False,
636
- )
637
-
638
- marlin_w13_qweight = ops.awq_marlin_moe_repack(
639
- layer.w13_qweight,
640
- layer.w13_g_idx_sort_indices,
641
- size_k=layer.w13_qweight.shape[1],
642
- size_n=layer.w13_qweight.shape[2] * self.quant_config.pack_factor,
643
- num_bits=self.quant_config.weight_bits,
644
- is_a_8bit=is_a_8bit,
629
+ (
630
+ w13,
631
+ w2,
632
+ w13_scale,
633
+ w2_scale,
634
+ w13_g_idx,
635
+ w2_g_idx,
636
+ w13_g_idx_sort_indices,
637
+ w2_g_idx_sort_indices,
638
+ w13_qzeros,
639
+ w2_qzeros,
640
+ w13_input_global_scale,
641
+ w2_input_global_scale,
642
+ w13_bias,
643
+ w2_bias,
644
+ ) = convert_to_wna16_moe_kernel_format(
645
+ backend=self.wna16_moe_backend,
646
+ layer=layer,
647
+ quant_config=self.quant_config,
648
+ input_dtype=self.input_dtype,
649
+ w13=layer.w13_qweight,
650
+ w2=layer.w2_qweight,
651
+ w13_scale=layer.w13_scales,
652
+ w2_scale=layer.w2_scales,
653
+ w13_qzeros=layer.w13_qzeros,
654
+ w2_qzeros=layer.w2_qzeros,
655
+ w13_bias=getattr(layer, "w13_bias", None),
656
+ w2_bias=getattr(layer, "w2_bias", None),
657
)
646
- replace_parameter(layer, "w13_qweight", marlin_w13_qweight)
658
648
- marlin_w2_qweight = ops.awq_marlin_moe_repack(
649
- layer.w2_qweight,
650
- layer.w2_g_idx_sort_indices,
651
- size_k=layer.w2_qweight.shape[1],
652
- size_n=layer.w2_qweight.shape[2] * self.quant_config.pack_factor,
653
- num_bits=self.quant_config.weight_bits,
654
- is_a_8bit=is_a_8bit,
655
- )
656
- replace_parameter(layer, "w2_qweight", marlin_w2_qweight)
659
+ replace_parameter(layer, "w13_qweight", w13)
660
+ replace_parameter(layer, "w2_qweight", w2)
661
662
# The modular kernel expects w13_weight and w2_weight,
663
# but AWQ uses w13_qweight and w2_qweight
@@ -662,70 +666,46 @@ class AWQMarlinMoEMethod(FusedMoEMethodBase):
666
# Alias for modular kernel
667
layer.w2_weight = layer.w2_qweight
668
665
- # Why does this take the intermediate size for size_k?
666
- marlin_w13_scales = marlin_moe_permute_scales(
667
- s=layer.w13_scales,
668
- size_k=layer.intermediate_size_per_partition,
669
- size_n=layer.w13_scales.shape[2],
670
- group_size=self.quant_config.group_size,
671
- is_a_8bit=is_a_8bit,
669
+ replace_parameter(layer, "w13_scales", w13_scale)
670
+ replace_parameter(layer, "w2_scales", w2_scale)
671
+ _replace_or_register_parameter(
672
+ layer, "w13_g_idx_sort_indices", w13_g_idx_sort_indices
673
)
673
- if self.input_dtype == torch.int8 and layer.num_groups_w13 > 1:
674
- marlin_w13_scales, w13_input_global_scale = marlin_act_int8_process_scales(
675
- marlin_w13_scales
676
- )
677
- layer.register_parameter(
678
- "w13_input_global_scale",
679
- Parameter(w13_input_global_scale, requires_grad=False),
680
- )
681
-
682
- replace_parameter(layer, "w13_scales", marlin_w13_scales)
683
-
684
- marlin_w2_scales = marlin_moe_permute_scales(
685
- s=layer.w2_scales,
686
- size_k=layer.intermediate_size_per_partition,
687
- size_n=layer.w2_scales.shape[2],
688
- group_size=self.quant_config.group_size,
689
- is_a_8bit=is_a_8bit,
674
+ _replace_or_register_parameter(
675
+ layer, "w2_g_idx_sort_indices", w2_g_idx_sort_indices
676
)
691
- if self.input_dtype == torch.int8 and layer.num_groups_w2 > 1:
692
- marlin_w2_scales, w2_input_global_scale = marlin_act_int8_process_scales(
693
- marlin_w2_scales
694
- )
695
- layer.register_parameter(
696
- "w2_input_global_scale",
697
- Parameter(w2_input_global_scale, requires_grad=False),
698
- )
699
-
700
- replace_parameter(layer, "w2_scales", marlin_w2_scales)
701
-
702
- marlin_w13_zp = moe_awq_to_marlin_zero_points(
703
- layer.w13_qzeros,
704
- size_k=layer.w13_qzeros.shape[1],
705
- size_n=layer.w13_qzeros.shape[2] * self.quant_config.pack_factor,
706
- num_bits=self.quant_config.weight_bits,
707
- is_a_8bit=is_a_8bit,
677
+ _replace_or_register_parameter(layer, "w13_g_idx", w13_g_idx)
678
+ _replace_or_register_parameter(layer, "w2_g_idx", w2_g_idx)
679
+ _replace_or_register_parameter(layer, "w13_qzeros", w13_qzeros)
680
+ _replace_or_register_parameter(layer, "w2_qzeros", w2_qzeros)
681
+ _replace_or_register_parameter(
682
+ layer, "w13_input_global_scale", w13_input_global_scale
683
)
709
- replace_parameter(layer, "w13_qzeros", marlin_w13_zp)
710
-
711
- marlin_w2_zp = moe_awq_to_marlin_zero_points(
712
- layer.w2_qzeros,
713
- size_k=layer.w2_qzeros.shape[1],
714
- size_n=layer.w2_qzeros.shape[2] * self.quant_config.pack_factor,
715
- num_bits=self.quant_config.weight_bits,
716
- is_a_8bit=is_a_8bit,
684
+ _replace_or_register_parameter(
685
+ layer, "w2_input_global_scale", w2_input_global_scale
686
)
718
- replace_parameter(layer, "w2_qzeros", marlin_w2_zp)
687
+ _replace_or_register_parameter(layer, "w13_bias", w13_bias)
688
+ _replace_or_register_parameter(layer, "w2_bias", w2_bias)
689
+
690
+ self._setup_kernel(layer)
691
720
- if hasattr(layer, "w13_bias") and layer.w13_bias is not None:
721
- layer.w13_bias.data = marlin_permute_bias(layer.w13_bias)
692
+ def _setup_kernel(self, layer: RoutedExperts) -> None:
693
+ """Build the FusedMoEKernel for this layer."""
694
723
- if hasattr(layer, "w2_bias") and layer.w2_bias is not None:
724
- layer.w2_bias.data = marlin_permute_bias(layer.w2_bias)
695
+ self.moe_quant_config = self.get_fused_moe_quant_config(layer)
696
+ self.moe_kernel = make_wna16_moe_kernel(
697
+ moe_quant_config=self.moe_quant_config,
698
+ moe_config=self.moe,
699
+ experts_cls=self.experts_cls,
700
+ is_k_full=self.is_k_full,
701
+ w13_g_idx=getattr(layer, "w13_g_idx", None),
702
+ w2_g_idx=getattr(layer, "w2_g_idx", None),
703
+ w13_g_idx_sort_indices=layer.w13_g_idx_sort_indices,
704
+ w2_g_idx_sort_indices=layer.w2_g_idx_sort_indices,
705
+ routing_tables=layer._expert_routing_tables(),
706
+ )
707
726
- def get_fused_moe_quant_config(
727
- self, layer: RoutedExperts
728
- ) -> FusedMoEQuantConfig | None:
708
+ def get_fused_moe_quant_config(self, layer: RoutedExperts) -> FusedMoEQuantConfig:
709
from vllm.model_executor.layers.fused_moe.config import (
710
awq_marlin_moe_quant_config,
711
)
@@ -743,6 +723,8 @@ class AWQMarlinMoEMethod(FusedMoEMethodBase):
723
else None,
724
w1_bias=getattr(layer, "w13_bias", None),
725
w2_bias=getattr(layer, "w2_bias", None),
726
+ a1_gscale=getattr(layer, "w13_input_global_scale", None),
727
+ a2_gscale=getattr(layer, "w2_input_global_scale", None),
728
)
729
730
def select_gemm_impl(
@@ -750,67 +732,11 @@ class AWQMarlinMoEMethod(FusedMoEMethodBase):
732
prepare_finalize,
733
layer: RoutedExperts,
734
):
753
- """
754
- Select the GEMM implementation for AWQ-Marlin MoE.
755
- Returns MarlinExperts configured for AWQ quantization.
756
- This is ONLY used when LoRA is enabled.
757
- Without LoRA, AWQ uses its own apply() method.
758
- """
759
- # Only use modular kernels when LoRA is enabled
760
- # Without LoRA, AWQ's own apply() method works fine and is more efficient
761
- if not self.moe.is_lora_enabled:
762
- raise NotImplementedError(
763
- "AWQ-Marlin uses its own apply() method when LoRA is not enabled. "
764
- "Modular kernels are only used for LoRA support."
765
- )
766
-
767
- from vllm.model_executor.layers.fused_moe import modular_kernel as mk
768
- from vllm.model_executor.layers.fused_moe.experts.marlin_moe import (
769
- BatchedMarlinExperts,
770
- MarlinExperts,
735
+ raise ValueError(
736
+ f"{self.__class__.__name__} uses the new modular kernel "
737
+ "initialization logic. This function should not be called."
738
)
739
773
- # Ensure quant config is initialized
774
- assert self.moe_quant_config is not None, (
775
- "moe_quant_config must be initialized before select_gemm_impl"
776
- )
777
-
778
- w13_g_idx = getattr(layer, "w13_g_idx", None)
779
- w2_g_idx = getattr(layer, "w2_g_idx", None)
780
- w13_g_idx_sort_indices = getattr(layer, "w13_g_idx_sort_indices", None)
781
- w2_g_idx_sort_indices = getattr(layer, "w2_g_idx_sort_indices", None)
782
-
783
- # Check if using batched expert format (for Expert Parallelism)
784
- if (
785
- prepare_finalize.activation_format
786
- == mk.FusedMoEActivationFormat.BatchedExperts
787
- ):
788
- # For batched format, use BatchedMarlinExperts
789
- max_num_tokens_per_rank = prepare_finalize.max_num_tokens_per_rank()
790
- assert max_num_tokens_per_rank is not None
791
- return BatchedMarlinExperts(
792
- max_num_tokens=max_num_tokens_per_rank,
793
- num_dispatchers=prepare_finalize.num_dispatchers(),
794
- moe_config=self.moe,
795
- quant_config=self.moe_quant_config,
796
- w13_g_idx=w13_g_idx,
797
- w2_g_idx=w2_g_idx,
798
- w13_g_idx_sort_indices=w13_g_idx_sort_indices,
799
- w2_g_idx_sort_indices=w2_g_idx_sort_indices,
800
- is_k_full=self.is_k_full,
801
- )
802
- else:
803
- # Standard Marlin experts for AWQ
804
- return MarlinExperts(
805
- moe_config=self.moe,
806
- quant_config=self.moe_quant_config,
807
- w13_g_idx=w13_g_idx,
808
- w2_g_idx=w2_g_idx,
809
- w13_g_idx_sort_indices=w13_g_idx_sort_indices,
810
- w2_g_idx_sort_indices=w2_g_idx_sort_indices,
811
- is_k_full=self.is_k_full,
812
- )
813
-
740
def apply(
741
self,
742
layer: RoutedExperts,
@@ -820,25 +746,18 @@ class AWQMarlinMoEMethod(FusedMoEMethodBase):
746
shared_experts: SharedExperts | None,
747
shared_experts_input: torch.Tensor | None,
748
) -> torch.Tensor:
823
- return fused_marlin_moe(
824
- x,
825
- layer.w13_qweight,
826
- layer.w2_qweight,
827
- getattr(layer, "w13_bias", None),
828
- getattr(layer, "w2_bias", None),
829
- layer.w13_scales,
830
- layer.w2_scales,
831
- topk_weights,
832
- topk_ids,
833
- input_global_scale1=getattr(layer, "w13_input_global_scale", None),
834
- input_global_scale2=getattr(layer, "w2_input_global_scale", None),
835
- quant_type_id=self.quant_type.id,
836
- apply_router_weight_on_input=layer.apply_router_weight_on_input,
749
+ assert not self.is_monolithic
750
+ assert self.moe_kernel is not None
751
+ return self.moe_kernel.apply(
752
+ hidden_states=x,
753
+ w1=layer.w13_qweight,
754
+ w2=layer.w2_qweight,
755
+ topk_weights=topk_weights,
756
+ topk_ids=topk_ids,
757
+ activation=layer.activation,
758
global_num_experts=layer.global_num_experts,
759
+ apply_router_weight_on_input=layer.apply_router_weight_on_input,
760
expert_map=layer.expert_map,
839
- w1_zeros=layer.w13_qzeros,
840
- w2_zeros=layer.w2_qzeros,
841
- workspace=layer.workspace,
842
- input_dtype=self.input_dtype,
843
- inplace=not self.moe.disable_inplace,
761
+ shared_experts=shared_experts,
762
+ shared_experts_input=shared_experts_input,
763
)