Skip to content

Commit f2d7990

Browse files
committed
Integrate with MoonEP and MXFP4 experts on the DeepGEMM runner
1 parent ffdf6f9 commit f2d7990

7 files changed

Lines changed: 719 additions & 52 deletions

File tree

python/sglang/srt/environ.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,11 @@ class Envs:
980980
# -1 uses MoonEP's training-safe default B = E / EP.
981981
SGLANG_MOONEP_NUM_PREFETCH_SLOTS = EnvInt(-1)
982982
SGLANG_MOONEP_TOKEN_PADDING = EnvInt(128)
983+
# Decode-phase token capacity; <= 0 derives it from max_running_requests.
984+
SGLANG_MOONEP_DECODE_MAX_DISPATCH_TOKENS_PER_RANK = EnvInt(-1)
983985
SGLANG_MOONEP_NUM_SMS = EnvInt(32)
986+
# MoonEP's static shapes should be capturable; off until that is shown.
987+
SGLANG_ENABLE_MOONEP_CUDA_GRAPH = EnvBool(False)
984988
SGLANG_PPLX_NUM_MAX_DISPATCH_TOKENS_PER_RANK = EnvInt(128)
985989
SGLANG_ENABLE_MOE_DEFERRED_FINALIZE = EnvBool(True)
986990
# DeepSeek/GLM MoE (deepseek_v2.py): quantize the (dp-gathered) MoE input

python/sglang/srt/layers/moe/fused_moe_triton/layer.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -397,15 +397,15 @@ def __init__(
397397
f"quant_method={type(self.quant_method).__name__})."
398398
)
399399

400-
moonep_global_weight_storage = get_moe_a2a_backend().is_moonep()
401-
if moonep_global_weight_storage:
402-
if quant_config is not None:
403-
raise NotImplementedError(
404-
"MoonEP PoC supports unquantized BF16 MoE weights only."
405-
)
400+
# Quantized experts instead keep the normal EP
401+
# shard and are relocated into a symmetric VMM range after loading
402+
moonep_global_weight_storage = (
403+
get_moe_a2a_backend().is_moonep() and quant_config is None
404+
)
405+
if get_moe_a2a_backend().is_moonep():
406406
if num_fused_shared_experts != 0:
407407
raise NotImplementedError(
408-
"MoonEP PoC does not support fused shared experts yet."
408+
"MoonEP does not support fused shared experts yet."
409409
)
410410

411411
self.quant_method.create_weights(

python/sglang/srt/layers/moe/moe_runner/deep_gemm.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
DeepEPNormalCombineInput,
5151
DeepEPNormalDispatchOutput,
5252
)
53+
from sglang.srt.layers.moe.token_dispatcher.moonep import (
54+
MoonEPCombineInput,
55+
MoonEPDispatchOutput,
56+
)
5357
from sglang.srt.layers.moe.token_dispatcher.standard import (
5458
StandardCombineInput,
5559
StandardDispatchOutput,
@@ -1240,6 +1244,170 @@ def post_permute_deep_gemm_to_deepep_normal(
12401244
)
12411245

12421246

1247+
def _moonep_m_indices(
1248+
cu_seqlens: torch.Tensor,
1249+
expert_ids: torch.Tensor,
1250+
all_tokens: int,
1251+
) -> torch.Tensor:
1252+
"""Expand MoonEP's per-group segment ends into DeepGEMM's per-row group ids.
1253+
1254+
``cu_seqlens[g]`` is the *end* offset of group ``g`` (no leading zero), so
1255+
``searchsorted(..., right=True)`` maps a row to the group that owns it and
1256+
naturally skips empty groups. Two kinds of rows get ``-1``, which DeepGEMM
1257+
skips entirely: rows past the last segment (MoonEP pads the receive buffer
1258+
to a static ``NvS``) and rows whose group is an unfilled prefetch slot
1259+
(``expert_ids`` already carries ``-1`` there).
1260+
1261+
``expert_ids`` -- not the group index -- is the value DeepGEMM wants: it
1262+
indexes the leading dimension of the expert weight tensor.
1263+
"""
1264+
num_groups = expert_ids.numel()
1265+
rows = torch.arange(all_tokens, device=cu_seqlens.device, dtype=cu_seqlens.dtype)
1266+
group = torch.searchsorted(cu_seqlens, rows, right=True)
1267+
m_indices = expert_ids[group.clamp(max=num_groups - 1)].to(torch.int32)
1268+
return torch.where(group < num_groups, m_indices, torch.full_like(m_indices, -1))
1269+
1270+
1271+
@register_pre_permute("moonep", "deep_gemm")
1272+
def pre_permute_moonep_to_deep_gemm(
1273+
dispatch_output: MoonEPDispatchOutput,
1274+
quant_info: DeepGemmMoeQuantInfo,
1275+
runner_config: MoeRunnerConfig,
1276+
running_state: dict,
1277+
) -> DeepGemmRunnerInput:
1278+
"""MoonEP dispatch output -> DeepGEMM m-grouped contiguous input.
1279+
1280+
Unlike the deepep/standard pre-permutes there is no scatter here: MoonEP's
1281+
``dispatch`` already returns rows grouped by expert and padded to
1282+
``token_padding``, which matches DeepGEMM's
1283+
``get_mk_alignment_for_contiguous_layout()``. All that is left is deriving
1284+
``m_indices`` and, for quantized experts, quantizing the activations.
1285+
"""
1286+
hidden_states = dispatch_output.hidden_states
1287+
if hidden_states.ndim != 2:
1288+
raise ValueError(
1289+
f"MoonEP hidden states must be [NvS, H], got {hidden_states.shape}"
1290+
)
1291+
1292+
all_tokens = hidden_states.shape[0]
1293+
running_state["all_tokens"] = all_tokens
1294+
running_state["hidden_states_shape"] = hidden_states.shape
1295+
running_state["hidden_states_dtype"] = hidden_states.dtype
1296+
running_state["hidden_states_device"] = hidden_states.device
1297+
# Carried through because MoonEP's combine reconstructs from the plan and
1298+
# does not apply route weights itself -- the post-permute must.
1299+
running_state["route_weights_nvs"] = dispatch_output.route_weights_nvs
1300+
running_state["plan"] = dispatch_output.plan
1301+
running_state["num_tokens"] = dispatch_output.num_tokens
1302+
1303+
expert_ids = dispatch_output.expert_ids
1304+
if quant_info.w13_weight.dtype != torch.bfloat16:
1305+
# Quantized experts live in MoonEP's symmetric pool, so the duplicated
1306+
# ones have to be pulled in before the GEMM reads them, and the plan's
1307+
# global expert ids have to become pool rows. This runs here rather
1308+
# than in DeepEPMoE.run_moe_core because MXFP4 on DeepGEMM sets
1309+
# deprecate_flag, which delegates past that method entirely.
1310+
from sglang.srt.layers.moe.token_dispatcher import moonep_weights
1311+
from sglang.srt.layers.moe.token_dispatcher.moonep import MoonEPBuffer
1312+
1313+
layer_id = runner_config.layer_id
1314+
assert layer_id is not None, "MoonEP pre-permute needs runner_config.layer_id"
1315+
weight_pairs, scale_pairs = moonep_weights.prefetch_pairs(layer_id)
1316+
MoonEPBuffer.get_existing_buffer().prefetch_weight(
1317+
plan=dispatch_output.plan,
1318+
async_finish=False,
1319+
weight_pairs=weight_pairs,
1320+
scale_pairs=scale_pairs or None,
1321+
experts_to_copy=moonep_weights.expert_rows(
1322+
layer_id,
1323+
dispatch_output.plan.experts_to_copy[get_tp_group().rank_in_group],
1324+
),
1325+
)
1326+
# The parameters are this rank's slice of the pool, but m_indices
1327+
# addresses the whole symmetric range -- a duplicated expert's rows
1328+
# live in another rank's chunk. Point the GEMM at the full ranges, of
1329+
# which the parameters are a sub-view.
1330+
pool = moonep_weights.get_pool()
1331+
quant_info.w13_weight = pool.ranges[moonep_weights.W13_WEIGHT].view(torch.int8)
1332+
quant_info.w2_weight = pool.ranges[moonep_weights.W2_WEIGHT].view(torch.int8)
1333+
quant_info.w13_scale = pool.ranges[moonep_weights.W13_SCALE].permute(0, 2, 1)
1334+
quant_info.w2_scale = pool.ranges[moonep_weights.W2_SCALE].permute(0, 2, 1)
1335+
1336+
expert_ids = moonep_weights.group_rows(
1337+
layer_id, expert_ids, runner_config.num_experts
1338+
)
1339+
1340+
m_indices = _moonep_m_indices(dispatch_output.cu_seqlens, expert_ids, all_tokens)
1341+
running_state["m_indices"] = m_indices
1342+
1343+
if quant_info.w13_weight.dtype == torch.bfloat16:
1344+
return DeepGemmRunnerInput(
1345+
hidden_states=hidden_states,
1346+
# Unused by the BF16 contiguous GEMM, but the field is non-optional.
1347+
hidden_states_scale=torch.empty(
1348+
(all_tokens, 1), device=hidden_states.device, dtype=torch.float32
1349+
),
1350+
use_masked_gemm=False,
1351+
m_indices=m_indices,
1352+
)
1353+
1354+
from sglang.kernels.ops.quantization.fp8_kernel import (
1355+
sglang_per_token_group_quant_fp8,
1356+
)
1357+
1358+
block_k = quant_info.block_shape[1] if quant_info.block_shape else 128
1359+
running_state["mxfp8_act_gran_k"] = block_k
1360+
hidden_states_fp8, hidden_states_scale = sglang_per_token_group_quant_fp8(
1361+
hidden_states,
1362+
block_k,
1363+
column_major_scales=deep_gemm_wrapper.DEEPGEMM_SCALE_UE8M0,
1364+
scale_tma_aligned=deep_gemm_wrapper.DEEPGEMM_SCALE_UE8M0,
1365+
scale_ue8m0=deep_gemm_wrapper.DEEPGEMM_SCALE_UE8M0,
1366+
)
1367+
return DeepGemmRunnerInput(
1368+
hidden_states=hidden_states_fp8,
1369+
hidden_states_scale=hidden_states_scale,
1370+
use_masked_gemm=False,
1371+
m_indices=m_indices,
1372+
)
1373+
1374+
1375+
@register_post_permute("deep_gemm", "moonep")
1376+
def post_permute_deep_gemm_to_moonep(
1377+
runner_output: DeepGemmRunnerOutput,
1378+
quant_info: DeepGemmMoeQuantInfo,
1379+
runner_config: MoeRunnerConfig,
1380+
running_state: dict,
1381+
) -> MoonEPCombineInput:
1382+
"""DeepGEMM output -> MoonEP combine input, still in dispatched row order.
1383+
1384+
No gather: MoonEP's ``combine`` consumes the ``[NvS, H]`` layout directly.
1385+
1386+
Rows DeepGEMM skipped (``m_indices == -1``) were never written, so they
1387+
still hold whatever ``torch.empty`` left behind and must be zeroed before
1388+
combine reduces them. Zeroing cannot be folded into the route-weight
1389+
multiply below, because uninitialized memory may decode to NaN and
1390+
``0 * NaN`` is NaN.
1391+
"""
1392+
from sglang.srt.layers.moe.token_dispatcher.moonep import MoonEPCombineInput
1393+
1394+
hidden_states = runner_output.hidden_states
1395+
hidden_states.masked_fill_((running_state["m_indices"] < 0).unsqueeze(-1), 0.0)
1396+
1397+
route_weights_nvs = running_state["route_weights_nvs"]
1398+
if route_weights_nvs is not None:
1399+
hidden_states.mul_(
1400+
route_weights_nvs.to(dtype=hidden_states.dtype).unsqueeze(-1)
1401+
)
1402+
1403+
return MoonEPCombineInput(
1404+
hidden_states=hidden_states,
1405+
route_weights_nvs=route_weights_nvs,
1406+
plan=running_state["plan"],
1407+
num_tokens=running_state["num_tokens"],
1408+
)
1409+
1410+
12431411
def _varlen_deep_gemm_situ_mul_quant(
12441412
gateup_output: torch.Tensor,
12451413
masked_m: torch.Tensor,

0 commit comments

Comments
 (0)