[MoE Refactor] Standardize Humming MoE experts + utilities (#43373)
Signed-off-by: Bill Nell <bnell@redhat.com> Co-authored-by: Robert Shaw <114415538+robertgshaw2-redhat@users.noreply.github.com>
bnellnm committed
Jun 29, 2026 at 09:19 UTC
bc8481af09cd4c7f7272ba7bc1913f1051649813
8 files changed
+1178
-392
vllm/model_executor/layers/fused_moe/experts/fused_humming_moe.py
+221
-104
@@ -36,28 +36,42 @@ from vllm.model_executor.layers.fused_moe.utils import (
36
_resize_cache,
37
swiglu_limit_func,
38
)
39
-from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey
39
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
40
+ QuantKey,
41
+ kFp8DynamicTokenSym,
42
+ kFp8Static128BlockSym,
43
+ kFp8StaticChannelSym,
44
+ kInt4Static,
45
+ kInt8Static,
46
+ kMxfp4Dynamic,
47
+ kMxfp4Static,
48
+ kMxfp8Dynamic,
49
+ kMxfp8Static,
50
+ kNvfp4Static,
51
+)
52
from vllm.platforms import current_platform
41
-from vllm.utils.humming import GemmType as HummingGemmType
42
-from vllm.utils.humming import HummingLayerMeta, HummingMethod, dtypes
53
+from vllm.utils.import_utils import has_humming
54
from vllm.v1.worker.workspace import current_workspace_manager
55
56
if TYPE_CHECKING:
57
from vllm.model_executor.layers.fused_moe import RoutedExperts
58
+ from vllm.utils.humming import GemmType as HummingGemmType
59
60
61
logger = init_logger(__name__)
62
63
52
-def get_humming_moe_gemm_type() -> str:
53
- env_gemm_type: str = envs.VLLM_HUMMING_MOE_GEMM_TYPE or ""
54
- env_gemm_type = env_gemm_type.lower()
55
- if env_gemm_type == "indexed":
56
- gemm_type = env_gemm_type
57
- elif env_gemm_type in ["grouped_contiguous", "grouped"]:
58
- gemm_type = "grouped_contiguous"
59
- else:
60
- gemm_type = "indexed"
64
+def get_humming_moe_gemm_type() -> str | None:
65
+ env_gemm_type: str | None = envs.VLLM_HUMMING_MOE_GEMM_TYPE
66
+ gemm_type = None
67
+ if env_gemm_type is not None:
68
+ env_gemm_type = env_gemm_type.lower()
69
+ if env_gemm_type == "indexed":
70
+ gemm_type = env_gemm_type
71
+ elif env_gemm_type in ["grouped_contiguous", "grouped"]:
72
+ gemm_type = "grouped_contiguous"
73
+ else:
74
+ gemm_type = "indexed"
75
76
logger.info_once(f"Using {gemm_type} gemm for humming moe") # noqa
77
return gemm_type
@@ -89,6 +103,8 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
103
self._permute_scratch: MoEPermuteScratch | None = None
104
105
def init_humming_moe(self):
106
+ from vllm.utils.humming import HummingMethod
107
+
108
self.compute_config = {
109
"use_batch_invariant": envs.VLLM_BATCH_INVARIANT,
110
"use_f16_accum": envs.VLLM_HUMMING_USE_F16_ACCUM,
@@ -141,7 +157,7 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
157
return math.ceil(global_valid_shape_m * num_experts / global_num_experts)
158
159
@staticmethod
144
- def humming_gemm_type() -> HummingGemmType:
160
+ def humming_gemm_type() -> "HummingGemmType":
161
raise NotImplementedError
162
163
@classmethod
@@ -153,12 +169,51 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
169
weight_key: QuantKey | None,
170
activation_key: QuantKey | None,
171
) -> bool:
172
+ SUPPORTED_W_A = [
173
+ (kMxfp4Static, None),
174
+ (kMxfp4Static, kMxfp4Dynamic),
175
+ (kMxfp4Static, kMxfp8Dynamic),
176
+ (kMxfp4Static, kFp8DynamicTokenSym),
177
+ (kNvfp4Static, None),
178
+ (kNvfp4Static, kFp8DynamicTokenSym),
179
+ (kMxfp8Static, None),
180
+ (kMxfp8Static, kFp8DynamicTokenSym),
181
+ (kFp8StaticChannelSym, None),
182
+ (kFp8StaticChannelSym, kFp8DynamicTokenSym),
183
+ (kFp8Static128BlockSym, None),
184
+ (kFp8Static128BlockSym, kFp8DynamicTokenSym),
185
+ (kInt4Static, None),
186
+ (kInt4Static, kFp8DynamicTokenSym),
187
+ (kInt8Static, None),
188
+ (kInt8Static, kFp8DynamicTokenSym),
189
+ ]
190
+ return (weight_key, activation_key) in SUPPORTED_W_A
191
+
192
+ @property
193
+ def expects_unquantized_inputs(self) -> bool:
194
+ """
195
+ Humming kernels handle input quantization internally via
196
+ HummingMethod.may_quant_input() in the apply() method.
197
+
198
+ This property tells the prepare/finalize step to skip input
199
+ quantization (by setting defer_input_quant=True) and pass
200
+ unquantized inputs to the experts. This prevents double
201
+ quantization: once in prepare and once in Humming's apply().
202
+
203
+ Returns:
204
+ True to indicate that this expert expects unquantized inputs
205
+ and will handle quantization internally.
206
+ """
207
return True
208
209
@staticmethod
210
def _supports_current_device() -> bool:
211
platform = current_platform
161
- return platform.is_cuda() and platform.has_device_capability((7, 5))
212
+ return (
213
+ has_humming()
214
+ and platform.is_cuda()
215
+ and platform.has_device_capability((7, 5))
216
+ )
217
218
@staticmethod
219
def _supports_no_act_and_mul() -> bool:
@@ -182,10 +237,7 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
237
238
@staticmethod
239
def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
185
- return not (
186
- moe_parallel_config.use_fi_nvl_two_sided_kernels
187
- or moe_parallel_config.use_fi_nvl_one_sided_kernels
188
- )
240
+ return True
241
242
def moe_problem_size(
243
self,
@@ -194,6 +246,8 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
246
w2: torch.Tensor,
247
topk_ids: torch.Tensor,
248
) -> tuple[int, int, int, int, int]:
249
+ from vllm.utils.humming import HummingLayerMeta
250
+
251
meta1: HummingLayerMeta = self.layer.humming_metas["w13"]
252
meta2: HummingLayerMeta = self.layer.humming_metas["w2"]
253
@@ -215,6 +269,9 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
269
return meta1.num_experts, num_tokens, meta1.shape_n // 2, meta1.shape_k, top_k
270
271
def get_buffer_metas(self, M: int, topk: int, activation: MoEActivation):
272
+ from vllm.utils.humming import GemmType as HummingGemmType
273
+ from vllm.utils.humming import dtypes
274
+
275
num_experts = self.num_experts
276
N = self.layer.intermediate_size_per_partition
277
K = self.layer.hidden_size
@@ -254,7 +311,9 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
311
torch_dtype_map = {
312
dtypes.float16: torch.float16,
313
dtypes.bfloat16: torch.bfloat16,
314
+ dtypes.float32: torch.float32,
315
dtypes.float8e4m3: torch.float8_e4m3fn,
316
+ dtypes.float8e5m2: torch.float8_e5m2,
317
dtypes.int8: torch.int8,
318
dtypes.int4: torch.uint8,
319
}
@@ -289,7 +348,13 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
348
for key in buffer_metas:
349
meta = buffer_metas[key]
350
if "quanted" in key and a_dtype.num_bits == 4:
292
- meta["shape"] = meta["shape"][:-1] + (meta["shape"][-1] // 2,)
351
+ last_dim = meta["shape"][-1]
352
+ if last_dim % 2 != 0:
353
+ raise ValueError(
354
+ f"Int4 packing requires last dimension to be even, "
355
+ f"got {last_dim} for buffer '{key}'"
356
+ )
357
+ meta["shape"] = meta["shape"][:-1] + (last_dim // 2,)
358
359
if num_bits == 16:
360
required_buffers = ["gate_up_output", "activation_output", "down_output"]
@@ -325,8 +390,13 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
390
391
output_key = "down_output" if self.is_batched() else "output"
392
output_shape = buffer_metas[output_key]["shape"]
393
+ elem_size = self.layer.params_dtype.itemsize
394
329
- return (workspace1_nbytes // 2,), (workspace2_nbytes // 2,), output_shape
395
+ return (
396
+ (workspace1_nbytes // elem_size,),
397
+ (workspace2_nbytes // elem_size,),
398
+ output_shape,
399
+ )
400
401
def workspace_shapes(
402
self,
@@ -344,7 +414,7 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
414
def make_workspaces(self, M: int, topk: int, activation: MoEActivation):
415
shapes = self._workspace_shapes(M, topk, activation)
416
workspace1_shape, workspace2_shape, output_shape = shapes
347
- torch_dtype = self.layer.param_dtype
417
+ torch_dtype = self.layer.params_dtype
418
workspace1, workspace2 = current_workspace_manager().get_simultaneous(
419
(workspace1_shape, torch_dtype),
420
(workspace2_shape, torch_dtype),
@@ -370,45 +440,8 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
440
441
return buffers
442
373
- def apply(
374
- self,
375
- output: torch.Tensor,
376
- hidden_states: torch.Tensor,
377
- w1: torch.Tensor,
378
- w2: torch.Tensor,
379
- topk_weights: torch.Tensor,
380
- topk_ids: torch.Tensor,
381
- activation: MoEActivation,
382
- global_num_experts: int,
383
- expert_map: torch.Tensor | None,
384
- a1q_scale: torch.Tensor | None,
385
- a2_scale: torch.Tensor | None,
386
- workspace13: torch.Tensor,
387
- workspace2: torch.Tensor,
388
- expert_tokens_meta: mk.ExpertTokensMetadata | None,
389
- apply_router_weight_on_input: bool,
390
- ):
391
- assert not apply_router_weight_on_input
392
-
393
- self.main_apply(
394
- hidden_states=hidden_states,
395
- topk_weights=topk_weights,
396
- topk_ids=topk_ids,
397
- workspace1=workspace13,
398
- workspace2=workspace2,
399
- expert_tokens_meta=expert_tokens_meta,
400
- )
401
-
402
- def main_apply(
403
- self,
404
- hidden_states: torch.Tensor,
405
- topk_weights: torch.Tensor,
406
- topk_ids: torch.Tensor,
407
- workspace1: torch.Tensor,
408
- workspace2: torch.Tensor,
409
- expert_tokens_meta: mk.ExpertTokensMetadata | None,
410
- ):
411
- raise NotImplementedError
443
+ # Note: apply method is implemented by subclasses following the
444
+ # standard FusedMoEExpertsModular.apply signature
445
446
@staticmethod
447
def is_supported_config(
@@ -418,24 +451,27 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
451
activation_key: QuantKey | None,
452
activation_format: mk.FusedMoEActivationFormat,
453
) -> tuple[bool, str | None]:
421
- if activation_format == mk.FusedMoEActivationFormat.BatchedExperts:
422
- supported = cls.activation_format() == activation_format
423
- reason = "activation_format mismatched"
424
- elif activation_format == mk.FusedMoEActivationFormat.Standard:
425
- if cls.activation_format() != mk.FusedMoEActivationFormat.Standard:
426
- supported = False
427
- reason = "activation_format mismatched"
428
- else:
429
- assert hasattr(cls, "humming_gemm_type")
430
- gemm_type = cls.humming_gemm_type().value.lower()
431
- preferred_gemm_type = get_humming_moe_gemm_type().lower()
432
- supported = preferred_gemm_type == gemm_type
433
- reason = "preferred gemm type mismatched"
434
- else:
435
- supported = False
436
- reason = "unsupported activation_format"
454
+ supported, reason = mk.FusedMoEExpertsModular.is_supported_config(
455
+ cls,
456
+ moe_config,
457
+ weight_key,
458
+ activation_key,
459
+ activation_format,
460
+ )
461
438
- return supported, None if supported else reason
462
+ if supported:
463
+ assert hasattr(cls, "humming_gemm_type")
464
+ gemm_type = cls.humming_gemm_type().value.lower()
465
+ preferred_gemm_type = get_humming_moe_gemm_type()
466
+ if preferred_gemm_type is not None:
467
+ supported = preferred_gemm_type.lower() == gemm_type
468
+ if not supported:
469
+ reason = (
470
+ f"preferred gemm type {preferred_gemm_type} != "
471
+ f"supported gemm type {gemm_type}"
472
+ )
473
+
474
+ return supported, reason
475
476
def apply_activation(
477
self,
@@ -459,7 +495,9 @@ class HummingIndexedExperts(HummingExpertsBase):
495
return mk.FusedMoEActivationFormat.Standard
496
497
@staticmethod
462
- def humming_gemm_type() -> HummingGemmType:
498
+ def humming_gemm_type() -> "HummingGemmType":
499
+ from vllm.utils.humming import GemmType as HummingGemmType
500
+
501
return HummingGemmType.INDEXED
502
503
def prepare_humming_moe_kwargs(
@@ -470,12 +508,18 @@ class HummingIndexedExperts(HummingExpertsBase):
508
) -> tuple[dict[str, Any], dict[str, Any]]:
509
valid_shape_m = self.estimate_local_valid_shape_m(topk_ids)
510
511
+ moe_block_size = None
512
for min_shape_m, max_shape_m, config in self.w13_tuning_config:
513
if valid_shape_m > min_shape_m and valid_shape_m <= max_shape_m:
514
moe_block_size = config["block_shape"][0]
515
break
477
- else:
478
- raise ValueError(f"cannot found moe_block_size for shape {valid_shape_m}")
516
+
517
+ if moe_block_size is None:
518
+ logger.warning_once(
519
+ "No tuning config found for shape %s, using default block_size=64",
520
+ valid_shape_m,
521
+ )
522
+ moe_block_size = 64
523
524
sorted_ids, expert_ids, num_tokens_padded = moe_align_block_size(
525
topk_ids=topk_ids,
@@ -501,27 +545,47 @@ class HummingIndexedExperts(HummingExpertsBase):
545
546
return moe_kwargs1, moe_kwargs2
547
504
- def main_apply(
548
+ def apply(
549
self,
550
+ output: torch.Tensor,
551
hidden_states: torch.Tensor,
552
+ w1: torch.Tensor,
553
+ w2: torch.Tensor,
554
topk_weights: torch.Tensor,
555
topk_ids: torch.Tensor,
509
- workspace1: torch.Tensor,
556
+ activation: MoEActivation,
557
+ global_num_experts: int,
558
+ expert_map: torch.Tensor | None,
559
+ a1q_scale: torch.Tensor | None,
560
+ a2_scale: torch.Tensor | None,
561
+ workspace13: torch.Tensor,
562
workspace2: torch.Tensor,
563
expert_tokens_meta: mk.ExpertTokensMetadata | None,
512
- ):
564
+ apply_router_weight_on_input: bool,
565
+ ) -> None:
566
+ """
567
+ Standard apply implementation for Humming indexed experts.
568
+
569
+ Note: Humming kernels handle weights and quantization internally through
570
+ the layer object, so w1, w2, a1q_scale, a2_scale parameters are not used.
571
+ The output is written into workspace13 via the buffer management.
572
+ """
573
+ from vllm.utils.humming import HummingMethod
574
+
575
+ assert not apply_router_weight_on_input
576
+
577
hidden_states = hidden_states.view(-1, hidden_states.size(-1))
578
buffers = self.prepare_buffers(
515
- workspace1,
579
+ workspace13,
580
workspace2,
581
topk_ids.size(0),
582
topk_ids.size(1),
519
- self.layer.activation,
583
+ activation,
584
)
585
586
moe_kwargs1, moe_kwargs2 = self.prepare_humming_moe_kwargs(
587
topk_ids=topk_ids,
524
- expert_map=self.layer.expert_map,
588
+ expert_map=expert_map,
589
expert_tokens_meta=expert_tokens_meta,
590
)
591
@@ -542,7 +606,7 @@ class HummingIndexedExperts(HummingExpertsBase):
606
)
607
608
self.apply_activation(
545
- activation=self.layer.activation,
609
+ activation=activation,
610
input=buffers["gate_up_output"],
611
output=buffers["activation_output"],
612
)
@@ -567,10 +631,13 @@ class HummingIndexedExperts(HummingExpertsBase):
631
inputs=buffers["down_output"].view(*topk_ids.shape, -1),
632
topk_weights=topk_weights,
633
topk_ids=topk_ids,
570
- expert_map=self.layer.expert_map,
634
+ expert_map=expert_map,
635
outputs=buffers["output"],
636
)
637
638
+ # Note: output is already written to buffers["output"]
639
+ # which aliases workspace13/output
640
+
641
642
class HummingGroupedExperts(HummingExpertsBase):
643
def finalize_weight_and_reduce_impl(self) -> mk.TopKWeightAndReduce:
@@ -581,35 +648,57 @@ class HummingGroupedExperts(HummingExpertsBase):
648
return mk.FusedMoEActivationFormat.Standard
649
650
@staticmethod
584
- def humming_gemm_type() -> HummingGemmType:
651
+ def humming_gemm_type() -> "HummingGemmType":
652
+ from vllm.utils.humming import GemmType as HummingGemmType
653
+
654
return HummingGemmType.GROUPED_CONTIGUOUS
655
587
- def main_apply(
656
+ def apply(
657
self,
658
+ output: torch.Tensor,
659
hidden_states: torch.Tensor,
660
+ w1: torch.Tensor,
661
+ w2: torch.Tensor,
662
topk_weights: torch.Tensor,
663
topk_ids: torch.Tensor,
592
- workspace1: torch.Tensor,
664
+ activation: MoEActivation,
665
+ global_num_experts: int,
666
+ expert_map: torch.Tensor | None,
667
+ a1q_scale: torch.Tensor | None,
668
+ a2_scale: torch.Tensor | None,
669
+ workspace13: torch.Tensor,
670
workspace2: torch.Tensor,
671
expert_tokens_meta: mk.ExpertTokensMetadata | None,
595
- ):
672
+ apply_router_weight_on_input: bool,
673
+ ) -> None:
674
+ """
675
+ Standard apply implementation for Humming grouped experts.
676
+
677
+ Note: Humming kernels handle weights and quantization internally through
678
+ the layer object, so w1, w2, a1q_scale, a2_scale parameters are not used.
679
+ The output is written into workspace13 via the buffer management.
680
+ """
681
+ from vllm.utils.humming import HummingMethod
682
+
683
+ assert not apply_router_weight_on_input
684
+
685
valid_shape_m = self.estimate_local_valid_shape_m(topk_ids)
686
687
buffers = self.prepare_buffers(
599
- workspace1,
688
+ workspace13,
689
workspace2,
690
topk_ids.size(0),
691
topk_ids.size(1),
603
- self.layer.activation,
692
+ activation,
693
)
694
695
hidden_states, _, expert_first_token_offset, inv_perm, _ = moe_permute(
696
hidden_states=hidden_states,
697
a1q_scale=None,
698
topk_ids=topk_ids,
610
- n_expert=self.global_num_experts,
699
+ n_expert=global_num_experts,
700
n_local_expert=self.num_experts,
612
- expert_map=self.layer.expert_map,
701
+ expert_map=expert_map,
702
scratch=self._get_permute_scratch(),
703
)
704
@@ -633,7 +722,7 @@ class HummingGroupedExperts(HummingExpertsBase):
722
)
723
724
self.apply_activation(
636
- activation=self.layer.activation,
725
+ activation=activation,
726
input=buffers["gate_up_output"],
727
output=buffers["activation_output"],
728
)
@@ -665,6 +754,9 @@ class HummingGroupedExperts(HummingExpertsBase):
754
expert_first_token_offset=expert_first_token_offset,
755
)
756
757
+ # Note: output is already written to buffers["output"]
758
+ # which aliases workspace13/output
759
+
760
761
class BatchedHummingGroupedExperts(HummingExpertsBase):
762
def finalize_weight_and_reduce_impl(self) -> mk.TopKWeightAndReduce:
@@ -675,29 +767,51 @@ class BatchedHummingGroupedExperts(HummingExpertsBase):
767
return mk.FusedMoEActivationFormat.BatchedExperts
768
769
@staticmethod
678
- def humming_gemm_type() -> HummingGemmType:
770
+ def humming_gemm_type() -> "HummingGemmType":
771
+ from vllm.utils.humming import GemmType as HummingGemmType
772
+
773
return HummingGemmType.GROUPED_MASKED
774
681
- def main_apply(
775
+ def apply(
776
self,
777
+ output: torch.Tensor,
778
hidden_states: torch.Tensor,
779
+ w1: torch.Tensor,
780
+ w2: torch.Tensor,
781
topk_weights: torch.Tensor,
782
topk_ids: torch.Tensor,
686
- workspace1: torch.Tensor,
783
+ activation: MoEActivation,
784
+ global_num_experts: int,
785
+ expert_map: torch.Tensor | None,
786
+ a1q_scale: torch.Tensor | None,
787
+ a2_scale: torch.Tensor | None,
788
+ workspace13: torch.Tensor,
789
workspace2: torch.Tensor,
790
expert_tokens_meta: mk.ExpertTokensMetadata | None,
689
- ):
791
+ apply_router_weight_on_input: bool,
792
+ ) -> None:
793
+ """
794
+ Standard apply implementation for Humming batched grouped experts.
795
+
796
+ Note: Humming kernels handle weights and quantization internally through
797
+ the layer object, so w1, w2, a1q_scale, a2_scale parameters are not used.
798
+ The output is written into workspace13 via the buffer management.
799
+ """
800
+ from vllm.utils.humming import HummingMethod
801
+
802
+ assert not apply_router_weight_on_input
803
assert expert_tokens_meta is not None
804
+
805
hidden_states = hidden_states.view(-1, hidden_states.size(-1))
806
valid_shape_m = self.estimate_local_valid_shape_m(topk_ids)
807
expert_num_tokens = expert_tokens_meta.expert_num_tokens
808
809
buffers = self.prepare_buffers(
696
- workspace1,
810
+ workspace13,
811
workspace2,
812
topk_ids.size(0),
813
topk_ids.size(1),
700
- self.layer.activation,
814
+ activation,
815
)
816
817
inputs, input_scale = HummingMethod.may_quant_input(
@@ -720,7 +834,7 @@ class BatchedHummingGroupedExperts(HummingExpertsBase):
834
)
835
836
self.apply_activation(
723
- activation=self.layer.activation,
837
+ activation=activation,
838
input=buffers["gate_up_output"],
839
output=buffers["activation_output"],
840
)
@@ -743,3 +857,6 @@ class BatchedHummingGroupedExperts(HummingExpertsBase):
857
tuning_config=self.w2_tuning_config_str,
858
sublayer_name="w2",
859
)
860
+
861
+ # Note: output is already written to buffers["down_output"]
862
+ # which aliases workspace13/output
vllm/model_executor/layers/fused_moe/oracle/mxfp4.py
+11
-6
@@ -711,10 +711,12 @@ def convert_gpt_oss_weight_to_mxfp4_moe_kernel_format(
711
712
if mxfp4_backend == Mxfp4MoeBackend.HUMMING:
713
from vllm.model_executor.layers.quantization.utils.humming_utils import (
714
- prepare_humming_moe_layer,
714
+ convert_to_humming_moe_kernel_format,
715
)
716
717
- prepare_humming_moe_layer(layer, {"quant_method": "gpt_oss_mxfp4"})
717
+ convert_to_humming_moe_kernel_format(
718
+ layer, quant_config={"quant_method": "gpt_oss_mxfp4"}
719
+ )
720
return (
721
layer.w13_weight,
722
layer.w2_weight,
@@ -1277,10 +1279,12 @@ def convert_weight_to_mxfp4_moe_kernel_format(
1279
1280
if mxfp4_backend == Mxfp4MoeBackend.HUMMING:
1281
from vllm.model_executor.layers.quantization.utils.humming_utils import (
1280
- prepare_humming_moe_layer,
1282
+ convert_to_humming_moe_kernel_format,
1283
)
1284
1283
- prepare_humming_moe_layer(layer, {"quant_method": "mxfp4"})
1285
+ convert_to_humming_moe_kernel_format(
1286
+ layer, quant_config={"quant_method": "mxfp4"}
1287
+ )
1288
return (
1289
layer.w13_weight,
1290
layer.w2_weight,
@@ -1569,7 +1573,7 @@ def make_mxfp4_moe_quant_config(
1573
w2_bias: torch.Tensor | None = None,
1574
a1_scale: torch.Tensor | None = None,
1575
a2_scale: torch.Tensor | None = None,
1572
- layer: torch.nn.Module | None = None,
1576
+ layer: "RoutedExperts | None" = None,
1577
) -> FusedMoEQuantConfig | None:
1578
"""Create a FusedMoEQuantConfig for the given MXFP4 backend."""
1579
if mxfp4_backend == Mxfp4MoeBackend.DEEPGEMM_MXFP4:
@@ -1662,7 +1666,7 @@ def make_mxfp4_moe_quant_config(
1666
get_humming_moe_quant_config,
1667
)
1668
1665
- assert isinstance(layer, RoutedExperts)
1669
+ assert layer is not None
1670
return get_humming_moe_quant_config(
1671
layer,
1672
gemm1_alpha=gemm1_alpha,
@@ -1703,6 +1707,7 @@ def make_mxfp4_moe_kernel(
1707
assert prepare_finalize is not None
1708
1709
logger.info_once("Using %s", prepare_finalize.__class__.__name__)
1710
+ logger.info_once("Using %s", experts_cls.__name__)
1711
1712
extra_kwargs = {}
1713
if mxfp4_backend == Mxfp4MoeBackend.HUMMING:
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_w4a4_mxfp4.py
+1
@@ -143,6 +143,7 @@ class CompressedTensorsW4A4Mxfp4MoEMethod(CompressedTensorsMoEMethod):
143
mxfp4_backend=self.mxfp4_backend,
144
w1_scale=layer.w13_weight_scale,
145
w2_scale=layer.w2_weight_scale,
146
+ layer=layer,
147
)
148
149
def process_weights_after_loading(self, layer: RoutedExperts) -> None:
vllm/model_executor/layers/quantization/fp8.py
+9
-9
@@ -706,15 +706,15 @@ class Fp8MoEMethod(FusedMoEMethodBase):
706
layer.w2_weight.is_shuffled = True
707
708
self.moe_quant_config = self.get_fused_moe_quant_config(layer)
709
- if self.moe_quant_config:
710
- assert self.experts_cls is not None
711
- self.moe_kernel = make_fp8_moe_kernel(
712
- moe_quant_config=self.moe_quant_config,
713
- moe_config=self.moe,
714
- fp8_backend=self.fp8_backend,
715
- experts_cls=self.experts_cls,
716
- routing_tables=layer._expert_routing_tables(),
717
- )
709
+ assert self.moe_quant_config is not None
710
+ assert self.experts_cls is not None
711
+ self.moe_kernel = make_fp8_moe_kernel(
712
+ moe_quant_config=self.moe_quant_config,
713
+ moe_config=self.moe,
714
+ fp8_backend=self.fp8_backend,
715
+ experts_cls=self.experts_cls,
716
+ routing_tables=layer._expert_routing_tables(),
717
+ )
718
719
def process_weights_after_loading(self, layer: RoutedExperts) -> None:
720
# Allow for accessing weights and scales in standard way.
vllm/model_executor/layers/quantization/humming.py
+75
-163
@@ -30,6 +30,14 @@ from vllm.model_executor.layers.quantization.base_config import (
30
QuantizationConfig,
31
QuantizeMethodBase,
32
)
33
+from vllm.model_executor.layers.quantization.utils.humming_utils import (
34
+ convert_to_humming_moe_kernel_format,
35
+ get_humming_moe_quant_config,
36
+ input_schema_to_quant_key,
37
+ make_humming_moe_kernel,
38
+ select_humming_moe_experts,
39
+ weight_schema_to_quant_key,
40
+)
41
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
42
from vllm.model_executor.parameter import (
43
BasevLLMParameter,
@@ -106,7 +114,7 @@ def prepare_param(tensor, name, extra_attrs):
114
return param
115
116
109
-def prepare_moe_param(tensor, name, extra_attrs):
117
+def prepare_moe_param(tensor: torch.Tensor, name: str, extra_attrs: dict[str, Any]):
118
param = torch.nn.Parameter(tensor, requires_grad=False)
119
if "scale_type" in extra_attrs:
120
extra_attrs["quant_method"] = extra_attrs["scale_type"]
@@ -605,12 +613,27 @@ class HummingMoEMethod(FusedMoEMethodBase):
613
) -> None:
614
super().__init__(moe)
615
self.quant_config = quant_config
608
- self.moe = moe
616
self.weight_schema = quant_config.weight_schema
617
self.input_schema = quant_config.input_schema
618
self.force_weight_schema = quant_config.force_weight_schema
619
self.force_input_schema = quant_config.force_input_schema
620
621
+ # Derive QuantKeys from humming schemas.
622
+ # Prefer force schemas (the final format after requant) over base.
623
+ weight_key = weight_schema_to_quant_key(
624
+ self.force_weight_schema or self.weight_schema
625
+ )
626
+ activation_key = input_schema_to_quant_key(
627
+ self.force_input_schema or self.input_schema
628
+ )
629
+
630
+ # Select Humming MoE experts
631
+ self.experts_cls = select_humming_moe_experts(
632
+ config=self.moe,
633
+ weight_key=weight_key,
634
+ activation_key=activation_key,
635
+ )
636
+
637
def prepare_weight_loader(self, layer, weight_loader):
638
def new_weight_loader(
639
param: torch.nn.Parameter,
@@ -647,7 +670,7 @@ class HummingMoEMethod(FusedMoEMethodBase):
670
sublayer_name = "w2" if shard_id == "w2" else "w13"
671
672
param = getattr(layer, sublayer_name + "_" + key)
650
- part_subccess = param.weight_loader(
673
+ part_success = param.weight_loader(
674
param=param,
675
loaded_weight=tensor.cpu(),
676
weight_name=shard_id + "_" + key,
@@ -655,7 +678,7 @@ class HummingMoEMethod(FusedMoEMethodBase):
678
expert_id=expert_id,
679
return_success=return_success,
680
)
658
- success = success and part_subccess
681
+ success = success and part_success
682
683
return success if return_success else None
684
@@ -677,7 +700,7 @@ class HummingMoEMethod(FusedMoEMethodBase):
700
701
def create_weights(
702
self,
680
- layer: torch.nn.Module,
703
+ layer: RoutedExperts,
704
num_experts: int,
705
hidden_size: int,
706
intermediate_size_per_partition: int,
@@ -734,160 +757,35 @@ class HummingMoEMethod(FusedMoEMethodBase):
757
locks = torch.zeros(1024, dtype=torch.int32)
758
layer.register_buffer("locks", locks)
759
737
- def get_fused_moe_quant_config(self, layer: torch.nn.Module) -> FusedMoEQuantConfig:
738
- from vllm.model_executor.layers.quantization.utils.humming_utils import (
739
- get_humming_moe_quant_config,
740
- )
741
-
760
+ def get_fused_moe_quant_config(self, layer: RoutedExperts) -> FusedMoEQuantConfig:
761
return get_humming_moe_quant_config(layer)
762
763
def process_weights_after_loading(self, layer: RoutedExperts) -> None:
764
if getattr(self, "processed", False):
765
return
766
self.processed = True
748
- layer.weight_schemas = {}
749
- layer.input_schemas = {}
750
- for sublayer_name, configs in layer.sublayer_configs.items():
751
- input_schema = self.input_schema
752
- weight_schema = self.weight_schema
753
- # convert from checkpoint format to humming format
754
- if not isinstance(weight_schema, _hm.HummingWeightSchema):
755
- tensors: dict[str, torch.Tensor] = dict(
756
- (key.removeprefix(sublayer_name + "_"), value)
757
- for key, value in layer.state_dict().items()
758
- if key.startswith(sublayer_name + "_")
759
- )
760
-
761
- shape_k_stacks = [configs["shape_k"]]
762
- shape_n_stacks = [configs["shape_n"]]
763
- if sublayer_name == "w13":
764
- shape_n_stacks = [configs["shape_n"] // 2] * 2
765
-
766
- weight_schema, tensors = weight_schema.convert_humming(
767
- tensors=tensors,
768
- shape_n_stacks=shape_n_stacks,
769
- shape_k_stacks=shape_k_stacks,
770
- param_dtype=layer.param_dtype,
771
- num_experts=layer.num_experts,
772
- )
773
-
774
- input_schema, _ = input_schema.convert_humming(
775
- tensors=tensors,
776
- shape_n_stacks=shape_n_stacks,
777
- shape_k_stacks=shape_k_stacks,
778
- param_dtype=layer.param_dtype,
779
- num_experts=layer.num_experts,
780
- )
781
-
782
- for name, _ in list(layer.named_parameters()):
783
- if not name.startswith(sublayer_name + "_"):
784
- continue
785
- delattr(layer, name)
786
-
787
- for name, tensor in tensors.items():
788
- name = f"{sublayer_name}_{name}"
789
- param = torch.nn.Parameter(tensor, requires_grad=False)
790
- setattr(layer, name, param)
791
-
792
- layer.weight_schemas[sublayer_name] = weight_schema
793
- layer.input_schemas[sublayer_name] = input_schema
794
-
795
- # force requant (origin quant setting -> fp16/bf16 -> new_quant setting)
796
- assert isinstance(weight_schema, _hm.HummingWeightSchema)
797
- force_requant = self.force_weight_schema is not None
798
- if force_requant and weight_schema != self.force_weight_schema:
799
- tensors = dict(
800
- (key.removeprefix(sublayer_name + "_"), value)
801
- for key, value in layer.state_dict().items()
802
- if key.startswith(sublayer_name + "_")
803
- )
804
-
805
- tensors = weight_schema.requant_tensors(
806
- tensors=tensors,
807
- target_weight_schema=self.force_weight_schema,
808
- param_dtype=layer.param_dtype,
809
- )
810
-
811
- weight_schema = self.force_weight_schema
812
-
813
- for name, _ in list(layer.named_parameters()):
814
- if not name.startswith(sublayer_name + "_"):
815
- continue
816
- if name == sublayer_name + "_bias":
817
- continue
818
- delattr(layer, name)
819
-
820
- for name, tensor in tensors.items():
821
- name = f"{sublayer_name}_{name}"
822
- param = torch.nn.Parameter(tensor, requires_grad=False)
823
- setattr(layer, name, param)
767
825
- del tensors
826
-
827
- # prepare layer config from humming kernel
828
- _hm.HummingMethod.prepare_layer_meta(
829
- layer=layer,
830
- shape_n=configs["shape_n"],
831
- shape_k=configs["shape_k"],
832
- pad_n_to_multiple=256,
833
- pad_k_to_multiple=128,
834
- input_schema=input_schema,
835
- weight_schema=weight_schema,
836
- has_bias=self.moe.has_bias,
837
- num_experts=layer.num_experts,
838
- torch_dtype=layer.param_dtype,
839
- sublayer_name=sublayer_name,
840
- )
841
-
842
- # preprocess weight for inference
843
- _hm.HummingMethod.transform_humming_layer(
844
- layer, sublayer_name=sublayer_name
845
- )
846
-
847
- from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
848
- HummingGroupedExperts,
849
- HummingIndexedExperts,
850
- get_humming_moe_gemm_type,
768
+ # Convert weights to Humming kernel format
769
+ convert_to_humming_moe_kernel_format(
770
+ layer=layer,
771
+ sublayer_configs=layer.sublayer_configs,
772
+ weight_schema=self.weight_schema,
773
+ input_schema=self.input_schema,
774
+ force_weight_schema=self.force_weight_schema,
775
)
776
853
- # use moe modular
854
- experts: HummingIndexedExperts | HummingGroupedExperts
855
- layer._ensure_moe_quant_config_init()
777
+ # Build the MoE kernel
778
+ self.moe_quant_config = self.get_fused_moe_quant_config(layer)
779
assert self.moe_quant_config is not None
857
- if get_humming_moe_gemm_type() == "indexed":
858
- experts = HummingIndexedExperts(layer, self.moe, self.moe_quant_config)
859
- else:
860
- experts = HummingGroupedExperts(layer, self.moe, self.moe_quant_config)
861
- self.experts = experts
862
-
863
- def select_gemm_impl(
864
- self,
865
- prepare_finalize,
866
- layer: torch.nn.Module,
867
- ):
868
- from vllm.model_executor.layers.fused_moe import modular_kernel as mk
869
- from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
870
- BatchedHummingGroupedExperts,
871
- HummingGroupedExperts,
872
- HummingIndexedExperts,
873
- get_humming_moe_gemm_type,
780
+ assert self.experts_cls is not None
781
+ self.moe_kernel = make_humming_moe_kernel(
782
+ self.moe_quant_config,
783
+ self.moe,
784
+ self.experts_cls,
785
+ layer=layer,
786
+ routing_tables=layer._expert_routing_tables(),
787
)
788
876
- activation_format = prepare_finalize.activation_format
877
- assert self.moe_quant_config is not None
878
- if activation_format == mk.FusedMoEActivationFormat.BatchedExperts:
879
- return BatchedHummingGroupedExperts(
880
- layer=layer,
881
- moe_config=self.moe,
882
- quant_config=self.moe_quant_config,
883
- max_num_tokens=prepare_finalize.max_num_tokens_per_rank(),
884
- num_dispatchers=prepare_finalize.num_dispatchers(),
885
- )
886
- elif get_humming_moe_gemm_type() == "indexed":
887
- return HummingIndexedExperts(layer, self.moe, self.moe_quant_config)
888
- else:
889
- return HummingGroupedExperts(layer, self.moe, self.moe_quant_config)
890
-
789
def apply(
790
self,
791
layer: RoutedExperts,
@@ -896,22 +794,36 @@ class HummingMoEMethod(FusedMoEMethodBase):
794
topk_ids: torch.Tensor,
795
shared_experts: SharedExperts | None,
796
shared_experts_input: torch.Tensor | None,
899
- ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
900
- workspace1, workspace2, output = self.experts.make_workspaces(
901
- M=topk_ids.size(0),
902
- topk=topk_ids.size(1),
903
- activation=layer.activation,
904
- )
905
-
906
- assert workspace1.data_ptr() == output.data_ptr()
907
-
908
- self.experts.main_apply(
797
+ ) -> torch.Tensor:
798
+ """
799
+ Apply Humming-quantized MoE computation using the standard kernel flow.
800
+
801
+ This method uses FusedMoEKernel.apply() which orchestrates:
802
+ 1. Preparation (quantization if needed - skipped for Humming via
803
+ expects_unquantized_inputs=True to prevent double quantization)
804
+ 2. Expert computation (via experts.apply())
805
+ 3. Finalization (weight application & reduction - no-op for Humming
806
+ since it's already done internally)
807
+
808
+ Humming handles all quantization, weight application, and reduction
809
+ internally in the experts.apply() method via HummingMethod calls.
810
+
811
+ Note: Although w1/w2 weights are passed to the kernel for interface
812
+ consistency, Humming's experts.apply() reads weights directly from
813
+ the layer object via HummingMethod.forward_layer() and ignores the
814
+ w1/w2 parameters.
815
+ """
816
+ assert self.moe_kernel is not None
817
+ return self.moe_kernel.apply(
818
hidden_states=x,
910
- topk_weights=topk_weights,
819
+ w1=layer.w13_weight,
820
+ w2=layer.w2_weight,
821
topk_ids=topk_ids,
912
- workspace1=workspace1,
913
- workspace2=workspace2,
914
- expert_tokens_meta=None,
822
+ topk_weights=topk_weights,
823
+ activation=layer.activation,
824
+ global_num_experts=layer.global_num_experts,
825
+ expert_map=layer.expert_map,
826
+ apply_router_weight_on_input=False,
827
+ shared_experts=shared_experts,
828
+ shared_experts_input=shared_experts_input,
829
)
916
-
917
- return output
vllm/model_executor/layers/quantization/quark/quark_moe.py
+1
@@ -1280,6 +1280,7 @@ class QuarkOCP_MX_MoEMethod(QuarkMoEMethod):
1280
gemm1_alpha=getattr(layer, "swiglu_alpha", None),
1281
gemm1_beta=getattr(layer, "swiglu_beta", None),
1282
swiglu_limit=getattr(layer, "swiglu_limit", None),
1283
+ layer=layer,
1284
)
1285
1286
# Emulation and other schemes
vllm/model_executor/layers/quantization/utils/humming_utils.py
+847
-110
@@ -1,20 +1,376 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import json
4
-from typing import Any
4
+from typing import TYPE_CHECKING, Any
5
6
import regex as re
7
import torch
8
9
+import vllm.model_executor.layers.fused_moe.modular_kernel as mk
10
from vllm import envs
10
-from vllm.model_executor.layers.fused_moe import RoutedExperts
11
+from vllm.logger import init_logger
12
+from vllm.model_executor.layers.fused_moe.all2all_utils import (
13
+ maybe_make_prepare_finalize,
14
+)
15
from vllm.model_executor.layers.fused_moe.config import (
16
+ FusedMoEConfig,
17
FusedMoEQuantConfig,
18
FusedMoEQuantDesc,
19
)
20
+from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
21
+ BatchedHummingGroupedExperts,
22
+ HummingGroupedExperts,
23
+ HummingIndexedExperts,
24
+)
25
from vllm.model_executor.layers.linear import LinearBase
16
-from vllm.model_executor.layers.quantization.utils.quant_utils import GroupShape
17
-from vllm.utils.humming import BaseWeightSchema, HummingInputSchema, HummingMethod
26
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
27
+ FP4_DTYPE,
28
+ FP8_DTYPE,
29
+ INT4_DTYPE,
30
+ INT8_DTYPE,
31
+ MXFP_SCALE_DTYPE,
32
+ GroupShape,
33
+ QuantKey,
34
+ ScaleDesc,
35
+)
36
+from vllm.utils.import_utils import has_humming
37
+
38
+if TYPE_CHECKING:
39
+ from vllm.model_executor.layers.fused_moe.routed_experts import RoutedExperts
40
+ from vllm.utils.humming import (
41
+ AWQWeightSchema,
42
+ BaseInputSchema,
43
+ BaseWeightSchema,
44
+ CompressedTensorsInputSchema,
45
+ CompressedTensorsWeightSchema,
46
+ Fp8WeightSchema,
47
+ GPTQWeightSchema,
48
+ HummingInputSchema,
49
+ HummingWeightSchema,
50
+ )
51
+ from vllm.utils.humming import dtypes as humming_dtypes
52
+
53
+logger = init_logger(__name__)
54
+
55
+if has_humming():
56
+ from vllm.utils.humming import dtypes as humming_dtypes
57
+
58
+ _HUMMING_TO_QUANT_DTYPE: dict[humming_dtypes.DataType, Any] = {
59
+ humming_dtypes.float4e2m1: FP4_DTYPE,
60
+ humming_dtypes.float8e4m3: FP8_DTYPE,
61
+ humming_dtypes.float8e5m2: torch.float8_e5m2,
62
+ humming_dtypes.int8: torch.int8,
63
+ humming_dtypes.uint4: INT4_DTYPE,
64
+ humming_dtypes.uint8: INT8_DTYPE,
65
+ humming_dtypes.uint2: torch.uint8,
66
+ humming_dtypes.uint3: torch.uint8,
67
+ }
68
+
69
+ _HUMMING_TO_SCALE_DTYPE: dict[humming_dtypes.DataType, torch.dtype] = {
70
+ humming_dtypes.float8e8m0: MXFP_SCALE_DTYPE,
71
+ humming_dtypes.float8e4m3: FP8_DTYPE,
72
+ humming_dtypes.float16: torch.float16,
73
+ humming_dtypes.bfloat16: torch.bfloat16,
74
+ humming_dtypes.float32: torch.float32,
75
+ }
76
+
77
+
78
+def _group_shape(group_size: int, group_size_n: int = 0) -> GroupShape:
79
+ """
80
+ Map humming group sizes to QuantKey GroupShape.
81
+
82
+ group_size: elements per group along K (col); 0 means full dimension.
83
+ group_size_n: elements per group along N (row); 0 means 1 (per-row).
84
+
85
+ GroupShape convention: row = N dim, col = K dim.
86
+ """
87
+ if group_size == 0 and group_size_n == 0:
88
+ return GroupShape.PER_CHANNEL
89
+
90
+ row = group_size_n if group_size_n > 0 else 1
91
+ col = group_size if group_size > 0 else -1
92
+ return GroupShape(row=row, col=col)
93
+
94
+
95
+# ---- HummingWeightSchema (post-conversion) --------------------------------
96
+
97
+
98
+def _humming_weight_schema_to_quant_key(
99
+ schema: "HummingWeightSchema",
100
+) -> QuantKey:
101
+ from vllm.utils.humming import WeightScaleType
102
+
103
+ """Convert a HummingWeightSchema to a QuantKey."""
104
+ dtype = _HUMMING_TO_QUANT_DTYPE[schema.b_dtype]
105
+
106
+ if schema.bs_dtype is not None:
107
+ scale_dtype = _HUMMING_TO_SCALE_DTYPE[schema.bs_dtype]
108
+ else:
109
+ scale_dtype = torch.float32
110
+
111
+ group_shape = _group_shape(
112
+ schema.weight_scale_group_size,
113
+ schema.weight_scale_group_size_n,
114
+ )
115
+
116
+ scale = ScaleDesc(dtype=scale_dtype, static=True, group_shape=group_shape)
117
+
118
+ scale2 = None
119
+ if schema.weight_scale_type == WeightScaleType.GROUP_TENSOR:
120
+ scale2 = ScaleDesc(
121
+ dtype=torch.float32,
122
+ static=True,
123
+ group_shape=GroupShape.PER_TENSOR,
124
+ )
125
+
126
+ return QuantKey(
127
+ dtype=dtype,
128
+ scale=scale,
129
+ scale2=scale2,
130
+ symmetric=not schema.has_zero_point,
131
+ )
132
+
133
+
134
+# ---- Checkpoint-format weight schemas (pre-conversion) --------------------
135
+
136
+
137
+def _fp8_weight_schema_to_quant_key(schema: "Fp8WeightSchema") -> QuantKey:
138
+ if schema.weight_block_size is not None:
139
+ gs_n, gs_k = schema.weight_block_size
140
+ group_shape = GroupShape(row=gs_n, col=gs_k)
141
+ else:
142
+ group_shape = GroupShape.PER_CHANNEL
143
+
144
+ scale = ScaleDesc(dtype=torch.float32, static=True, group_shape=group_shape)
145
+ return QuantKey(dtype=FP8_DTYPE, scale=scale, symmetric=True)
146
+
147
+
148
+def _awq_weight_schema_to_quant_key(schema: "AWQWeightSchema") -> QuantKey:
149
+ group_shape = _group_shape(schema.group_size)
150
+ scale = ScaleDesc(
151
+ dtype=torch.float16,
152
+ static=True,
153
+ group_shape=group_shape,
154
+ )
155
+ return QuantKey(
156
+ dtype=INT4_DTYPE,
157
+ scale=scale,
158
+ symmetric=not schema.zero_point,
159
+ )
160
+
161
+
162
+def _gptq_weight_schema_to_quant_key(schema: "GPTQWeightSchema") -> QuantKey:
163
+ group_shape = _group_shape(schema.group_size)
164
+ scale = ScaleDesc(
165
+ dtype=torch.float16,
166
+ static=True,
167
+ group_shape=group_shape,
168
+ )
169
+ return QuantKey(dtype=INT4_DTYPE, scale=scale, symmetric=schema.sym)
170
+
171
+
172
+def _compressed_tensors_weight_schema_to_quant_key(
173
+ schema: "CompressedTensorsWeightSchema",
174
+) -> QuantKey:
175
+ # Determine dtype from format/type/num_bits
176
+ fmt = schema.format
177
+ if fmt in ("int-quantized", "float-quantized", "naive-quantized"):
178
+ dtype = INT8_DTYPE if schema.type == "int" else FP8_DTYPE
179
+ elif "nvfp4" in fmt or "mxfp4" in fmt:
180
+ dtype = FP4_DTYPE
181
+ else:
182
+ dtype = _HUMMING_TO_QUANT_DTYPE[
183
+ humming_dtypes.DataType.from_str(f"uint{schema.num_bits}")
184
+ ]
185
+
186
+ # Determine group shape from strategy
187
+ if schema.strategy in ("group", "tensor_group"):
188
+ group_shape = _group_shape(schema.group_size or 0)
189
+ elif schema.strategy == "block" and schema.block_structure is not None:
190
+ group_shape = GroupShape(
191
+ row=schema.block_structure[0],
192
+ col=schema.block_structure[1],
193
+ )
194
+ else:
195
+ group_shape = GroupShape.PER_CHANNEL
196
+
197
+ # Determine scale dtype
198
+ if "mxfp" in fmt:
199
+ scale_dtype = MXFP_SCALE_DTYPE
200
+ elif "nvfp4" in fmt:
201
+ scale_dtype = FP8_DTYPE
202
+ else:
203
+ scale_dtype = torch.float32
204
+
205
+ scale = ScaleDesc(dtype=scale_dtype, static=True, group_shape=group_shape)
206
+
207
+ scale2 = None
208
+ if "nvfp4" in fmt or schema.strategy == "tensor_group":
209
+ scale2 = ScaleDesc(
210
+ dtype=torch.float32,
211
+ static=True,
212
+ group_shape=GroupShape.PER_TENSOR,
213
+ )
214
+
215
+ return QuantKey(
216
+ dtype=dtype,
217
+ scale=scale,
218
+ scale2=scale2,
219
+ symmetric=schema.symmetric,
220
+ )
221
+
222
+
223
+# ---- Dispatch for any BaseWeightSchema ------------------------------------
224
+
225
+
226
+def weight_schema_to_quant_key(
227
+ schema: "BaseWeightSchema",
228
+) -> QuantKey:
229
+ from vllm.utils.humming import (
230
+ AWQWeightSchema,
231
+ BitnetWeightSchema,
232
+ CompressedTensorsWeightSchema,
233
+ Fp8WeightSchema,
234
+ GptOssMxfp4WeightSchema,
235
+ GPTQWeightSchema,
236
+ HummingWeightSchema,
237
+ ModeloptMxfp8WeightSchema,
238
+ ModeloptNvfp4WeightSchema,
239
+ Mxfp4WeightSchema,
240
+ )
241
+
242
+ """Convert any BaseWeightSchema to a QuantKey."""
243
+ if isinstance(schema, HummingWeightSchema):
244
+ return _humming_weight_schema_to_quant_key(schema)
245
+
246
+ # Schemas with fixed QuantKeys
247
+ if isinstance(schema, (Mxfp4WeightSchema, GptOssMxfp4WeightSchema)):
248
+ return QuantKey(
249
+ dtype=FP4_DTYPE,
250
+ scale=ScaleDesc(MXFP_SCALE_DTYPE, True, GroupShape(1, 32)),
251
+ )
252
+ if isinstance(schema, ModeloptMxfp8WeightSchema):
253
+ return QuantKey(
254
+ dtype=FP8_DTYPE,
255
+ scale=ScaleDesc(MXFP_SCALE_DTYPE, True, GroupShape(1, 32)),
256
+ )
257
+ if isinstance(schema, ModeloptNvfp4WeightSchema):
258
+ return QuantKey(
259
+ dtype=FP4_DTYPE,
260
+ scale=ScaleDesc(FP8_DTYPE, True, GroupShape(1, 16)),
261
+ scale2=ScaleDesc(torch.float32, True, GroupShape.PER_TENSOR),
262
+ )
263
+ if isinstance(schema, BitnetWeightSchema):
264
+ return QuantKey(
265
+ dtype=torch.uint8,
266
+ scale=ScaleDesc(torch.float32, True, GroupShape.PER_CHANNEL),
267
+ )
268
+
269
+ # Schemas requiring config inspection
270
+ if isinstance(schema, Fp8WeightSchema):
271
+ return _fp8_weight_schema_to_quant_key(schema)
272
+ if isinstance(schema, AWQWeightSchema):
273
+ return _awq_weight_schema_to_quant_key(schema)
274
+ if isinstance(schema, GPTQWeightSchema):
275
+ return _gptq_weight_schema_to_quant_key(schema)
276
+ if isinstance(schema, CompressedTensorsWeightSchema):
277
+ return _compressed_tensors_weight_schema_to_quant_key(schema)
278
+
279
+ raise TypeError(f"Unsupported weight schema type: {type(schema)}")
280
+
281
+
282
+# ---- HummingInputSchema (post-conversion) ----------------------------------
283
+
284
+
285
+def _humming_input_schema_to_quant_key(
286
+ schema: "HummingInputSchema",
287
+) -> QuantKey | None:
288
+ """Convert a HummingInputSchema to a QuantKey. Returns None if
289
+ the schema represents unquantized (bf16/fp16) inputs."""
290
+ if schema.a_dtype is None or schema.a_dtype.num_bits >= 16:
291
+ return None
292
+
293
+ dtype = _HUMMING_TO_QUANT_DTYPE[schema.a_dtype]
294
+
295
+ gs = schema.input_scale_group_size
296
+ group_shape = GroupShape(row=1, col=gs) if gs > 0 else GroupShape.PER_TOKEN
297
+
298
+ scale_dtype = MXFP_SCALE_DTYPE if gs > 0 else torch.float32
299
+
300
+ scale = ScaleDesc(dtype=scale_dtype, static=False, group_shape=group_shape)
301
+
302
+ return QuantKey(dtype=dtype, scale=scale, symmetric=True)
303
+
304
+
305
+# ---- Checkpoint-format input schemas (pre-conversion) ----------------------
306
+
307
+
308
+def _resolve_input_quant_key(
309
+ origin_a_dtype: "humming_dtypes.DataType",
310
+ group_size: int,
311
+) -> QuantKey | None:
312
+ from vllm.utils.humming import HummingInputSchema
313
+
314
+ """Resolve the actual activation QuantKey after platform fallback."""
315
+ a_dtype = HummingInputSchema().get_fallback_input_dtype(origin_a_dtype)
316
+ if a_dtype is None or a_dtype.num_bits >= 16:
317
+ return None
318
+
319
+ dtype = _HUMMING_TO_QUANT_DTYPE[a_dtype]
320
+ gs = group_size if a_dtype == humming_dtypes.float4e2m1 else 0
321
+ group_shape = GroupShape(row=1, col=gs) if gs > 0 else GroupShape.PER_TOKEN
322
+ scale_dtype = MXFP_SCALE_DTYPE if gs > 0 else torch.float32
323
+
324
+ scale = ScaleDesc(dtype=scale_dtype, static=False, group_shape=group_shape)
325
+ return QuantKey(dtype=dtype, scale=scale, symmetric=True)
326
+
327
+
328
+def _compressed_tensors_input_schema_to_quant_key(
329
+ schema: "CompressedTensorsInputSchema",
330
+) -> QuantKey | None:
331
+ type_bits_to_dtype = {
332
+ ("float", 8): humming_dtypes.float8e4m3,
333
+ ("float", 4): humming_dtypes.float4e2m1,
334
+ ("int", 8): humming_dtypes.int8,
335
+ ("int", 4): humming_dtypes.int4,
336
+ }
337
+ origin = type_bits_to_dtype.get((schema.type, schema.num_bits))
338
+ if origin is None:
339
+ return None
340
+ return _resolve_input_quant_key(origin, schema.group_size)
341
+
342
+
343
+# ---- Dispatch for any BaseInputSchema -------------------------------------
344
+
345
+
346
+def input_schema_to_quant_key(
347
+ schema: "BaseInputSchema",
348
+) -> QuantKey | None:
349
+ from vllm.utils.humming import (
350
+ CompressedTensorsInputSchema,
351
+ Fp8InputSchema,
352
+ HummingInputSchema,
353
+ ModeloptNvfp4InputSchema,
354
+ )
355
+
356
+ """Convert any BaseInputSchema to a QuantKey. Returns None if
357
+ the schema represents unquantized (bf16/fp16) inputs."""
358
+ if isinstance(schema, HummingInputSchema):
359
+ return _humming_input_schema_to_quant_key(schema)
360
+
361
+ if isinstance(schema, Fp8InputSchema):
362
+ return _resolve_input_quant_key(humming_dtypes.float8e4m3, 0)
363
+
364
+ if isinstance(schema, ModeloptNvfp4InputSchema):
365
+ return _resolve_input_quant_key(
366
+ humming_dtypes.float8e4m3,
367
+ schema.group_size,
368
+ )
369
+
370
+ if isinstance(schema, CompressedTensorsInputSchema):
371
+ return _compressed_tensors_input_schema_to_quant_key(schema)
372
+
373
+ raise TypeError(f"Unsupported input schema type: {type(schema)}")
374
375
376
def humming_is_layer_skipped(config: dict[str, Any], prefix: str):
@@ -24,8 +380,9 @@ def humming_is_layer_skipped(config: dict[str, Any], prefix: str):
380
keys = ["ignored_layers", "ignore", "modules_to_not_convert"]
381
ignored_layers: list[str] = []
382
for key in keys:
27
- ignored_layers = config.get(key, []) or []
28
- if not ignored_layers:
383
+ candidate = config.get(key, []) or []
384
+ if candidate:
385
+ ignored_layers = candidate
386
break
387
388
if any(module_name in prefix for module_name in ignored_layers):
@@ -79,6 +436,12 @@ def convert_linear_layer_to_humming_standard(
436
437
438
def prepare_humming_layer(layer: LinearBase, quant_config: dict):
439
+ from vllm.utils.humming import (
440
+ BaseWeightSchema,
441
+ HummingInputSchema,
442
+ HummingMethod,
443
+ )
444
+
445
weight_schema = BaseWeightSchema.from_config(quant_config)
446
input_schema = HummingInputSchema()
447
@@ -140,90 +503,59 @@ def prepare_humming_layer(layer: LinearBase, quant_config: dict):
503
layer.compute_config = json.dumps(compute_config)
504
505
143
-def prepare_humming_moe_layer(layer: RoutedExperts, quant_config: dict):
144
- weight_schema = BaseWeightSchema.from_config(quant_config)
145
- input_quant_config = envs.VLLM_HUMMING_INPUT_QUANT_CONFIG or {}
146
- if humming_is_layer_skipped(input_quant_config, layer.layer_name):
147
- input_schema = HummingInputSchema()
506
+def make_humming_moe_quant_config(
507
+ quant_dtype: torch.dtype | str | None,
508
+ weight_dtype: torch.dtype | str | None,
509
+ weight_group_shape: GroupShape | None = None,
510
+ w1_scale: torch.Tensor | None = None,
511
+ w2_scale: torch.Tensor | None = None,
512
+ w1_zp: torch.Tensor | None = None,
513
+ w2_zp: torch.Tensor | None = None,
514
+ w1_bias: torch.Tensor | None = None,
515
+ w2_bias: torch.Tensor | None = None,
516
+ w1_gscale: torch.Tensor | None = None,
517
+ w2_gscale: torch.Tensor | None = None,
518
+ gemm1_alpha: float | None = None,
519
+ gemm1_beta: float | None = None,
520
+ gemm1_clamp_limit: float | None = None,
521
+) -> FusedMoEQuantConfig:
522
+ if quant_dtype is None:
523
+ a_quant_desc = FusedMoEQuantDesc(dtype=None)
524
else:
149
- # TODO: read input_quant_config from quant_config
150
- input_schema = HummingInputSchema.from_config(input_quant_config)
151
-
152
- is_gated = layer.activation.is_gated
153
- shape_config = {
154
- "w13": (
155
- layer.moe_config.intermediate_size_per_partition * 2,
156
- layer.moe_config.hidden_dim,
157
- ),
158
- "w2": (
159
- layer.moe_config.hidden_dim,
160
- layer.moe_config.intermediate_size_per_partition * (1 if is_gated else 2),
161
- ),
162
- }
163
-
164
- layer.weight_schemas = {}
165
- layer.input_schemas = {}
166
-
167
- for sublayer_name in shape_config:
168
- # Step 1: convert weight to humming standard format
169
- tensors: dict[str, torch.Tensor] = dict(
170
- (key.removeprefix(sublayer_name + "_"), value)
171
- for key, value in layer.state_dict().items()
172
- if key.startswith(sublayer_name + "_")
173
- )
174
-
175
- shape_n, shape_k = shape_config[sublayer_name]
176
- shape_n_stacks = [shape_n]
177
- shape_k_stacks = [shape_k]
178
- if sublayer_name == "w13":
179
- shape_n_stacks = [shape_n // 2] * 2
180
-
181
- weight_schema_new, tensors = weight_schema.convert_humming(
182
- tensors=tensors,
183
- shape_n_stacks=shape_n_stacks,
184
- shape_k_stacks=shape_k_stacks,
185
- num_experts=layer.local_num_experts,
186
- param_dtype=layer.params_dtype,
187
- )
188
-
189
- layer.weight_schemas[sublayer_name] = weight_schema_new
190
- layer.input_schemas[sublayer_name] = input_schema
191
-
192
- for name, _ in list(layer.named_parameters()):
193
- if not name.startswith(sublayer_name + "_"):
194
- continue
195
- delattr(layer, name)
196
-
197
- for name, tensor in tensors.items():
198
- name = f"{sublayer_name}_{name}"
199
- param = torch.nn.Parameter(tensor, requires_grad=False)
200
- setattr(layer, name, param)
525
+ shape = GroupShape(row=1, col=-1)
526
+ a_quant_desc = FusedMoEQuantDesc(dtype=quant_dtype, shape=shape)
527
202
- # Step 2: transform weight (humming standard format) for forwarding
203
- HummingMethod.prepare_layer_meta(
204
- layer=layer,
205
- shape_n=shape_n,
206
- shape_k=shape_k,
207
- pad_n_to_multiple=256,
208
- pad_k_to_multiple=128,
209
- input_schema=input_schema,
210
- weight_schema=weight_schema_new,
211
- has_bias=layer.moe_config.has_bias,
212
- num_experts=layer.num_experts,
213
- torch_dtype=layer.params_dtype,
214
- sublayer_name=sublayer_name,
215
- )
528
+ w1_quant_desc = FusedMoEQuantDesc(
529
+ dtype=weight_dtype,
530
+ shape=weight_group_shape,
531
+ scale=w1_scale,
532
+ alpha_or_gscale=w1_gscale,
533
+ zp=w1_zp,
534
+ bias=w1_bias,
535
+ )
536
217
- HummingMethod.transform_humming_layer(layer, sublayer_name=sublayer_name)
537
+ w2_quant_desc = FusedMoEQuantDesc(
538
+ dtype=weight_dtype,
539
+ shape=weight_group_shape,
540
+ scale=w2_scale,
541
+ alpha_or_gscale=w2_gscale,
542
+ zp=w2_zp,
543
+ bias=w2_bias,
544
+ )
545
219
- if not hasattr(layer, "locks"):
220
- device = layer.w13_weight.device
221
- locks = torch.zeros(1024, dtype=torch.int32, device=device)
222
- layer.register_buffer("locks", locks)
546
+ return FusedMoEQuantConfig(
547
+ _a1=a_quant_desc,
548
+ _a2=a_quant_desc,
549
+ _w1=w1_quant_desc,
550
+ _w2=w2_quant_desc,
551
+ gemm1_alpha=gemm1_alpha,
552
+ gemm1_beta=gemm1_beta,
553
+ gemm1_clamp_limit=gemm1_clamp_limit,
554
+ )
555
556
557
def get_humming_moe_quant_config(
226
- layer: RoutedExperts,
558
+ layer: "RoutedExperts",
559
gemm1_alpha: float | None = None,
560
gemm1_beta: float | None = None,
561
gemm1_clamp_limit: float | None = None,
@@ -231,12 +563,10 @@ def get_humming_moe_quant_config(
563
input_schema = layer.input_schemas["w13"]
564
weight_schema = layer.weight_schemas["w13"]
565
234
- a_dtype = input_schema.a_dtype
235
- if a_dtype is None or a_dtype.num_bits == 16:
236
- a_quant_desc = FusedMoEQuantDesc(dtype=None)
566
+ if input_schema.a_dtype is None or input_schema.a_dtype.num_bits == 16:
567
+ q_dtype = None
568
else:
238
- shape = GroupShape(row=1, col=-1)
239
- a_quant_desc = FusedMoEQuantDesc(dtype=str(a_dtype), shape=shape)
569
+ q_dtype = str(input_schema.a_dtype)
570
571
weight_scale_group_size = weight_schema.weight_scale_group_size
572
weight_scale_group_size_n = weight_schema.weight_scale_group_size_n
@@ -251,30 +581,437 @@ def get_humming_moe_quant_config(
581
else:
582
weight_group_shape = GroupShape(row=weight_scale_group_size, col=1)
583
254
- w1_quant_desc = FusedMoEQuantDesc(
255
- dtype=str(weight_schema.b_dtype),
256
- shape=weight_group_shape,
257
- scale=getattr(layer, "w13_weight_scale", None),
258
- alpha_or_gscale=getattr(layer, "w13_global_scale", None),
259
- zp=getattr(layer, "w13_zero_point", None),
260
- bias=getattr(layer, "w13_bias", None),
584
+ return make_humming_moe_quant_config(
585
+ quant_dtype=q_dtype,
586
+ weight_dtype=str(weight_schema.b_dtype),
587
+ weight_group_shape=weight_group_shape,
588
+ w1_scale=getattr(layer, "w13_weight_scale", None),
589
+ w1_gscale=getattr(layer, "w13_global_scale", None),
590
+ w1_zp=getattr(layer, "w13_zero_point", None),
591
+ w1_bias=getattr(layer, "w13_bias", None),
592
+ w2_scale=getattr(layer, "w2_weight_scale", None),
593
+ w2_gscale=getattr(layer, "w2_global_scale", None),
594
+ w2_zp=getattr(layer, "w2_zero_point", None),
595
+ w2_bias=getattr(layer, "w2_bias", None),
596
)
597
263
- w2_quant_desc = FusedMoEQuantDesc(
264
- dtype=str(weight_schema.b_dtype),
265
- shape=weight_group_shape,
266
- scale=getattr(layer, "w2_weight_scale", None),
267
- alpha_or_gscale=getattr(layer, "w2_global_scale", None),
268
- zp=getattr(layer, "w2_zero_point", None),
269
- bias=getattr(layer, "w2_bias", None),
598
+
599
+def select_humming_moe_experts(
600
+ config: FusedMoEConfig,
601
+ weight_key: QuantKey | None,
602
+ activation_key: QuantKey | None,
603
+) -> type[mk.FusedMoEExperts] | None:
604
+ """
605
+ Select the primary Humming MoE Experts class
606
+ Note: Shape-specific fallbacks may still occur at runtime.
607
+ """
608
+
609
+ if not has_humming():
610
+ return None
611
+
612
+ # NOTE: the kernels are selected in the following order.
613
+ AVAILABLE_EXPERTS: list[type[mk.FusedMoEExperts]] = [
614
+ BatchedHummingGroupedExperts,
615
+ HummingGroupedExperts,
616
+ HummingIndexedExperts,
617
+ ]
618
+
619
+ # NOTE(rob): We need to peak into the P/F selection to determine
620
+ # if we are using the batched or standard expert format, which
621
+ # if not ideal. Once we unify TP + DP/EP, we can select P/F first.
622
+ activation_format = (
623
+ mk.FusedMoEActivationFormat.BatchedExperts
624
+ if config.moe_parallel_config.use_batched_activation_format
625
+ else mk.FusedMoEActivationFormat.Standard
626
)
627
272
- return FusedMoEQuantConfig(
273
- _a1=a_quant_desc,
274
- _a2=a_quant_desc,
275
- _w1=w1_quant_desc,
276
- _w2=w2_quant_desc,
277
- gemm1_alpha=gemm1_alpha,
278
- gemm1_beta=gemm1_beta,
279
- gemm1_clamp_limit=gemm1_clamp_limit,
628
+ def _make_log_backend(experts_cls: type[mk.FusedMoEExperts]):
629
+ return f"Using {experts_cls.__name__} Humming MoE backend."
630
+
631
+ def _make_log_unsupported(
632
+ experts_cls: type[mk.FusedMoEExperts], reason: str | None
633
+ ) -> str:
634
+ if reason:
635
+ return (
636
+ f"Humming MoE experts {experts_cls.__name__} does not support the "
637
+ f"deployment configuration since {reason}."
638
+ )
639
+ else:
640
+ return (
641
+ f"Humming MoE experts '{experts_cls.__name__}' does not support the "
642
+ "deployment configuration."
643
+ )
644
+
645
+ for k_cls in AVAILABLE_EXPERTS:
646
+ supported, reason = k_cls.is_supported_config(
647
+ k_cls,
648
+ config,
649
+ weight_key,
650
+ activation_key,
651
+ activation_format,
652
+ )
653
+ if supported:
654
+ logger.info_once(_make_log_backend(k_cls))
655
+ return k_cls
656
+ else:
657
+ logger.debug_once(_make_log_unsupported(k_cls, reason))
658
+
659
+ return None
660
+
661
+
662
+def make_humming_moe_kernel(
663
+ moe_quant_config: FusedMoEQuantConfig,
664
+ moe_config: FusedMoEConfig,
665
+ experts_cls: type[mk.FusedMoEExperts],
666
+ layer: "RoutedExperts",
667
+ routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
668
+) -> mk.FusedMoEKernel:
669
+ # Create Prepare/Finalize.
670
+ prepare_finalize = maybe_make_prepare_finalize(
671
+ moe=moe_config,
672
+ quant_config=moe_quant_config,
673
+ routing_tables=routing_tables,
674
+ allow_new_interface=True,
675
+ use_monolithic=issubclass(experts_cls, mk.FusedMoEExpertsMonolithic),
676
+ )
677
+ assert prepare_finalize is not None
678
+
679
+ logger.info_once("Using %s", prepare_finalize.__class__.__name__)
680
+
681
+ extra_args: dict[str, Any] = {"layer": layer}
682
+
683
+ # Create Experts.
684
+ if prepare_finalize.activation_format == mk.FusedMoEActivationFormat.BatchedExperts:
685
+ max_num_tokens = prepare_finalize.max_num_tokens_per_rank()
686
+ assert max_num_tokens is not None
687
+ experts = experts_cls(
688
+ moe_config=moe_config,
689
+ quant_config=moe_quant_config,
690
+ max_num_tokens=max_num_tokens,
691
+ num_dispatchers=prepare_finalize.num_dispatchers(),
692
+ **extra_args,
693
+ )
694
+ else:
695
+ experts = experts_cls(
696
+ moe_config=moe_config,
697
+ quant_config=moe_quant_config,
698
+ **extra_args,
699
+ )
700
+
701
+ kernel = mk.FusedMoEKernel(
702
+ prepare_finalize,
703
+ experts,
704
+ )
705
+
706
+ return kernel
707
+
708
+
709
+def _extract_sublayer_tensors(
710
+ layer: "RoutedExperts",
711
+ sublayer_name: str,
712
+) -> dict[str, torch.Tensor]:
713
+ """Extract tensors for a specific sublayer from the layer's state dict."""
714
+ return dict(
715
+ (key.removeprefix(sublayer_name + "_"), value)
716
+ for key, value in layer.state_dict().items()
717
+ if key.startswith(sublayer_name + "_")
718
+ )
719
+
720
+
721
+def _replace_layer_parameters(
722
+ layer: "RoutedExperts",
723
+ sublayer_name: str,
724
+ tensors: dict[str, torch.Tensor],
725
+ preserve_bias: bool = False,
726
+) -> None:
727
+ """
728
+ Replace layer parameters for a sublayer with new tensors.
729
+
730
+ Args:
731
+ layer: The RoutedExperts layer
732
+ sublayer_name: Name of the sublayer (e.g., "w13", "w2")
733
+ tensors: Dict of parameter name to tensor
734
+ preserve_bias: If True, don't delete bias parameters
735
+ """
736
+ # Delete old parameters
737
+ for name, _ in list(layer.named_parameters()):
738
+ if not name.startswith(sublayer_name + "_"):
739
+ continue
740
+ if preserve_bias and name == sublayer_name + "_bias":
741
+ continue
742
+ delattr(layer, name)
743
+
744
+ # Set new parameters
745
+ for name, tensor in tensors.items():
746
+ param_name = f"{sublayer_name}_{name}"
747
+ param = torch.nn.Parameter(tensor, requires_grad=False)
748
+ setattr(layer, param_name, param)
749
+
750
+
751
+def _convert_sublayer_to_humming(
752
+ layer: "RoutedExperts",
753
+ sublayer_name: str,
754
+ shape_n: int,
755
+ shape_k: int,
756
+ weight_schema: Any,
757
+ input_schema: Any,
758
+ num_experts: int,
759
+ param_dtype: torch.dtype,
760
+) -> tuple[Any, Any]:
761
+ """
762
+ Convert a sublayer's weights from checkpoint format to Humming format.
763
+
764
+ Returns:
765
+ Tuple of (converted_weight_schema, converted_input_schema)
766
+ """
767
+ from humming.schema import HummingWeightSchema
768
+
769
+ if isinstance(weight_schema, HummingWeightSchema):
770
+ # Already in Humming format
771
+ return weight_schema, input_schema
772
+
773
+ tensors = _extract_sublayer_tensors(layer, sublayer_name)
774
+
775
+ shape_k_stacks = [shape_k]
776
+ shape_n_stacks = [shape_n]
777
+ if sublayer_name == "w13":
778
+ shape_n_stacks = [shape_n // 2] * 2
779
+
780
+ converted_weight_schema, converted_tensors = weight_schema.convert_humming(
781
+ tensors=tensors,
782
+ shape_n_stacks=shape_n_stacks,
783
+ shape_k_stacks=shape_k_stacks,
784
+ param_dtype=param_dtype,
785
+ num_experts=num_experts,
786
+ )
787
+
788
+ converted_input_schema, _ = input_schema.convert_humming(
789
+ tensors=converted_tensors,
790
+ shape_n_stacks=shape_n_stacks,
791
+ shape_k_stacks=shape_k_stacks,
792
+ param_dtype=param_dtype,
793
+ num_experts=num_experts,
794
+ )
795
+
796
+ _replace_layer_parameters(layer, sublayer_name, converted_tensors)
797
+
798
+ return converted_weight_schema, converted_input_schema
799
+
800
+
801
+def _prepare_and_transform_sublayer(
802
+ layer: "RoutedExperts",
803
+ sublayer_name: str,
804
+ shape_n: int,
805
+ shape_k: int,
806
+ weight_schema: Any,
807
+ input_schema: Any,
808
+ has_bias: bool,
809
+ num_experts: int,
810
+ param_dtype: torch.dtype,
811
+) -> None:
812
+ """
813
+ Prepare layer metadata and transform weights for a sublayer.
814
+
815
+ This calls Humming's prepare_layer_meta and transform_humming_layer.
816
+ """
817
+ from humming.layer import HummingMethod
818
+
819
+ HummingMethod.prepare_layer_meta(
820
+ layer=layer,
821
+ shape_n=shape_n,
822
+ shape_k=shape_k,
823
+ pad_n_to_multiple=256,
824
+ pad_k_to_multiple=128,
825
+ input_schema=input_schema,
826
+ weight_schema=weight_schema,
827
+ has_bias=has_bias,
828
+ num_experts=num_experts,
829
+ torch_dtype=param_dtype,
830
+ sublayer_name=sublayer_name,
831
+ )
832
+
833
+ HummingMethod.transform_humming_layer(layer, sublayer_name=sublayer_name)
834
+
835
+
836
+def _process_single_sublayer(
837
+ layer: "RoutedExperts",
838
+ sublayer_name: str,
839
+ shape_n: int,
840
+ shape_k: int,
841
+ weight_schema: Any,
842
+ input_schema: Any,
843
+ has_bias: bool,
844
+ num_experts: int,
845
+ param_dtype: torch.dtype,
846
+ force_weight_schema: Any | None = None,
847
+) -> tuple[Any, Any]:
848
+ """
849
+ Process a single sublayer: convert, optionally requant, prepare, and transform.
850
+
851
+ This combines the common logic from convert_to_humming_moe_kernel_format
852
+ for processing a single sublayer.
853
+
854
+ Args:
855
+ layer: The RoutedExperts layer
856
+ sublayer_name: Name of the sublayer (e.g., "w13", "w2")
857
+ shape_n: Output dimension size
858
+ shape_k: Input dimension size
859
+ weight_schema: Initial weight quantization schema
860
+ input_schema: Initial input quantization schema
861
+ has_bias: Whether the layer has bias terms
862
+ num_experts: Number of experts
863
+ param_dtype: Parameter data type
864
+ force_weight_schema: Optional schema to force requantization to
865
+
866
+ Returns:
867
+ Tuple of (final_weight_schema, final_input_schema)
868
+ """
869
+ from humming.schema import HummingWeightSchema
870
+
871
+ # Step 1: Convert from checkpoint format to humming format if needed
872
+ current_weight_schema, current_input_schema = _convert_sublayer_to_humming(
873
+ layer=layer,
874
+ sublayer_name=sublayer_name,
875
+ shape_n=shape_n,
876
+ shape_k=shape_k,
877
+ weight_schema=weight_schema,
878
+ input_schema=input_schema,
879
+ num_experts=num_experts,
880
+ param_dtype=param_dtype,
881
+ )
882
+
883
+ # Step 2: Force requant if needed
884
+ assert isinstance(current_weight_schema, HummingWeightSchema)
885
+ if force_weight_schema is not None and current_weight_schema != force_weight_schema:
886
+ tensors = _extract_sublayer_tensors(layer, sublayer_name)
887
+
888
+ tensors = current_weight_schema.requant_tensors(
889
+ tensors=tensors,
890
+ target_weight_schema=force_weight_schema,
891
+ param_dtype=param_dtype,
892
+ )
893
+
894
+ current_weight_schema = force_weight_schema
895
+ _replace_layer_parameters(layer, sublayer_name, tensors, preserve_bias=True)
896
+ del tensors
897
+
898
+ # Step 3: Prepare layer metadata and transform weights
899
+ _prepare_and_transform_sublayer(
900
+ layer=layer,
901
+ sublayer_name=sublayer_name,
902
+ shape_n=shape_n,
903
+ shape_k=shape_k,
904
+ weight_schema=current_weight_schema,
905
+ input_schema=current_input_schema,
906
+ has_bias=has_bias,
907
+ num_experts=num_experts,
908
+ param_dtype=param_dtype,
909
)
910
+
911
+ return current_weight_schema, current_input_schema
912
+
913
+
914
+def convert_to_humming_moe_kernel_format(
915
+ layer: "RoutedExperts",
916
+ quant_config: dict | None = None,
917
+ sublayer_configs: dict[str, Any] | None = None,
918
+ weight_schema: Any | None = None,
919
+ input_schema: Any | None = None,
920
+ force_weight_schema: Any | None = None,
921
+) -> None:
922
+ """
923
+ Convert MoE weights from checkpoint format to Humming kernel format.
924
+
925
+ This function processes weights for each sublayer (w13, w2) by:
926
+ 1. Converting from checkpoint format to humming format if needed
927
+ 2. Force requanting if a different quantization schema is specified
928
+ 3. Preparing layer metadata for the Humming kernel
929
+ 4. Transforming weights for inference
930
+
931
+ Args:
932
+ layer: The RoutedExperts layer containing weights to process
933
+ quant_config: Optional quantization config dict. Required if weight_schema
934
+ or input_schema are None. Used to build schemas via
935
+ BaseWeightSchema.from_config().
936
+ sublayer_configs: Optional configuration dict for each sublayer (w13, w2).
937
+ Each config must have "shape_n" and "shape_k" keys.
938
+ If None, configs are built from layer.moe_config properties.
939
+ weight_schema: Optional initial weight quantization schema.
940
+ If None, built from quant_config.
941
+ input_schema: Optional initial input quantization schema.
942
+ If None, built from quant_config or env vars.
943
+ force_weight_schema: Optional schema to force requantization to
944
+
945
+ Side effects:
946
+ - Modifies layer parameters in place
947
+ - Sets layer.weight_schemas and layer.input_schemas
948
+ """
949
+
950
+ # Build schemas from quant_config if not provided
951
+ has_bias = layer.moe_config.has_bias
952
+ num_experts = layer.moe_config.num_local_experts
953
+ param_dtype = layer.params_dtype
954
+
955
+ if weight_schema is None or input_schema is None:
956
+ if quant_config is None:
957
+ raise ValueError(
958
+ "Must provide either weight_schema/input_schema or quant_config"
959
+ )
960
+
961
+ from humming.layer import HummingInputSchema
962
+ from humming.schema import BaseWeightSchema
963
+
964
+ from vllm.model_executor.layers.quantization.utils.humming_utils import (
965
+ humming_is_layer_skipped,
966
+ )
967
+
968
+ if weight_schema is None:
969
+ weight_schema = BaseWeightSchema.from_config(quant_config)
970
+
971
+ if input_schema is None:
972
+ input_quant_config = envs.VLLM_HUMMING_INPUT_QUANT_CONFIG or {}
973
+ if humming_is_layer_skipped(input_quant_config, layer.layer_name):
974
+ input_schema = HummingInputSchema()
975
+ else:
976
+ # TODO: read input_quant_config from quant_config
977
+ input_schema = HummingInputSchema.from_config(input_quant_config)
978
+
979
+ # Build sublayer configs from layer properties if not provided
980
+ if sublayer_configs is None:
981
+ is_gated = layer.moe_config.activation.is_gated
982
+ sublayer_configs = {
983
+ "w13": {
984
+ "shape_n": layer.moe_config.intermediate_size_per_partition * 2,
985
+ "shape_k": layer.moe_config.hidden_dim,
986
+ },
987
+ "w2": {
988
+ "shape_n": layer.moe_config.hidden_dim,
989
+ "shape_k": layer.moe_config.intermediate_size_per_partition
990
+ * (1 if is_gated else 2),
991
+ },
992
+ }
993
+
994
+ layer.weight_schemas = {}
995
+ layer.input_schemas = {}
996
+
997
+ for sublayer_name, configs in sublayer_configs.items():
998
+ final_weight_schema, final_input_schema = _process_single_sublayer(
999
+ layer=layer,
1000
+ sublayer_name=sublayer_name,
1001
+ shape_n=configs["shape_n"],
1002
+ shape_k=configs["shape_k"],
1003
+ weight_schema=weight_schema,
1004
+ input_schema=input_schema,
1005
+ has_bias=has_bias,
1006
+ num_experts=num_experts,
1007
+ param_dtype=param_dtype,
1008
+ force_weight_schema=force_weight_schema,
1009
+ )
1010
+
1011
+ layer.weight_schemas[sublayer_name] = final_weight_schema
1012
+ layer.input_schemas[sublayer_name] = final_input_schema
1013
+
1014
+ if not hasattr(layer, "locks"):
1015
+ device = layer.w13_weight.device
1016
+ locks = torch.zeros(1024, dtype=torch.int32, device=device)
1017
+ layer.register_buffer("locks", locks)
vllm/utils/humming.py
+13
@@ -15,6 +15,7 @@ _EXPORTS: dict[str, str] = {
15
"dtypes": "humming.dtypes",
16
"DataType": "humming.dtypes:DataType",
17
"GemmType": "humming.config:GemmType",
18
+ "WeightScaleType": "humming.config:WeightScaleType",
19
"HummingMethod": "humming.layer:HummingMethod",
20
"HummingLayerMeta": "humming.layer:HummingLayerMeta",
21
"BaseInputSchema": "humming.schema:BaseInputSchema",
@@ -22,6 +23,18 @@ _EXPORTS: dict[str, str] = {
23
"HummingInputSchema": "humming.schema:HummingInputSchema",
24
"HummingWeightSchema": "humming.schema:HummingWeightSchema",
25
"quantize_weight": "humming.utils.weight:quantize_weight",
26
+ "AWQWeightSchema": "humming.schema:AWQWeightSchema",
27
+ "BitnetWeightSchema": "humming.schema:BitnetWeightSchema",
28
+ "ModeloptMxfp8WeightSchema": "humming.schema.modelopt:ModeloptMxfp8WeightSchema",
29
+ "ModeloptNvfp4InputSchema": "humming.schema.modelopt:ModeloptNvfp4InputSchema",
30
+ "ModeloptNvfp4WeightSchema": "humming.schema.modelopt:ModeloptNvfp4WeightSchema",
31
+ "CompressedTensorsInputSchema": "humming.schema:CompressedTensorsInputSchema",
32
+ "CompressedTensorsWeightSchema": "humming.schema:CompressedTensorsWeightSchema",
33
+ "Fp8InputSchema": "humming.schema:Fp8InputSchema",
34
+ "Fp8WeightSchema": "humming.schema.fp8:Fp8WeightSchema",
35
+ "Mxfp4WeightSchema": "humming.schema:Mxfp4WeightSchema",
36
+ "GptOssMxfp4WeightSchema": "humming.schema:GptOssMxfp4WeightSchema",
37
+ "GPTQWeightSchema": "humming.schema:GPTQWeightSchema",
38
}
39
40