diff --git a/python/sglang/srt/layers/quantization/modelopt_quant.py b/python/sglang/srt/layers/quantization/modelopt_quant.py index efa58209b..330fa6c21 100755 --- a/python/sglang/srt/layers/quantization/modelopt_quant.py +++ b/python/sglang/srt/layers/quantization/modelopt_quant.py @@ -2027,6 +2027,17 @@ class ModelOptNvFp4FusedMoEMethod(FusedMoEMethodBase): else: w13_input_scale = layer.w13_input_scale.max(dim=-1).values.to(torch.float32) w2_input_scale = layer.w2_input_scale + # w13_input_scale/w2_input_scale are loaded GLOBAL (num_experts) because + # create_weights tags them _sglang_require_global_experts, while + # w13_weight_scale_2 is EP-local (num_local_experts). Under EP>1 this + # non-flashinfer path must slice the global input scales down to this + # rank's local experts so the gemm1/gemm2 alpha products line up + # (mirrors the cutedsl branch above). No-op for moe_ep_size == 1. + if layer.moe_ep_size > 1 and w13_input_scale.shape[0] == layer.num_experts: + start = layer.moe_ep_rank * layer.num_local_experts + end = start + layer.num_local_experts + w13_input_scale = w13_input_scale[start:end] + w2_input_scale = w2_input_scale[start:end] if self.quant_config.use_per_token_activation: # FlashInfer computes activation scales dynamically per token, so