Repository navigation
[minimax-m3] Split 4/4: model + VL + glue + function-call + fp8 quant + generic infra - #28715
Conversation
|
CI note: this PR is part of a 4-way split of #27944 and depends on #28712 + #28713 + #28714. It is not stacked on the other split branches — all four are based on |
|
Heads-up on the function-call detector in this split, from production use of an M3 build that includes it. We fixed it by looking through We have a fix plus a streamed regression test (including the mixed-union guard) and would be glad to open a PR once the M3 detector lands on main. |
|
/rerun-failed-ci |
Companion to e23602ee5. The sglang.kernels.ops.moe.topk_softmax wrapper (introduced by sgl-project#28715) forwards 6 args (adds moe_softcapping and correction_bias) to sgl_kernel:topk_softmax, but the AOT topk_softmax that sgl-kernel-xpu ships still has the pre-sgl-project#28715 4-arg signature. On XPU this raises TypeError: topk_softmax() takes from 3 to 4 positional arguments but 6 were given whenever the softmax scoring path is hit (e.g. TestDeepSeekOCR in test/registered/xpu/test_deepseek_ocr.py). Mirror the topk_sigmoid fix and route XPU directly to sgl_kernel.topk_softmax; CUDA/HIP keep the wrapper. Verified stage-b XPU CI locally: 8/8 passed (was 5/6, test_deepseek_ocr failing).
sgl-kernel-xpu#285 aligns topk_sigmoid with the post-sgl-project#28715 CUDA signature (routed_scaling_factor, num_fused_shared_experts), so the existing 7-arg call site works unchanged. The only stale piece was the "pre-sgl-project#28715" comment on the XPU import branch.
…e yet) sgl-project#31610 optimistically swapped MiniMax-M3's NVIDIA images to :latest, but the model itself (PR sgl-project#28715, "Split 4/4: model + VL + glue + fp8") landed on 2026-07-11 — after the v0.5.15.post1 cut (2026-07-13, tagged without it). minimax_m3.py / minimax_m3_vl.py do not exist at v0.5.15.post1, so :latest pulls but cannot load M3. Restore the model-specific dev images (dev-minimax-m3 / dev-cu13-minimax-m3 / dev-cu12-minimax-m3), which carry both the model code and the pre-installed MSA fmha_sm100 kernel, and restore the MSA prose. Added a guard comment to prevent re-swapping to :latest until sgl-project#28715 ships in a tagged release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + generic infra (sgl-project#28715) Co-authored-by: Xinyuan Tong <xinyuan-tong@users.noreply.github.com> Co-authored-by: zijiexia <37504505+zijiexia@users.noreply.github.com>
…w2 MoE intermediate size Why: - MiniMax-M2.7 fp8 per-channel MoE (per_channel_quant=True -> _run_masked_fp8_channel_gemm) regressed on 0.5.17_rel: the masked activation Triton kernel _silu_and_mul_post_quant_kernel is materially slower than on 0.5.13_rel for the same shapes, same kernel body. - The BLOCK_N dispatch in silu_and_mul_masked_post_quant_fwd came in with the minimax-m3 series (0663ebc, sgl-project#28715) and picks BLOCK_N = next_power_of_2(quant_group_size) * gpb, gpb in {4, 2, 1}, accepting gpb=4 whenever groups_total % 4 == 0. - The fp8-channel caller passes quant_group_size = moe_intermediate_size. MiniMax-M2.7 uses 1536, which is not a power of two, so next_power_of_2 rounds the group size up above size_n: groups_total becomes 0 and 0 % 4 == 0 selects gpb=4. BLOCK_N jumps from 2048 (0.5.13_rel) to 8192 while num_warps stays 1, so every token loads, computes and reduces 4x the lanes with only 1536 of 8192 useful, blowing up register pressure and dominating the EP MoE step. How: - Require groups_total >= gpb before aggregating groups, so groups_total == 0 keeps gpb == 1 and BLOCK_N stays next_power_of_2(quant_group_size), matching 0.5.13_rel. - No behavior change elsewhere: groups_total in {1, 2, 3} already resolved to the same gpb under the old condition, groups_total >= 4 still aggregates up to 4 groups, and the int8 / mxfp4 branches are untouched. Dependency: - None Test: - Unit: - None - E2E: - MiniMax-M2.7 E2E Perf test on DP + EP (cherry picked from commit be5efb42)
…w2 MoE intermediate size Why: - MiniMax-M2.7 fp8 per-channel MoE (per_channel_quant=True -> _run_masked_fp8_channel_gemm) regressed on 0.5.17_rel: the masked activation Triton kernel _silu_and_mul_post_quant_kernel is materially slower than on 0.5.13_rel for the same shapes, same kernel body. - The BLOCK_N dispatch in silu_and_mul_masked_post_quant_fwd came in with the minimax-m3 series (0663ebc, sgl-project#28715) and picks BLOCK_N = next_power_of_2(quant_group_size) * gpb, gpb in {4, 2, 1}, accepting gpb=4 whenever groups_total % 4 == 0. - The fp8-channel caller passes quant_group_size = moe_intermediate_size. MiniMax-M2.7 uses 1536, which is not a power of two, so next_power_of_2 rounds the group size up above size_n: groups_total becomes 0 and 0 % 4 == 0 selects gpb=4. BLOCK_N jumps from 2048 (0.5.13_rel) to 8192 while num_warps stays 1, so every token loads, computes and reduces 4x the lanes with only 1536 of 8192 useful, blowing up register pressure and dominating the EP MoE step. How: - Require groups_total >= gpb before aggregating groups, so groups_total == 0 keeps gpb == 1 and BLOCK_N stays next_power_of_2(quant_group_size), matching 0.5.13_rel. - No behavior change elsewhere: groups_total in {1, 2, 3} already resolved to the same gpb under the old condition, groups_total >= 4 still aggregates up to 4 groups, and the int8 / mxfp4 branches are untouched. Dependency: - None Test: - Unit: - None - E2E: - MiniMax-M2.7 E2E Perf test on DP + EP
…w2 MoE intermediate size Why: - MiniMax-M2.7 fp8 per-channel MoE (per_channel_quant=True -> _run_masked_fp8_channel_gemm) regressed on 0.5.17_rel: the masked activation Triton kernel _silu_and_mul_post_quant_kernel is materially slower than on 0.5.13_rel for the same shapes, same kernel body. - The BLOCK_N dispatch in silu_and_mul_masked_post_quant_fwd came in with the minimax-m3 series (0663ebc, sgl-project#28715) and picks BLOCK_N = next_power_of_2(quant_group_size) * gpb, gpb in {4, 2, 1}, accepting gpb=4 whenever groups_total % 4 == 0. - The fp8-channel caller passes quant_group_size = moe_intermediate_size. MiniMax-M2.7 uses 1536, which is not a power of two, so next_power_of_2 rounds the group size up above size_n: groups_total becomes 0 and 0 % 4 == 0 selects gpb=4. BLOCK_N jumps from 2048 (0.5.13_rel) to 8192 while num_warps stays 1, so every token loads, computes and reduces 4x the lanes with only 1536 of 8192 useful, blowing up register pressure and dominating the EP MoE step. How: - Require groups_total >= gpb before aggregating groups, so groups_total == 0 keeps gpb == 1 and BLOCK_N stays next_power_of_2(quant_group_size), matching 0.5.13_rel. - No behavior change elsewhere: groups_total in {1, 2, 3} already resolved to the same gpb under the old condition, groups_total >= 4 still aggregates up to 4 groups, and the int8 / mxfp4 branches are untouched. Dependency: - None Test: - Unit: - None - E2E: - MiniMax-M2.7 E2E Perf test on DP + EP
…w2 MoE intermediate size Why: - MiniMax-M2.7 fp8 per-channel MoE (per_channel_quant=True -> _run_masked_fp8_channel_gemm) regressed on 0.5.17_rel: the masked activation Triton kernel _silu_and_mul_post_quant_kernel is materially slower than on 0.5.13_rel for the same shapes, same kernel body. - The BLOCK_N dispatch in silu_and_mul_masked_post_quant_fwd came in with the minimax-m3 series (0663ebc, sgl-project#28715) and picks BLOCK_N = next_power_of_2(quant_group_size) * gpb, gpb in {4, 2, 1}, accepting gpb=4 whenever groups_total % 4 == 0. - The fp8-channel caller passes quant_group_size = moe_intermediate_size. MiniMax-M2.7 uses 1536, which is not a power of two, so next_power_of_2 rounds the group size up above size_n: groups_total becomes 0 and 0 % 4 == 0 selects gpb=4. BLOCK_N jumps from 2048 (0.5.13_rel) to 8192 while num_warps stays 1, so every token loads, computes and reduces 4x the lanes with only 1536 of 8192 useful, blowing up register pressure and dominating the EP MoE step. How: - Require groups_total >= gpb before aggregating groups, so groups_total == 0 keeps gpb == 1 and BLOCK_N stays next_power_of_2(quant_group_size), matching 0.5.13_rel. - No behavior change elsewhere: groups_total in {1, 2, 3} already resolved to the same gpb under the old condition, groups_total >= 4 still aggregates up to 4 groups, and the int8 / mxfp4 branches are untouched. Dependency: - None Test: - Unit: - None - E2E: - MiniMax-M2.7 E2E Perf test on DP + EP
…w2 MoE intermediate size Why: - MiniMax-M2.7 fp8 per-channel MoE (per_channel_quant=True -> _run_masked_fp8_channel_gemm) regressed on 0.5.17_rel: the masked activation Triton kernel _silu_and_mul_post_quant_kernel is materially slower than on 0.5.13_rel for the same shapes, same kernel body. - The BLOCK_N dispatch in silu_and_mul_masked_post_quant_fwd came in with the minimax-m3 series (0663ebc, sgl-project#28715) and picks BLOCK_N = next_power_of_2(quant_group_size) * gpb, gpb in {4, 2, 1}, accepting gpb=4 whenever groups_total % 4 == 0. - The fp8-channel caller passes quant_group_size = moe_intermediate_size. MiniMax-M2.7 uses 1536, which is not a power of two, so next_power_of_2 rounds the group size up above size_n: groups_total becomes 0 and 0 % 4 == 0 selects gpb=4. BLOCK_N jumps from 2048 (0.5.13_rel) to 8192 while num_warps stays 1, so every token loads, computes and reduces 4x the lanes with only 1536 of 8192 useful, blowing up register pressure and dominating the EP MoE step. How: - Require groups_total >= gpb before aggregating groups, so groups_total == 0 keeps gpb == 1 and BLOCK_N stays next_power_of_2(quant_group_size), matching 0.5.13_rel. - No behavior change elsewhere: groups_total in {1, 2, 3} already resolved to the same gpb under the old condition, groups_total >= 4 still aggregates up to 4 groups, and the int8 / mxfp4 branches are untouched. Dependency: - None Test: - Unit: - None - E2E: - MiniMax-M2.7 E2E Perf test on DP + EP
…w2 MoE intermediate size Why: - MiniMax-M2.7 fp8 per-channel MoE (per_channel_quant=True -> _run_masked_fp8_channel_gemm) regressed on 0.5.17_rel: the masked activation Triton kernel _silu_and_mul_post_quant_kernel is materially slower than on 0.5.13_rel for the same shapes, same kernel body. - The BLOCK_N dispatch in silu_and_mul_masked_post_quant_fwd came in with the minimax-m3 series (0663ebc, sgl-project#28715) and picks BLOCK_N = next_power_of_2(quant_group_size) * gpb, gpb in {4, 2, 1}, accepting gpb=4 whenever groups_total % 4 == 0. - The fp8-channel caller passes quant_group_size = moe_intermediate_size. MiniMax-M2.7 uses 1536, which is not a power of two, so next_power_of_2 rounds the group size up above size_n: groups_total becomes 0 and 0 % 4 == 0 selects gpb=4. BLOCK_N jumps from 2048 (0.5.13_rel) to 8192 while num_warps stays 1, so every token loads, computes and reduces 4x the lanes with only 1536 of 8192 useful, blowing up register pressure and dominating the EP MoE step. How: - Require groups_total >= gpb before aggregating groups, so groups_total == 0 keeps gpb == 1 and BLOCK_N stays next_power_of_2(quant_group_size), matching 0.5.13_rel. - No behavior change elsewhere: groups_total in {1, 2, 3} already resolved to the same gpb under the old condition, groups_total >= 4 still aggregates up to 4 groups, and the int8 / mxfp4 branches are untouched. Dependency: - None Test: - Unit: - None - E2E: - MiniMax-M2.7 E2E Perf test on DP + EP
…w2 MoE intermediate size Why: - MiniMax-M2.7 fp8 per-channel MoE (per_channel_quant=True -> _run_masked_fp8_channel_gemm) regressed on 0.5.17_rel: the masked activation Triton kernel _silu_and_mul_post_quant_kernel is materially slower than on 0.5.13_rel for the same shapes, same kernel body. - The BLOCK_N dispatch in silu_and_mul_masked_post_quant_fwd came in with the minimax-m3 series (0663ebc, sgl-project#28715) and picks BLOCK_N = next_power_of_2(quant_group_size) * gpb, gpb in {4, 2, 1}, accepting gpb=4 whenever groups_total % 4 == 0. - The fp8-channel caller passes quant_group_size = moe_intermediate_size. MiniMax-M2.7 uses 1536, which is not a power of two, so next_power_of_2 rounds the group size up above size_n: groups_total becomes 0 and 0 % 4 == 0 selects gpb=4. BLOCK_N jumps from 2048 (0.5.13_rel) to 8192 while num_warps stays 1, so every token loads, computes and reduces 4x the lanes with only 1536 of 8192 useful, blowing up register pressure and dominating the EP MoE step. How: - Require groups_total >= gpb before aggregating groups, so groups_total == 0 keeps gpb == 1 and BLOCK_N stays next_power_of_2(quant_group_size), matching 0.5.13_rel. - No behavior change elsewhere: groups_total in {1, 2, 3} already resolved to the same gpb under the old condition, groups_total >= 4 still aggregates up to 4 groups, and the int8 / mxfp4 branches are untouched. Dependency: - None Test: - Unit: - None - E2E: - MiniMax-M2.7 E2E Perf test on DP + EP
Part 4 (final) of the 4-PR split of #27944 (MiniMax-M3). The integration PR — the only split PR that moves forward / quant / attention behavior and carries the e2e validation. Splits 1-3 (#28712 / #28713 / #28714) have merged to
main; this PR is rebased on top.What's in this PR
minimax_m3.py,minimax_m3_vl.py,minimax_vl_common.py+ VL processor/configsattention_registry,minimax_sparse_backend,server_args,scheduler,template_detection,multi_tokenizer_mixin,serving_chat,warmup,forward_batch_info,utils/common,hf_transformers/common,reasoning_parserfunction_call/minimax_m3.py+function_call_parser.py+ detector test + template_manager testfp8.py,fp8_kernel.py,fp8_utils.py,deep_gemm_wrapper/entrypoint.py,mxfp8_block_convert.pyep_moe/kernels,topk,token_dispatcher/standard,layernorm,moe_fused_gate,per_token_group_quant_8bit(+ cuh/bench/test)deep_gemm.py,radix_attention.py— whole files (cross-bucket imports + generic hunk changes)environ— PR4-owned descriptors (the rest resolved on rebase from Splits 1/2)Review-driven changes (thanks @BBuf)
SGLANG_OPT_USE_JIT_PER_TOKEN_GROUP_QUANTdefaulted toFalse— it reroutes the generic fp8 path (every model, not just MiniMax); M3's deep_gemm MoE quant usesfuse_silu_and_mul=Truewhich selects V2 regardless, so the V1 JIT path is opt-in only. Renameduse_jit_quant→use_jit_per_token_group_v1_quant, extracted_run_per_token_group_quant_8bit_kernelhelper, addedand not _is_musaguards (V1 .cuh is CUDA-only).moe_runner_backendreverted to main'smarlin(carried in from Support MiniMax-M3 #27944, unrelated to M3).fused_topksigmoid branch restructured per review:assert→raise ValueError, singlescalehonoringapply_routed_scaling_factor_on_output.mxfp8allow-list in_moe_runner_backend_quant_constraintskeepsdeep_gemmfor M3 and whiteliststritonon AMD gfx950 (CDNA4 / MI355X, where flashinfer is unavailable);auto/override resolve totritonthere. (gfx950 fix co-authored with @zijiexia, who verified MI355X e2e.)e2e validation (4×GB300, M3-MXFP8)
</mm:think>leak regression: cleanpost_reorder_deepgemmunit test: 14 cases (rsf × pad × tokens) all passNotes
moe_runner_backendresolution moved toarg_groups/overrides.py(_moe_runner_backend_quant_constraints) on a recent main rebase; M3'smxfp8 + deep_gemmand gfx950mxfp8 + tritonare both preserved there.get_server_args()(runtime_context) instead of the legacyget_global_server_args(), matching main's ratchet direction.CI States
Latest PR Test (Base): ✅ Run #29085664737
Latest PR Test (Extra): ✅ Run #29085664528