Support MXFP8 and deferred route weighting in DeepEP v2 (#40030)

Co-authored-by: metamergebot <324680979+metamergebot@users.noreply.github.com>
Co-authored-by: Xingyu Liu <38244988+charlotte12l@users.noreply.github.com>
Co-authored-by: pranjalssh <14260275+pranjalssh@users.noreply.github.com>
This commit is contained in:
metamergebot
2026-09-18 15:40:38 -07:00
committed by GitHub
co-authored by metamergebot Xingyu Liu pranjalssh
parent 6cc9090d1f
commit 0e5347db82
20 changed files with 629 additions and 122 deletions
@@ -144,6 +144,7 @@ class TestDeepEPv2BufferLifecycle(CustomTestCase):
impl = object.__new__(deepep_v2._DeepEPv2Impl)
impl.num_max_dispatch_tokens_per_rank = 4
impl.hidden_size = 128
impl.activation_scale_block_size = 128
impl.router_topk = 2
impl._validate_common(torch.empty(4, 128), torch.zeros(4, 2))
with self.assertRaisesRegex(ValueError, "per-rank buffer capacity"):
@@ -1,6 +1,7 @@
"""Tests for the DeepEP v2 expanded/masked repack kernels."""
import unittest
from types import SimpleNamespace
import torch
@@ -113,6 +114,80 @@ class TestDeepEPv2MaskedSlab(CustomTestCase):
def test_empty_experts(self):
self._check_expand_roundtrip([0, 0, 0, 0], torch.bfloat16, with_scale=False)
def test_runner_defers_expanded_route_weighting(self):
from sglang.srt.layers.moe.moe_runner.base import MoeRunnerConfig
from sglang.srt.layers.moe.moe_runner.deep_gemm import (
DeepGemmRunnerOutput,
post_permute_deep_gemm_to_deepep_v2,
)
from sglang.srt.layers.moe.token_dispatcher.base import RoutewiseLayout
counts = [3, 0, 2]
recv_x, _, psum, starts, total = _build_layout(
counts, self.ALIGN, self.HIDDEN, torch.bfloat16
)
masked_x, _, _ = expand_to_masked_slab(
recv_x, None, psum, len(counts), self.MAX_M, self.ALIGN
)
weights = torch.full((total,), 0.25, device=DEVICE)
state = {
"deepep_v2_expanded": True,
"deepep_v2_masked": True,
"deepep_v2_psum": psum,
"deepep_v2_total_expanded": total,
"deepep_v2_expert_alignment": self.ALIGN,
"topk_weights": weights,
}
rows = _real_rows(starts, counts)
for no_combine in (False, True):
with self.subTest(no_combine=no_combine):
output = post_permute_deep_gemm_to_deepep_v2(
DeepGemmRunnerOutput(masked_x),
None,
MoeRunnerConfig(no_combine=no_combine),
state,
)
expected = recv_x[rows] if no_combine else recv_x[rows] * 0.25
self.assertTrue(torch.equal(output.hidden_states[rows], expected))
self.assertEqual(
output.routewise_layout,
RoutewiseLayout.EXPANDED if no_combine else None,
)
if no_combine:
self.assertTrue(torch.equal(output.topk_weights, weights))
def test_runner_restores_token_topk_routes_and_masks_nonlocal_slots(self):
from sglang.srt.layers.moe.moe_runner.base import MoeRunnerConfig
from sglang.srt.layers.moe.moe_runner.deep_gemm import (
DeepGemmRunnerOutput,
post_permute_deep_gemm_to_deepep_v2,
)
from sglang.srt.layers.moe.token_dispatcher.base import RoutewiseLayout
hidden = torch.tensor([[3.0], [5.0], [7.0]], device=DEVICE)
weights = torch.tensor([[0.25, 0.0], [0.5, 0.75]], device=DEVICE)
state = {
"topk_ids": torch.tensor([[0, -1], [1, 0]], device=DEVICE),
"topk_weights": weights,
"output_index": torch.tensor([[1, -1], [0, 2]], device=DEVICE),
}
for empty in (False, True):
with self.subTest(empty=empty):
if empty:
state["output_index"] = torch.full((2, 2), -1, device=DEVICE)
output = post_permute_deep_gemm_to_deepep_v2(
DeepGemmRunnerOutput(hidden[:0] if empty else hidden),
None,
MoeRunnerConfig(no_combine=True),
state,
)
expected = torch.tensor([[[5.0], [0.0]], [[3.0], [7.0]]], device=DEVICE)
if empty:
expected.zero_()
self.assertTrue(torch.equal(output.hidden_states, expected))
self.assertIs(output.topk_weights, weights)
self.assertEqual(output.routewise_layout, RoutewiseLayout.TOKEN_TOPK)
def test_single_hot_expert(self):
self._check_expand_roundtrip(
[0, self.MAX_M, 0, 0], torch.bfloat16, with_scale=False, topk=True
@@ -133,7 +208,7 @@ class TestDeepEPv2MaskedSlab(CustomTestCase):
recv_x, None, psum, len(counts), self.MAX_M, self.ALIGN
)
def _production_packed_ue8m0_layout(self, counts):
def _production_packed_ue8m0_layout(self, counts, group_size):
"""Build expanded rows with the production packed UE8M0 quantizer."""
from sglang.kernels.ops.quantization.fp8_kernel import (
sglang_per_token_group_quant_fp8,
@@ -146,7 +221,7 @@ class TestDeepEPv2MaskedSlab(CustomTestCase):
)
recv_x, recv_x_scale = sglang_per_token_group_quant_fp8(
raw,
128,
group_size,
column_major_scales=True,
scale_tma_aligned=True,
scale_ue8m0=True,
@@ -157,9 +232,14 @@ class TestDeepEPv2MaskedSlab(CustomTestCase):
return recv_x, recv_x_scale, psum, starts, total, hidden
def test_fp8_packed_ue8m0_scale_from_production_quantizer(self):
for group_size in (32, 128):
with self.subTest(group_size=group_size):
self._check_packed_scale(group_size)
def _check_packed_scale(self, group_size):
counts = [3, 1, 6, 2]
recv_x, recv_x_scale, psum, starts, _, hidden = (
self._production_packed_ue8m0_layout(counts)
self._production_packed_ue8m0_layout(counts, group_size)
)
E = len(counts)
masked_x, masked_x_scale, masked_m = expand_to_masked_slab(
@@ -175,10 +255,15 @@ class TestDeepEPv2MaskedSlab(CustomTestCase):
torch.testing.assert_close(masked_x_scale[e, j], recv_x_scale[s + j])
def test_expand_under_cuda_graph_capture(self):
for group_size in (32, 128):
with self.subTest(group_size=group_size):
self._check_graph_capture(group_size)
def _check_graph_capture(self, group_size):
# Exercise replay with the production packed scale layout.
counts = [3, 1, 6, 2]
recv_x, recv_x_scale, psum, starts, _, _ = self._production_packed_ue8m0_layout(
counts
counts, group_size
)
E = len(counts)
warm = torch.cuda.Stream()
@@ -235,10 +320,34 @@ class TestDeepEPv2HandleLifecycle(CustomTestCase):
impl._get_buffer = _boom
with self.assertRaisesRegex(RuntimeError, "boom"):
impl.combine(None)
impl.combine(SimpleNamespace(routewise_layout=None))
self.assertIsNone(impl._handle)
self.assertFalse(impl._pad_empty_combine)
def test_unfinalized_routes_are_rejected_and_release_handle(self):
from sglang.srt.layers.moe.token_dispatcher.base import (
CombineInputChecker,
RoutewiseLayout,
)
from sglang.srt.layers.moe.token_dispatcher.deepep_v2 import (
DeepEPv2CombineInput,
)
impl = self._bare_impl()
impl._handle = object()
output = DeepEPv2CombineInput(
torch.empty(2, 8), torch.ones(2), RoutewiseLayout.EXPANDED
)
self.assertTrue(CombineInputChecker.needs_model_route_finalization(output))
with self.assertRaisesRegex(ValueError, "model route finalization"):
impl.combine(output)
self.assertIsNone(impl._handle)
self.assertFalse(
CombineInputChecker.needs_model_route_finalization(
DeepEPv2CombineInput(torch.empty(2, 8), None)
)
)
if __name__ == "__main__":
unittest.main()
@@ -52,6 +52,13 @@ class _FakeBuffer:
num_recv = (x[0] if isinstance(x, tuple) else x).shape[0]
topk_idx = kwargs["topk_idx"]
topk_weights = kwargs["topk_weights"]
if kwargs["do_expand"]:
if isinstance(x, tuple):
x = tuple(t.repeat_interleave(TOPK, dim=0) for t in x)
else:
x = x.repeat_interleave(TOPK, dim=0)
topk_idx = None
topk_weights = topk_weights.flatten()
event = SimpleNamespace(event=None, current_stream_wait=lambda: None)
return x, topk_idx, topk_weights, _FakeHandle(num_recv), event
@@ -93,7 +100,9 @@ class _DeepEPv2WireDtypeBase(CustomTestCase):
for item in reversed(self._patches):
item.stop()
def _dispatch(self, use_fp8_dispatch, num_tokens=8, is_extend_in_batch=True):
def _dispatch(
self, use_fp8_dispatch, num_tokens=8, is_extend_in_batch=True, group_size=128
):
dispatcher = deepep_v2.DeepEPv2Dispatcher(
group=_FakeGroup(),
router_topk=TOPK,
@@ -102,6 +111,7 @@ class _DeepEPv2WireDtypeBase(CustomTestCase):
hidden_size=HIDDEN,
params_dtype=torch.bfloat16,
use_fp8_dispatch=use_fp8_dispatch,
activation_scale_block_size=group_size,
)
dispatcher._impl.num_max_dispatch_tokens_per_rank = NUM_MAX_TOKENS
hidden_states = torch.randn((num_tokens, HIDDEN), dtype=torch.bfloat16)
@@ -116,6 +126,18 @@ class _DeepEPv2WireDtypeBase(CustomTestCase):
class TestDeepEPv2WireDtype(_DeepEPv2WireDtypeBase):
def test_mxfp8_scale_group_survives_both_dispatch_layouts(self):
for is_extend in (True, False):
with self.subTest(is_extend=is_extend):
_, out = self._dispatch(
use_fp8_dispatch=True,
is_extend_in_batch=is_extend,
group_size=32,
)
self.assertEqual(out.activation_scale_block_size, 32)
self.assertEqual(out.hidden_states_scale.shape[-1], HIDDEN // 32)
self.assertEqual(out.is_expanded, not is_extend)
def test_bf16_dispatch_sends_unquantized_activations(self):
hidden_states, out = self._dispatch(use_fp8_dispatch=False)
self.assertIs(_FakeBuffer.last.dispatch_x, hidden_states)
@@ -76,13 +76,18 @@ def _fp8_method(**overrides):
return method
def test_deepep_v2_quant_contract_accepts_blockwise_fp8(_moe_flags):
@pytest.mark.parametrize("use_mxfp8,block_size", [(False, [128, 128]), (True, [1, 32])])
def test_deepep_v2_quant_contract_accepts_blockwise_fp8(
_moe_flags, use_mxfp8, block_size
):
from sglang.srt.layers.moe.fused_moe_triton.layer import (
_validate_deepep_v2_quant_method,
)
_moe_flags.a2a_backend = MoeA2ABackend.DEEPEP_V2
_validate_deepep_v2_quant_method(_fp8_method(weight_block_size=[128, 128]))
_validate_deepep_v2_quant_method(
_fp8_method(weight_block_size=block_size, use_mxfp8=use_mxfp8)
)
@pytest.mark.parametrize(
@@ -90,7 +95,7 @@ def test_deepep_v2_quant_contract_accepts_blockwise_fp8(_moe_flags):
[
({"activation_scheme": "static"}, "activation_scheme"),
({"weight_block_size": None}, "weight_block_size"),
({"weight_block_size": (1, 32), "use_mxfp8": True}, "MXFP8"),
({"weight_block_size": (128, 128), "use_mxfp8": True}, "MXFP8"),
({"is_fp4_expert": True}, "FP4 experts"),
],
)
@@ -149,5 +154,46 @@ def test_deepep_v2_runner_backstop(_moe_flags):
assert MoeRunner(MoeRunnerBackend.DEEP_GEMM, MoeRunnerConfig()).runner_core
def test_deepep_v2_registration_uses_primary_architecture_and_rejects_conflicts():
from sglang.srt.configs.moe_model_registry import (
model_requires_fp32_silu_mul,
model_supports_deepep_v2,
register_deepep_v2_model,
)
register_deepep_v2_model("TestRoutewiseMoe", silu_mul_keep_fp32=True)
config = SimpleNamespace(architectures=["TestRoutewiseMoe"])
assert model_supports_deepep_v2(config)
assert model_requires_fp32_silu_mul(config)
config.architectures = ["UnsupportedMoe", "TestRoutewiseMoe"]
assert not model_supports_deepep_v2(config)
assert not model_requires_fp32_silu_mul(config)
with pytest.raises(ValueError, match="Conflicting"):
register_deepep_v2_model("TestRoutewiseMoe", silu_mul_keep_fp32=False)
@pytest.mark.parametrize("block_size,width", [(32, 16), (128, 4)])
def test_mxfp8_recipes_keep_activation_and_weight_groups_separate(block_size, width):
# The packed format is a layout contract, independent of the host GPU.
from unittest.mock import patch
from sglang.srt.layers import deep_gemm_wrapper
from sglang.srt.layers.moe.moe_runner.deep_gemm import DeepGemmMoeQuantInfo
with patch.object(deep_gemm_wrapper, "DEEPGEMM_SCALE_UE8M0", True):
quant = DeepGemmMoeQuantInfo(
None, None, True, block_shape=[1, 32], use_mxfp8=True
)
assert quant.scale_recipes(
activation_block_size=block_size, hidden_size=2048, activation_scale_width=width
) == ((1, block_size), (1, 32))
with pytest.raises(AssertionError, match="activation scale mismatch"):
quant.scale_recipes(
activation_block_size=block_size,
hidden_size=2048,
activation_scale_width=width + 1,
)
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-v"]))