Files
sglang/test/registered/unit/moe/test_glm_megamoe.py
LuminolT ebf11be355
Some checks failed
CI Failure Monitor / failure-analysis (push) Has been cancelled
Release PyPI Nightly Wheels / build-nightly-wheel (push) Has been cancelled
Release PyPI Nightly Wheels / release-nightly (push) Has been cancelled
Nightly Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Nightly Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Nightly Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
Nightly Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Nightly Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Nightly Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Nightly Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Nightly Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Nightly Release SGLang Model Gateway to PyPI / Upload to TestPyPI (push) Has been cancelled
CI Coverage Overview / Summary (push) Has been cancelled
CI Coverage Overview / Tests by Folder (push) Has been cancelled
CI Coverage Overview / Tests by Suite (push) Has been cancelled
CI Coverage Overview / Unit Test Code Coverage (push) Has been cancelled
CI Coverage Overview / JSON Export (push) Has been cancelled
Runner Utilization Report / Generate Report (push) Has been cancelled
Release Docker Images ROCm 7.2.0 Nightly Preview (AMD) / publish (all, gfx942-rocm720) (push) Has been cancelled
Release Docker Images ROCm 7.2.0 Nightly Preview (AMD) / publish (all, gfx950-rocm720) (push) Has been cancelled
Release Docker Images Nightly (AMD) / publish (all, gfx942) (push) Has been cancelled
Release Docker Images Nightly (AMD) / publish (all, gfx950) (push) Has been cancelled
Release Docker Images Nightly (AMD) / cache (all, gfx942) (push) Has been cancelled
AMD AITER Scout / resolve-aiter (push) Has been cancelled
AMD AITER Scout / call-nightly-amd (push) Has been cancelled
AMD AITER Scout / call-nightly-amd-rocm720 (push) Has been cancelled
AMD AITER Scout / call-pr-test-amd (push) Has been cancelled
AMD AITER Scout / call-pr-test-amd-rocm720 (push) Has been cancelled
AMD AITER Scout / check-all-jobs (push) Has been cancelled
Release Docker Images Nightly (NPU) / build (8.5.0, 910b) (push) Has been cancelled
Release Docker Images Nightly (NPU) / build (8.5.0, a3) (push) Has been cancelled
Build and Push Development Docker Images / build-dev (arm64, all, 1, linux/arm64, arm-docker-build-node, 12.9.1) (push) Has been cancelled
Build and Push Development Docker Images / build-dev (arm64-cu13, all, 1, linux/arm64, arm-docker-build-node, 13.0.1) (push) Has been cancelled
Build and Push Development Docker Images / build-dev (x86, all, 0, linux/amd64, x64-docker-build-node, 12.9.1) (push) Has been cancelled
Build and Push Development Docker Images / build-dev (x86-cu13, all, 0, linux/amd64, x64-docker-build-node, 13.0.1) (push) Has been cancelled
Build and Push Development Docker Images / create-manifests (map[arm64:arm64 base:dev x86:x86]) (push) Has been cancelled
Build and Push Development Docker Images / create-manifests (map[arm64:arm64-cu13 base:dev-cu13 x86:x86-cu13]) (push) Has been cancelled
Close Inactive Issues / close-inactive-issues (push) Has been cancelled
AMD CI Job Monitor / Custom Job Report (push) Has been cancelled
AMD CI Job Monitor / Parse Workflow Jobs (push) Has been cancelled
AMD CI Job Monitor / PR - ${{ matrix.job_name }} (push) Has been cancelled
AMD CI Job Monitor / Nightly - ${{ matrix.job_name }} (push) Has been cancelled
Weekly Test (Nvidia) / weekly-test-8-gpu-h200 (push) Has been cancelled
fix(moe): gate fp8 megamoe on deepgemm capability
Avoid entering the FP8 MegaMoE fused path when the installed DeepGEMM exposes fp8_mega_moe but cannot allocate an fp8xfp8 MegaMoE symmetric buffer. Current B300 sgl-deep-gemm asserts that MegaMoE buffer mma_type is fp8xfp4, so FP8 weights must fall back until dependency support lands.

Tested: PYTHONPYCACHEPREFIX=/private/tmp/sglang_pycache python3 -m py_compile python/sglang/srt/layers/moe/mega_moe.py test/registered/unit/moe/test_glm_megamoe.py

Tested: git diff --check

Remote-tested: root@95.133.252.48 B300 DeepGEMM capability probe showed fp8xfp8 MegaMoE buffer fails with mma_type_str == fp8xfp4.
2026-07-08 12:05:46 +08:00

237 lines
9.1 KiB
Python

import sys
import types
import unittest
from types import SimpleNamespace
from unittest.mock import Mock, patch
import torch
from sglang.srt.environ import envs
from sglang.srt.layers.moe import mega_moe
from sglang.srt.models import glm4_moe
from sglang.test.ci.ci_register import register_cpu_ci
register_cpu_ci(est_time=1, suite="stage-a-test-cpu")
class _MegaBackend:
def is_megamoe(self):
return True
class _NonDeepEPBackend:
def is_deepep(self):
return False
class _Param:
def __init__(self, data):
self.data = data
class TestGLMMegaMoE(unittest.TestCase):
def test_should_use_mega_moe_respects_env_and_token_cap(self):
hidden_states = torch.empty((2, 32))
moe = SimpleNamespace(
experts=SimpleNamespace(
_mega_moe_weights_built=True,
_mega_moe_weight_format="fp4",
),
)
with patch.object(
mega_moe, "get_moe_a2a_backend", return_value=_MegaBackend()
), patch.object(
mega_moe, "get_dp_global_num_tokens", return_value=None
), patch.object(
mega_moe, "get_is_capture_mode", return_value=False
), envs.SGLANG_OPT_DEEPGEMM_MEGA_MOE_USE_FP4_ACTS.override(False):
self.assertFalse(mega_moe.should_use_mega_moe(moe, hidden_states))
with patch.object(
mega_moe, "get_moe_a2a_backend", return_value=_MegaBackend()
), patch.object(
mega_moe, "get_dp_global_num_tokens", return_value=None
), patch.object(
mega_moe, "get_is_capture_mode", return_value=False
), envs.SGLANG_OPT_DEEPGEMM_MEGA_MOE_USE_FP4_ACTS.override(
True
), envs.SGLANG_OPT_DEEPGEMM_MEGA_MOE_NUM_MAX_TOKENS_PER_RANK.override(1):
self.assertFalse(mega_moe.should_use_mega_moe(moe, hidden_states))
with patch.object(
mega_moe, "get_moe_a2a_backend", return_value=_MegaBackend()
), patch.object(
mega_moe, "get_dp_global_num_tokens", return_value=None
), patch.object(
mega_moe, "get_is_capture_mode", return_value=False
), envs.SGLANG_OPT_DEEPGEMM_MEGA_MOE_USE_FP4_ACTS.override(
True
), envs.SGLANG_OPT_DEEPGEMM_MEGA_MOE_NUM_MAX_TOKENS_PER_RANK.override(2):
self.assertTrue(mega_moe.should_use_mega_moe(moe, hidden_states))
def test_should_use_mega_moe_fp8_does_not_require_fp4_act_env(self):
hidden_states = torch.empty((2, 32))
moe = SimpleNamespace(
experts=SimpleNamespace(
_mega_moe_weights_built=True,
_mega_moe_weight_format="fp8",
),
)
with patch.object(
mega_moe, "get_moe_a2a_backend", return_value=_MegaBackend()
), patch.object(
mega_moe, "_deep_gemm_supports_fp8_mega_moe", return_value=True
), envs.SGLANG_OPT_DEEPGEMM_MEGA_MOE_USE_FP4_ACTS.override(False):
self.assertTrue(mega_moe.should_use_mega_moe(moe, hidden_states))
def test_glm_forward_uses_megamoe_fast_path(self):
block = object.__new__(glm4_moe.Glm4MoeSparseMoeBlock)
hidden_states = torch.empty((1, 32))
forward_batch = object()
expected = torch.empty_like(hidden_states)
with patch.object(
mega_moe, "should_use_mega_moe", return_value=True
) as should, patch.object(
mega_moe, "forward_mega_moe", return_value=expected
) as forward:
output = block.forward(hidden_states, forward_batch=forward_batch)
self.assertIs(output, expected)
should.assert_called_once_with(block, hidden_states)
forward.assert_called_once_with(block, hidden_states, forward_batch)
def test_glm_forward_falls_back_to_normal_path(self):
block = object.__new__(glm4_moe.Glm4MoeSparseMoeBlock)
object.__setattr__(block, "alt_stream", None)
object.__setattr__(block, "num_fused_shared_experts", 0)
object.__setattr__(block, "forward_normal", Mock(return_value="normal-output"))
hidden_states = torch.empty((1, 32))
with patch.object(
mega_moe, "should_use_mega_moe", return_value=False
), patch.object(
glm4_moe, "get_moe_a2a_backend", return_value=_NonDeepEPBackend()
):
output = block.forward(hidden_states)
self.assertEqual(output, "normal-output")
block.forward_normal.assert_called_once_with(hidden_states, False, False)
def test_build_mega_moe_weights_preserves_runner_layout(self):
def fake_transform_sf(sf, *, mn, k, recipe, num_groups, disable_ue8m0_cast):
del sf, disable_ue8m0_cast
return torch.zeros((num_groups, mn, k // recipe[1]), dtype=torch.int32)
fake_deep_gemm = types.SimpleNamespace(
transform_sf_into_required_layout=fake_transform_sf,
)
experts = SimpleNamespace(
w13_weight=_Param(torch.arange(2 * 256 * 16).reshape(2, 256, 16)),
w2_weight=_Param(torch.arange(2 * 128 * 16).reshape(2, 128, 16)),
w13_weight_scale=_Param(torch.ones((2, 256, 2), dtype=torch.float32)),
w2_weight_scale=_Param(torch.ones((2, 128, 2), dtype=torch.float32)),
)
original_w13 = experts.w13_weight.data.clone()
with patch.dict(sys.modules, {"deep_gemm": fake_deep_gemm}):
mega_moe.build_mega_moe_experts_weights(
experts, preserve_runner_layout=True
)
self.assertTrue(experts._mega_moe_weights_built)
self.assertTrue(experts._mega_moe_preserve_runner_layout)
self.assertEqual(experts._mega_moe_weight_group_size, 16)
self.assertEqual(experts._mega_moe_weight_format, "fp4")
self.assertTrue(torch.equal(experts.w13_weight.data, original_w13))
self.assertNotEqual(
experts.mega_l1_weights[0].data_ptr(),
experts.w13_weight.data.data_ptr(),
)
experts.w13_weight.data.zero_()
self.assertFalse(
torch.equal(experts.mega_l1_weights[0], experts.w13_weight.data)
)
def test_fp4_weights_use_int8_view_for_deep_gemm_ffi(self):
raw = torch.arange(16, dtype=torch.uint8).reshape(2, 8)
fp4 = raw.view(torch.float4_e2m1fn_x2)
scale = torch.ones((2, 8), dtype=torch.int32)
data, out_scale = mega_moe._as_deep_gemm_packed_fp4_weights((fp4, scale))
self.assertEqual(data.dtype, torch.int8)
self.assertEqual(data.data_ptr(), fp4.data_ptr())
self.assertEqual(data.shape, fp4.shape)
self.assertIs(out_scale, scale)
int8_data = raw.view(torch.int8)
same_data, same_scale = mega_moe._as_deep_gemm_packed_fp4_weights(
(int8_data, scale)
)
self.assertIs(same_data, int8_data)
self.assertIs(same_scale, scale)
def test_build_fp8_mega_moe_weights_preserves_runner_layout(self):
experts = SimpleNamespace(
w13_weight=_Param(torch.arange(2 * 8 * 4).reshape(2, 8, 4)),
w2_weight=_Param(torch.arange(2 * 4 * 4).reshape(2, 4, 4)),
w13_weight_scale=_Param(torch.arange(2, dtype=torch.float32) + 1),
w2_weight_scale=_Param(torch.arange(2, dtype=torch.float32) + 3),
quant_method=SimpleNamespace(
quant_config=SimpleNamespace(weight_block_size=None)
),
)
original_w13 = experts.w13_weight.data.clone()
fake_deep_gemm = types.SimpleNamespace(
transform_sf_into_required_layout=(
lambda sf, *, mn, k, recipe, num_groups, disable_ue8m0_cast: torch.zeros(
(num_groups, mn, max(k // recipe[1], 1)), dtype=torch.int32
)
),
transform_weights_for_mega_moe=lambda l1, l2: (l1, l2),
)
with patch.dict(sys.modules, {"deep_gemm": fake_deep_gemm}):
mega_moe.build_mega_moe_fp8_experts_weights(
experts, preserve_runner_layout=True, swap_w13_halves=True
)
self.assertTrue(experts._mega_moe_weights_built)
self.assertEqual(experts._mega_moe_weight_format, "fp8")
self.assertFalse(experts._mega_moe_fp8_block_quant)
self.assertTrue(torch.equal(experts.w13_weight.data, original_w13))
self.assertTrue(
torch.equal(
experts.mega_fp8_w13_weight,
torch.cat((original_w13[:, 4:], original_w13[:, :4]), dim=1),
)
)
self.assertNotEqual(
experts.mega_fp8_w13_weight.data_ptr(),
experts.w13_weight.data.data_ptr(),
)
def test_transform_sf_reports_group16_dependency_gap(self):
def fake_transform_sf(*args, **kwargs):
del args, kwargs
raise RuntimeError("Unknown SF transformation")
with self.assertRaisesRegex(RuntimeError, "group16"):
mega_moe._transform_mega_moe_sf(
fake_transform_sf,
torch.ones((2, 256, 2), dtype=torch.float32),
mn=256,
k=32,
group_size=16,
num_groups=2,
name="w13",
)
if __name__ == "__main__":
unittest.main()