spectator2026 commited on
Commit
b1ceb5f
·
verified ·
1 Parent(s): b0a7f85

Upload folder using huggingface_hub

Browse files
vllm-patches/A_wna16_marlin.py ADDED
@@ -0,0 +1,537 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+ from typing import Any
5
+
6
+ import torch
7
+ from compressed_tensors.quantization import (
8
+ QuantizationArgs,
9
+ )
10
+
11
+ from vllm.logger import init_logger
12
+ from vllm.model_executor.layers.fused_moe import (
13
+ RoutedExperts,
14
+ SharedExperts,
15
+ )
16
+ from vllm.model_executor.layers.fused_moe.config import (
17
+ FusedMoEConfig,
18
+ FusedMoEQuantConfig,
19
+ )
20
+ from vllm.model_executor.layers.fused_moe.oracle.int_wna16 import (
21
+ WNA16MoEBackend,
22
+ convert_to_wna16_moe_kernel_format,
23
+ make_wna16_moe_kernel,
24
+ make_wna16_moe_quant_config,
25
+ select_wna16_moe_backend,
26
+ )
27
+ from vllm.model_executor.layers.quantization.compressed_tensors.compressed_tensors_moe import ( # noqa E501
28
+ CompressedTensorsMoEMethod,
29
+ )
30
+ from vllm.model_executor.layers.quantization.compressed_tensors.schemes.compressed_tensors_wNa16 import ( # noqa
31
+ WNA16_SUPPORTED_TYPES_MAP,
32
+ WNA16_ZP_SUPPORTED_TYPES_MAP,
33
+ )
34
+ from vllm.model_executor.layers.quantization.utils.marlin_utils import (
35
+ get_marlin_input_dtype,
36
+ marlin_make_workspace_new,
37
+ )
38
+ from vllm.model_executor.layers.quantization.utils.quant_utils import (
39
+ QuantKey,
40
+ kInt4Static32GroupScale,
41
+ kInt4StaticGroupScale,
42
+ kInt8StaticGroupScale,
43
+ )
44
+ from vllm.model_executor.utils import replace_parameter, set_weight_attrs
45
+
46
+ logger = init_logger(__name__)
47
+
48
+
49
+ class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
50
+ def __init__(
51
+ self,
52
+ weight_quant: QuantizationArgs,
53
+ input_quant: QuantizationArgs | None,
54
+ moe: FusedMoEConfig,
55
+ layer_name: str | None = None,
56
+ ):
57
+ super().__init__(moe)
58
+ self.weight_quant = weight_quant
59
+ self.input_quant = input_quant
60
+ self.symmetric = weight_quant.symmetric
61
+ # Extract properties from weight_quant
62
+ self.num_bits = weight_quant.num_bits
63
+ self.packed_factor = 32 // weight_quant.num_bits
64
+ self.strategy = weight_quant.strategy
65
+ self.group_size = weight_quant.group_size
66
+ self.actorder = weight_quant.actorder
67
+
68
+ self.quant_type = (
69
+ WNA16_SUPPORTED_TYPES_MAP[self.num_bits]
70
+ if self.symmetric
71
+ else WNA16_ZP_SUPPORTED_TYPES_MAP[self.num_bits]
72
+ )
73
+
74
+ self.marlin_input_dtype = get_marlin_input_dtype(layer_name)
75
+
76
+ if self.num_bits == 4:
77
+ if self.group_size == 32:
78
+ scale = kInt4Static32GroupScale
79
+ else:
80
+ scale = kInt4StaticGroupScale
81
+ elif self.num_bits == 8:
82
+ assert self.group_size == -1
83
+ scale = kInt8StaticGroupScale
84
+ else:
85
+ raise ValueError(
86
+ "CompressedTensorsWNA16MarlinMoEMethod only supports int4 and int8 now."
87
+ )
88
+
89
+ weight_key = QuantKey(self.quant_type, scale, symmetric=self.symmetric)
90
+
91
+ # Select WNA16 MoE backend via oracle.
92
+ self.wna16_backend, self.experts_cls = select_wna16_moe_backend(
93
+ config=self.moe,
94
+ weight_key=weight_key,
95
+ )
96
+
97
+ def get_weight_shape(
98
+ self,
99
+ weight_name: str,
100
+ num_experts: int,
101
+ hidden_size: int,
102
+ intermediate_size_per_partition: int,
103
+ num_groups_w2: int | None = None,
104
+ num_groups_w13: int | None = None,
105
+ ) -> tuple[int, int, int]:
106
+ """
107
+ Get the shape of the weight based on the weight name, number of experts
108
+ hidden size, intermediate size per partition, number of groups for w2,
109
+ and number of groups for w13. Pass in num_groups_w2 and num_groups_w13
110
+ for weight scales/zero_points.
111
+ """
112
+ if weight_name in ("w13_scale", "w13_zp"):
113
+ assert num_groups_w13 is not None, (
114
+ "num_groups_w13 must be provided for weight scales/zero_points"
115
+ )
116
+ if weight_name in ("w2_scale", "w2_zp"):
117
+ assert num_groups_w2 is not None, (
118
+ "num_groups_w2 must be provided for weight scales/zero_points"
119
+ )
120
+ w13_num_shards = 2 if self.moe.is_act_and_mul else 1
121
+ is_flashinfer = self.wna16_backend == WNA16MoEBackend.FLASHINFER_TRTLLM
122
+ shape_map = {
123
+ "w13_weight": {
124
+ "Flashinfer": (
125
+ num_experts,
126
+ w13_num_shards * intermediate_size_per_partition,
127
+ hidden_size // self.packed_factor,
128
+ ),
129
+ "Marlin": (
130
+ num_experts,
131
+ hidden_size // self.packed_factor,
132
+ w13_num_shards * intermediate_size_per_partition,
133
+ ),
134
+ },
135
+ "w13_scale": {
136
+ "Flashinfer": (
137
+ num_experts,
138
+ w13_num_shards * intermediate_size_per_partition,
139
+ num_groups_w13,
140
+ ),
141
+ "Marlin": (
142
+ num_experts,
143
+ num_groups_w13,
144
+ w13_num_shards * intermediate_size_per_partition,
145
+ ),
146
+ },
147
+ "w13_zp": {
148
+ "Marlin": (
149
+ num_experts,
150
+ num_groups_w13,
151
+ w13_num_shards
152
+ * intermediate_size_per_partition
153
+ // self.packed_factor,
154
+ ),
155
+ },
156
+ "w2_weight": {
157
+ "Flashinfer": (
158
+ num_experts,
159
+ hidden_size,
160
+ intermediate_size_per_partition // self.packed_factor,
161
+ ),
162
+ "Marlin": (
163
+ num_experts,
164
+ intermediate_size_per_partition // self.packed_factor,
165
+ hidden_size,
166
+ ),
167
+ },
168
+ "w2_scale": {
169
+ "Flashinfer": (num_experts, hidden_size, num_groups_w2),
170
+ "Marlin": (num_experts, num_groups_w2, hidden_size),
171
+ },
172
+ "w2_zp": {
173
+ "Marlin": (
174
+ num_experts,
175
+ num_groups_w2,
176
+ hidden_size // self.packed_factor,
177
+ ),
178
+ },
179
+ }
180
+ backend_key = "Flashinfer" if is_flashinfer else "Marlin"
181
+ return shape_map[weight_name][backend_key]
182
+
183
+ def create_weights(
184
+ self,
185
+ layer: torch.nn.Module,
186
+ num_experts: int,
187
+ hidden_size: int,
188
+ intermediate_size_per_partition: int,
189
+ params_dtype: torch.dtype,
190
+ **extra_weight_attrs,
191
+ ):
192
+ intermediate_size_full = extra_weight_attrs.pop("intermediate_size_full")
193
+
194
+ # Will transpose the loaded weight along the
195
+ # intermediate and hidden dim sizes. Will
196
+ # shard for TP along the transposed dims
197
+ is_transposed = self.wna16_backend != WNA16MoEBackend.FLASHINFER_TRTLLM
198
+ extra_weight_attrs.update(
199
+ {"is_transposed": is_transposed, "quant_method": self.strategy}
200
+ )
201
+
202
+ w13_weight = torch.nn.Parameter(
203
+ torch.empty(
204
+ *self.get_weight_shape(
205
+ "w13_weight",
206
+ num_experts,
207
+ hidden_size,
208
+ intermediate_size_per_partition,
209
+ ),
210
+ dtype=torch.int32,
211
+ ),
212
+ requires_grad=False,
213
+ )
214
+ layer.register_parameter("w13_weight_packed", w13_weight)
215
+ set_weight_attrs(w13_weight, extra_weight_attrs)
216
+
217
+ w2_weight = torch.nn.Parameter(
218
+ torch.empty(
219
+ *self.get_weight_shape(
220
+ "w2_weight",
221
+ num_experts,
222
+ hidden_size,
223
+ intermediate_size_per_partition,
224
+ ),
225
+ dtype=torch.int32,
226
+ ),
227
+ requires_grad=False,
228
+ )
229
+ layer.register_parameter("w2_weight_packed", w2_weight)
230
+ set_weight_attrs(w2_weight, extra_weight_attrs)
231
+
232
+ # In the case where we have actorder/g_idx,
233
+ # we do not partition the w2 scales
234
+ load_full_w2 = self.actorder and self.group_size != -1
235
+ w2_scales_size = (
236
+ intermediate_size_full if load_full_w2 else intermediate_size_per_partition
237
+ )
238
+
239
+ self.is_k_full = (not self.actorder) or (
240
+ intermediate_size_per_partition == intermediate_size_full
241
+ )
242
+
243
+ if self.strategy == "channel":
244
+ num_groups_w2 = num_groups_w13 = 1
245
+ self.group_size = -1
246
+ else:
247
+ num_groups_w2 = w2_scales_size // self.group_size
248
+ num_groups_w13 = hidden_size // self.group_size
249
+
250
+ layer.num_groups_w13 = num_groups_w13
251
+ layer.num_groups_w2 = num_groups_w2
252
+
253
+ w13_scale = torch.nn.Parameter(
254
+ torch.ones(
255
+ *self.get_weight_shape(
256
+ "w13_scale",
257
+ num_experts,
258
+ hidden_size,
259
+ intermediate_size_per_partition,
260
+ num_groups_w13=num_groups_w13,
261
+ ),
262
+ dtype=params_dtype,
263
+ ),
264
+ requires_grad=False,
265
+ )
266
+ layer.register_parameter("w13_weight_scale", w13_scale)
267
+ set_weight_attrs(w13_scale, extra_weight_attrs)
268
+
269
+ w2_scale = torch.nn.Parameter(
270
+ torch.ones(
271
+ *self.get_weight_shape(
272
+ "w2_scale",
273
+ num_experts,
274
+ hidden_size,
275
+ intermediate_size_per_partition,
276
+ num_groups_w2=num_groups_w2,
277
+ ),
278
+ dtype=params_dtype,
279
+ ),
280
+ requires_grad=False,
281
+ )
282
+ layer.register_parameter("w2_weight_scale", w2_scale)
283
+ set_weight_attrs(w2_scale, extra_weight_attrs)
284
+ set_weight_attrs(w2_scale, {"load_full_w2": load_full_w2})
285
+
286
+ if not self.symmetric:
287
+ w13_zp = torch.nn.Parameter(
288
+ torch.zeros(
289
+ *self.get_weight_shape(
290
+ "w13_zp",
291
+ num_experts,
292
+ hidden_size,
293
+ intermediate_size_per_partition,
294
+ num_groups_w13=num_groups_w13,
295
+ ),
296
+ dtype=torch.int32,
297
+ ),
298
+ requires_grad=False,
299
+ )
300
+ layer.register_parameter("w13_weight_zero_point", w13_zp)
301
+ set_weight_attrs(w13_zp, extra_weight_attrs)
302
+
303
+ w2_zp = torch.nn.Parameter(
304
+ torch.zeros(
305
+ *self.get_weight_shape(
306
+ "w2_zp",
307
+ num_experts,
308
+ hidden_size,
309
+ intermediate_size_per_partition,
310
+ num_groups_w2=num_groups_w2,
311
+ ),
312
+ dtype=torch.int32,
313
+ ),
314
+ requires_grad=False,
315
+ )
316
+ layer.register_parameter("w2_weight_zero_point", w2_zp)
317
+ set_weight_attrs(w2_zp, extra_weight_attrs)
318
+
319
+ w2_weight_shape = torch.nn.Parameter(
320
+ torch.empty(num_experts, 2), requires_grad=False
321
+ )
322
+ layer.register_parameter("w2_weight_shape", w2_weight_shape)
323
+ set_weight_attrs(w2_weight_shape, extra_weight_attrs)
324
+ w13_weight_shape = torch.nn.Parameter(
325
+ torch.empty(num_experts, 2), requires_grad=False
326
+ )
327
+
328
+ layer.register_parameter("w13_weight_shape", w13_weight_shape)
329
+ set_weight_attrs(w13_weight_shape, extra_weight_attrs)
330
+
331
+ w13_g_idx = torch.nn.Parameter(
332
+ torch.empty(
333
+ num_experts,
334
+ hidden_size,
335
+ dtype=torch.int32,
336
+ ),
337
+ requires_grad=False,
338
+ )
339
+ layer.register_parameter("w13_weight_g_idx", w13_g_idx)
340
+ set_weight_attrs(w13_g_idx, extra_weight_attrs)
341
+
342
+ w2_g_idx = torch.nn.Parameter(
343
+ torch.empty(
344
+ num_experts,
345
+ intermediate_size_per_partition,
346
+ dtype=torch.int32,
347
+ ),
348
+ requires_grad=False,
349
+ )
350
+ layer.register_parameter("w2_weight_g_idx", w2_g_idx)
351
+ set_weight_attrs(w2_g_idx, extra_weight_attrs)
352
+
353
+ w13_g_idx_sort_indices = torch.nn.Parameter(
354
+ torch.empty(
355
+ num_experts,
356
+ hidden_size,
357
+ dtype=torch.int32,
358
+ ),
359
+ requires_grad=False,
360
+ )
361
+ layer.register_parameter("w13_g_idx_sort_indices", w13_g_idx_sort_indices)
362
+ set_weight_attrs(w13_g_idx_sort_indices, extra_weight_attrs)
363
+
364
+ w2_g_idx_sort_indices = torch.nn.Parameter(
365
+ torch.empty(
366
+ num_experts,
367
+ intermediate_size_per_partition,
368
+ dtype=torch.int32,
369
+ ),
370
+ requires_grad=False,
371
+ )
372
+ layer.register_parameter("w2_g_idx_sort_indices", w2_g_idx_sort_indices)
373
+ set_weight_attrs(w2_g_idx_sort_indices, extra_weight_attrs)
374
+
375
+ layer.a13_scale = None
376
+ layer.a2_scale = None
377
+
378
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
379
+ # Process weights using the shared oracle infrastructure
380
+ is_flashinfer = self.wna16_backend == WNA16MoEBackend.FLASHINFER_TRTLLM
381
+ (
382
+ w13_qweight,
383
+ w2_qweight,
384
+ w13_scales,
385
+ w2_scales,
386
+ w13_g_idx_processed,
387
+ w2_g_idx_processed,
388
+ w13_g_idx_sort_indices,
389
+ w2_g_idx_sort_indices,
390
+ w13_qzeros,
391
+ w2_qzeros,
392
+ w13_input_global_scale,
393
+ w2_input_global_scale,
394
+ _, # w13_bias
395
+ _, # w2_bias
396
+ ) = convert_to_wna16_moe_kernel_format(
397
+ backend=self.wna16_backend,
398
+ layer=layer,
399
+ quant_config=self.weight_quant,
400
+ input_dtype=self.marlin_input_dtype,
401
+ w13=layer.w13_weight_packed,
402
+ w2=layer.w2_weight_packed,
403
+ w13_scale=layer.w13_weight_scale,
404
+ w2_scale=layer.w2_weight_scale,
405
+ w13_g_idx=layer.w13_weight_g_idx,
406
+ w2_g_idx=layer.w2_weight_g_idx,
407
+ w13_qzeros=getattr(layer, "w13_weight_zero_point", None),
408
+ w2_qzeros=getattr(layer, "w2_weight_zero_point", None),
409
+ )
410
+
411
+ # Replace common parameters
412
+ replace_parameter(layer, "w13_weight_packed", w13_qweight)
413
+ replace_parameter(layer, "w2_weight_packed", w2_qweight)
414
+ replace_parameter(layer, "w13_weight_scale", w13_scales)
415
+ replace_parameter(layer, "w2_weight_scale", w2_scales)
416
+
417
+ if not self.symmetric:
418
+ replace_parameter(layer, "w13_weight_zero_point", w13_qzeros)
419
+ replace_parameter(layer, "w2_weight_zero_point", w2_qzeros)
420
+
421
+ # Marlin-specific parameters (not needed for Flashinfer)
422
+ if not is_flashinfer:
423
+ replace_parameter(layer, "w13_weight_g_idx", w13_g_idx_processed)
424
+ replace_parameter(layer, "w2_weight_g_idx", w2_g_idx_processed)
425
+ replace_parameter(layer, "w13_g_idx_sort_indices", w13_g_idx_sort_indices)
426
+ replace_parameter(layer, "w2_g_idx_sort_indices", w2_g_idx_sort_indices)
427
+
428
+ # Register input global scales if present
429
+ if w13_input_global_scale is not None:
430
+ layer.register_parameter(
431
+ "w13_input_global_scale",
432
+ torch.nn.Parameter(w13_input_global_scale, requires_grad=False),
433
+ )
434
+ if w2_input_global_scale is not None:
435
+ layer.register_parameter(
436
+ "w2_input_global_scale",
437
+ torch.nn.Parameter(w2_input_global_scale, requires_grad=False),
438
+ )
439
+
440
+ layer.workspace = marlin_make_workspace_new(
441
+ layer.w13_weight_g_idx.device, 4
442
+ )
443
+
444
+ # Alias packed weights to w13_weight/w2_weight for the modular kernel interface
445
+ layer.w13_weight = layer.w13_weight_packed
446
+ layer.w2_weight = layer.w2_weight_packed
447
+
448
+ assert self.experts_cls is not None
449
+ self.moe_quant_config = self.get_fused_moe_quant_config(layer)
450
+ assert self.moe_quant_config is not None
451
+
452
+ # Add Marlin-specific arguments
453
+ marlin_args: dict[str, Any] = {}
454
+ if not is_flashinfer:
455
+ marlin_args = {
456
+ "w13_g_idx": layer.w13_weight_g_idx,
457
+ "w2_g_idx": layer.w2_weight_g_idx,
458
+ "w13_g_idx_sort_indices": layer.w13_g_idx_sort_indices,
459
+ "w2_g_idx_sort_indices": layer.w2_g_idx_sort_indices,
460
+ "is_k_full": self.is_k_full,
461
+ }
462
+
463
+ self.moe_kernel = make_wna16_moe_kernel(
464
+ moe_quant_config=self.moe_quant_config,
465
+ moe_config=self.moe,
466
+ experts_cls=self.experts_cls,
467
+ routing_tables=layer._expert_routing_tables(),
468
+ **marlin_args,
469
+ )
470
+
471
+ def get_fused_moe_quant_config(
472
+ self, layer: torch.nn.Module
473
+ ) -> FusedMoEQuantConfig | None:
474
+ return make_wna16_moe_quant_config(
475
+ w1_scale=layer.w13_weight_scale,
476
+ w2_scale=layer.w2_weight_scale,
477
+ group_size=self.group_size,
478
+ num_bits=self.num_bits,
479
+ w1_zp=getattr(layer, "w13_weight_zero_point", None),
480
+ w2_zp=getattr(layer, "w2_weight_zero_point", None),
481
+ # SwiGLU/swigluoai gate params live on the layer; plumb them into the
482
+ # quant config so the fused activation (swigluoai_uninterleave on
483
+ # MiniMax-M3) receives gemm1_clamp_limit/alpha/beta. Mirrors the
484
+ # fp8/nvfp4/mxfp8 CT MoE methods which already do this.
485
+ gemm1_clamp_limit=getattr(layer, "swiglu_limit", None),
486
+ gemm1_alpha=getattr(layer, "swiglu_alpha", None),
487
+ gemm1_beta=getattr(layer, "swiglu_beta", None),
488
+ )
489
+
490
+ def apply_monolithic(
491
+ self,
492
+ layer: RoutedExperts,
493
+ x: torch.Tensor,
494
+ router_logits: torch.Tensor,
495
+ input_ids: torch.Tensor | None = None,
496
+ ) -> torch.Tensor:
497
+ assert self.is_monolithic
498
+ assert self.moe_kernel is not None
499
+ return self.moe_kernel.apply_monolithic(
500
+ x,
501
+ layer.w13_weight,
502
+ layer.w2_weight,
503
+ router_logits,
504
+ activation=layer.activation,
505
+ global_num_experts=layer.global_num_experts,
506
+ expert_map=layer.expert_map,
507
+ apply_router_weight_on_input=layer.apply_router_weight_on_input,
508
+ num_expert_group=layer.num_expert_group,
509
+ topk_group=layer.topk_group,
510
+ e_score_correction_bias=layer.e_score_correction_bias,
511
+ routed_scaling_factor=layer.routed_scaling_factor,
512
+ )
513
+
514
+ def apply(
515
+ self,
516
+ layer: RoutedExperts,
517
+ x: torch.Tensor,
518
+ topk_weights: torch.Tensor,
519
+ topk_ids: torch.Tensor,
520
+ shared_experts: SharedExperts | None,
521
+ shared_experts_input: torch.Tensor | None,
522
+ ) -> torch.Tensor:
523
+ assert not self.is_monolithic
524
+ assert self.moe_kernel is not None
525
+ return self.moe_kernel.apply(
526
+ x,
527
+ layer.w13_weight,
528
+ layer.w2_weight,
529
+ topk_weights,
530
+ topk_ids,
531
+ activation=layer.activation,
532
+ global_num_experts=layer.global_num_experts,
533
+ expert_map=layer.expert_map,
534
+ apply_router_weight_on_input=layer.apply_router_weight_on_input,
535
+ shared_experts=shared_experts,
536
+ shared_experts_input=shared_experts_input,
537
+ )
vllm-patches/B_int_wna16.py ADDED
@@ -0,0 +1,917 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+ import sys
4
+ from enum import Enum
5
+ from typing import Any
6
+
7
+ import torch
8
+ from compressed_tensors.quantization import (
9
+ QuantizationArgs,
10
+ )
11
+
12
+ import vllm._custom_ops as ops
13
+ import vllm.model_executor.layers.fused_moe.modular_kernel as mk
14
+ from vllm.logger import init_logger
15
+ from vllm.model_executor.layers.fused_moe.config import (
16
+ FusedMoEConfig,
17
+ FusedMoEQuantConfig,
18
+ int4_w4a16_moe_quant_config,
19
+ int8_w8a16_moe_quant_config,
20
+ )
21
+ from vllm.model_executor.layers.fused_moe.experts.marlin_moe import (
22
+ BatchedMarlinExperts,
23
+ MarlinExperts,
24
+ MarlinExpertsBase,
25
+ )
26
+ from vllm.model_executor.layers.fused_moe.experts.trtllm_mxint4_moe import (
27
+ TrtLlmMxint4ExpertsMonolithic,
28
+ )
29
+ from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
30
+ from vllm.model_executor.layers.quantization.utils.marlin_utils import (
31
+ marlin_act_int8_process_scales,
32
+ marlin_moe_permute_scales,
33
+ marlin_permute_bias,
34
+ moe_awq_to_marlin_zero_points,
35
+ moe_packed_to_marlin_zero_points,
36
+ )
37
+ from vllm.model_executor.layers.quantization.utils.quant_utils import (
38
+ QuantKey,
39
+ )
40
+ from vllm.platforms import current_platform
41
+
42
+ logger = init_logger(__name__)
43
+
44
+
45
+ class WNA16MoEBackend(Enum):
46
+ MARLIN = "MARLIN"
47
+ BATCHED_MARLIN = "BATCHED_MARLIN"
48
+ FLASHINFER_TRTLLM = "FLASHINFER_TRTLLM"
49
+ XPU = "XPU"
50
+
51
+
52
+ def backend_to_kernel_cls(
53
+ backend: WNA16MoEBackend,
54
+ ) -> list[type[mk.FusedMoEExperts]]:
55
+ """Return the experts class for the given backend, or None for NONE."""
56
+ if backend == WNA16MoEBackend.MARLIN:
57
+ return [MarlinExperts]
58
+ elif backend == WNA16MoEBackend.BATCHED_MARLIN:
59
+ return [BatchedMarlinExperts]
60
+ elif backend == WNA16MoEBackend.FLASHINFER_TRTLLM:
61
+ return [TrtLlmMxint4ExpertsMonolithic]
62
+ elif backend == WNA16MoEBackend.XPU:
63
+ from vllm.model_executor.layers.fused_moe.experts.xpu_moe import (
64
+ XPUExpertsWNA16,
65
+ )
66
+
67
+ return [XPUExpertsWNA16]
68
+ else:
69
+ raise ValueError(f"Unknown WNA16 MoE backend: {backend.value}")
70
+
71
+
72
+ def _get_priority_backends() -> list[WNA16MoEBackend]:
73
+ """
74
+ Get available backends in priority order based on platform and config.
75
+ """
76
+ if current_platform.is_xpu():
77
+ return [WNA16MoEBackend.XPU]
78
+
79
+ _AVAILABLE_BACKENDS = [
80
+ WNA16MoEBackend.FLASHINFER_TRTLLM,
81
+ WNA16MoEBackend.MARLIN,
82
+ WNA16MoEBackend.BATCHED_MARLIN,
83
+ ]
84
+ return _AVAILABLE_BACKENDS
85
+
86
+
87
+ def select_wna16_moe_backend(
88
+ config: FusedMoEConfig,
89
+ weight_key: QuantKey,
90
+ ) -> tuple[WNA16MoEBackend, type[mk.FusedMoEExperts]]:
91
+ """Select the WNA16 MoE backend.
92
+
93
+ Args:
94
+ config: the shared ``FusedMoEConfig`` for this layer.
95
+ weight_key: The QuantKey describing the weight quantization.
96
+ Must have int4 or int8 type.
97
+
98
+ Returns:
99
+ A tuple of (``WNA16MoEBackend``, experts class or ``None``).
100
+ """
101
+
102
+ activation_format = (
103
+ mk.FusedMoEActivationFormat.BatchedExperts
104
+ if config.moe_parallel_config.use_batched_activation_format
105
+ else mk.FusedMoEActivationFormat.Standard
106
+ )
107
+
108
+ def _make_log_backend(backend: WNA16MoEBackend):
109
+ return f"Using '{backend.value}' WNA16 MoE backend."
110
+
111
+ def _make_log_unsupported(backend: WNA16MoEBackend, reason: str | None) -> str:
112
+ if reason:
113
+ return (
114
+ f"WNA16 MoE backend '{backend.value}' does not support the "
115
+ f"deployment configuration since {reason}."
116
+ )
117
+ return (
118
+ f"WNA16 MoE backend '{backend.value}' does not support the "
119
+ "deployment configuration."
120
+ )
121
+
122
+ def _return_or_raise(
123
+ backend: WNA16MoEBackend,
124
+ config: FusedMoEConfig,
125
+ weight_key: QuantKey | None,
126
+ activation_key: QuantKey | None,
127
+ activation_format: mk.FusedMoEActivationFormat,
128
+ ) -> tuple[WNA16MoEBackend, type[mk.FusedMoEExperts]]:
129
+ reason: str | None = None
130
+ for k_cls in backend_to_kernel_cls(backend):
131
+ supported, reason = k_cls.is_supported_config(
132
+ k_cls, config, weight_key, activation_key, activation_format
133
+ )
134
+ if supported:
135
+ logger.info_once(_make_log_backend(backend), scope="local")
136
+ return backend, k_cls
137
+ raise ValueError(_make_log_unsupported(backend, reason))
138
+
139
+ # Select kernels in order of backend.
140
+ AVAILABLE_BACKENDS = _get_priority_backends()
141
+
142
+ for backend in AVAILABLE_BACKENDS:
143
+ activation_key = None # always BF16 activation for WNA16 MoE
144
+ for k_cls in backend_to_kernel_cls(backend):
145
+ supported, reason = k_cls.is_supported_config(
146
+ k_cls, config, weight_key, activation_key, activation_format
147
+ )
148
+ if supported:
149
+ logger.info_once(_make_log_backend(backend), scope="local")
150
+ return backend, k_cls
151
+ else:
152
+ logger.debug_once(_make_log_unsupported(backend, reason), scope="local")
153
+
154
+ raise NotImplementedError(
155
+ "No WNA16 MoE backend supports the deployment configuration."
156
+ )
157
+
158
+
159
+ def make_wna16_moe_quant_config(
160
+ w1_scale: torch.Tensor,
161
+ w2_scale: torch.Tensor,
162
+ group_size: int,
163
+ num_bits: int,
164
+ w1_zp: torch.Tensor | None = None,
165
+ w2_zp: torch.Tensor | None = None,
166
+ w1_bias: torch.Tensor | None = None,
167
+ w2_bias: torch.Tensor | None = None,
168
+ a1_gscale: torch.Tensor | None = None,
169
+ a2_gscale: torch.Tensor | None = None,
170
+ gemm1_clamp_limit: float | None = None,
171
+ gemm1_alpha: float | None = None,
172
+ gemm1_beta: float | None = None,
173
+ ) -> FusedMoEQuantConfig:
174
+ """Create the FusedMoEQuantConfig for 4 or 8-bit WNA16 MoE."""
175
+ if num_bits == 4:
176
+ return int4_w4a16_moe_quant_config(
177
+ w1_scale=w1_scale,
178
+ w2_scale=w2_scale,
179
+ w1_zp=w1_zp,
180
+ w2_zp=w2_zp,
181
+ w1_bias=w1_bias,
182
+ w2_bias=w2_bias,
183
+ block_shape=[0, group_size],
184
+ a1_gscale=a1_gscale,
185
+ a2_gscale=a2_gscale,
186
+ gemm1_clamp_limit=gemm1_clamp_limit,
187
+ gemm1_alpha=gemm1_alpha,
188
+ gemm1_beta=gemm1_beta,
189
+ )
190
+ else:
191
+ assert num_bits == 8
192
+ return int8_w8a16_moe_quant_config(
193
+ w1_scale=w1_scale,
194
+ w2_scale=w2_scale,
195
+ w1_zp=w1_zp,
196
+ w2_zp=w2_zp,
197
+ w1_bias=w1_bias,
198
+ w2_bias=w2_bias,
199
+ block_shape=[0, group_size],
200
+ a1_gscale=a1_gscale,
201
+ a2_gscale=a2_gscale,
202
+ gemm1_clamp_limit=gemm1_clamp_limit,
203
+ gemm1_alpha=gemm1_alpha,
204
+ gemm1_beta=gemm1_beta,
205
+ )
206
+
207
+
208
+ def make_wna16_moe_kernel(
209
+ moe_quant_config: FusedMoEQuantConfig,
210
+ moe_config: FusedMoEConfig,
211
+ experts_cls: type[mk.FusedMoEExperts],
212
+ is_k_full: bool = False,
213
+ w13_g_idx: torch.Tensor | None = None,
214
+ w2_g_idx: torch.Tensor | None = None,
215
+ w13_g_idx_sort_indices: torch.Tensor | None = None,
216
+ w2_g_idx_sort_indices: torch.Tensor | None = None,
217
+ routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
218
+ ) -> mk.FusedMoEKernel:
219
+ from vllm.model_executor.layers.fused_moe.all2all_utils import (
220
+ maybe_make_prepare_finalize,
221
+ )
222
+ from vllm.model_executor.layers.fused_moe.experts.xpu_moe import (
223
+ XPUExpertsWNA16,
224
+ )
225
+
226
+ # Currently, we only support TrtLlmMxint4ExpertsMonolithic, MarlinExperts
227
+ # and BatchedMarlinExperts
228
+ assert experts_cls in (
229
+ MarlinExperts,
230
+ BatchedMarlinExperts,
231
+ TrtLlmMxint4ExpertsMonolithic,
232
+ XPUExpertsWNA16,
233
+ )
234
+
235
+ is_monolithic = experts_cls.is_monolithic()
236
+
237
+ prepare_finalize = maybe_make_prepare_finalize(
238
+ moe=moe_config,
239
+ quant_config=moe_quant_config,
240
+ routing_tables=routing_tables,
241
+ allow_new_interface=True,
242
+ use_monolithic=is_monolithic,
243
+ )
244
+ assert prepare_finalize is not None
245
+
246
+ logger.info_once("Using %s", prepare_finalize.__class__.__name__, scope="local")
247
+
248
+ extra_args: dict[str, Any] = {}
249
+ if issubclass(experts_cls, MarlinExpertsBase):
250
+ extra_args = {
251
+ "w13_g_idx": w13_g_idx,
252
+ "w2_g_idx": w2_g_idx,
253
+ "w13_g_idx_sort_indices": w13_g_idx_sort_indices,
254
+ "w2_g_idx_sort_indices": w2_g_idx_sort_indices,
255
+ "is_k_full": is_k_full,
256
+ }
257
+
258
+ if experts_cls is XPUExpertsWNA16:
259
+ assert (
260
+ prepare_finalize.activation_format == mk.FusedMoEActivationFormat.Standard
261
+ ), (
262
+ "XPUExpertsWNA16 only supports the Standard activation format; "
263
+ "xpu_fused_moe(is_int4=True) does not implement BatchedExperts."
264
+ )
265
+ experts: mk.FusedMoEExperts = XPUExpertsWNA16(
266
+ moe_config=moe_config,
267
+ quant_config=moe_quant_config,
268
+ )
269
+ elif (
270
+ prepare_finalize.activation_format == mk.FusedMoEActivationFormat.BatchedExperts
271
+ ):
272
+ max_num_tokens = prepare_finalize.max_num_tokens_per_rank()
273
+ assert max_num_tokens is not None
274
+ experts = experts_cls(
275
+ max_num_tokens=max_num_tokens,
276
+ num_dispatchers=prepare_finalize.num_dispatchers(),
277
+ moe_config=moe_config,
278
+ quant_config=moe_quant_config,
279
+ **extra_args,
280
+ )
281
+ else:
282
+ experts = experts_cls(
283
+ moe_config=moe_config,
284
+ quant_config=moe_quant_config,
285
+ **extra_args,
286
+ )
287
+
288
+ return mk.FusedMoEKernel(
289
+ prepare_finalize,
290
+ experts,
291
+ )
292
+
293
+
294
+ # ---------------------------------------------------------------------------
295
+ # Per-backend weight post-processing
296
+ # ---------------------------------------------------------------------------
297
+
298
+
299
+ def _process_weights_flashinfer(
300
+ w13_qweight: torch.Tensor,
301
+ w2_qweight: torch.Tensor,
302
+ w13_scales: torch.Tensor,
303
+ w2_scales: torch.Tensor,
304
+ w13_g_idx: torch.Tensor,
305
+ w2_g_idx: torch.Tensor,
306
+ w13_bias: torch.Tensor | None = None,
307
+ w2_bias: torch.Tensor | None = None,
308
+ ) -> tuple[
309
+ torch.Tensor, # w13_qweight
310
+ torch.Tensor, # w2_qweight
311
+ torch.Tensor, # w13_scales
312
+ torch.Tensor, # w2_scales
313
+ torch.Tensor, # w13_g_idx
314
+ torch.Tensor, # w2_g_idx
315
+ torch.Tensor | None, # w13_g_idx_sort_indices
316
+ torch.Tensor | None, # w2_g_idx_sort_indices
317
+ torch.Tensor | None, # w13_qzeros
318
+ torch.Tensor | None, # w2_qzeros
319
+ torch.Tensor | None, # w13_input_global_scale
320
+ torch.Tensor | None, # w2_input_global_scale
321
+ torch.Tensor | None, # w13_bias
322
+ torch.Tensor | None, # w2_bias
323
+ ]:
324
+ """Flashinfer (TRT-LLM MXINT4) weight post-processing.
325
+
326
+ Steps
327
+ -----
328
+ 1. Transform weights/scales via ``prepare_static_weights_for_trtllm_mxint4_moe``.
329
+ 2. Return transformed tensors, passing through g_idx/bias unchanged.
330
+ """
331
+ from vllm.model_executor.layers.quantization.utils.flashinfer_mxint4_moe import (
332
+ prepare_static_weights_for_trtllm_mxint4_moe,
333
+ )
334
+
335
+ dict_weights_mxint4 = prepare_static_weights_for_trtllm_mxint4_moe(
336
+ w13_qweight,
337
+ w13_scales,
338
+ w2_qweight,
339
+ w2_scales,
340
+ )
341
+
342
+ return (
343
+ dict_weights_mxint4["gemm1_weights"],
344
+ dict_weights_mxint4["gemm2_weights"],
345
+ dict_weights_mxint4["gemm1_scales"],
346
+ dict_weights_mxint4["gemm2_scales"],
347
+ w13_g_idx,
348
+ w2_g_idx,
349
+ None,
350
+ None,
351
+ None,
352
+ None,
353
+ None,
354
+ None,
355
+ w13_bias,
356
+ w2_bias,
357
+ )
358
+
359
+
360
+ def _process_weights_marlin(
361
+ layer: torch.nn.Module,
362
+ input_dtype: torch.dtype | None,
363
+ num_bits: int,
364
+ pack_factor: int,
365
+ group_size: int,
366
+ actorder: str | None,
367
+ w13_qweight: torch.Tensor,
368
+ w2_qweight: torch.Tensor,
369
+ w13_scales: torch.Tensor,
370
+ w2_scales: torch.Tensor,
371
+ w13_g_idx: torch.Tensor,
372
+ w2_g_idx: torch.Tensor,
373
+ w13_qzeros: torch.Tensor | None = None,
374
+ w2_qzeros: torch.Tensor | None = None,
375
+ w13_bias: torch.Tensor | None = None,
376
+ w2_bias: torch.Tensor | None = None,
377
+ ) -> tuple[
378
+ torch.Tensor, # w13_qweight
379
+ torch.Tensor, # w2_qweight
380
+ torch.Tensor, # w13_scales
381
+ torch.Tensor, # w2_scales
382
+ torch.Tensor, # w13_g_idx
383
+ torch.Tensor, # w2_g_idx
384
+ torch.Tensor, # w13_g_idx_sort_indices
385
+ torch.Tensor, # w2_g_idx_sort_indices
386
+ torch.Tensor | None, # w13_qzeros
387
+ torch.Tensor | None, # w2_qzeros
388
+ torch.Tensor | None, # w13_input_global_scale
389
+ torch.Tensor | None, # w2_input_global_scale
390
+ torch.Tensor | None, # w13_bias
391
+ torch.Tensor | None, # w2_bias
392
+ ]:
393
+ """Standard Marlin weight post-processing shared by MARLIN and
394
+ BATCHED_MARLIN backends.
395
+
396
+ Steps
397
+ -----
398
+ 1. Optional FP8 preprocessing of packed weights / scales.
399
+ 2. Sort / reset g_idx tensors for act-order handling.
400
+ 3. Repack weights via ``gptq_marlin_moe_repack``.
401
+ 4. Permute scales (and optionally extract INT8 global scales).
402
+ 5. Permute bias tensors.
403
+ """
404
+ is_a_8bit = input_dtype is not None and input_dtype.itemsize == 1
405
+
406
+ marlin_w13_qweight: torch.Tensor
407
+ marlin_w2_qweight: torch.Tensor
408
+ marlin_w13_scales: torch.Tensor
409
+ marlin_w2_scales: torch.Tensor
410
+ w13_g_idx_sort_indices: torch.Tensor | None = None
411
+ w2_g_idx_sort_indices: torch.Tensor | None = None
412
+ w13_input_global_scale: torch.Tensor | None = None
413
+ w2_input_global_scale: torch.Tensor | None = None
414
+ w13_bias_out: torch.Tensor | None = None
415
+ w2_bias_out: torch.Tensor | None = None
416
+
417
+ # --- FP8 weight / scale adjustment ---
418
+ if input_dtype == torch.float8_e4m3fn:
419
+ # NOTE: for non-zp quantization format only
420
+ marlin_w13_qweight = ops.marlin_int4_fp8_preprocess(w13_qweight, inplace=False)
421
+ marlin_w2_qweight = ops.marlin_int4_fp8_preprocess(w2_qweight, inplace=False)
422
+ marlin_w13_scales = w13_scales.data * 512
423
+ marlin_w2_scales = w2_scales.data * 512
424
+ else:
425
+ marlin_w13_qweight = w13_qweight
426
+ marlin_w2_qweight = w2_qweight
427
+ marlin_w13_scales = w13_scales
428
+ marlin_w2_scales = w2_scales
429
+
430
+ # --- Process act_order (g_idx) ---
431
+ if actorder == "group":
432
+ num_experts = w13_g_idx.shape[0]
433
+ w13_g_idx_sort_indices = torch.empty_like(w13_g_idx)
434
+ w2_g_idx_sort_indices = torch.empty_like(w2_g_idx)
435
+ w13_sorted_g_idx = torch.empty_like(w13_g_idx)
436
+ w2_sorted_g_idx = torch.empty_like(w2_g_idx)
437
+ for e in range(num_experts):
438
+ w13_g_idx_sort_indices[e] = torch.argsort(w13_g_idx[e]).to(torch.int32)
439
+ w2_g_idx_sort_indices[e] = torch.argsort(w2_g_idx[e]).to(torch.int32)
440
+ w13_sorted_g_idx[e] = w13_g_idx[e][w13_g_idx_sort_indices[e]]
441
+ w2_sorted_g_idx[e] = w2_g_idx[e][w2_g_idx_sort_indices[e]]
442
+ w13_g_idx = w13_sorted_g_idx
443
+ w2_g_idx = w2_sorted_g_idx
444
+ else:
445
+ num_experts = w13_g_idx.shape[0]
446
+ device = w13_g_idx.device
447
+ w13_g_idx = torch.nn.Parameter(
448
+ torch.empty((num_experts, 0), dtype=torch.int32, device=device),
449
+ requires_grad=False,
450
+ )
451
+ w2_g_idx = torch.nn.Parameter(
452
+ torch.empty((num_experts, 0), dtype=torch.int32, device=device),
453
+ requires_grad=False,
454
+ )
455
+ w13_g_idx_sort_indices = torch.nn.Parameter(
456
+ torch.empty((num_experts, 0), dtype=torch.int32, device=device),
457
+ requires_grad=False,
458
+ )
459
+ w2_g_idx_sort_indices = torch.nn.Parameter(
460
+ torch.empty((num_experts, 0), dtype=torch.int32, device=device),
461
+ requires_grad=False,
462
+ )
463
+
464
+ # --- Repack weights ---
465
+ marlin_w13_qweight = ops.gptq_marlin_moe_repack(
466
+ marlin_w13_qweight,
467
+ w13_g_idx_sort_indices,
468
+ marlin_w13_qweight.shape[1] * pack_factor,
469
+ marlin_w13_qweight.shape[2],
470
+ num_bits,
471
+ is_a_8bit=is_a_8bit,
472
+ )
473
+ marlin_w2_qweight = ops.gptq_marlin_moe_repack(
474
+ marlin_w2_qweight,
475
+ w2_g_idx_sort_indices,
476
+ marlin_w2_qweight.shape[1] * pack_factor,
477
+ marlin_w2_qweight.shape[2],
478
+ num_bits,
479
+ is_a_8bit=is_a_8bit,
480
+ )
481
+
482
+ # --- Permute scales ---
483
+ marlin_w13_scales = marlin_moe_permute_scales(
484
+ s=marlin_w13_scales,
485
+ size_k=layer.intermediate_size_per_partition,
486
+ size_n=marlin_w13_scales.shape[2],
487
+ group_size=group_size,
488
+ is_a_8bit=is_a_8bit,
489
+ )
490
+ group_size_or_pack_factor = group_size if group_size != -1 else pack_factor
491
+ marlin_w2_scales = marlin_moe_permute_scales(
492
+ s=marlin_w2_scales,
493
+ size_k=marlin_w2_scales.shape[1] * group_size_or_pack_factor,
494
+ size_n=marlin_w2_scales.shape[2],
495
+ group_size=group_size,
496
+ is_a_8bit=is_a_8bit,
497
+ )
498
+
499
+ if input_dtype == torch.int8:
500
+ if layer.num_groups_w13 > 1:
501
+ marlin_w13_scales, w13_input_global_scale = marlin_act_int8_process_scales(
502
+ marlin_w13_scales
503
+ )
504
+ if layer.num_groups_w2 > 1:
505
+ marlin_w2_scales, w2_input_global_scale = marlin_act_int8_process_scales(
506
+ marlin_w2_scales
507
+ )
508
+
509
+ # --- Permute zero points ---
510
+ if w13_qzeros is not None and w2_qzeros is not None:
511
+ w13_qzeros = moe_packed_to_marlin_zero_points(
512
+ w13_qzeros,
513
+ size_k=w13_qzeros.shape[1],
514
+ size_n=w13_qzeros.shape[2] * pack_factor,
515
+ num_bits=num_bits,
516
+ is_a_8bit=is_a_8bit,
517
+ )
518
+ w2_qzeros = moe_packed_to_marlin_zero_points(
519
+ w2_qzeros,
520
+ size_k=w2_qzeros.shape[1],
521
+ size_n=w2_qzeros.shape[2] * pack_factor,
522
+ num_bits=num_bits,
523
+ is_a_8bit=is_a_8bit,
524
+ )
525
+
526
+ # --- Permute bias ---
527
+ if w13_bias is not None:
528
+ w13_bias_out = marlin_permute_bias(w13_bias)
529
+ if w2_bias is not None:
530
+ w2_bias_out = marlin_permute_bias(w2_bias)
531
+
532
+ return (
533
+ marlin_w13_qweight,
534
+ marlin_w2_qweight,
535
+ marlin_w13_scales,
536
+ marlin_w2_scales,
537
+ w13_g_idx,
538
+ w2_g_idx,
539
+ w13_g_idx_sort_indices,
540
+ w2_g_idx_sort_indices,
541
+ w13_qzeros,
542
+ w2_qzeros,
543
+ w13_input_global_scale,
544
+ w2_input_global_scale,
545
+ w13_bias_out,
546
+ w2_bias_out,
547
+ )
548
+
549
+
550
+ def _process_awq_weights_marlin(
551
+ layer: torch.nn.Module,
552
+ weight_bits: int,
553
+ pack_factor: int,
554
+ group_size: int,
555
+ input_dtype: torch.dtype | None,
556
+ w13_qweight: torch.Tensor,
557
+ w2_qweight: torch.Tensor,
558
+ w13_scales: torch.Tensor,
559
+ w2_scales: torch.Tensor,
560
+ w13_qzeros: torch.Tensor,
561
+ w2_qzeros: torch.Tensor,
562
+ w13_bias: torch.Tensor | None = None,
563
+ w2_bias: torch.Tensor | None = None,
564
+ ) -> tuple[
565
+ torch.Tensor, # w13_qweight
566
+ torch.Tensor, # w2_qweight
567
+ torch.Tensor, # w13_scales
568
+ torch.Tensor, # w2_scales
569
+ torch.Tensor | None, # w13_g_idx
570
+ torch.Tensor | None, # w2_g_idx
571
+ torch.Tensor | None, # w13_g_idx_sort_indices
572
+ torch.Tensor | None, # w2_g_idx_sort_indices
573
+ torch.Tensor | None, # w13_qzeros
574
+ torch.Tensor | None, # w2_qzeros
575
+ torch.Tensor | None, # w13_input_global_scale
576
+ torch.Tensor | None, # w2_input_global_scale
577
+ torch.Tensor | None, # w13_bias
578
+ torch.Tensor | None, # w2_bias
579
+ ]:
580
+ """AWQ-specific Marlin weight post-processing.
581
+
582
+ AWQ checkpoints use a different packing order than GPTQ, so they need
583
+ AWQ-specific weight repacking and zero-point conversion before Marlin runs.
584
+ """
585
+ num_experts = w13_qweight.shape[0]
586
+ device = w13_qweight.device
587
+ is_a_8bit = input_dtype is not None and input_dtype.itemsize == 1
588
+ w13_input_global_scale: torch.Tensor | None = None
589
+ w2_input_global_scale: torch.Tensor | None = None
590
+ w13_bias_out: torch.Tensor | None = None
591
+ w2_bias_out: torch.Tensor | None = None
592
+
593
+ if input_dtype == torch.float8_e4m3fn:
594
+ ops.marlin_int4_fp8_preprocess(
595
+ w13_qweight.view(-1, w13_qweight.size(2)),
596
+ w13_qzeros.view(-1, w13_qzeros.size(2)),
597
+ inplace=True,
598
+ )
599
+ ops.marlin_int4_fp8_preprocess(
600
+ w2_qweight.view(-1, w2_qweight.size(2)),
601
+ w2_qzeros.view(-1, w2_qzeros.size(2)),
602
+ inplace=True,
603
+ )
604
+ w13_scales = w13_scales.data * 512
605
+ w2_scales = w2_scales.data * 512
606
+
607
+ w13_g_idx_sort_indices = torch.nn.Parameter(
608
+ torch.empty((num_experts, 0), dtype=torch.int32, device=device),
609
+ requires_grad=False,
610
+ )
611
+ w2_g_idx_sort_indices = torch.nn.Parameter(
612
+ torch.empty((num_experts, 0), dtype=torch.int32, device=device),
613
+ requires_grad=False,
614
+ )
615
+
616
+ marlin_w13_qweight = ops.awq_marlin_moe_repack(
617
+ w13_qweight,
618
+ w13_g_idx_sort_indices,
619
+ size_k=w13_qweight.shape[1],
620
+ size_n=w13_qweight.shape[2] * pack_factor,
621
+ num_bits=weight_bits,
622
+ is_a_8bit=is_a_8bit,
623
+ )
624
+ marlin_w2_qweight = ops.awq_marlin_moe_repack(
625
+ w2_qweight,
626
+ w2_g_idx_sort_indices,
627
+ size_k=w2_qweight.shape[1],
628
+ size_n=w2_qweight.shape[2] * pack_factor,
629
+ num_bits=weight_bits,
630
+ is_a_8bit=is_a_8bit,
631
+ )
632
+
633
+ marlin_w13_scales = marlin_moe_permute_scales(
634
+ s=w13_scales,
635
+ size_k=layer.intermediate_size_per_partition,
636
+ size_n=w13_scales.shape[2],
637
+ group_size=group_size,
638
+ is_a_8bit=is_a_8bit,
639
+ )
640
+ if input_dtype == torch.int8 and layer.num_groups_w13 > 1:
641
+ marlin_w13_scales, w13_input_global_scale = marlin_act_int8_process_scales(
642
+ marlin_w13_scales
643
+ )
644
+
645
+ marlin_w2_scales = marlin_moe_permute_scales(
646
+ s=w2_scales,
647
+ size_k=layer.intermediate_size_per_partition,
648
+ size_n=w2_scales.shape[2],
649
+ group_size=group_size,
650
+ is_a_8bit=is_a_8bit,
651
+ )
652
+ if input_dtype == torch.int8 and layer.num_groups_w2 > 1:
653
+ marlin_w2_scales, w2_input_global_scale = marlin_act_int8_process_scales(
654
+ marlin_w2_scales
655
+ )
656
+
657
+ marlin_w13_qzeros = moe_awq_to_marlin_zero_points(
658
+ w13_qzeros,
659
+ size_k=w13_qzeros.shape[1],
660
+ size_n=w13_qzeros.shape[2] * pack_factor,
661
+ num_bits=weight_bits,
662
+ is_a_8bit=is_a_8bit,
663
+ )
664
+ marlin_w2_qzeros = moe_awq_to_marlin_zero_points(
665
+ w2_qzeros,
666
+ size_k=w2_qzeros.shape[1],
667
+ size_n=w2_qzeros.shape[2] * pack_factor,
668
+ num_bits=weight_bits,
669
+ is_a_8bit=is_a_8bit,
670
+ )
671
+
672
+ if w13_bias is not None:
673
+ w13_bias_out = marlin_permute_bias(w13_bias)
674
+ if w2_bias is not None:
675
+ w2_bias_out = marlin_permute_bias(w2_bias)
676
+
677
+ return (
678
+ marlin_w13_qweight,
679
+ marlin_w2_qweight,
680
+ marlin_w13_scales,
681
+ marlin_w2_scales,
682
+ None,
683
+ None,
684
+ w13_g_idx_sort_indices,
685
+ w2_g_idx_sort_indices,
686
+ marlin_w13_qzeros,
687
+ marlin_w2_qzeros,
688
+ w13_input_global_scale,
689
+ w2_input_global_scale,
690
+ w13_bias_out,
691
+ w2_bias_out,
692
+ )
693
+
694
+
695
+ def _process_weights_xpu(
696
+ layer: torch.nn.Module,
697
+ quant_config: QuantizationConfig,
698
+ w13_qweight: torch.Tensor,
699
+ w2_qweight: torch.Tensor,
700
+ w13_scales: torch.Tensor,
701
+ w2_scales: torch.Tensor,
702
+ w13_bias: torch.Tensor | None = None,
703
+ w2_bias: torch.Tensor | None = None,
704
+ ) -> tuple[
705
+ torch.Tensor, # w13_qweight
706
+ torch.Tensor, # w2_qweight
707
+ torch.Tensor, # w13_scales
708
+ torch.Tensor, # w2_scales
709
+ torch.Tensor | None, # w13_bias
710
+ torch.Tensor | None, # w2_bias
711
+ ]:
712
+ """Repack GPTQ-format INT4 MoE weights into the layout
713
+ `vllm_xpu_kernels.fused_moe_interface.xpu_fused_moe(is_int4=True)` expects:
714
+
715
+ w13: [E, 2*N, K] int4 (uint8 storage [E, 2*N, K // 2])
716
+ w13_scales: [E, 2*N, K // group_size] params_dtype
717
+ w2: [E, K, N] int4 (uint8 storage [E, K, N // 2])
718
+ w2_scales: [E, K, N // group_size] params_dtype
719
+
720
+ Input GPTQ layout from FusedMoE.weight_loader:
721
+ w13: [E, K // 8, 2*N] int32 (8 nibbles per int32 along the input dim)
722
+ w13_scales: [E, K // group_size, 2*N] params_dtype
723
+ w2: [E, N // 8, K] int32
724
+ w2_scales: [E, N // group_size, K] params_dtype
725
+
726
+ Transpose dim 1 ↔ dim 2 then view int32 → uint8 to recover sequential
727
+ int4-packed bytes along the input dim. Each packed int32 holds 8 nibbles
728
+ `(n7<<28)|(n6<<24)|...|(n1<<4)|n0` in ascending K order; on a
729
+ little-endian host the int32→uint8 view exposes them as bytes
730
+ `[n1<<4|n0, n3<<4|n2, n5<<4|n4, n7<<4|n6]`, i.e. two nibbles per byte
731
+ with the lower nibble = lower input-K index. xpu_fused_moe(is_int4=True)
732
+ expects this convention; on a big-endian host the byte order reverses
733
+ and the kernel would silently miscompute, so we hard-fail.
734
+ """
735
+ del layer, quant_config # unused — kept for parity with the marlin helper
736
+
737
+ if sys.byteorder != "little":
738
+ raise NotImplementedError(
739
+ "_process_weights_xpu requires a little-endian host: the GPTQ "
740
+ "int32 → uint8 nibble repack relies on LE byte ordering."
741
+ )
742
+
743
+ w13_xpu = w13_qweight.transpose(1, 2).contiguous().view(torch.uint8)
744
+ w2_xpu = w2_qweight.transpose(1, 2).contiguous().view(torch.uint8)
745
+ w13_scales_xpu = w13_scales.transpose(1, 2).contiguous()
746
+ w2_scales_xpu = w2_scales.transpose(1, 2).contiguous()
747
+
748
+ return (
749
+ w13_xpu,
750
+ w2_xpu,
751
+ w13_scales_xpu,
752
+ w2_scales_xpu,
753
+ w13_bias,
754
+ w2_bias,
755
+ )
756
+
757
+
758
+ def convert_to_wna16_moe_kernel_format(
759
+ backend: WNA16MoEBackend,
760
+ layer: torch.nn.Module,
761
+ quant_config: QuantizationConfig | QuantizationArgs | None,
762
+ input_dtype: torch.dtype | None,
763
+ w13: torch.Tensor,
764
+ w2: torch.Tensor,
765
+ w13_scale: torch.Tensor,
766
+ w2_scale: torch.Tensor,
767
+ w13_g_idx: torch.Tensor | None = None,
768
+ w2_g_idx: torch.Tensor | None = None,
769
+ w13_qzeros: torch.Tensor | None = None,
770
+ w2_qzeros: torch.Tensor | None = None,
771
+ w13_bias: torch.Tensor | None = None,
772
+ w2_bias: torch.Tensor | None = None,
773
+ ) -> tuple[
774
+ torch.Tensor, # w13_qweight
775
+ torch.Tensor, # w2_qweight
776
+ torch.Tensor, # w13_scales
777
+ torch.Tensor, # w2_scales
778
+ torch.Tensor | None, # w13_g_idx
779
+ torch.Tensor | None, # w2_g_idx
780
+ torch.Tensor | None, # w13_g_idx_sort_indices
781
+ torch.Tensor | None, # w2_g_idx_sort_indices
782
+ torch.Tensor | None, # w13_qzeros
783
+ torch.Tensor | None, # w2_qzeros
784
+ torch.Tensor | None, # w13_input_global_scale
785
+ torch.Tensor | None, # w2_input_global_scale
786
+ torch.Tensor | None, # w13_bias
787
+ torch.Tensor | None, # w2_bias
788
+ ]:
789
+ """Dispatch weight post-processing to the appropriate per-backend handler.
790
+
791
+ To add a new backend, implement a ``_process_weights_<name>`` helper and
792
+ add a branch here.
793
+
794
+ Args:
795
+ backend: the selected ``WNA16MoEBackend``.
796
+ layer: the ``FusedMoE`` layer whose parameters are being prepared.
797
+ quant_config: the ``QuantizationConfig`` for this layer.
798
+ input_dtype: optional activation dtype, usually should be 16 bit.
799
+ """
800
+ if backend in (
801
+ WNA16MoEBackend.MARLIN,
802
+ WNA16MoEBackend.BATCHED_MARLIN,
803
+ ):
804
+ from vllm.model_executor.layers.quantization.auto_gptq import (
805
+ AutoGPTQConfig,
806
+ )
807
+ from vllm.model_executor.layers.quantization.awq_marlin import (
808
+ AWQMarlinConfig,
809
+ )
810
+
811
+ if isinstance(quant_config, AWQMarlinConfig):
812
+ if w13_qzeros is None or w2_qzeros is None:
813
+ raise ValueError("AWQ Marlin MoE requires zero-point tensors.")
814
+
815
+ weight_bits = quant_config.weight_bits
816
+ pack_factor = quant_config.pack_factor
817
+ group_size = quant_config.group_size
818
+
819
+ return _process_awq_weights_marlin(
820
+ layer,
821
+ weight_bits,
822
+ pack_factor,
823
+ group_size,
824
+ input_dtype,
825
+ w13,
826
+ w2,
827
+ w13_scale,
828
+ w2_scale,
829
+ w13_qzeros,
830
+ w2_qzeros,
831
+ w13_bias,
832
+ w2_bias,
833
+ )
834
+ elif isinstance(quant_config, AutoGPTQConfig):
835
+ num_bits = quant_config.quant_type.size_bits
836
+ pack_factor = quant_config.pack_factor
837
+ group_size = quant_config.group_size
838
+ actorder = "group" if quant_config.desc_act else None
839
+ elif isinstance(quant_config, QuantizationArgs):
840
+ num_bits = quant_config.num_bits
841
+ pack_factor = 32 // quant_config.num_bits
842
+ group_size = quant_config.group_size
843
+ actorder = quant_config.actorder
844
+ else:
845
+ raise TypeError(
846
+ "Marlin WNA16 MoE backend requires AutoGPTQConfig, AWQMarlinConfig or "
847
+ f"QuantizationArgs, got {type(quant_config).__name__}."
848
+ )
849
+ if w13_g_idx is None or w2_g_idx is None:
850
+ raise ValueError("GPTQ Marlin MoE requires g_idx tensors.")
851
+ return _process_weights_marlin(
852
+ layer,
853
+ input_dtype,
854
+ num_bits,
855
+ pack_factor,
856
+ group_size,
857
+ actorder,
858
+ w13,
859
+ w2,
860
+ w13_scale,
861
+ w2_scale,
862
+ w13_g_idx,
863
+ w2_g_idx,
864
+ w13_qzeros,
865
+ w2_qzeros,
866
+ w13_bias,
867
+ w2_bias,
868
+ )
869
+ elif backend == WNA16MoEBackend.FLASHINFER_TRTLLM:
870
+ return _process_weights_flashinfer(
871
+ w13,
872
+ w2,
873
+ w13_scale,
874
+ w2_scale,
875
+ w13_g_idx,
876
+ w2_g_idx,
877
+ w13_bias,
878
+ w2_bias,
879
+ )
880
+ elif backend == WNA16MoEBackend.XPU:
881
+ assert quant_config is not None
882
+ (
883
+ w13_xpu,
884
+ w2_xpu,
885
+ w13_scale_xpu,
886
+ w2_scale_xpu,
887
+ w13_bias_out,
888
+ w2_bias_out,
889
+ ) = _process_weights_xpu(
890
+ layer,
891
+ quant_config,
892
+ w13,
893
+ w2,
894
+ w13_scale,
895
+ w2_scale,
896
+ w13_bias,
897
+ w2_bias,
898
+ )
899
+ empty = torch.empty((0,), dtype=torch.int32, device=w13.device)
900
+ return (
901
+ w13_xpu,
902
+ w2_xpu,
903
+ w13_scale_xpu,
904
+ w2_scale_xpu,
905
+ empty, # w13_g_idx
906
+ empty, # w2_g_idx
907
+ empty, # w13_g_idx_sort_indices
908
+ empty, # w2_g_idx_sort_indices
909
+ None, # w13_qzeros — sym int4 on XPU has none; kernel does uint4b8→s4
910
+ None, # w2_qzeros
911
+ None, # w13_input_global_scale
912
+ None, # w2_input_global_scale
913
+ w13_bias_out,
914
+ w2_bias_out,
915
+ )
916
+ else:
917
+ raise ValueError(f"Unsupported wna16 MoE backend: {backend.value}")
vllm-patches/C_config.py ADDED
@@ -0,0 +1,1436 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+ from dataclasses import dataclass
4
+ from enum import IntEnum
5
+ from typing import Union
6
+
7
+ import torch
8
+
9
+ from vllm.config import ParallelConfig, SchedulerConfig
10
+ from vllm.config.kernel import MoEBackend
11
+ from vllm.distributed import get_dp_group, get_pcp_group, get_tensor_model_parallel_rank
12
+ from vllm.logger import init_logger
13
+ from vllm.model_executor.layers.fused_moe.activation import MoEActivation
14
+ from vllm.model_executor.layers.quantization.utils.ocp_mx_utils import (
15
+ OCP_MX_DTYPES,
16
+ OCP_MX_Scheme,
17
+ )
18
+ from vllm.model_executor.layers.quantization.utils.quant_utils import GroupShape
19
+ from vllm.platforms import current_platform
20
+ from vllm.utils.import_utils import has_triton_kernels
21
+ from vllm.utils.math_utils import cdiv
22
+
23
+ logger = init_logger(__name__)
24
+
25
+ if has_triton_kernels():
26
+ try:
27
+ from triton_kernels.matmul_ogs import PrecisionConfig
28
+ except (ImportError, AttributeError) as e:
29
+ logger.error(
30
+ "Failed to import Triton kernels. Please make sure your triton "
31
+ "version is compatible. Error: %s",
32
+ e,
33
+ )
34
+
35
+
36
+ def _get_config_dtype_str(
37
+ dtype: torch.dtype,
38
+ use_fp8_w8a8: bool = False,
39
+ use_fp8_w8a16: bool = False,
40
+ use_int8_w8a16: bool = False,
41
+ use_int4_w4a16: bool = False,
42
+ ocp_mx_scheme: str | None = None,
43
+ ) -> str | None:
44
+ """
45
+ Return a string used to construct the filename that contains the
46
+ tuning info for a particular quantization scheme. See
47
+ try_get_optimal_moe_config in fused_moe.py.
48
+ """
49
+ if use_fp8_w8a8:
50
+ return "fp8_w8a8"
51
+ elif use_fp8_w8a16:
52
+ return "fp8_w8a16"
53
+ elif use_int8_w8a16:
54
+ return "int8_w8a16"
55
+ elif use_int4_w4a16:
56
+ return "int4_w4a16"
57
+ elif ocp_mx_scheme is not None:
58
+ # The output of this function is passed to `try_get_optimal_moe_config`,
59
+ # and as we only simulate OCP MX execution in fused_moe for now,
60
+ # we will NOT look for `*,dtype=w_mxfp4_a_mxfp4.json` for now.
61
+ return None
62
+ elif dtype == torch.float:
63
+ # avoiding cases where kernel fails when float32 MoE
64
+ # use fp16/bfloat16 configs
65
+ return "float32"
66
+ return None
67
+
68
+
69
+ def _quant_flags_to_group_shape(
70
+ quant_dtype: torch.dtype | str | None,
71
+ per_act_token_quant: bool,
72
+ per_out_ch_quant: bool,
73
+ block_shape: list[int] | None,
74
+ ) -> tuple[GroupShape | None, GroupShape | None]:
75
+ """
76
+ Convert MoE quantization flags into more generic GroupShapes.
77
+ """
78
+ a_shape: GroupShape | None
79
+ w_shape: GroupShape | None
80
+ if block_shape is not None:
81
+ assert not per_act_token_quant
82
+ assert not per_out_ch_quant
83
+ # TODO(bnell): this is not quite right for activations since first
84
+ # dim should be 1.
85
+ a_shape = GroupShape(row=block_shape[0], col=block_shape[1])
86
+ w_shape = GroupShape(row=block_shape[0], col=block_shape[1])
87
+ else:
88
+ w_shape = None
89
+ a_shape = None if quant_dtype is None else GroupShape.PER_TENSOR
90
+
91
+ if per_act_token_quant:
92
+ a_shape = GroupShape.PER_TOKEN
93
+
94
+ if per_out_ch_quant:
95
+ w_shape = GroupShape.PER_TOKEN
96
+
97
+ return a_shape, w_shape
98
+
99
+
100
+ # The type of method in top-K routing
101
+ # Please keep this in sync with the counterpart defined in https://github.com/flashinfer-ai/flashinfer/blob/main/include/flashinfer/trtllm/fused_moe/runner.h
102
+ class RoutingMethodType(IntEnum):
103
+ # Default: Softmax -> TopK
104
+ Default = (0,)
105
+ # Renormalize: TopK -> Softmax
106
+ Renormalize = (1,)
107
+ # DeepSeekV3: Sigmoid -> RoutingBiasAdd -> Top2 in group -> Top4 groups
108
+ # -> Top8 experts from the Top4 groups
109
+ DeepSeekV3 = (2,)
110
+ # Llama4: Top1 -> Sigmoid
111
+ Llama4 = (3,)
112
+ # RenormalizeNaive: Softmax -> TopK -> Renormalize
113
+ RenormalizeNaive = (4,)
114
+ # TopK: TopK (no softmax)
115
+ TopK = (5,)
116
+ # SigmoidRenorm: Sigmoid -> TopK -> Renormalize (divide by sum of top-K)
117
+ SigmoidRenorm = (6,)
118
+ # MiniMax2: Sigmoid + Bias -> TopK -> ScaledSumNormalize
119
+ # (routeScale=1.0, epsilon=1e-20)
120
+ MiniMax2 = (7,)
121
+ # Sigmoid: Sigmoid -> TopK (no renormalization)
122
+ Sigmoid = (8,)
123
+ # Unspecified
124
+ Unspecified = (9,)
125
+ # other routing types (not passed to FlashInfer kernels)
126
+ # Deepseek V4 -> sqrtsoftplus + Bias + Normalize
127
+ DeepseekV4 = (100,)
128
+ Custom = (101,)
129
+ Simulated = (102,)
130
+
131
+
132
+ def get_routing_method_type(
133
+ scoring_func: str,
134
+ top_k: int,
135
+ renormalize: bool,
136
+ num_expert_group: int | None,
137
+ has_e_score_bias: bool,
138
+ routed_scaling_factor: float | None = 1.0,
139
+ ) -> RoutingMethodType:
140
+ if scoring_func == "sqrtsoftplus":
141
+ # DeepSeek V4 uses sqrtsoftplus routing with optional routing bias
142
+ # and top-k renormalization.
143
+ if renormalize:
144
+ return RoutingMethodType.DeepseekV4
145
+ else:
146
+ return RoutingMethodType.Unspecified
147
+
148
+ if has_e_score_bias:
149
+ if scoring_func == "sigmoid":
150
+ if not renormalize:
151
+ return RoutingMethodType.Unspecified
152
+ if (num_expert_group or 0) > 0:
153
+ return RoutingMethodType.DeepSeekV3
154
+ if routed_scaling_factor in (None, 1.0):
155
+ return RoutingMethodType.MiniMax2
156
+ return RoutingMethodType.Unspecified
157
+ else:
158
+ return RoutingMethodType.Unspecified
159
+
160
+ if scoring_func == "sigmoid":
161
+ if renormalize:
162
+ return RoutingMethodType.SigmoidRenorm
163
+ return RoutingMethodType.Sigmoid
164
+
165
+ if scoring_func == "softmax":
166
+ if renormalize:
167
+ return RoutingMethodType.RenormalizeNaive
168
+ else:
169
+ return RoutingMethodType.Default
170
+
171
+ return RoutingMethodType.Unspecified
172
+
173
+
174
+ @dataclass
175
+ class FusedMoEQuantDesc:
176
+ """
177
+ A quantization descriptor for fused MoE ops. This class can describe
178
+ either activations or weights.
179
+ """
180
+
181
+ # The quantized type of this parameters. None means unquantized or
182
+ # already quantized.
183
+ # TODO (bnell): use scalar_type instead of Union.
184
+ dtype: torch.dtype | str | None = None
185
+
186
+ # A field that describes the quantization group shape, from quant_utils.py.
187
+ # * (-1, -1) for per-tensor quantization
188
+ # * (1, -1) for per-row quantization
189
+ # * (-1, 1) for per-column quantization
190
+ # * (128, 128) for 128x128 deepseek style block quantization
191
+ # * (1, 128) for deepseek style activation quantization
192
+ # (i.e. per-token-per-group)
193
+ shape: GroupShape | None = None
194
+
195
+ # Quantization scales.
196
+ # TODO(bnell): maybe put PrecisionConfigs in subclass of QuantDesc?
197
+ scale: Union[torch.Tensor, "PrecisionConfig", None] = None
198
+
199
+ # Quantization alphas or gscales, used for nvfp4 types.
200
+ # W4A8 FP8: used for per-channel scales
201
+ # TODO(bnell): put some of these in subclasses
202
+ alpha_or_gscale: torch.Tensor | None = None
203
+
204
+ # Zero points for int4/int8 types
205
+ zp: torch.Tensor | None = None
206
+
207
+ # Biases for GPT triton MoE
208
+ bias: torch.Tensor | None = None
209
+
210
+
211
+ # TODO(bnell): have subclasses for specific moe methods?
212
+ # e.g. for specific arguments bias, precision, etc.
213
+ @dataclass
214
+ class FusedMoEQuantConfig:
215
+ """
216
+ The FusedMoEQuantConfig contains all the quantization parameters for
217
+ a single FusedMoEMethodBase operation. It consists of four
218
+ FusedMoEQuantDescs, one for each activation and set of weights.
219
+
220
+ Each FusedMoEMethodBase must implement a get_fused_moe_quant_config
221
+ method to construct a FusedMoEQuantConfig for use with that class.
222
+
223
+ FusedMoEQuant configs are only used for modular kernels, fused_experts
224
+ (from fused_moe.py), cutlass_moe_fp[48], rocm_aiter_fused_experts and
225
+ triton_kernel_moe_forward. Other MoE methods can ignore the
226
+ FusedMoEQuantConfig (for now) and hardcode it to None.
227
+
228
+ There are currently some restrictions on what can be expressed:
229
+ - Most MoE ops only support similar quantization strategies for
230
+ each parameter, e.g. both weights must have the same GroupShape
231
+ and both activations must share the same GroupShape. One exception to
232
+ this is the cutlass moe which allows per channel quantization on the
233
+ outputs. Note: this restrictions are not always rigorously checked.
234
+ - Not all fused MoE functions support all the parameters, e.g. zero points,
235
+ global scales, alphas and biases are not universally supported.
236
+ - Fully general GroupShapes are not allowed. Activations only support
237
+ per token, per tensor or K-blocked.
238
+ - Weights are not required to have a GroupShape since they have already
239
+ been quantized.
240
+
241
+ Other notes:
242
+ - PrecisionConfigs are specific to GPT OSS Triton.
243
+ - As a follow up it would probably make sense to subclass FusedMoEQuantDesc
244
+ or FusedMoEQuantConfig for particular FusedMoEMethodBase subclasses
245
+ so that only the required quantization parameters are used/stored.
246
+ """
247
+
248
+ # TODO(bnell) make sure a1_scales/a2_scales don't interfere with chunking
249
+ _a1: FusedMoEQuantDesc
250
+ _a2: FusedMoEQuantDesc
251
+ _w1: FusedMoEQuantDesc
252
+ _w2: FusedMoEQuantDesc
253
+ is_scale_swizzled: bool = True
254
+
255
+ # MXFP4-specific TRTLLM parameters for SwiGLU activation clamping.
256
+ # These correspond to gemm1_alpha, gemm1_beta, gemm1_clamp_limit
257
+ # in TrtLlmMxfp4ExpertsBase.
258
+ gemm1_alpha: float | None = None
259
+ gemm1_beta: float | None = None
260
+ gemm1_clamp_limit: float | None = None
261
+
262
+ mx_alignment: int = 0
263
+
264
+ def __post_init__(self):
265
+ assert not self.per_act_token_quant or self.block_shape is None, (
266
+ "illegal quantization"
267
+ )
268
+
269
+ #
270
+ # Convenience accessors for various properties.
271
+ #
272
+
273
+ @property
274
+ def quant_dtype(self) -> torch.dtype | str | None:
275
+ return self._a1.dtype
276
+
277
+ @property
278
+ def weight_quant_dtype(self) -> torch.dtype | str | None:
279
+ return self._w1.dtype
280
+
281
+ @property
282
+ def is_quantized(self) -> bool:
283
+ return self.quant_dtype is not None
284
+
285
+ @property
286
+ def is_per_act_token(self) -> bool:
287
+ return self._a1.shape == GroupShape.PER_TOKEN
288
+
289
+ @property
290
+ def per_act_token_quant(self) -> bool:
291
+ return self._a1.shape == GroupShape.PER_TOKEN
292
+
293
+ @property
294
+ def per_out_ch_quant(self) -> bool:
295
+ return self._w1.shape == GroupShape.PER_TOKEN
296
+
297
+ @property
298
+ def is_per_tensor(self) -> bool:
299
+ return self._a1.shape == GroupShape.PER_TENSOR
300
+
301
+ @property
302
+ def block_shape(self) -> list[int] | None:
303
+ if (
304
+ self._a1.shape is not None
305
+ and self._a1.shape != GroupShape.PER_TENSOR
306
+ and self._a1.shape != GroupShape.PER_TOKEN
307
+ ):
308
+ return [self._a1.shape.row, self._a1.shape.col]
309
+ else:
310
+ return None
311
+
312
+ @property
313
+ def is_block_quantized(self) -> bool:
314
+ return self.block_shape is not None
315
+
316
+ @property
317
+ def a1_scale(self) -> torch.Tensor | None:
318
+ assert self._a1.scale is None or isinstance(self._a1.scale, torch.Tensor)
319
+ return self._a1.scale
320
+
321
+ @property
322
+ def a1_gscale(self) -> torch.Tensor | None:
323
+ return self._a1.alpha_or_gscale
324
+
325
+ @property
326
+ def a2_scale(self) -> torch.Tensor | None:
327
+ assert self._a2.scale is None or isinstance(self._a2.scale, torch.Tensor)
328
+ return self._a2.scale
329
+
330
+ @property
331
+ def a2_gscale(self) -> torch.Tensor | None:
332
+ return self._a2.alpha_or_gscale
333
+
334
+ @property
335
+ def w1_scale(self) -> torch.Tensor | None:
336
+ assert self._w1.scale is None or isinstance(self._w1.scale, torch.Tensor)
337
+ return self._w1.scale
338
+
339
+ @property
340
+ def w1_zp(self) -> torch.Tensor | None:
341
+ return self._w1.zp
342
+
343
+ @property
344
+ def w1_bias(self) -> torch.Tensor | None:
345
+ return self._w1.bias
346
+
347
+ @property
348
+ def w1_precision(self) -> "PrecisionConfig | None":
349
+ assert self._w1.scale is None or isinstance(self._w1.scale, PrecisionConfig)
350
+ return self._w1.scale
351
+
352
+ @property
353
+ def g1_alphas(self) -> torch.Tensor | None:
354
+ return self._w1.alpha_or_gscale
355
+
356
+ @property
357
+ def w2_scale(self) -> torch.Tensor | None:
358
+ assert self._w2.scale is None or isinstance(self._w2.scale, torch.Tensor)
359
+ return self._w2.scale
360
+
361
+ @property
362
+ def w2_zp(self) -> torch.Tensor | None:
363
+ return self._w2.zp
364
+
365
+ @property
366
+ def w2_bias(self) -> torch.Tensor | None:
367
+ return self._w2.bias
368
+
369
+ @property
370
+ def w2_precision(self) -> "PrecisionConfig | None":
371
+ assert self._w2.scale is None or isinstance(self._w2.scale, PrecisionConfig)
372
+ return self._w2.scale
373
+
374
+ @property
375
+ def g2_alphas(self) -> torch.Tensor | None:
376
+ return self._w2.alpha_or_gscale
377
+
378
+ @property
379
+ def use_fp8_w8a8(self) -> bool:
380
+ return self.quant_dtype == current_platform.fp8_dtype()
381
+
382
+ @property
383
+ def use_int8_w8a8(self) -> bool:
384
+ return self.quant_dtype == torch.int8
385
+
386
+ @property
387
+ def use_int8_w8a16(self) -> bool:
388
+ return self._a1.dtype is None and self._w1.dtype == torch.int8
389
+
390
+ @property
391
+ def use_fp8_w8a16(self) -> bool:
392
+ return self._a1.dtype is None and self._w1.dtype == current_platform.fp8_dtype()
393
+
394
+ @property
395
+ def use_int4_w4a16(self) -> bool:
396
+ return self._a1.dtype is None and self._w1.dtype == "int4"
397
+
398
+ @property
399
+ def use_nvfp4_w4a16(self) -> bool:
400
+ return self._a1.dtype is None and self._w1.dtype == "nvfp4"
401
+
402
+ @property
403
+ def ocp_mx_scheme(self) -> str | None:
404
+ if not hasattr(self, "_ocp_mx_scheme"):
405
+ if (self._a1.dtype is not None and not isinstance(self._a1.dtype, str)) or (
406
+ self._w1.dtype is not None and not isinstance(self._w1.dtype, str)
407
+ ):
408
+ self._ocp_mx_scheme = None
409
+ else:
410
+ ocp_mx_scheme = OCP_MX_Scheme.from_quant_dtype(
411
+ self._a1.dtype, self._w1.dtype
412
+ )
413
+
414
+ if ocp_mx_scheme is not None:
415
+ ocp_mx_scheme = ocp_mx_scheme.value
416
+
417
+ self._ocp_mx_scheme = ocp_mx_scheme
418
+
419
+ return self._ocp_mx_scheme
420
+
421
+ @property
422
+ def use_mxfp4_w4a16(self) -> bool:
423
+ return self._a1.dtype is None and self._w1.dtype == "mxfp4"
424
+
425
+ @property
426
+ def use_mxfp4_w4a4(self) -> bool:
427
+ return self._a1.dtype == "mxfp4" and self._w1.dtype == "mxfp4"
428
+
429
+ @property
430
+ def use_nvfp4_w4a4(self) -> bool:
431
+ return self.quant_dtype == "nvfp4"
432
+
433
+ @property
434
+ def use_mxfp4_w4a8(self) -> bool:
435
+ return self._a1.dtype == "fp8" and self._w1.dtype == "mxfp4"
436
+
437
+ def config_name(self, dtype: torch.dtype) -> str | None:
438
+ """
439
+ Return a string used to construct the filename that contains the
440
+ tuning info for a particular quantization scheme. See
441
+ try_get_optimal_moe_config in fused_moe.py.
442
+ """
443
+ return _get_config_dtype_str(
444
+ use_fp8_w8a8=self.use_fp8_w8a8,
445
+ use_fp8_w8a16=self.use_fp8_w8a16,
446
+ use_int8_w8a16=self.use_int8_w8a16,
447
+ use_int4_w4a16=self.use_int4_w4a16,
448
+ ocp_mx_scheme=self.ocp_mx_scheme,
449
+ dtype=dtype,
450
+ )
451
+
452
+ def scale_shape(
453
+ self,
454
+ max_tokens: int,
455
+ hidden_dim: int,
456
+ ) -> tuple[int, int] | None:
457
+ """
458
+ Construct the proper activation scale shape for this
459
+ config.
460
+ """
461
+ if self.is_quantized:
462
+ if self.is_block_quantized:
463
+ assert self.block_shape is not None
464
+ _, block_k = self.block_shape
465
+ k_tiles = cdiv(hidden_dim, block_k)
466
+ return (max_tokens, k_tiles)
467
+ elif self.is_per_act_token:
468
+ return (max_tokens, 1)
469
+ else:
470
+ return (1, 1)
471
+ else:
472
+ return None
473
+
474
+ def batched_scale_shape(
475
+ self,
476
+ num_experts: int,
477
+ max_tokens: int,
478
+ hidden_dim: int,
479
+ ) -> tuple[int, int, int] | None:
480
+ """
481
+ Construct the proper activation batched scale shape for this
482
+ config, e.g. (num experts, *scale_shape).
483
+ """
484
+ if self.is_quantized:
485
+ scale_shape = self.scale_shape(max_tokens, hidden_dim)
486
+ assert scale_shape is not None
487
+ return (num_experts, *scale_shape)
488
+ else:
489
+ return None
490
+
491
+ @staticmethod
492
+ def make(
493
+ quant_dtype: torch.dtype | str | None = None,
494
+ per_act_token_quant: bool = False,
495
+ per_out_ch_quant: bool = False,
496
+ block_shape: list[int] | None = None,
497
+ w1_scale: Union[torch.Tensor, "PrecisionConfig", None] = None,
498
+ w2_scale: Union[torch.Tensor, "PrecisionConfig", None] = None,
499
+ a1_scale: torch.Tensor | None = None,
500
+ a2_scale: torch.Tensor | None = None,
501
+ g1_alphas: torch.Tensor | None = None,
502
+ g2_alphas: torch.Tensor | None = None,
503
+ a1_gscale: torch.Tensor | None = None,
504
+ a2_gscale: torch.Tensor | None = None,
505
+ w1_bias: torch.Tensor | None = None,
506
+ w2_bias: torch.Tensor | None = None,
507
+ w1_zp: torch.Tensor | None = None,
508
+ w2_zp: torch.Tensor | None = None,
509
+ weight_dtype: torch.dtype | str | None = None,
510
+ is_scale_swizzled: bool = True,
511
+ gemm1_alpha: float | None = None,
512
+ gemm1_beta: float | None = None,
513
+ gemm1_clamp_limit: float | None = None,
514
+ ) -> "FusedMoEQuantConfig":
515
+ """
516
+ General builder function for a FusedMoEQuantConfig.
517
+ - quant_dtype: Optional quantization type. None if activations are
518
+ unquantized or quantized prior to calling. Note: "nvfp4", "mxfp4",
519
+ "mxfp6_e3m2", "mxfp6_e2m3" are the only valid string values
520
+ for quant_dtype.
521
+ - per_act_token_quant: Activations have per token quantization.
522
+ - per_out_ch_quant: Outputs have per channel quantization. (only
523
+ for cutlass).
524
+ - block_shape: Optional block size for block-wise quantization.
525
+ Incompatible with per_act_token and per_out_ch quant.
526
+ - w1_scale: Optional scale to be used for w1.
527
+ - w2_scale: Optional scale to be used for w2.
528
+ - a1_scale: Optional scale to be used for a1.
529
+ - a2_scale: Optional scale to be used for a2.
530
+ - g1_alphas: Optional global quantization scales for w1 (for nvfp4).
531
+ Optional per-channel scales for w1 (for W4A8 FP8).
532
+ Optional dq scale i.e. w_scale * a_scale (for W8A8 fp8).
533
+ - g2_alphas: Optional global quantization scales for w2 (for nvfp4).
534
+ Optional per-channel scales for w2 (for W4A8 FP8).
535
+ Optional dq scale i.e. w_scale * a_scale (for W8A8 fp8).
536
+ - a1_gscale: Optional global quantization scales for a1 (1.0 /a2_scale).
537
+ - a2_gscale: Optional global quantization scales for a2 (1.0 /a2_scale).
538
+
539
+ - w1_bias: Optional biases for w1 (GPT OSS Triton).
540
+ - w2_bias: Optional biases for w1 (GPT OSS Triton).
541
+ - w1_zp: Optional w1 zero points for int4/int8 quantization.
542
+ - w2_zp: Optional w2 zero points for int4/int8 quantization.
543
+ - is_scale_swizzled: Whether the activation scale-factor layout is
544
+ swizzled. Pass through to the underlying quantization kernel for
545
+ dtypes that distinguish layouts (nvfp4, mxfp8). Defaults to True.
546
+ - gemm1_alpha: Optional MXFP4 TRTLLM SwiGLU alpha parameter.
547
+ - gemm1_beta: Optional MXFP4 TRTLLM SwiGLU beta parameter.
548
+ - gemm1_clamp_limit: Optional MXFP4 TRTLLM SwiGLU clamp limit.
549
+ """
550
+ assert not isinstance(quant_dtype, str) or quant_dtype in {
551
+ "nvfp4",
552
+ "mxfp4",
553
+ "mxfp6_e3m2",
554
+ "mxfp6_e2m3",
555
+ "mxfp8",
556
+ }
557
+ assert not isinstance(weight_dtype, str) or weight_dtype in {
558
+ "nvfp4",
559
+ "mxfp4",
560
+ "mxfp6_e3m2",
561
+ "mxfp6_e2m3",
562
+ "int4",
563
+ "mxfp8",
564
+ }
565
+
566
+ if weight_dtype is None:
567
+ weight_dtype = quant_dtype
568
+
569
+ a_shape, w_shape = _quant_flags_to_group_shape(
570
+ quant_dtype, per_act_token_quant, per_out_ch_quant, block_shape
571
+ )
572
+ quant_config = FusedMoEQuantConfig(
573
+ _a1=FusedMoEQuantDesc(quant_dtype, a_shape, a1_scale, a1_gscale),
574
+ _a2=FusedMoEQuantDesc(quant_dtype, a_shape, a2_scale, a2_gscale),
575
+ _w1=FusedMoEQuantDesc(
576
+ weight_dtype, w_shape, w1_scale, g1_alphas, w1_zp, w1_bias
577
+ ),
578
+ _w2=FusedMoEQuantDesc(
579
+ weight_dtype, w_shape, w2_scale, g2_alphas, w2_zp, w2_bias
580
+ ),
581
+ is_scale_swizzled=is_scale_swizzled,
582
+ gemm1_alpha=gemm1_alpha,
583
+ gemm1_beta=gemm1_beta,
584
+ gemm1_clamp_limit=gemm1_clamp_limit,
585
+ )
586
+ assert quant_config.per_act_token_quant == per_act_token_quant
587
+ assert quant_config.per_out_ch_quant == per_out_ch_quant
588
+ assert quant_config.block_shape == block_shape
589
+ return quant_config
590
+
591
+
592
+ def fp8_w8a8_moe_quant_config(
593
+ w1_scale: torch.Tensor,
594
+ w2_scale: torch.Tensor,
595
+ a1_scale: torch.Tensor | None = None,
596
+ a2_scale: torch.Tensor | None = None,
597
+ w1_bias: torch.Tensor | None = None,
598
+ w2_bias: torch.Tensor | None = None,
599
+ per_act_token_quant: bool = False,
600
+ per_out_ch_quant: bool = False,
601
+ block_shape: list[int] | None = None,
602
+ a1_gscale: torch.Tensor | None = None,
603
+ a2_gscale: torch.Tensor | None = None,
604
+ g1_alphas: torch.Tensor | None = None,
605
+ g2_alphas: torch.Tensor | None = None,
606
+ gemm1_clamp_limit: float | None = None,
607
+ ) -> FusedMoEQuantConfig:
608
+ """
609
+ Construct a quant config for fp8 activations and fp8 weights.
610
+ """
611
+ return FusedMoEQuantConfig.make(
612
+ current_platform.fp8_dtype(),
613
+ w1_scale=w1_scale,
614
+ g1_alphas=g1_alphas,
615
+ w2_scale=w2_scale,
616
+ g2_alphas=g2_alphas,
617
+ w1_bias=w1_bias,
618
+ w2_bias=w2_bias,
619
+ a1_scale=a1_scale,
620
+ a1_gscale=a1_gscale,
621
+ a2_scale=a2_scale,
622
+ a2_gscale=a2_gscale,
623
+ per_act_token_quant=per_act_token_quant,
624
+ per_out_ch_quant=per_out_ch_quant,
625
+ block_shape=block_shape,
626
+ gemm1_clamp_limit=gemm1_clamp_limit,
627
+ )
628
+
629
+
630
+ def int8_w8a8_moe_quant_config(
631
+ w1_scale: torch.Tensor,
632
+ w2_scale: torch.Tensor,
633
+ a1_scale: torch.Tensor | None,
634
+ a2_scale: torch.Tensor | None,
635
+ w1_bias: torch.Tensor | None = None,
636
+ w2_bias: torch.Tensor | None = None,
637
+ per_act_token_quant: bool = False,
638
+ ) -> FusedMoEQuantConfig:
639
+ """
640
+ Construct a quant config for int8 activations and int8 weights.
641
+ """
642
+ return FusedMoEQuantConfig.make(
643
+ torch.int8,
644
+ w1_scale=w1_scale,
645
+ w2_scale=w2_scale,
646
+ a1_scale=a1_scale,
647
+ a2_scale=a2_scale,
648
+ w1_bias=w1_bias,
649
+ w2_bias=w2_bias,
650
+ per_act_token_quant=per_act_token_quant,
651
+ per_out_ch_quant=False,
652
+ block_shape=None,
653
+ )
654
+
655
+
656
+ def gptq_marlin_moe_quant_config(
657
+ w1_scale: torch.Tensor,
658
+ w2_scale: torch.Tensor,
659
+ weight_bits: int,
660
+ group_size: int,
661
+ w1_zp: torch.Tensor | None = None,
662
+ w2_zp: torch.Tensor | None = None,
663
+ w1_bias: torch.Tensor | None = None,
664
+ w2_bias: torch.Tensor | None = None,
665
+ ):
666
+ """
667
+ Construct a quant config for gptq marlin quantization.
668
+ """
669
+ from vllm.model_executor.layers.quantization.utils.quant_utils import GroupShape
670
+
671
+ w_shape = None if group_size == -1 else GroupShape(row=1, col=group_size)
672
+
673
+ # Activations are NOT quantized for GPTQ (fp16/bf16)
674
+ a_shape = w_shape # Same as weight shape for alignment
675
+
676
+ # Determine weight dtype
677
+ if weight_bits == 4:
678
+ weight_dtype = "int4"
679
+ elif weight_bits == 8:
680
+ weight_dtype = torch.int8
681
+ else:
682
+ raise ValueError(f"Unsupported weight_bits: {weight_bits}")
683
+
684
+ return FusedMoEQuantConfig(
685
+ _a1=FusedMoEQuantDesc(dtype=None, shape=a_shape),
686
+ _a2=FusedMoEQuantDesc(dtype=None, shape=a_shape),
687
+ _w1=FusedMoEQuantDesc(weight_dtype, w_shape, w1_scale, None, w1_zp, w1_bias),
688
+ _w2=FusedMoEQuantDesc(weight_dtype, w_shape, w2_scale, None, w2_zp, w2_bias),
689
+ )
690
+
691
+
692
+ def mxfp4_w4a16_moe_quant_config(
693
+ w1_scale: Union[torch.Tensor, "PrecisionConfig"],
694
+ w2_scale: Union[torch.Tensor, "PrecisionConfig"],
695
+ w1_bias: torch.Tensor | None = None,
696
+ w2_bias: torch.Tensor | None = None,
697
+ gemm1_alpha: float | None = None,
698
+ gemm1_beta: float | None = None,
699
+ gemm1_clamp_limit: float | None = None,
700
+ ) -> FusedMoEQuantConfig:
701
+ """
702
+ Construct a quant config for unquantized activations and mxfp4 weights.
703
+ """
704
+ return FusedMoEQuantConfig(
705
+ _a1=FusedMoEQuantDesc(),
706
+ _a2=FusedMoEQuantDesc(),
707
+ _w1=FusedMoEQuantDesc("mxfp4", None, w1_scale, None, None, w1_bias),
708
+ _w2=FusedMoEQuantDesc("mxfp4", None, w2_scale, None, None, w2_bias),
709
+ gemm1_alpha=gemm1_alpha,
710
+ gemm1_beta=gemm1_beta,
711
+ gemm1_clamp_limit=gemm1_clamp_limit,
712
+ )
713
+
714
+
715
+ def mxfp4_mxfp8_moe_quant_config(
716
+ w1_scale: Union[torch.Tensor, "PrecisionConfig"],
717
+ w2_scale: Union[torch.Tensor, "PrecisionConfig"],
718
+ a1_scale: torch.Tensor | None = None,
719
+ a2_scale: torch.Tensor | None = None,
720
+ w1_bias: torch.Tensor | None = None,
721
+ w2_bias: torch.Tensor | None = None,
722
+ block_shape: list[int] | None = None,
723
+ gemm1_alpha: float | None = None,
724
+ gemm1_beta: float | None = None,
725
+ gemm1_clamp_limit: float | None = None,
726
+ mx_alignment: int = 0,
727
+ is_scale_swizzled: bool = True,
728
+ ) -> FusedMoEQuantConfig:
729
+ """
730
+ Construct a quant config for mxfp4 activations and mxfp4 weights.
731
+ """
732
+ return FusedMoEQuantConfig(
733
+ _a1=FusedMoEQuantDesc("mxfp8"),
734
+ _a2=FusedMoEQuantDesc("mxfp8"),
735
+ _w1=FusedMoEQuantDesc("mxfp4", None, w1_scale, None, None, w1_bias),
736
+ _w2=FusedMoEQuantDesc("mxfp4", None, w2_scale, None, None, w2_bias),
737
+ gemm1_alpha=gemm1_alpha,
738
+ gemm1_beta=gemm1_beta,
739
+ gemm1_clamp_limit=gemm1_clamp_limit,
740
+ mx_alignment=mx_alignment,
741
+ is_scale_swizzled=is_scale_swizzled,
742
+ )
743
+
744
+
745
+ def mxfp4_w4a8_moe_quant_config(
746
+ w1_scale: Union[torch.Tensor, "PrecisionConfig"],
747
+ w2_scale: Union[torch.Tensor, "PrecisionConfig"],
748
+ a1_scale: torch.Tensor | None = None,
749
+ a2_scale: torch.Tensor | None = None,
750
+ w1_bias: torch.Tensor | None = None,
751
+ w2_bias: torch.Tensor | None = None,
752
+ block_shape: list[int] | None = None,
753
+ gemm1_clamp_limit: float | None = None,
754
+ ) -> FusedMoEQuantConfig:
755
+ """
756
+ Construct a quant config for fp8 activations and mxfp4 weights.
757
+ """
758
+ return FusedMoEQuantConfig(
759
+ _a1=FusedMoEQuantDesc("fp8", None, a1_scale, None, None, None),
760
+ _a2=FusedMoEQuantDesc("fp8", None, a2_scale, None, None, None),
761
+ _w1=FusedMoEQuantDesc("mxfp4", None, w1_scale, None, None, w1_bias),
762
+ _w2=FusedMoEQuantDesc("mxfp4", None, w2_scale, None, None, w2_bias),
763
+ gemm1_clamp_limit=gemm1_clamp_limit,
764
+ )
765
+
766
+
767
+ def ocp_mx_moe_quant_config(
768
+ quant_dtype: str,
769
+ w1_scale: Union[torch.Tensor, "PrecisionConfig"],
770
+ w2_scale: Union[torch.Tensor, "PrecisionConfig"],
771
+ weight_dtype: str | None = None,
772
+ a1_scale: torch.Tensor | None = None,
773
+ a2_scale: torch.Tensor | None = None,
774
+ w1_bias: torch.Tensor | None = None,
775
+ w2_bias: torch.Tensor | None = None,
776
+ block_shape: list[int] | None = None,
777
+ gemm1_alpha: float | None = None,
778
+ gemm1_beta: float | None = None,
779
+ gemm1_clamp_limit: float | None = None,
780
+ ) -> FusedMoEQuantConfig:
781
+ """
782
+ Construct a quant config for mxfp4 activations and mxfp4 weights.
783
+ """
784
+ assert quant_dtype in OCP_MX_DTYPES
785
+ return FusedMoEQuantConfig.make(
786
+ quant_dtype=quant_dtype,
787
+ weight_dtype=weight_dtype,
788
+ w1_scale=w1_scale,
789
+ w2_scale=w2_scale,
790
+ a1_scale=a1_scale,
791
+ a2_scale=a2_scale,
792
+ w1_bias=w1_bias,
793
+ w2_bias=w2_bias,
794
+ per_act_token_quant=False,
795
+ per_out_ch_quant=False,
796
+ block_shape=block_shape,
797
+ gemm1_alpha=gemm1_alpha,
798
+ gemm1_beta=gemm1_beta,
799
+ gemm1_clamp_limit=gemm1_clamp_limit,
800
+ )
801
+
802
+
803
+ def nvfp4_moe_quant_config(
804
+ g1_alphas: torch.Tensor,
805
+ g2_alphas: torch.Tensor,
806
+ a1_gscale: torch.Tensor,
807
+ a2_gscale: torch.Tensor,
808
+ w1_scale: torch.Tensor,
809
+ w2_scale: torch.Tensor,
810
+ w1_bias: torch.Tensor | None = None,
811
+ w2_bias: torch.Tensor | None = None,
812
+ is_scale_swizzled: bool = True,
813
+ gemm1_clamp_limit: float | None = None,
814
+ ) -> FusedMoEQuantConfig:
815
+ """
816
+ Construct a quant config for mxfp4 activations and nvp4 weights.
817
+ """
818
+ return FusedMoEQuantConfig.make(
819
+ "nvfp4",
820
+ w1_scale=w1_scale,
821
+ w2_scale=w2_scale,
822
+ w1_bias=w1_bias,
823
+ w2_bias=w2_bias,
824
+ a1_gscale=a1_gscale,
825
+ a2_gscale=a2_gscale,
826
+ g1_alphas=g1_alphas,
827
+ g2_alphas=g2_alphas,
828
+ per_act_token_quant=False,
829
+ per_out_ch_quant=False,
830
+ block_shape=None,
831
+ is_scale_swizzled=is_scale_swizzled,
832
+ gemm1_clamp_limit=gemm1_clamp_limit,
833
+ )
834
+
835
+
836
+ def mxfp4_moe_quant_config(
837
+ w1_scale: torch.Tensor,
838
+ w2_scale: torch.Tensor,
839
+ ) -> FusedMoEQuantConfig:
840
+ """
841
+ Construct a quant config for MXFP4 x MXFP4 MoE.
842
+ MXFP4 uses block scaling only (E8M0 scales, 32-element groups), with no
843
+ separate alphas / global activation scales in this config.
844
+ """
845
+ return FusedMoEQuantConfig.make(
846
+ "mxfp4",
847
+ w1_scale=w1_scale,
848
+ w2_scale=w2_scale,
849
+ per_act_token_quant=False,
850
+ per_out_ch_quant=False,
851
+ block_shape=None,
852
+ )
853
+
854
+
855
+ def nvfp4_w4a16_moe_quant_config(
856
+ g1_alphas: torch.Tensor,
857
+ g2_alphas: torch.Tensor,
858
+ w1_scale: torch.Tensor,
859
+ w2_scale: torch.Tensor,
860
+ ) -> FusedMoEQuantConfig:
861
+ """
862
+ Construct a quant config for 16-but activations and nvp4 weights.
863
+ """
864
+ return FusedMoEQuantConfig.make(
865
+ quant_dtype=None,
866
+ w1_scale=w1_scale,
867
+ w2_scale=w2_scale,
868
+ g1_alphas=g1_alphas,
869
+ g2_alphas=g2_alphas,
870
+ weight_dtype="nvfp4",
871
+ )
872
+
873
+
874
+ def int4_w4a16_moe_quant_config(
875
+ w1_scale: torch.Tensor,
876
+ w2_scale: torch.Tensor,
877
+ w1_zp: torch.Tensor | None = None,
878
+ w2_zp: torch.Tensor | None = None,
879
+ w1_bias: torch.Tensor | None = None,
880
+ w2_bias: torch.Tensor | None = None,
881
+ block_shape: list[int] | None = None,
882
+ a1_gscale: torch.Tensor | None = None,
883
+ a2_gscale: torch.Tensor | None = None,
884
+ gemm1_alpha: float | None = None,
885
+ gemm1_beta: float | None = None,
886
+ gemm1_clamp_limit: float | None = None,
887
+ ) -> FusedMoEQuantConfig:
888
+ """
889
+ Construct a quant config for 16-bit float activations and int4 weights.
890
+ """
891
+ group_shape = GroupShape(*block_shape) if block_shape is not None else None
892
+ return FusedMoEQuantConfig(
893
+ _a1=FusedMoEQuantDesc(shape=group_shape, alpha_or_gscale=a1_gscale),
894
+ _a2=FusedMoEQuantDesc(shape=group_shape, alpha_or_gscale=a2_gscale),
895
+ _w1=FusedMoEQuantDesc("int4", group_shape, w1_scale, None, w1_zp, w1_bias),
896
+ _w2=FusedMoEQuantDesc("int4", group_shape, w2_scale, None, w2_zp, w2_bias),
897
+ gemm1_alpha=gemm1_alpha,
898
+ gemm1_beta=gemm1_beta,
899
+ gemm1_clamp_limit=gemm1_clamp_limit,
900
+ )
901
+
902
+
903
+ def fp8_w8a16_moe_quant_config(
904
+ w1_scale: torch.Tensor,
905
+ w2_scale: torch.Tensor,
906
+ w1_bias: torch.Tensor | None = None,
907
+ w2_bias: torch.Tensor | None = None,
908
+ block_shape: list[int] | None = None,
909
+ gemm1_alpha: float | None = None,
910
+ gemm1_beta: float | None = None,
911
+ gemm1_clamp_limit: float | None = None,
912
+ ) -> FusedMoEQuantConfig:
913
+ """
914
+ Construct a quant config for 16-bit float activations and fp8 weights.
915
+ """
916
+ group_shape = GroupShape(*block_shape) if block_shape is not None else None
917
+ fp8_dtype = current_platform.fp8_dtype()
918
+ return FusedMoEQuantConfig(
919
+ _a1=FusedMoEQuantDesc(),
920
+ _a2=FusedMoEQuantDesc(),
921
+ _w1=FusedMoEQuantDesc(
922
+ fp8_dtype,
923
+ group_shape,
924
+ w1_scale,
925
+ None,
926
+ None,
927
+ w1_bias,
928
+ ),
929
+ _w2=FusedMoEQuantDesc(
930
+ fp8_dtype,
931
+ group_shape,
932
+ w2_scale,
933
+ None,
934
+ None,
935
+ w2_bias,
936
+ ),
937
+ gemm1_alpha=gemm1_alpha,
938
+ gemm1_beta=gemm1_beta,
939
+ gemm1_clamp_limit=gemm1_clamp_limit,
940
+ )
941
+
942
+
943
+ def int8_w8a16_moe_quant_config(
944
+ w1_scale: torch.Tensor,
945
+ w2_scale: torch.Tensor,
946
+ w1_zp: torch.Tensor | None = None,
947
+ w2_zp: torch.Tensor | None = None,
948
+ w1_bias: torch.Tensor | None = None,
949
+ w2_bias: torch.Tensor | None = None,
950
+ block_shape: list[int] | None = None,
951
+ a1_gscale: torch.Tensor | None = None,
952
+ a2_gscale: torch.Tensor | None = None,
953
+ gemm1_alpha: float | None = None,
954
+ gemm1_beta: float | None = None,
955
+ gemm1_clamp_limit: float | None = None,
956
+ ) -> FusedMoEQuantConfig:
957
+ """
958
+ Construct a quant config for 16-bit float activations and int8 weights.
959
+ """
960
+ group_shape = GroupShape(*block_shape) if block_shape is not None else None
961
+ return FusedMoEQuantConfig(
962
+ _a1=FusedMoEQuantDesc(shape=group_shape, alpha_or_gscale=a1_gscale),
963
+ _a2=FusedMoEQuantDesc(shape=group_shape, alpha_or_gscale=a2_gscale),
964
+ _w1=FusedMoEQuantDesc(torch.int8, group_shape, w1_scale, None, w1_zp, w1_bias),
965
+ _w2=FusedMoEQuantDesc(torch.int8, group_shape, w2_scale, None, w2_zp, w2_bias),
966
+ gemm1_alpha=gemm1_alpha,
967
+ gemm1_beta=gemm1_beta,
968
+ gemm1_clamp_limit=gemm1_clamp_limit,
969
+ )
970
+
971
+
972
+ def int4_w4afp8_moe_quant_config(
973
+ w1_scale: torch.Tensor,
974
+ w2_scale: torch.Tensor,
975
+ g1_alphas: torch.Tensor,
976
+ g2_alphas: torch.Tensor,
977
+ per_act_token_quant: bool = False,
978
+ per_out_ch_quant: bool = False,
979
+ block_shape: list[int] | None = None,
980
+ ) -> FusedMoEQuantConfig:
981
+ """
982
+ Construct a quant config for fp8 activations and int4 weights.
983
+ """
984
+ return FusedMoEQuantConfig.make(
985
+ torch.float8_e4m3fn, # quant dtype for activations
986
+ w1_scale=w1_scale,
987
+ w2_scale=w2_scale,
988
+ g1_alphas=g1_alphas,
989
+ g2_alphas=g2_alphas,
990
+ per_act_token_quant=per_act_token_quant,
991
+ per_out_ch_quant=per_out_ch_quant,
992
+ block_shape=block_shape,
993
+ weight_dtype="int4", # weight dtype for weights
994
+ )
995
+
996
+
997
+ def biased_moe_quant_config(
998
+ w1_bias: torch.Tensor | None,
999
+ w2_bias: torch.Tensor | None,
1000
+ gemm1_alpha: float | None = None,
1001
+ gemm1_beta: float | None = None,
1002
+ gemm1_clamp_limit: float | None = None,
1003
+ ) -> FusedMoEQuantConfig:
1004
+ """
1005
+ Construct a quant config for unquantized activations with biases.
1006
+
1007
+ gemm1_alpha/gemm1_beta/gemm1_clamp_limit carry the SwiGLU gate params
1008
+ through to the fused activation kernel (e.g. swigluoai_uninterleave).
1009
+ """
1010
+ return FusedMoEQuantConfig(
1011
+ _a1=FusedMoEQuantDesc(),
1012
+ _a2=FusedMoEQuantDesc(),
1013
+ _w1=FusedMoEQuantDesc(bias=w1_bias),
1014
+ _w2=FusedMoEQuantDesc(bias=w2_bias),
1015
+ gemm1_alpha=gemm1_alpha,
1016
+ gemm1_beta=gemm1_beta,
1017
+ gemm1_clamp_limit=gemm1_clamp_limit,
1018
+ )
1019
+
1020
+
1021
+ # A FusedMoEQuantConfig constant for an unquantized MoE op.
1022
+ FUSED_MOE_UNQUANTIZED_CONFIG: FusedMoEQuantConfig = FusedMoEQuantConfig.make()
1023
+
1024
+
1025
+ @dataclass
1026
+ class FusedMoEParallelConfig:
1027
+ tp_size: int
1028
+ pcp_size: int
1029
+ dp_size: int
1030
+ ep_size: int
1031
+ tp_rank: int
1032
+ pcp_rank: int
1033
+ dp_rank: int
1034
+ ep_rank: int
1035
+ sp_size: int
1036
+
1037
+ use_ep: bool # whether to use EP or not
1038
+ all2all_backend: str # all2all backend for MoE communication
1039
+ enable_eplb: bool # whether to enable expert load balancing
1040
+
1041
+ @property
1042
+ def is_sequence_parallel(self) -> bool:
1043
+ return self.sp_size > 1
1044
+
1045
+ @property
1046
+ def use_all2all_kernels(self):
1047
+ return self.dp_size > 1 and self.use_ep
1048
+
1049
+ @property
1050
+ def use_deepep_ht_kernels(self):
1051
+ return (
1052
+ self.use_all2all_kernels
1053
+ and self.all2all_backend == "deepep_high_throughput"
1054
+ )
1055
+
1056
+ @property
1057
+ def use_deepep_ll_kernels(self):
1058
+ return self.use_all2all_kernels and self.all2all_backend == "deepep_low_latency"
1059
+
1060
+ @property
1061
+ def use_fi_nvl_two_sided_kernels(self):
1062
+ return self.use_all2all_kernels and (
1063
+ self.all2all_backend == "flashinfer_all2allv"
1064
+ or self.all2all_backend == "flashinfer_nvlink_two_sided"
1065
+ )
1066
+
1067
+ @property
1068
+ def use_fi_nvl_one_sided_kernels(self):
1069
+ return (
1070
+ self.use_all2all_kernels
1071
+ and self.all2all_backend == "flashinfer_nvlink_one_sided"
1072
+ )
1073
+
1074
+ @property
1075
+ def use_batched_activation_format(self):
1076
+ return self.use_deepep_ll_kernels or self.use_nixl_ep_kernels
1077
+
1078
+ @property
1079
+ def needs_round_robin_routing_tables(self):
1080
+ return self.use_deepep_ll_kernels or self.use_nixl_ep_kernels
1081
+
1082
+ @property
1083
+ def use_ag_rs_all2all_kernels(self):
1084
+ return (
1085
+ self.use_all2all_kernels
1086
+ and self.all2all_backend == "allgather_reducescatter"
1087
+ )
1088
+
1089
+ @property
1090
+ def use_mori_kernels(self):
1091
+ return self.use_all2all_kernels and self.all2all_backend in (
1092
+ "mori_high_throughput",
1093
+ "mori_low_latency",
1094
+ )
1095
+
1096
+ @property
1097
+ def use_nixl_ep_kernels(self):
1098
+ return self.use_all2all_kernels and self.all2all_backend == "nixl_ep"
1099
+
1100
+ @property
1101
+ def use_deepep_v2_kernels(self):
1102
+ return self.use_all2all_kernels and self.all2all_backend == "deepep_v2"
1103
+
1104
+ @staticmethod
1105
+ def flatten_tp_across_dp_and_pcp(
1106
+ tp_size: int, dp_size: int, dp_rank: int, pcp_size: int, pcp_rank: int
1107
+ ) -> tuple[int, int]:
1108
+ tp_rank = 0 if tp_size == 1 else get_tensor_model_parallel_rank()
1109
+ # There are actually dp_size * pcp_size * tp_size devices.
1110
+ # Update tp_size and tp_rank so we shard across all devices.
1111
+ flatten_tp_size = dp_size * pcp_size * tp_size
1112
+ flatten_tp_rank = dp_rank * pcp_size * tp_size + pcp_rank * tp_size + tp_rank
1113
+ return flatten_tp_size, flatten_tp_rank
1114
+
1115
+ @staticmethod
1116
+ def make(
1117
+ tp_size_: int,
1118
+ pcp_size_: int,
1119
+ dp_size_: int,
1120
+ sp_size_: int,
1121
+ vllm_parallel_config: ParallelConfig,
1122
+ ) -> "FusedMoEParallelConfig":
1123
+ """
1124
+ Determine MoE parallel configuration. Based on the input `tp_size_`,
1125
+ `dp_size_` and vllm's parallel config, determine what
1126
+ level's of parallelism to use in the fused moe layer.
1127
+
1128
+ Args:
1129
+ tp_size_ (int): `tp_size` passed into the FusedMoE constructor.
1130
+ pcp_size_ (int): `pcp_size` passed into the FusedMoE constructor.
1131
+ dp_size_ (int): `dp_size` passed into the FusedMoE constructor.
1132
+ vllm_parallel_config (ParallelConfig): vLLM's parallel config
1133
+ object which contains the `enable_expert_parallel` flag.
1134
+
1135
+ Examples:
1136
+ When there is no parallelism requested,
1137
+ i.e. `tp_size_` = `pcp_size_` = `dp_size_` = 1, we simply return the sizes
1138
+ unaltered and the ranks set to 0.
1139
+
1140
+ Expert Parallelism is considered only when either `dp_size_`, `pcp_size_` or
1141
+ `tp_size_` is non trivial.
1142
+
1143
+ Note that PCP serves the same function as DP here.
1144
+
1145
+ When TP = 2, DP(PCP) = 1 and EP = False, the configuration on different
1146
+ devices:
1147
+
1148
+ - device 0 : TP = {2, 0} DP = {1, 0} EP = {1, 0} //
1149
+ legend : {size, rank}
1150
+ - device 1 : TP = {2, 1} DP = {1, 0} EP = {1, 0}
1151
+ - Comment : Tensors are sharded across 2 devices.
1152
+
1153
+ When TP = 1, DP(PCP) = 2 and EP = False, the configuration on different
1154
+ devices:
1155
+
1156
+ - device 0 : TP = {2, 0} DP = {2, 0} EP = {1, 0}
1157
+ - device 1 : TP = {2, 1} DP = {2, 1} EP = {1, 0}
1158
+ - Comment: There are 2 engine instances and the tensors are sharded
1159
+ across 2 decvices.
1160
+
1161
+ When TP = 2, DP(PCP) = 2 and EP = False, the configuration on different
1162
+ devices:
1163
+
1164
+ - device 0: TP = {4, 0} DP = {2, 0} EP = {1, 0}
1165
+ - device 1: TP = {4, 1} DP = {2, 0} EP = {1, 0}
1166
+ - device 2: TP = {4, 2} DP = {2, 1} EP = {1, 0}
1167
+ - device 3: TP = {4, 3} DP = {2, 1} EP = {1, 0}
1168
+ - Comment: There are 2 engine instances and the tensors are sharded
1169
+ across 4 devices.
1170
+
1171
+ When, TP = 2, DP(PCP) = 1 and EP = True, the configuration on different
1172
+ devices:
1173
+
1174
+ - device 0: TP = {1, 0} DP = {1, 0} EP = {2, 0}
1175
+ - device 1: TP = {1, 0} DP = {1, 0} EP = {2, 1}
1176
+ - Comment: The experts are split between the 2 devices.
1177
+
1178
+ When, TP = 1, DP(PCP) = 2 and EP = True, the configuration on different
1179
+ devices:
1180
+
1181
+ - device 0: TP = {1, 0} DP = {2, 0} EP = {2, 0}
1182
+ - device 1: TP = {1, 0} DP = {2, 1} EP = {2, 1}
1183
+ - Comment: There are 2 engine instances and the experts are split
1184
+ between the 2 devices.
1185
+
1186
+ When TP = 2, DP(PCP) = 2 and EP = True, the configuration on different
1187
+ devices:
1188
+
1189
+ - device 0: TP = {1, 0} DP = {2, 0} EP = {4, 0}
1190
+ - device 1: TP = {1, 0} DP = {2, 0} EP = {4, 1}
1191
+ - device 2: TP = {1, 0} DP = {2, 1} EP = {4, 2}
1192
+ - device 3: TP = {1, 0} DP = {2, 1} EP = {4, 3}
1193
+ - Comment: There are 2 engine instances and the experts are split
1194
+ between the 4 devices.
1195
+ """
1196
+ use_ep = (
1197
+ dp_size_ * pcp_size_ * tp_size_ > 1
1198
+ and vllm_parallel_config.enable_expert_parallel
1199
+ )
1200
+
1201
+ dp_size = dp_size_
1202
+ dp_rank = get_dp_group().rank_in_group if dp_size > 1 else 0
1203
+ pcp_size = pcp_size_
1204
+ pcp_rank = get_pcp_group().rank_in_group if pcp_size > 1 else 0
1205
+ tp_size, tp_rank = FusedMoEParallelConfig.flatten_tp_across_dp_and_pcp(
1206
+ tp_size_, dp_size_, dp_rank, pcp_size_, pcp_rank
1207
+ )
1208
+
1209
+ if not use_ep:
1210
+ return FusedMoEParallelConfig(
1211
+ tp_size=tp_size,
1212
+ tp_rank=tp_rank,
1213
+ pcp_size=pcp_size,
1214
+ pcp_rank=pcp_rank,
1215
+ dp_size=dp_size,
1216
+ dp_rank=dp_rank,
1217
+ ep_size=1,
1218
+ ep_rank=0,
1219
+ sp_size=sp_size_,
1220
+ use_ep=False,
1221
+ all2all_backend=vllm_parallel_config.all2all_backend,
1222
+ enable_eplb=vllm_parallel_config.enable_eplb,
1223
+ )
1224
+ # DP + EP / TP + EP / DP + TP + EP
1225
+ assert use_ep
1226
+ # In EP, each device owns a set of experts fully. There is no tensor
1227
+ # parallel update tp_size, tp_rank, ep_size and ep_rank to reflect that.
1228
+ ep_size = tp_size
1229
+ ep_rank = tp_rank
1230
+ return FusedMoEParallelConfig(
1231
+ tp_size=1,
1232
+ tp_rank=0,
1233
+ pcp_size=pcp_size,
1234
+ pcp_rank=pcp_rank,
1235
+ dp_size=dp_size,
1236
+ dp_rank=dp_rank,
1237
+ ep_size=ep_size,
1238
+ ep_rank=ep_rank,
1239
+ sp_size=sp_size_,
1240
+ use_ep=True,
1241
+ all2all_backend=vllm_parallel_config.all2all_backend,
1242
+ enable_eplb=vllm_parallel_config.enable_eplb,
1243
+ )
1244
+
1245
+ @classmethod
1246
+ def make_no_parallel(cls) -> "FusedMoEParallelConfig":
1247
+ """For usage in CI/CD and testing."""
1248
+ return FusedMoEParallelConfig(
1249
+ tp_size=1,
1250
+ tp_rank=0,
1251
+ pcp_size=1,
1252
+ pcp_rank=0,
1253
+ dp_size=1,
1254
+ dp_rank=0,
1255
+ ep_size=1,
1256
+ ep_rank=0,
1257
+ sp_size=1,
1258
+ use_ep=False,
1259
+ all2all_backend="allgather_reducescatter",
1260
+ enable_eplb=False,
1261
+ )
1262
+
1263
+
1264
+ # Adapted from pplx-kernels tests/all_to_all_utils.py
1265
+ @dataclass
1266
+ class FusedMoEConfig:
1267
+ num_experts: int
1268
+ experts_per_token: int
1269
+ hidden_dim: int
1270
+ intermediate_size: int
1271
+ num_local_experts: int
1272
+ num_logical_experts: int
1273
+ activation: MoEActivation
1274
+ device: torch.device | str
1275
+ routing_method: RoutingMethodType
1276
+ moe_parallel_config: FusedMoEParallelConfig
1277
+
1278
+ # The activation type.
1279
+ in_dtype: torch.dtype
1280
+
1281
+ # Defaults to in_dtype if not specified.
1282
+ router_logits_dtype: torch.dtype | None = None
1283
+
1284
+ # Defaults to hidden_dim if not specified.
1285
+ hidden_dim_unpadded: int | None = None
1286
+ # Defaults to intermediate_size_per_partition if not specified.
1287
+ intermediate_size_per_partition_unpadded: int | None = None
1288
+
1289
+ moe_backend: MoEBackend = "auto"
1290
+ max_num_tokens: int = SchedulerConfig.DEFAULT_MAX_NUM_BATCHED_TOKENS_FOR_BATCHED_DP
1291
+ has_bias: bool = False
1292
+ is_lora_enabled: bool = False
1293
+
1294
+ # SwiGLU clamp limit. When set, backends that do not implement the clamp
1295
+ # are filtered out by `FusedMoEExperts.is_supported_config` so the oracle
1296
+ # cannot silently select one and drop the clamp.
1297
+ swiglu_limit: float | None = None
1298
+ swiglu_alpha: float | None = None
1299
+ swiglu_beta: float | None = None
1300
+
1301
+ max_capture_size: int = 0
1302
+
1303
+ # Set by __post_init__
1304
+ intermediate_size_per_partition: int = -1
1305
+ rocm_aiter_fmoe_enabled: bool = False
1306
+ aiter_fmoe_shared_expert_enabled: bool = False
1307
+
1308
+ def __post_init__(self):
1309
+ from vllm._aiter_ops import rocm_aiter_ops
1310
+
1311
+ tp_size = self.moe_parallel_config.tp_size
1312
+ assert self.intermediate_size % tp_size == 0
1313
+ self.intermediate_size_per_partition = self.intermediate_size // tp_size
1314
+
1315
+ if self.dp_size > 1:
1316
+ logger.debug_once(
1317
+ "Using FusedMoEConfig::max_num_tokens=%d", self.max_num_tokens
1318
+ )
1319
+
1320
+ assert self.max_num_tokens > 0
1321
+
1322
+ if self.router_logits_dtype is None:
1323
+ self.router_logits_dtype = self.in_dtype
1324
+
1325
+ if self.hidden_dim_unpadded is None:
1326
+ self.hidden_dim_unpadded = self.hidden_dim
1327
+ if self.intermediate_size_per_partition_unpadded is None:
1328
+ self.intermediate_size_per_partition_unpadded = (
1329
+ self.intermediate_size_per_partition
1330
+ )
1331
+
1332
+ if self.is_act_and_mul:
1333
+ self.rocm_aiter_fmoe_enabled = rocm_aiter_ops.is_fused_moe_enabled()
1334
+ self.aiter_fmoe_shared_expert_enabled = (
1335
+ rocm_aiter_ops.is_fusion_moe_shared_experts_enabled()
1336
+ )
1337
+
1338
+ if self.use_mori_kernels:
1339
+ assert self.rocm_aiter_fmoe_enabled, (
1340
+ "Mori needs to be used with aiter fused_moe for now."
1341
+ )
1342
+ assert not self.aiter_fmoe_shared_expert_enabled, (
1343
+ "Mori does not support fusion shared expert now. "
1344
+ "Turn it off by setting VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS=0"
1345
+ )
1346
+
1347
+ if not self.is_act_and_mul and not (
1348
+ current_platform.is_cuda_alike() or current_platform.is_xpu()
1349
+ ):
1350
+ raise NotImplementedError(
1351
+ "is_act_and_mul=False is supported only for CUDA, XPU and ROCm for now"
1352
+ )
1353
+
1354
+ @property
1355
+ def is_act_and_mul(self) -> bool:
1356
+ return self.activation.is_gated
1357
+
1358
+ @property
1359
+ def tp_size(self):
1360
+ return self.moe_parallel_config.tp_size
1361
+
1362
+ @property
1363
+ def dp_size(self):
1364
+ return self.moe_parallel_config.dp_size
1365
+
1366
+ @property
1367
+ def pcp_size(self):
1368
+ return self.moe_parallel_config.pcp_size
1369
+
1370
+ @property
1371
+ def ep_size(self):
1372
+ return self.moe_parallel_config.ep_size
1373
+
1374
+ @property
1375
+ def sp_size(self):
1376
+ return self.moe_parallel_config.sp_size
1377
+
1378
+ @property
1379
+ def is_sequence_parallel(self):
1380
+ return self.moe_parallel_config.is_sequence_parallel
1381
+
1382
+ @property
1383
+ def tp_rank(self):
1384
+ return self.moe_parallel_config.tp_rank
1385
+
1386
+ @property
1387
+ def dp_rank(self):
1388
+ return self.moe_parallel_config.dp_rank
1389
+
1390
+ @property
1391
+ def pcp_rank(self):
1392
+ return self.moe_parallel_config.pcp_rank
1393
+
1394
+ @property
1395
+ def ep_rank(self):
1396
+ return self.moe_parallel_config.ep_rank
1397
+
1398
+ @property
1399
+ def use_ep(self):
1400
+ return self.moe_parallel_config.use_ep
1401
+
1402
+ @property
1403
+ def use_deepep_ht_kernels(self):
1404
+ return self.moe_parallel_config.use_deepep_ht_kernels
1405
+
1406
+ @property
1407
+ def use_deepep_ll_kernels(self):
1408
+ return self.moe_parallel_config.use_deepep_ll_kernels
1409
+
1410
+ @property
1411
+ def use_mori_kernels(self):
1412
+ return self.moe_parallel_config.use_mori_kernels
1413
+
1414
+ @property
1415
+ def use_fi_nvl_two_sided_kernels(self):
1416
+ return self.moe_parallel_config.use_fi_nvl_two_sided_kernels
1417
+
1418
+ @property
1419
+ def use_fi_nvl_one_sided_kernels(self):
1420
+ return self.moe_parallel_config.use_fi_nvl_one_sided_kernels
1421
+
1422
+ @property
1423
+ def use_ag_rs_all2all_kernels(self):
1424
+ return self.moe_parallel_config.use_ag_rs_all2all_kernels
1425
+
1426
+ @property
1427
+ def use_nixl_ep_kernels(self):
1428
+ return self.moe_parallel_config.use_nixl_ep_kernels
1429
+
1430
+ @property
1431
+ def use_deepep_v2_kernels(self):
1432
+ return self.moe_parallel_config.use_deepep_v2_kernels
1433
+
1434
+ @property
1435
+ def needs_round_robin_routing_tables(self):
1436
+ return self.moe_parallel_config.needs_round_robin_routing_tables
vllm-patches/PATCHES.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Serving MiniMax-M3-AWQ-int4 on vLLM (A100 / Ampere)
2
+
3
+ This is an **int4 AWQ** quant (group-size 128, routed-experts-only, GPTQ-mse refinement,
4
+ Gemma-norm fold) of MiniMax-M3. It needs **three small vLLM patches** to serve coherently:
5
+ they make the WNA16-Marlin fused-MoE kernel honor M3s clamped SwiGLU and dequantize the
6
+ int4 packing correctly. **Stock vLLM will load the weights but emit incoherent output.**
7
+
8
+ ## Requirements
9
+ - Base image: `vllm/vllm-openai:minimax-m3`
10
+ - `--block-size 128` is **mandatory** (M3 MSA sparse-attention index-cache alignment).
11
+ - 8x A100 80GB (TP8) for long context; smaller TP works at reduced context (e.g. 2x TP4 ~ up to 64K).
12
+
13
+ ## Patches (mount read-only over the image)
14
+ Inside the container, `V=/usr/local/lib/python3.12/dist-packages/vllm`:
15
+
16
+ | file in this folder | mount target |
17
+ |---|---|
18
+ | `A_wna16_marlin.py` | `$V/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16_marlin.py` |
19
+ | `B_int_wna16.py` | `$V/model_executor/layers/fused_moe/oracle/int_wna16.py` |
20
+ | `C_config.py` | `$V/model_executor/layers/fused_moe/config.py` |
21
+
22
+ ## Launch (TP8)
23
+ See `launch_m3_awq.sh`. Core flags: `--tensor-parallel-size 8 --block-size 128
24
+ --enable-expert-parallel --max-model-len 262144 --gpu-memory-utilization 0.95
25
+ --reasoning-parser minimax_m3 --tool-call-parser minimax_m3 --enable-auto-tool-choice
26
+ --trust-remote-code`.
27
+
28
+ ## Notes
29
+ - KV dtype = bf16/auto. **fp8-KV is NOT available on A100** for this model: block-128 + fp8
30
+ routes to a Blackwell-only FlashInfer (trtllm-gen) kernel and fails at init.
31
+ - Sampling: `temperature 1.0, top_p 0.95, top_k 40`.
32
+ - The patches are modified Apache-2.0 vLLM source files.
vllm-patches/launch_m3_awq.sh ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # M3 AWQ-int4 at TP8 — identical to the proven RTN launch, only the model mount differs.
3
+ set -u
4
+ V=/usr/local/lib/python3.12/dist-packages/vllm
5
+ P=/srv/models/m3-clamp-patch
6
+ MODEL=${1:-/srv/quant/m3-awq-int4-v2}
7
+ docker rm -f vllm-m3-awq 2>/dev/null
8
+ docker run -d --name vllm-m3-awq \
9
+ --init \
10
+ --gpus all \
11
+ --ipc=host --shm-size=16g \
12
+ -p 127.0.0.1:8005:8000 \
13
+ -e HF_HUB_OFFLINE=1 \
14
+ -v $MODEL:/model:ro \
15
+ -v $P/A_wna16_marlin.py:$V/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16_marlin.py:ro \
16
+ -v $P/B_int_wna16.py:$V/model_executor/layers/fused_moe/oracle/int_wna16.py:ro \
17
+ -v $P/C_config.py:$V/model_executor/layers/fused_moe/config.py:ro \
18
+ vllm/vllm-openai:minimax-m3 \
19
+ --model /model --served-model-name m3 \
20
+ --tensor-parallel-size 8 \
21
+ --block-size 128 \
22
+ --enable-expert-parallel \
23
+ --enable-prefix-caching \
24
+ --max-model-len 262144 \
25
+ --max-num-seqs 64 \
26
+ --gpu-memory-utilization 0.95 \
27
+ --safetensors-load-strategy=prefetch \
28
+ --reasoning-parser minimax_m3 --tool-call-parser minimax_m3 --enable-auto-tool-choice \
29
+ --trust-remote-code
30
+ echo "launched: $(docker ps --filter name=vllm-m3-awq --format '{{.Names}} {{.Status}}')"