@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / f11e3c516b
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 05:30 UTC

[Kernels] Support blocked fp8 quantization for compressed tensors MoE (#25219)

Signed-off-by: Bill Nell <bnell@redhat.com> Co-authored-by: Michael Goin <mgoin64@gmail.com>

bnellnm committed Sep 23, 2025 at 12:11 UTC f11e3c516be3d88733ea4b0c79f47e33cc319197
4 files changed +112 -29
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe.py
+96 -13
@@ -13,6 +13,7 @@ from compressed_tensors.quantization import (ActivationOrdering,
13 import vllm.envs as envs
14 import vllm.model_executor.layers.fused_moe.modular_kernel as mk
15 from vllm import _custom_ops as ops
16 +from vllm.distributed import get_tensor_model_parallel_world_size
17 from vllm.logger import init_logger
18 from vllm.model_executor.layers.fused_moe import (
19 FusedMoE, FusedMoEActivationFormat, FusedMoEConfig, FusedMoEMethodBase,
@@ -31,6 +32,9 @@ from vllm.model_executor.layers.quantization.utils import replace_parameter
32 from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
33 build_flashinfer_fp4_cutlass_moe_prepare_finalize, reorder_w1w3_to_w3w1,
34 select_nvfp4_gemm_impl)
35 +from vllm.model_executor.layers.quantization.utils.fp8_utils import (
36 + expert_weight_is_col_major, get_col_major_tma_aligned_tensor,
37 + requant_weight_ue8m0_inplace)
38 from vllm.model_executor.layers.quantization.utils.marlin_utils import (
39 check_moe_marlin_supports_layer, marlin_make_workspace_new,
40 marlin_moe_permute_scales)
@@ -45,6 +49,7 @@ from vllm.model_executor.layers.quantization.utils.w8a8_utils import (
49 from vllm.model_executor.utils import set_weight_attrs
50 from vllm.platforms import current_platform
51 from vllm.scalar_type import scalar_types
52 +from vllm.utils.deep_gemm import is_deep_gemm_e8m0_used
53
54 logger = init_logger(__name__)
55
@@ -505,10 +510,12 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
510 self.weight_quant.strategy == QuantizationStrategy.CHANNEL
511 and self.input_quant.strategy == QuantizationStrategy.TOKEN)
512 if not (per_tensor or per_channel):
508 - raise ValueError(
509 - "For FP8 Fused MoE layers, we require per tensor "
510 - "or channelwise, dynamic per token quantization. Found "
511 - f"{self.weight_quant}, {self.input_quant}")
513 + assert self.weight_quant.strategy == QuantizationStrategy.BLOCK
514 + self.weight_block_size = self.weight_quant.block_structure
515 + assert self.weight_quant.dynamic is not None
516 + else:
517 + self.weight_block_size = None
518 + self.block_quant = self.weight_block_size is not None
519
520 self.static_input_scales = not self.input_quant.dynamic
521 if self.static_input_scales and per_channel:
@@ -519,7 +526,8 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
526 # For GPUs that lack FP8 hardware support, we can leverage the Marlin
527 # kernel for fast weight-only FP8 quantization
528 self.use_marlin = (not current_platform.has_device_capability(89)
522 - or envs.VLLM_TEST_FORCE_FP8_MARLIN)
529 + or envs.VLLM_TEST_FORCE_FP8_MARLIN
530 + and not self.block_quant)
531 # Disable marlin for rocm
532 if current_platform.is_rocm():
533 self.use_marlin = False
@@ -531,8 +539,9 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
539 # cutlass path
540 self.is_fp8_w8a8_sm100 = quant_config._is_fp8_w8a8_sm100(
541 self.weight_quant, self.input_quant)
534 - self.use_cutlass = (quant_config._is_fp8_w8a8_sm90(
535 - self.weight_quant, self.input_quant) or self.is_fp8_w8a8_sm100)
542 + self.use_cutlass = not self.block_quant and (
543 + quant_config._is_fp8_w8a8_sm90(self.weight_quant, self.input_quant)
544 + or self.is_fp8_w8a8_sm100)
545 self.disable_expert_map = False
546
547 def create_weights(self, layer: torch.nn.Module, num_experts: int,
@@ -547,6 +556,31 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
556
557 params_dtype = torch.float8_e4m3fn
558
559 + if self.block_quant:
560 + assert self.weight_block_size is not None
561 + layer.weight_block_size = self.weight_block_size
562 + tp_size = get_tensor_model_parallel_world_size()
563 + block_n, block_k = (
564 + self.weight_block_size[0],
565 + self.weight_block_size[1],
566 + )
567 + # NOTE: To ensure proper alignment of the block-wise quantization
568 + # scales, the output_size of the weights for both the gate and up
569 + # layers must be divisible by block_n.
570 + # Required by column parallel or enabling merged weights
571 + if intermediate_size_per_partition % block_n != 0:
572 + raise ValueError(
573 + f"The output_size of gate's and up's weight = "
574 + f"{intermediate_size_per_partition} is not divisible by "
575 + f"weight quantization block_n = {block_n}.")
576 + if (tp_size > 1
577 + and intermediate_size_per_partition % block_k != 0):
578 + # Required by row parallel
579 + raise ValueError(
580 + f"The input_size of down's weight = "
581 + f"{intermediate_size_per_partition} is not divisible by "
582 + f"weight quantization block_k = {block_k}.")
583 +
584 # WEIGHTS
585 w13_weight = torch.nn.Parameter(torch.empty(
586 num_experts,
@@ -602,6 +636,27 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
636 set_weight_attrs(w13_weight_scale, extra_weight_attrs)
637 set_weight_attrs(w2_weight_scale, extra_weight_attrs)
638
639 + elif self.weight_quant.strategy == QuantizationStrategy.BLOCK:
640 + w13_weight_scale = torch.nn.Parameter(torch.ones(
641 + num_experts,
642 + 2 *
643 + ((intermediate_size_per_partition + block_n - 1) // block_n),
644 + (hidden_size + block_k - 1) // block_k,
645 + dtype=torch.float32),
646 + requires_grad=False)
647 + layer.register_parameter("w13_weight_scale", w13_weight_scale)
648 + w2_weight_scale = torch.nn.Parameter(torch.ones(
649 + num_experts, (hidden_size + block_n - 1) // block_n,
650 + (intermediate_size_per_partition + block_k - 1) // block_k,
651 + dtype=torch.float32),
652 + requires_grad=False)
653 + layer.register_parameter("w2_weight_scale", w2_weight_scale)
654 + # Add PER-CHANNEL quantization for FusedMoE.weight_loader.
655 + extra_weight_attrs.update(
656 + {"quant_method": FusedMoeWeightScaleSupported.BLOCK.value})
657 + set_weight_attrs(w13_weight_scale, extra_weight_attrs)
658 + set_weight_attrs(w2_weight_scale, extra_weight_attrs)
659 +
660 # INPUT_SCALES
661 if self.static_input_scales:
662 w13_input_scale = torch.nn.Parameter(torch.ones(
@@ -706,6 +761,7 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
761 del layer.w2_input_scale
762
763 if self.use_cutlass:
764 + assert self.weight_quant.strategy != QuantizationStrategy.BLOCK
765 device = layer.w13_weight.device
766 # ab_strides1 and c_strides2 are the same
767 self.ab_strides1_c_strides2 = torch.full(
@@ -724,6 +780,29 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
780 device=device,
781 dtype=torch.int64)
782
783 + if is_deep_gemm_e8m0_used() and self.block_quant:
784 + assert layer.weight_block_size is not None
785 + # Re-quantise the expert weights so their scales are UE8M0.
786 + block_sz = tuple(layer.weight_block_size)
787 + requant_weight_ue8m0_inplace(
788 + layer.w13_weight.data,
789 + layer.w13_weight_scale.data,
790 + block_sz,
791 + )
792 + requant_weight_ue8m0_inplace(
793 + layer.w2_weight.data,
794 + layer.w2_weight_scale.data,
795 + block_sz,
796 + )
797 +
798 + # Ensure column-major TMA alignment expected by DeepGEMM.
799 + if expert_weight_is_col_major(layer.w13_weight_scale):
800 + layer.w13_weight_scale = get_col_major_tma_aligned_tensor(
801 + layer.w13_weight_scale)
802 + if expert_weight_is_col_major(layer.w2_weight_scale):
803 + layer.w2_weight_scale = get_col_major_tma_aligned_tensor(
804 + layer.w2_weight_scale)
805 +
806 def maybe_make_prepare_finalize(
807 self) -> Optional[mk.FusedMoEPrepareAndFinalize]:
808 if self.use_marlin or self.rocm_aiter_moe_enabled:
@@ -777,9 +856,10 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
856 return experts
857
858 # triton path
780 - from vllm.model_executor.layers.fused_moe import TritonExperts
781 - from vllm.model_executor.layers.fused_moe.fused_batched_moe import (
782 - BatchedTritonExperts)
859 + from vllm.model_executor.layers.fused_moe.batched_triton_or_deep_gemm_moe import ( # noqa: E501
860 + BatchedTritonOrDeepGemmExperts)
861 + from vllm.model_executor.layers.fused_moe.triton_deep_gemm_moe import (
862 + TritonOrDeepGemmExperts)
863
864 assert not self.rocm_aiter_moe_enabled and not self.use_marlin
865
@@ -790,14 +870,16 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
870 assert max_num_tokens_per_rank is not None
871
872 logger.debug("BatchedTritonExperts(%s)", self.__class__.__name__)
793 - return BatchedTritonExperts(
873 + return BatchedTritonOrDeepGemmExperts(
874 max_num_tokens=max_num_tokens_per_rank,
875 num_dispatchers=prepare_finalize.num_dispatchers(),
876 quant_config=self.moe_quant_config,
877 )
878 else:
799 - logger.debug("TritonExperts(%s)", self.__class__.__name__)
800 - return TritonExperts(self.moe_quant_config)
879 + logger.debug("TritonOrDeepGemmExperts(%s)",
880 + self.__class__.__name__)
881 + return TritonOrDeepGemmExperts(self.moe_quant_config,
882 + allow_deep_gemm=True)
883
884 def get_fused_moe_quant_config(
885 self, layer: torch.nn.Module) -> Optional[FusedMoEQuantConfig]:
@@ -816,6 +898,7 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
898 a2_scale=layer.w2_input_scale,
899 per_act_token_quant=per_act_token,
900 per_out_ch_quant=per_channel_quant,
901 + block_shape=layer.weight_block_size,
902 )
903
904 def apply(
vllm/model_executor/layers/quantization/fp8.py
+8 -14
@@ -33,10 +33,10 @@ from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
33 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
34 apply_fp8_block_linear, check_aiter_fp8_linear_support,
35 create_fp8_input_scale, create_fp8_scale_parameter,
36 - create_fp8_weight_parameter, get_col_major_tma_aligned_tensor,
37 - maybe_post_process_fp8_weight_block, process_fp8_weight_block_strategy,
38 - process_fp8_weight_tensor_strategy, requant_weight_ue8m0_inplace,
39 - validate_fp8_block_shape)
36 + create_fp8_weight_parameter, expert_weight_is_col_major,
37 + get_col_major_tma_aligned_tensor, maybe_post_process_fp8_weight_block,
38 + process_fp8_weight_block_strategy, process_fp8_weight_tensor_strategy,
39 + requant_weight_ue8m0_inplace, validate_fp8_block_shape)
40 from vllm.model_executor.layers.quantization.utils.marlin_utils_fp8 import (
41 apply_fp8_marlin_linear, prepare_fp8_layer_for_marlin,
42 prepare_moe_fp8_layer_for_marlin)
@@ -64,12 +64,6 @@ ACTIVATION_SCHEMES = ["static", "dynamic"]
64 logger = init_logger(__name__)
65
66
67 -def _is_col_major(x: torch.Tensor) -> bool:
68 - assert x.dim() == 3
69 - b, m, n = x.shape
70 - return x.stride(0) == m * n and x.stride(1) == 1 and x.stride(2) == m
71 -
72 -
67 class Fp8Config(QuantizationConfig):
68 """Config class for FP8."""
69
@@ -660,10 +654,10 @@ class Fp8MoEMethod(FusedMoEMethodBase):
654 # DeepGemm scales need to be transposed and aligned. We try to do
655 # it ahead of time for performance reasons.
656 if self.allow_deep_gemm and not is_deep_gemm_e8m0_used():
663 - if _is_col_major(layer.w13_weight_scale_inv):
657 + if expert_weight_is_col_major(layer.w13_weight_scale_inv):
658 layer.w13_weight_scale_inv = \
659 get_col_major_tma_aligned_tensor(layer.w13_weight_scale_inv)
666 - if _is_col_major(layer.w2_weight_scale_inv):
660 + if expert_weight_is_col_major(layer.w2_weight_scale_inv):
661 layer.w2_weight_scale_inv = \
662 get_col_major_tma_aligned_tensor(layer.w2_weight_scale_inv)
663
@@ -811,10 +805,10 @@ class Fp8MoEMethod(FusedMoEMethodBase):
805 )
806
807 # Ensure column-major TMA alignment expected by DeepGEMM.
814 - if _is_col_major(layer.w13_weight_scale_inv):
808 + if expert_weight_is_col_major(layer.w13_weight_scale_inv):
809 layer.w13_weight_scale_inv = get_col_major_tma_aligned_tensor(
810 layer.w13_weight_scale_inv)
817 - if _is_col_major(layer.w2_weight_scale_inv):
811 + if expert_weight_is_col_major(layer.w2_weight_scale_inv):
812 layer.w2_weight_scale_inv = get_col_major_tma_aligned_tensor(
813 layer.w2_weight_scale_inv)
814
vllm/model_executor/layers/quantization/utils/fp8_utils.py
+6
@@ -1014,3 +1014,9 @@ def apply_fp8_block_linear(layer: torch.nn.Module, input: torch.Tensor,
1014 cutlass_block_fp8_supported=cutlass_block_fp8_supported,
1015 use_aiter_and_is_supported=use_aiter_and_is_supported,
1016 )
1017 +
1018 +
1019 +def expert_weight_is_col_major(x: torch.Tensor) -> bool:
1020 + assert x.dim() == 3
1021 + b, m, n = x.shape
1022 + return x.stride(0) == m * n and x.stride(1) == 1 and x.stride(2) == m
vllm/model_executor/warmup/deep_gemm_warmup.py
+2 -2
@@ -53,9 +53,9 @@ def _extract_data_from_fused_moe_module(
53 """
54 assert isinstance(m, FusedMoE)
55 w13 = m.w13_weight
56 - w13_s = m.w13_weight_scale_inv
56 + w13_s = getattr(m, "w13_weight_scale_inv", m.w13_weight_scale)
57 w2 = m.w2_weight
58 - w2_s = m.w2_weight_scale_inv
58 + w2_s = getattr(m, "w2_weight_scale_inv", m.w2_weight_scale)
59 num_topk = m.top_k
60
61 assert isinstance(w13, torch.Tensor)