|
50 | 50 | DeepEPNormalCombineInput, |
51 | 51 | DeepEPNormalDispatchOutput, |
52 | 52 | ) |
| 53 | + from sglang.srt.layers.moe.token_dispatcher.moonep import ( |
| 54 | + MoonEPCombineInput, |
| 55 | + MoonEPDispatchOutput, |
| 56 | + ) |
53 | 57 | from sglang.srt.layers.moe.token_dispatcher.standard import ( |
54 | 58 | StandardCombineInput, |
55 | 59 | StandardDispatchOutput, |
@@ -1240,6 +1244,170 @@ def post_permute_deep_gemm_to_deepep_normal( |
1240 | 1244 | ) |
1241 | 1245 |
|
1242 | 1246 |
|
| 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 | + |
1243 | 1411 | def _varlen_deep_gemm_situ_mul_quant( |
1244 | 1412 | gateup_output: torch.Tensor, |
1245 | 1413 | masked_m: torch.Tensor, |
|
0 commit comments