@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / f8848b2f2d
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 3, 15:15 UTC

[Bugfix] Add swiglu limits to deepgemm fp8 methods (#41986)

Signed-off-by: Yongye Zhu <zyy1102000@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>

Yongye Zhu committed May 14, 2026 at 18:43 UTC f8848b2f2da56418480fa4be1a8a9adbe4b960b4
13 files changed +85 -1
tests/kernels/moe/test_silu_mul_per_token_group_quant_fp8_colmajor.py
+47
@@ -66,6 +66,24 @@ def reference(x: torch.Tensor, use_ue8m0: bool) -> tuple[torch.Tensor, torch.Ten
66 return reference_quant(ref_act_out, use_ue8m0)
67
68
69 +def reference_with_clamp(
70 + x: torch.Tensor, use_ue8m0: bool, clamp_limit: float
71 +) -> tuple[torch.Tensor, torch.Tensor]:
72 + """Pre-clamp inputs (gate from above, up symmetric) at the input dtype to
73 + match the C++ compute() template, then run the standard silu_and_mul +
74 + quant reference."""
75 + N_2 = x.size(1) // 2
76 + dtype = x.dtype
77 + gate = x[..., :N_2].to(torch.float32).clamp(max=clamp_limit).to(dtype)
78 + up = (
79 + x[..., N_2:]
80 + .to(torch.float32)
81 + .clamp(min=-clamp_limit, max=clamp_limit)
82 + .to(dtype)
83 + )
84 + return reference(torch.cat([gate, up], dim=-1), use_ue8m0)
85 +
86 +
87 @pytest.mark.parametrize("T", [128, 256, 512])
88 @pytest.mark.parametrize("N", [128 * 2, 256 * 2, 768 * 2, 2048 * 2, 7168 * 2])
89 @pytest.mark.skipif(
@@ -89,3 +107,32 @@ def test_silu_mul_fp8_quant_deep_gemm(T: int, N: int):
107
108 torch.testing.assert_close(output.to(torch.float32), ref_output.to(torch.float32))
109 torch.testing.assert_close(output_scales, ref_output_scales)
110 +
111 +
112 +@pytest.mark.parametrize("T", [128, 256, 512])
113 +@pytest.mark.parametrize("N", [128 * 2, 256 * 2, 768 * 2, 2048 * 2, 7168 * 2])
114 +@pytest.mark.parametrize("clamp_limit", [7.0, 10.0])
115 +@pytest.mark.skipif(
116 + current_platform.is_rocm(),
117 + reason="ROCm does not support DeepGemm.",
118 +)
119 +def test_silu_mul_fp8_quant_deep_gemm_clamp(T: int, N: int, clamp_limit: float):
120 + set_random_seed(42)
121 +
122 + # Use a wide distribution so values routinely exceed both clamp limits and
123 + # the clamp branch is actually exercised (uniform [0, 1) inputs would never
124 + # trigger it).
125 + input = torch.randn((T, N), dtype=torch.bfloat16, device="cuda") * 8.0
126 +
127 + use_ue8m0 = is_deep_gemm_e8m0_used()
128 +
129 + # Test
130 + output, output_scales = silu_mul_per_token_group_quant_fp8_colmajor(
131 + input, use_ue8m0=use_ue8m0, clamp_limit=clamp_limit
132 + )
133 +
134 + # Reference
135 + ref_output, ref_output_scales = reference_with_clamp(input, use_ue8m0, clamp_limit)
136 +
137 + torch.testing.assert_close(output.to(torch.float32), ref_output.to(torch.float32))
138 + torch.testing.assert_close(output_scales, ref_output_scales)
vllm/model_executor/layers/fused_moe/config.py
+4
@@ -598,6 +598,7 @@ def fp8_w8a8_moe_quant_config(
598 a2_gscale: torch.Tensor | None = None,
599 g1_alphas: torch.Tensor | None = None,
600 g2_alphas: torch.Tensor | None = None,
601 + gemm1_clamp_limit: float | None = None,
602 ) -> FusedMoEQuantConfig:
603 """
604 Construct a quant config for fp8 activations and fp8 weights.
@@ -617,6 +618,7 @@ def fp8_w8a8_moe_quant_config(
618 per_act_token_quant=per_act_token_quant,
619 per_out_ch_quant=per_out_ch_quant,
620 block_shape=block_shape,
621 + gemm1_clamp_limit=gemm1_clamp_limit,
622 )
623
624
@@ -743,6 +745,7 @@ def mxfp4_w4a8_moe_quant_config(
745 w1_bias: torch.Tensor | None = None,
746 w2_bias: torch.Tensor | None = None,
747 block_shape: list[int] | None = None,
748 + gemm1_clamp_limit: float | None = None,
749 ) -> FusedMoEQuantConfig:
750 """
751 Construct a quant config for fp8 activations and mxfp4 weights.
@@ -752,6 +755,7 @@ def mxfp4_w4a8_moe_quant_config(
755 _a2=FusedMoEQuantDesc("fp8", None, a2_scale, None, None, None),
756 _w1=FusedMoEQuantDesc("mxfp4", None, w1_scale, None, None, w1_bias),
757 _w2=FusedMoEQuantDesc("mxfp4", None, w2_scale, None, None, w2_bias),
758 + gemm1_clamp_limit=gemm1_clamp_limit,
759 )
760
761
vllm/model_executor/layers/fused_moe/experts/deep_gemm_moe.py
+5
@@ -128,6 +128,8 @@ class DeepGemmExperts(mk.FusedMoEExpertsModular):
128 assert not quant_config.per_act_token_quant
129 assert not quant_config.per_out_ch_quant
130
131 + self.gemm1_clamp_limit = quant_config.gemm1_clamp_limit
132 +
133 @staticmethod
134 def activation_format() -> mk.FusedMoEActivationFormat:
135 return mk.FusedMoEActivationFormat.Standard
@@ -209,6 +211,7 @@ class DeepGemmExperts(mk.FusedMoEExpertsModular):
211 input=input,
212 output_q=output,
213 group_size=block_k,
214 + clamp_limit=self.gemm1_clamp_limit,
215 )
216 act_out = torch.empty(
217 (M_sum, activation_out_dim), dtype=input.dtype, device=input.device
@@ -228,6 +231,7 @@ class DeepGemmExperts(mk.FusedMoEExpertsModular):
231 input=input,
232 output=output,
233 use_ue8m0=use_ue8m0,
234 + clamp_limit=self.gemm1_clamp_limit,
235 )
236
237 # 3. fallback path for non-SiLU activations in non‑UE8M0 cases.
@@ -437,6 +441,7 @@ class DeepGemmFP4Experts(mk.FusedMoEExpertsModular):
441 input=input,
442 output=output,
443 use_ue8m0=use_ue8m0,
444 + clamp_limit=self.gemm1_clamp_limit,
445 )
446
447 act_out = torch.empty(
vllm/model_executor/layers/fused_moe/oracle/fp8.py
+4
@@ -511,6 +511,7 @@ def make_fp8_moe_quant_config(
511 block_shape: list[int] | None = None,
512 per_act_token_quant: bool = False,
513 per_out_ch_quant: bool = False,
514 + swiglu_limit: float | None = None,
515 ) -> FusedMoEQuantConfig:
516 """
517 Create FusedMoEQuantConfig for the specified FP8 Backend.
@@ -554,6 +555,7 @@ def make_fp8_moe_quant_config(
555 a2_gscale=(1.0 / a2_scale),
556 g1_alphas=(w1_scale * a1_scale).squeeze(),
557 g2_alphas=(w2_scale * a2_scale).squeeze(),
558 + gemm1_clamp_limit=swiglu_limit,
559 )
560 # MXFP8 uses "mxfp8" quant_dtype so the prepare step dispatches to
561 # _mxfp8_e4m3_quantize rather than standard FP8 block quantization.
@@ -568,6 +570,7 @@ def make_fp8_moe_quant_config(
570 a2_scale=a2_scale,
571 block_shape=block_shape,
572 is_scale_swizzled=False,
573 + gemm1_clamp_limit=swiglu_limit,
574 )
575
576 # All other backends use normal config.
@@ -579,6 +582,7 @@ def make_fp8_moe_quant_config(
582 block_shape=block_shape,
583 per_act_token_quant=per_act_token_quant,
584 per_out_ch_quant=per_out_ch_quant,
585 + gemm1_clamp_limit=swiglu_limit,
586 )
587
588
vllm/model_executor/layers/fused_moe/oracle/mxfp4.py
+1
@@ -1488,6 +1488,7 @@ def make_mxfp4_moe_quant_config(
1488 w1_bias=w1_bias,
1489 w2_bias=w2_bias,
1490 block_shape=None,
1491 + gemm1_clamp_limit=swiglu_limit,
1492 )
1493 elif mxfp4_backend in (
1494 Mxfp4MoeBackend.MARLIN,
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_w8a8_fp8.py
+1
@@ -360,6 +360,7 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
360 per_act_token_quant=is_per_token,
361 per_out_ch_quant=is_per_token,
362 block_shape=self.weight_block_size,
363 + swiglu_limit=getattr(layer, "swiglu_limit", None),
364 )
365
366 def apply_monolithic(
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_w8a8_mxfp8.py
+1
@@ -152,6 +152,7 @@ class CompressedTensorsW8A8Mxfp8MoEMethod(CompressedTensorsMoEMethod):
152 a1_scale=layer.w13_input_scale,
153 a2_scale=layer.w2_input_scale,
154 block_shape=self.weight_block_size,
155 + swiglu_limit=getattr(layer, "swiglu_limit", None),
156 )
157
158 def maybe_make_prepare_finalize(
vllm/model_executor/layers/quantization/fp8.py
+1
@@ -856,6 +856,7 @@ class Fp8MoEMethod(FusedMoEMethodBase):
856 a1_scale=a1_scale,
857 a2_scale=a2_scale,
858 block_shape=self.weight_block_size,
859 + swiglu_limit=getattr(layer, "swiglu_limit", None),
860 )
861
862 # Inject biases into the quant config if the model has them
vllm/model_executor/layers/quantization/modelopt.py
+1
@@ -944,6 +944,7 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
944 w2_scale=w2_scale,
945 a1_scale=a1_scale,
946 a2_scale=a2_scale,
947 + swiglu_limit=getattr(layer, "swiglu_limit", None),
948 )
949
950 def apply_monolithic(
vllm/model_executor/layers/quantization/online/fp8.py
+1
@@ -376,6 +376,7 @@ class _Fp8OnlineMoEBase(OnlineMoEMethodBase):
376 a1_scale=a1_scale,
377 a2_scale=a2_scale,
378 block_shape=self.weight_block_size,
379 + swiglu_limit=getattr(layer, "swiglu_limit", None),
380 )
381
382 self._maybe_inject_biases(quant_config, layer)
vllm/model_executor/layers/quantization/online/mxfp8.py
+1
@@ -221,6 +221,7 @@ class Mxfp8OnlineMoEMethod(OnlineMoEMethodBase):
221 a1_scale=a1_scale,
222 a2_scale=a2_scale,
223 block_shape=self.weight_block_size,
224 + swiglu_limit=getattr(layer, "swiglu_limit", None),
225 )
226
227 self._maybe_inject_biases(quant_config, layer)
vllm/model_executor/layers/quantization/quark/quark_moe.py
+2
@@ -449,6 +449,7 @@ class QuarkW8A8Fp8MoEMethod(QuarkMoEMethod):
449 w2_bias=layer.w2_bias,
450 per_act_token_quant=self.input_qscheme == "per_channel",
451 per_out_ch_quant=self.weight_qscheme == "per_channel",
452 + gemm1_clamp_limit=getattr(layer, "swiglu_limit", None),
453 )
454
455 def apply(
@@ -941,6 +942,7 @@ class QuarkW4A8Fp8MoEMethod(QuarkMoEMethod):
942 w1_scale=layer.w13_weight_scale_2,
943 w2_scale=layer.w2_weight_scale_2,
944 per_out_ch_quant=True,
945 + gemm1_clamp_limit=getattr(layer, "swiglu_limit", None),
946 )
947
948 def apply(
vllm/model_executor/layers/quantization/utils/fp8_utils.py
+16 -1
@@ -302,9 +302,11 @@ def _silu_mul_per_token_group_quant_fp8_colmajor(
302 y_s_col_stride: tl.int64,
303 # Information for float8
304 eps,
305 + clamp_limit,
306 fp8_min: tl.constexpr,
307 fp8_max: tl.constexpr,
308 use_ue8m0: tl.constexpr,
309 + HAS_CLAMP: tl.constexpr,
310 # Meta-parameters
311 GROUP_SIZE: tl.constexpr,
312 BLOCK_M: tl.constexpr,
@@ -336,7 +338,16 @@ def _silu_mul_per_token_group_quant_fp8_colmajor(
338 act_in = tl.load(act_in_ptrs)
339 mul_in = tl.load(act_in_ptrs + N_2)
340
339 - # silu & mul
341 + # silu & mul — match C++ silu_and_mul: clamp in fp32 then store back to the
342 + # input dtype, run silu in fp32 then narrow, and do the mul at input
343 + # precision so HAS_CLAMP True/False share the same multiplication path.
344 + if HAS_CLAMP:
345 + act_in = tl.minimum(act_in.to(tl.float32), clamp_limit).to(
346 + y_ptr.dtype.element_ty
347 + )
348 + mul_in = tl.clamp(mul_in.to(tl.float32), -clamp_limit, clamp_limit).to(
349 + y_ptr.dtype.element_ty
350 + )
351 act_in = act_in.to(tl.float32)
352 one_f32 = tl.cast(1, tl.float32)
353 silu_out = (act_in / (one_f32 + tl.exp(-act_in))).to(y_ptr.dtype.element_ty)
@@ -367,6 +378,7 @@ def silu_mul_per_token_group_quant_fp8_colmajor(
378 output: torch.Tensor | None = None, # [M, N // 2]
379 use_ue8m0: bool | None = None,
380 eps: float = 1e-10,
381 + clamp_limit: float | None = None,
382 ):
383 """
384 silu+mul + block-fp8 quant with group size 128.
@@ -409,6 +421,7 @@ def silu_mul_per_token_group_quant_fp8_colmajor(
421 assert N_2 % BLOCK_N == 0
422 grid = (M // BLOCK_M, N_2 // BLOCK_N)
423
424 + has_clamp = clamp_limit is not None
425 _silu_mul_per_token_group_quant_fp8_colmajor[grid](
426 input,
427 output,
@@ -417,9 +430,11 @@ def silu_mul_per_token_group_quant_fp8_colmajor(
430 N,
431 output_scales.stride(-1),
432 eps,
433 + clamp_limit if has_clamp else 0.0,
434 fp8_min,
435 fp8_max,
436 use_ue8m0,
437 + has_clamp,
438 GROUP_SIZE,
439 BLOCK_M,
440 BLOCK_N,