Skip to content

[minimax-m3] Split 4/4: model + VL + glue + function-call + fp8 quant + generic infra - #28715

Merged
BBuf merged 39 commits into
sgl-project:mainfrom
JustinTong0323:minimax-m3-split-pr4
Jul 11, 2026
Merged

BBuf merged 39 commits into
sgl-project:mainfrom
JustinTong0323:minimax-m3-split-pr4

Conversation

@JustinTong0323

@JustinTong0323 JustinTong0323 commented Jun 19, 2026 •

Copy link
Copy Markdown
Collaborator

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

  • Models + VL: minimax_m3.py, minimax_m3_vl.py, minimax_vl_common.py + VL processor/configs
  • Glue: attention_registry, minimax_sparse_backend, server_args, scheduler, template_detection, multi_tokenizer_mixin, serving_chat, warmup, forward_batch_info, utils/common, hf_transformers/common, reasoning_parser
  • Function-call: function_call/minimax_m3.py + function_call_parser.py + detector test + template_manager test
  • fp8 quant group: fp8.py, fp8_kernel.py, fp8_utils.py, deep_gemm_wrapper/entrypoint.py, mxfp8_block_convert.py
  • Generic MoE infra: ep_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_QUANT defaulted to False — it reroutes the generic fp8 path (every model, not just MiniMax); M3's deep_gemm MoE quant uses fuse_silu_and_mul=True which selects V2 regardless, so the V1 JIT path is opt-in only. Renamed use_jit_quant → use_jit_per_token_group_v1_quant, extracted _run_per_token_group_quant_8bit_kernel helper, added and not _is_musa guards (V1 .cuh is CUDA-only).
  • GPT-OSS SM120+MXFP4 moe_runner_backend reverted to main's marlin (carried in from Support MiniMax-M3 #27944, unrelated to M3).
  • fused_topk sigmoid branch restructured per review: assert → raise ValueError, single scale honoring apply_routed_scaling_factor_on_output.
  • mxfp8 allow-list in _moe_runner_backend_quant_constraints keeps deep_gemm for M3 and whitelists triton on AMD gfx950 (CDNA4 / MI355X, where flashinfer is unavailable); auto/override resolve to triton there. (gfx950 fix co-authored with @zijiexia, who verified MI355X e2e.)

e2e validation (4×GB300, M3-MXFP8)

  • GSM8K (1319, no-thinking): acc 95.2%, stop-rate 97.4%, 0 errors
  • AIME26 (thinking, 480 requests): pass@1 80.8%, pass@16 86.7%, 0 errors
  • Multi-turn </mm:think> leak regression: clean
  • V1 vs V2 per-token-group quant benchmark (B200 + H100, 96 configs each): V1 ≥ V2 in all 192 configs, median 1.10-1.13×, max 1.86×
  • post_reorder_deepgemm unit test: 14 cases (rsf × pad × tokens) all pass

Notes

  • The moe_runner_backend resolution moved to arg_groups/overrides.py (_moe_runner_backend_quant_constraints) on a recent main rebase; M3's mxfp8 + deep_gemm and gfx950 mxfp8 + triton are both preserved there.
  • M3 model files use get_server_args() (runtime_context) instead of the legacy get_global_server_args(), matching main's ratchet direction.

CI States

Latest PR Test (Base): ✅ Run #29085664737
Latest PR Test (Extra): ✅ Run #29085664528

@JustinTong0323

Copy link
Copy Markdown
Collaborator Author

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 main independently. So until the dependency lands on main, this branch's CI is expected to fail at import time on PR1/2/3 的符号 (symbols defined in the dependency PR). That failure is expected, not a real regression. Plan: land the dependency on main, then rebase this PR on main and re-run CI; the import resolves and CI should go green.

@JustinTong0323
JustinTong0323 marked this pull request as draft June 19, 2026 07:08
@shanemort1982

Copy link
Copy Markdown
Contributor

Heads-up on the function-call detector in this split, from production use of an M3 build that includes it. MinimaxM3Detector._schema_types / _get_array_item_schema only read a top-level type. A nullable array argument (Pydantic Optional[list], rendered as anyOf:[{type:array,...},{type:null}] with no top-level type) is therefore treated as a scalar, and under streaming the native list markup (]<]minimax[>[<item>...) leaks verbatim into the argument value instead of parsing to a JSON list.

We fixed it by looking through anyOf/oneOf to resolve the element type and item schema. One caveat that is easy to get wrong: only treat a union as a container when it is unambiguous (a single non-null branch type). A genuinely mixed union such as Union[str, list[str]] (anyOf:[{string},{array}]) must NOT be forced down the array path, or when the model emits the scalar branch the value is silently dropped.

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.

@sgl-project sgl-project deleted a comment from gemini-code-assist Bot Jun 20, 2026
@JustinTong0323

Copy link
Copy Markdown
Collaborator Author

/rerun-failed-ci

@BBuf
BBuf merged commit 0663ebc into sgl-project:main Jul 11, 2026
273 of 301 checks passed
arathi-hlab pushed a commit to arathi-hlab/sglang that referenced this pull request Jul 13, 2026
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).
arathi-hlab added a commit to arathi-hlab/sglang that referenced this pull request Jul 14, 2026
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.
dougyster added a commit to dougyster/sglang that referenced this pull request Jul 20, 2026
…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>
Chronostasys pushed a commit to MindLab-Research/sglang that referenced this pull request Aug 24, 2026
… + 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>
thmyl added a commit to flytiger-eco/sglang-for-sail that referenced this pull request Sep 20, 2026
…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)
Vinsmoke86 added a commit to t-head/sglang-for-sail that referenced this pull request Oct 13, 2026
…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
Vinsmoke86 added a commit to t-head/sglang-for-sail that referenced this pull request Oct 13, 2026
…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
Vinsmoke86 added a commit to t-head/sglang-for-sail that referenced this pull request Oct 16, 2026
…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
Vinsmoke86 added a commit to t-head/sglang-for-sail that referenced this pull request Oct 19, 2026
…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
Vinsmoke86 added a commit to t-head/sglang-for-sail that referenced this pull request Oct 21, 2026
…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
Wuulayaha added a commit to flytiger-eco/sglang-for-sail that referenced this pull request Oct 26, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bypass-fastfail jit-kernel quant LLM Quantization run-ci CI: run the baseline test suite on this PR run-ci-extra CI: also run the extra suite (requires run-ci)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants