Skip to content

[Fix] moe: Count routed_scaling_factor once when top-k folds it - #44901

Merged
ch-wan merged 5 commits into
mainfrom
cheng/hot-fix/moe-prefolded-routed-scaling
Oct 19, 2026
Merged

ch-wan merged 5 commits into
mainfrom
cheng/hot-fix/moe-prefolded-routed-scaling

Conversation

@ch-wan

@ch-wan ch-wan commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

This PR is part of a stack (oldest at bottom):

Motivation

DeepSeek-style MoE blocks scale the routed experts' output by routed_scaling_factor (rsf; 2.5 for DeepSeek-V3). Some quant methods and runners fold the factor into the top-k weights instead (FusedMoE.should_fuse_routed_scaling_factor_in_topk: ModelOpt NVFP4 on non-Marlin runners, FP8 on CUTLASS or FlashInfer TRT-LLM routed, unquantized TRT-LLM routed, and the MXFP4 methods that declare it), and on ROCm two aiter routes fold it on their own: grouped routing with a correction bias (aiter_biased_grouped_topk, or the unified Triton router) and ungrouped sqrtsoftplus routing (aiter topk_gating / rocm_router_gate, DeepSeek-V4's gate). Grouped routing without a bias, the other ungrouped routes and HashTopK do not. Once the factor is in the top-k weights, no later step may apply it again, and a fused shared expert's weight must not compensate for a multiply that never happens.

Each step after top-k decided this for itself, and six of them got it wrong (static reading at main):

  1. Two-batch overlap. DeepseekV2MoE.op_output, the TBO epilogue, skipped the multiply only on aiter, while forward_deepep also skips it when the quant method folds the factor. With --enable-two-batch-overlap, an all-to-all backend and a folding quant method (for example ModelOpt NVFP4 with --moe-a2a-backend deepep --moe-runner-backend flashinfer_cutedsl; nothing in server-arg resolution rejects it), every MoE layer's routed output was scaled by rsf², 6.25 instead of 2.5 for DeepSeek-V3.
  2. Per-rank shared-expert slot. remap_topk_for_per_rank_shared_slots gave the fused shared expert a weight of 1/rsf unless on aiter (its comment noted the gap). On the per-rank slot path (DeepEP-class and MegaMoE backends with --enforce-shared-experts-fusion) with a folding quant method nothing scales the output afterwards, so the always-on shared expert counted 0.4x in every MoE layer. DeepSeek-V4.1's vision routing worked around this for MegaMoE in [1/N][DSv41] Enable DP attention with MegaMoE #43228; the other paths did not.
  3. Waterfill. WaterfillBalancer always used 1/rsf, wrong both with a folding quant method and on aiter. --enable-waterfill turns shared-experts fusion back on even for runners that disable it (such as flashinfer_cutedsl), so ModelOpt NVFP4 with DeepEP and Waterfill reaches it.
  4. Unified grouped router on aiter. With SGLANG_OPT_USE_JIT_KERNEL_GROUPED_TOPK=1 on aiter, biased_grouped_topk_gpu folded the factor only when given fused shared slots, but since MiniMax-M3: allow shared-experts fusion on ROCm gfx942 and newer #36576 select_experts gives it none on aiter. The routed weights then lacked the factor while every later step assumed it, leaving the routed output 1/rsf too small.
  5. GLM, dots3 and bailing all-to-all epilogues on aiter. The forward_deepep of glm4_moe, glm4_moe_lite and the dots3 model, and bailing_moe_v3's all-to-all scaling, applied the multiply unless the quant method's fold flag was set, also on aiter where their biased grouped routing already folds it (GLM's gates always carry a bias). With a DeepEP-class backend on ROCm (mori, for glm4_moe) the routed output was scaled twice and a fused shared expert by rsf. For bailing_moe_v3 this became true for the unified router too once item 4 is fixed.
  6. Routes that do not fold, on aiter. DeepseekV2MoE treated every aiter route as folded. DeepSeek-V4's hash layers (HashTopK uses the factor only for the 1/rsf shared weight) and grouped routing without a correction bias (DeepSeek-V2's group_limited_greedy) therefore got the factor zero times on aiter, in forward_normal and the all-to-all epilogues alike.

Modifications

topk.py now owns the answer:

  • is_routed_scaling_factor_fused_in_topk(topk_config): whether the routed weights a TopK with that config emits already carry the factor: its fold flag, or on aiter one of the two folding routes above. A HashTopK folds only for its own flag.
  • fused_shared_expert_weight(routed_scaling_factor=..., fused_in_topk=...): the shared slot's weight derived from that answer (1.0 when folded or when there is no factor, else 1/rsf).

How each site decides now:

Site Before Now
DeepseekV2MoE.forward_deepep fold flag or aiter routed_scaling_factor_fused_in_topk, set once in __init__ from the router it built (the helper for TopK, the flag for HashTopK)
DeepseekV2MoE.forward_normal, forward_normal_dual_stream skip the multiply on aiter skip it on aiter only when that attribute says the route folded
DeepseekV2MoE.op_output (TBO) aiter only the same attribute, through the epilogue it now shares with forward_deepep (_scale_routed_and_add_shared)
MegaMoE, DeepGEMM and FlyDSL (_run_mega_routed) fold flag; fold flag or aiter the same attribute
DeepSeek-V4 TopK fold flag True on aiter, else fold flag unchanged; comment corrected (aiter's topk_gating folds anyway, the flag covers the other routes)
remap_topk_for_per_rank_shared_slots 1.0 on aiter, else 1/rsf fused_shared_expert_weight from the config's answer; HashTopK now passes its fold flag in that config
aiter fused append on the per-rank path literal 1.0 fused_shared_expert_weight (still 1.0 there)
WaterfillBalancer always 1/rsf takes shared_weight, which prepare_moe_topk computes with fused_shared_expert_weight from the TopK's answer or the HashTopK's flag
unified grouped router (biased_grouped_topk_gpu) folds on aiter only when given shared slots always folds on aiter, like aiter_biased_grouped_topk
DeepSeek-V4.1 vision routing on MegaMoE its own override of the shared weight to 1.0 removed; the remap now writes 1.0
forward_deepep of glm4_moe, glm4_moe_lite, dots3; bailing_moe_v3's all-to-all scaling fold flag the helper on their TopK config (same on CUDA)

Existing normalized CUDA routes without a folding quant method retain their scaling behavior. The per-token-bias route now retains the configured fold even without normalization and emits a routed-sum-independent shared coefficient. On aiter, routes that do not fold now get the factor once instead of zero times in DeepseekV2MoE. The global AITER append retains its existing default. DeepSeek's nonfolding global route supplies an inverse-rsf weight, multiplied by its existing EP replication factor, because that model restores rsf after the fused MoE output. One edge changes: with no rsf (None or 0) and no fold, the per-rank remap now writes 1.0 instead of leaving whatever the router put in the shared column.

The dynamic per-token-bias router now uses the same folding policy as the static biased AITER route. Its torch implementation applies an explicit fold even without normalization and writes the shared coefficient independently of the routed-score sum. The eager/fused remap equivalence test exercises folding and nonfolding configurations explicitly instead of inferring folding from the platform.

Accuracy Tests

Unit tests:

  • test/registered/unit/layers/moe/test_fused_shared_expert_scaling.py (CPU), extended:
    • Waterfill's shared weight, built through prepare_moe_topk, and the per-rank slot's are 1.0 exactly where the route folded: post-MoE multiply, folded by the flag, aiter biased grouped routing, and aiter grouped routing without a bias (1/rsf);
    • DeepseekV2MoE.__init__ (built with a stub gate and experts) derives routed_scaling_factor_fused_in_topk from the experts' fold flag and the route: biased or unbiased grouped routing, DeepSeek-V4's gate, and hash layers;
    • the TBO epilogue (DeepseekV2MoE.op_output) scales the routed output once, with and without a shared output.
  • test/registered/kernels/ops/moe/test_jit_grouped_topk.py (GPU): on aiter the unified router's routed weights carry rsf when given no shared slots; elsewhere they do not unless asked.
  • test/registered/moe/test_hash_topk.py: the per-rank shared slot of a HashTopK keeps 1/rsf on aiter too.
  • Stubs that model a DeepseekV2MoE gained the new attribute: test_mega_moe_deepgemm_api.py, test_mega_moe_decode_capacity.py, test/registered/moe/test_hash_topk.py.

Runs on one 8x H200 machine (Python 3.12, torch 2.14.1), each file in its own process, CPU-registered files with the GPUs hidden and CUDA-registered files on one GPU:

  • Before the fix (86e67f7d93 with only the first commit's new tests applied): test_fused_shared_expert_scaling.py ran 9 tests with 4 failures and 1 error, exactly the new cases: the folded per-rank slot (0.4 instead of 1.0), Waterfill when folded and on aiter (0.4 instead of 1.0), and both folded TBO epilogue cases. test_jit_grouped_topk.py: the aiter case failed and the other passed (1 failed, 7 passed, 4 skipped single-group cases that CUDA does not route).
  • After the first commit (on 86e67f7d93), 91 unit test files for these areas (unit/layer_boundary/, unit/layers/moe/, unit/models/test_deepseek_*.py, unit/models/test_shared_experts_fusion_gates.py, unit/eplb/, unit/batch_overlap/, unit/lora/test_lora_moe_inplace_unit.py, moe/test_topk_*.py) all exited 0: 626 unittest and 186 pytest cases passed, 13 skipped by platform gates.
  • First commit's head (e19a5728f8): the targeted files (the tests above, test_fused_append_remap_per_rank_shared_slots.py on GPU, test_stage_producers_leave_sums.py, test_moe_output_reduction.py, the GLM and dots tests) all passed.
  • Second commit (route-aware answer), its new tests against the first commit's code: test_fused_shared_expert_scaling.py failed exactly the route cases (aiter grouped routing without a bias in the construction test and in the shared-weight table, and an aiter DeepSeek-V4 hash layer; 3 failures), and test_hash_topk.py's aiter case failed (shared weight 1.0 instead of 0.4).
  • Second commit's head (bd4de12cfa): the same 13 targeted files all passed, glm4_moe, glm4_moe_lite, dots3 and bailing_moe_v3 import, and the same 91 area files as above all exited 0 (626 unittest and 186 pytest cases passed). The tested tree is identical to the pushed commit's.

Owed, not run:

  • A GSM8K and long-generation accuracy A/B on a folding configuration under TBO, for example DeepSeek-V3/R1 ModelOpt NVFP4 with --moe-a2a-backend deepep --moe-runner-backend flashinfer_cutedsl --enable-two-batch-overlap.
  • The same A/B with fused shared experts on the per-rank path: --moe-a2a-backend megamoe --enforce-shared-experts-fusion, or DeepEP with --enable-waterfill.
  • ROCm aiter runs: DeepSeek-V3 with SGLANG_OPT_USE_JIT_KERNEL_GROUPED_TOPK=1, DeepSeek-V4 (hash layers), and a GLM-4.5-class Glm4MoeForCausalLM with --moe-a2a-backend mori. No unit test reaches the GLM, dots3 and bailing all-to-all epilogues.

Additional regression validation at 6c1b70e909 (one H200; torch 2.14.1+cu130, FlashInfer 0.7.2, Triton 3.8.0, sgl-kernel 0.4.10):

  • All 12 scaling unit tests pass with GPUs hidden. Running the new cases against the previous production code produces six numerical failures: dynamic bias with/without normalization, unnormalized dynamic shared coefficients in both folding modes, and DeepSeek's unbiased global shared route at EP1/EP4.
  • All six registered CUDA append/remap tests pass. With the AITER policy flag forced while using real CUDA kernels, the previous equivalence test fails in seven cases; the repaired test passes in both folding modes. This checks policy compatibility, not AMD execution.
  • Applicable pre-commit hooks pass.

Speed Tests and Profiling

No kernel changes. The decisions are made once at construction or read from a stored boolean, as before.

Related

Checklist

CI fixture follow-up at cdf052beb7

The ROCm 10 MI35x job failed at faaf4bb4e6 because test_deepseek_v4_amd_tp4.py::_moe_stub bypasses DeepseekV2MoE.__init__ and omitted routed_scaling_factor_fused_in_topk. The stub now supplies that construction state. The other five test files in that partition passed. This fixes the test fixture; production executable behavior is unchanged.

On one H200, the original fixture reproduces the same AttributeError with the AITER epilogue flags forced; after repair, all four normal/dual-stream and shared-TP1 combinations pass through the actual forward methods. This checks the Python fixture/epilogue contract, not native ROCm collectives or graph replay. The complete AMD TP4 file still needs CI validation.

TopKConfig documents its total ordinary output width and the global AITER absolute shared coefficient versus the standard-path multiplier. Eager/fused subtest labels include the folding mode. Duplicate ordinary model-context scopes and a derived routed-count property remain optional follow-ups. Applicable pre-commit hooks pass. No native AMD rerun or model accuracy result is claimed for this follow-up.

Landing cleanup (2026-10-19)

Current head: 8f95be3e63, rebased onto current main.

Trimmed repetitive comments and docstrings while retaining scaling and layout contracts. The cleanup preserves the executable AST; configured pre-commit checks passed. Earlier test results above retain their recorded revisions. No new accelerator or full-model accuracy run is claimed for this cleanup/rebase.

The final PR in this stack moves the extended routing matrix and compile-context check to opt-in Extra coverage while keeping the focused regressions on the normal path.


CI States

Latest PR Test (Base): ❌ Run #39344262524
Latest PR Test (Extra): ❌ Run #39344262390
Latest PR Test (AMD ROCm 10): ❌ Run #39344262320

@xutizhou

Copy link
Copy Markdown
Collaborator

Adding an A/B for the Waterfill item (#3 in the description) that the PR lists as owed: "DeepEP with --enable-waterfill".

Setup. 8x H20-3e, DeepSeek-V3 (FP8 checkpoint), --tp 8 --ep-size 8 --moe-a2a-backend deepep --deepep-mode auto --moe-runner-backend deep_gemm --enable-waterfill, image lmsysorg/sglang:nightly-dev-cu13-20261019-3c560089 with main @ 617c8b6 mounted. Hopper cannot run the ModelOpt NVFP4 configuration, so I exercised the folded branch with a test-only switch that makes _fuses_routed_scaling_factor_in_topk() return True. On FP8 + deep_gemm + DeepEP that is mathematically equivalent to the real folding quant methods: the router multiplies the routed weights by routed_scaling_factor (checked: row sums go 1.0 -> 2.5) and forward_deepep skips the epilogue multiply, so the only remaining question is the shared slot's weight.

Two metrics: GSM8K (sglang.test.few_shot_gsm8k, 1319 questions, 5-shot) and a teacher-forced probe (mean NLL per token over the answer region of 64 GSM8K examples, 13,252 tokens; greedy continuations of 20 prompts).

Case code rsf folded in top-k Waterfill shared slot weight GSM8K acc NLL / token
reference main no off (shared expert computed separately) – 0.938 0.7056
default FP8 path fixed no on 0.4 (= 1/rsf) 0.933 / 0.938 (two runs) 0.7037
folded, unfixed main yes on 0.4 0.936 / 0.935 (two runs) 0.8579
folded, fixed fixed yes on 1.0 0.936 0.7016

"fixed" = WaterfillBalancer(shared_weight=fused_shared_expert_weight(routed_scaling_factor=..., fused_in_topk=is_routed_scaling_factor_fused_in_topk(topk_config))) in prepare_moe_topk, i.e. the same change as this PR's waterfill.py / moe_ep_setup.py hunks (I carried the two helpers over with the same names and semantics).

Observations:

  • The 0.4x shared expert is invisible to GSM8K accuracy: all four configurations land in 0.933–0.938, run-to-run noise. Please don't use GSM8K alone to sign this one off.
  • The teacher-forced probe sees it clearly: +21.6% NLL/token (0.706 -> 0.858), and every one of the 64 examples gets worse (+31.5 +/- 11.7 nats per example). Greedy outputs stay fluent, which is why accuracy does not move.
  • With the shared weight derived from the fold, the folded path is back at the reference (0.7016 vs 0.7056; per-example differences are +/- 2.6 nats around zero, half better, half worse, i.e. bf16/FP8 kernel noise). The default non-folding path is unchanged by the fix (shared weight stays 1/rsf), as expected.

I'll also add a GPU regression test for Waterfill (test/registered/moe/test_waterfill.py: expansion layout, rank selection, shared weight in both scaling modes, and a DeepseekV2MoE.forward_deepep end-to-end check with a linear stand-in for the experts) so that later changes around top-k / epilogue scaling trip a test instead of a silent 0.4x.

@xutizhou

Copy link
Copy Markdown
Collaborator

Follow-up: the Waterfill regression test mentioned above is #45088 (test only, depends on this PR; on current main exactly the folded-mode cases fail, with this PR's Waterfill change it passes on H20).

Some quant methods and runners fold routed_scaling_factor into the top-k
weights (FusedMoE.should_fuse_routed_scaling_factor_in_topk: ModelOpt NVFP4
on non-Marlin runners, FP8 on CUTLASS or TRT-LLM routed, unquantized
TRT-LLM routed), and aiter's grouped router folds it on its own. Several
steps after top-k decided separately whether that had happened, and some
got it wrong:

- DeepseekV2MoE.op_output, the two-batch-overlap epilogue, checked only
  aiter, so it scaled a folded routed output a second time.
- remap_topk_for_per_rank_shared_slots gave the fused shared expert
  1/routed_scaling_factor unless on aiter, so with a folding quant method
  the shared expert counted 1/rsf instead of once.
- WaterfillBalancer used 1/routed_scaling_factor always, wrong on aiter
  and with a folding quant method.
- The forward_deepep of glm4_moe, glm4_moe_lite and dots3 checked only
  the quant method's fold flag, not aiter, while their own forward_normal
  and the shared-slot weight treat aiter as folded.

The opt-in unified grouped router (SGLANG_OPT_USE_JIT_KERNEL_GROUPED_TOPK)
on aiter folded the factor only when given shared slots, which
select_experts no longer passes on aiter, so the factor was lost there.

topk.is_routed_scaling_factor_fused_in_topk now answers the question once
(the fold flag, or aiter), and fused_shared_expert_weight derives the
shared slot's weight from it. DeepseekV2MoE stores the answer at
construction; forward_deepep and op_output share one epilogue that reads
it, and MegaMoE reads it too. The per-rank remap, the aiter fused append,
Waterfill, the unified router and the GLM and dots3 epilogues use the same
helpers. The DeepSeek-V4.1 vision routing no longer needs its own
shared-weight override on MegaMoE.
The first change answered "are the routed top-k weights already scaled by
routed_scaling_factor?" with "the quant method's fold flag, or aiter". The
fold flag covers the quant methods and runners that fold the factor in
top-k (ModelOpt NVFP4 on non-Marlin runners, FP8 on CUTLASS or TRT-LLM
routed, unquantized TRT-LLM routed, and the MXFP4 FlashInfer TRT-LLM,
FlashInfer CUTLASS and Marlin methods that declare it). But on aiter only
two routes fold the factor whatever the flag says: grouped routing with a
correction bias (aiter_biased_grouped_topk, or the unified Triton router)
and ungrouped sqrtsoftplus routing (aiter topk_gating or rocm_router_gate).
Grouped routing without a bias, the other ungrouped routes and HashTopK do
not, so on aiter they lost the factor wherever a later step trusted the
answer.

- is_routed_scaling_factor_fused_in_topk now takes the TopKConfig and
  answers for the route it describes; fused_shared_expert_weight takes
  that answer.
- DeepseekV2MoE reads the answer from the router it built. HashTopK layers
  fold only for their flag, which fixes DeepSeek-V4 hash layers on aiter.
  forward_normal and forward_normal_dual_stream now multiply on aiter when
  the route did not fold, instead of whenever aiter is on.
- HashTopK passes its fold flag to the per-rank shared-slot remap, so its
  shared weight comes from the same answer.
- bailing_moe_v3's all-to-all epilogue uses the helper like the GLM and
  dots3 ones; the unified router now folds on aiter, so its old check
  would have scaled that path twice.
- The DeepSeek-V4 TopK keeps folding on aiter, with an accurate comment.
@ch-wan
ch-wan force-pushed the cheng/hot-fix/moe-prefolded-routed-scaling branch from cdf052b to 8f95be3 Compare October 19, 2026 23:52
@ch-wan
ch-wan merged commit 3a059de into main Oct 19, 2026
106 of 123 checks passed
@ch-wan
ch-wan deleted the cheng/hot-fix/moe-prefolded-routed-scaling branch October 19, 2026 23:54
detain pushed a commit to detain/sglang that referenced this pull request Oct 29, 2026
…project#44901)

cherry-pick -x 3a059de. PORT ORDER: landed FIRST in this family because
its merged mainline chronology makes it the strict precondition of sgl-project#44904
(main merges: sgl-project#44901 16:54 -> sgl-project#44904 16:56 -> sgl-project#44912 16:57 -> sgl-project#44913 16:59;
sgl-project#44904's diff preimage literally consumes fused_shared_expert_weight /
is_routed_scaling_factor_fused_in_topk introduced here, and sgl-project#44913's preimage
deletes the helpers sgl-project#44912 introduces -- the only cherry-pickable order is the
mainline one; later commits in this family build on earlier ones).

Conflict resolutions (2):
  * models/deepseek_v2.py: the pick's hunk anchors on `class
    MergedMoEOutput(MoEOutput)` as CONTEXT, but that class is upstream
    sgl-project#44657 content this fork never took (review-record skip family), so the
    region conflicted empty-vs-context. Kept ONLY sgl-project#44901's own addition:
    _scale_routed_and_add_shared (verified byte-identical to the upstream
    post-image function text) plus all nine routed_scaling_factor_fused_in_topk
    sites (two __init__ sets, the aiter shared-scaling compensation block,
    two non-CUDA post-MoE-multiply guards, and the forward_deepep/state-based
    forward replacements). Nothing in this tree references MergedMoEOutput;
    did not smuggle it in.
  * test/registered/unit/layers/moe/test_mega_moe_decode_capacity.py:
    modify/delete conflict (the file was created by sgl-project#44657, absent here;
    sgl-project#44901 merely touched 1 line inside it). Kept it deleted.

Fidelity: 7/12 python files are BYTE-IDENTICAL to the upstream post-image
(topk.py, hash_topk.py, waterfill.py, moe_ep_setup.py, glm4_moe.py,
glm4_moe_lite.py, vl_routing.py). mega_moe/mega_moe_flydsl/bailing_moe_v3/
dots3 landed inside files carrying nemotron-era (sgl-project#44518) upstream drift we do
not have; their fold changes were diff-reviewed line by line (old
`should_fuse_routed_scaling_factor_in_topk or _use_aiter` predicates replaced
by the new config-level predicate; residual old-form reads match upstream's
own post-image -- legitimately kept). WaterfillBalancer's routed_scaling_factor
kwarg became shared_weight (one production constructor, moe_ep_setup, updated
by the pick; no other call sites in the tree).

Battery (CVD=9) vs base 0df18e9: test_fused_shared_expert_scaling.py
6P -> 12P/25sub (upstream's +343-line fold matrix, green); test_hash_topk.py
3P -> 4P; test_jit_grouped_topk 10S -> 12S (CUDA-gated adds); every pin held
(standard_dispatcher_runner_backend 12P, smallm 28P/66sub, b12x 67P/46sub,
kernels_namespace 76P, model_overrides 111P/1S/179sub, modelopt suites exact).
Pre-existing classes unchanged (trtllm-gen 8F, fused_gate 46F, round-robin 37F,
triton-fused-moe 96F, mem_pool_ep 1F, mega_moe_deepgemm 1F).
ruff --select=F401,F821,F823,UP037 + ruff format clean on all 18 files.

Fold-predicate e2e on real NVFP4 weights stays GPU-window debt (the standalone
CPU contract probe follows this family as its own commit).

(cherry picked from commit 3a059de)

Co-authored-by: port-moe-contracts subagent
(cherry picked from commit c4511d515077ea018200eeccfff256cc25ff18e5)
detain pushed a commit to detain/sglang that referenced this pull request Oct 29, 2026
…ts only (sgl-project#44904)

cherry-pick -x 48ea486. Landed second of the renorm pair: mainline makes
sgl-project#44904 build ON TOP of sgl-project#44901 (its diff preimage contains fused_shared_
expert_weight/is_routed_scaling_factor_fused_in_topk), so the family follows
the mainline order (see sgl-project#44901's note here).

Wrong-output class: before this, the fused per-rank shared-slot paths asked
the gate for top_k while num_fused_shared_experts markers also went through,
renormalizing over one routed expert too many (routed sum 0.85-0.97 on CUDA).

Conflict resolution (1): test/registered/kernels/ops/moe/test_moe_fused_gate.py
carries pre-sgl-project#44690 docstring drift here (this fork never took sgl-project#44518's
nemotron rework of that file's header); took the upstream post-image's new
one-line docstring, kept this fork's file otherwise. The _reference_gate
renorm-order fix, the renorm x apply_scale parametrization, and the
production-impl comparison changes all landed.

Fidelity: topk.py, hash_topk.py, state_capturer/routed_experts.py,
test_fused_append_remap_per_rank_shared_slots.py and
test_fused_shared_expert_scaling.py are BYTE-IDENTICAL to the upstream
post-image; _router_triton_kernel (kernels/ops/moe/moe_fused_gate.py) verified
byte-identical function body against upstream (the file's OTHER regions carry
this fork's jit-loader drift and were not touched); fused_moe_triton_kernels.py
hunks are docstring-only.

Battery (CVD=9) vs sgl-project#44901 commit: test_fused_shared_expert_scaling 12P ->
13P/25sub; waterfill_eplb 4P -> 5P; scattered_a2a 3P -> 5P/2sub (the new
routed-width capture case); hash_topk 4P held; append 6S -> 9S, padded 10S ->
11S (CUDA-gated adds); moe_fused_gate 46F -> 52F = the file's pre-existing
no-CUDA class +6 new parametrized members (collects clean; needs hardware);
kernels_namespace 76P (inventory guard, nothing added/removed); every other
pin exact. ruff + format clean on all 11 files.

Remap-on-CUDA numerics (torch.cat width change + padded-row remask) remain
GPU-window debt, pinned at contract level by the fold-predicate probe commit
that follows.

(cherry picked from commit 48ea486)

Co-authored-by: port-moe-contracts subagent
(cherry picked from commit 0432a8fce0b4919b4a85fc6d3e724c6de30189be)
detain added a commit to detain/sglang that referenced this pull request Oct 29, 2026
…lass NVFP4 route

Standalone CPU contract test pinning WHAT WE NOW BELIEVE after the two
fold fixes (sgl-project#44901 + sgl-project#44904) land on our production FP4 arm --
ModelOptNvFp4FusedMoEMethod on a FLASHINFER_CUTLASS runner, the backend
auto resolves to on SM110/SM120 (and the value entry 230's fork patch
publishes for it):

  * FusedMoE._fuses_routed_scaling_factor_in_topk answers True for NVFP4
    on every non-Marlin runner (and False on Marlin) -- the predicate
    list sgl-project#44901's shared-slot fix keys off; no NVFP4 class-level
    fuse_routed_scaling_factor_in_topk flag exists, so the verdict comes
    from the runner arm.
  * topk.is_routed_scaling_factor_fused_in_topk (sgl-project#44901) agrees via
    apply_routed_scaling_factor_on_output.
  * topk.fused_shared_expert_weight (sgl-project#44901) therefore writes the fused
    shared column 1.0 -- not 1/routed_scaling_factor -- exactly where
    sgl-project#44904 moved the kernel/append fill to AFTER routed renormalization.

8 passed here (import/config level only, register_cpu_ci base-a-test-cpu).
Red proof of newness: is_routed_scaling_factor_fused_in_topk and
fused_shared_expert_weight do not exist at the port base (0df18e9),
so the import alone fails pre-pick -- this file cannot pass without
sgl-project#44901/sgl-project#44904 landed.

GPU-WINDOW DEBT, explicitly NOT covered here: the end-to-end
double-scale numeric check (generate against a single-scale reference on
4xSM120 with the real checkpoint; sgl-project#44901's routed-sum 0.85-0.97 symptom
and the shared-contribution 1.0x property only OBSERVABLE on hardware).
Test-side of the same property: kernels/ops/moe/test_moe_fused_gate.py's
new renorm x apply_scale parametrizations are CUDA-gated (52F here, the
file's pre-existing device class at base was 46F -- same class, +6 new).

Co-authored-by: port-moe-contracts subagent
(cherry picked from commit 22ae8e7e609fce945cdc85d3f985a253cfba1aa2)
detain pushed a commit to detain/sglang that referenced this pull request Oct 29, 2026
…gl-project#44912)

cherry-pick -x 964ab2b. Applies CLEANLY (zero conflicts): the fork never
edited topk.py/hash_topk.py since merge sgl-project#4, and sgl-project#44901/sgl-project#44904's post-images
(landed in the two preceding commits) exactly reconstruct upstream's preimage
at the touched lines.

Semantics: TopKConfig.num_fused_shared_experts (the count of fused shared
experts) splits into num_shared_topk_slots (the trailing columns the ROUTER
writes -- zero when Waterfill appends the shared expert after routing), and
TopK.__init__ now derives top_k for the config as routed-per-token + slots
instead of mutating the caller's numbers. New helpers waterfill_
routes_shared_experts / shared_topk_slots live in moe/utils.py; the
enable_waterfill branch stops silently redefining top_k (waterfill now forces
STANDARD output format explicitly).

Entry-247 interaction preserved: b12x's _keeps_global_expert_ids and the
slot-count vocabulary this family touched before were not in sgl-project#44912's hunk
regions; moe/utils.py post-image differs from upstream ONLY by the 7 b12x
enum/predicate lines already in the tree (git diff vs 964ab2b: +7/-0),
so the split landed upstream-identical and test_b12x_w4a16_route stayed
67P/46sub unchanged.

test_manual NPU file (test_npu_topk.py) took the 1-line rename too (hardware_
backend/npu/moe/topk.py likewise); NPU paths are inert here.

Battery (CVD=9) vs sgl-project#44904: waterfill_eplb 5P -> 6P/2sub (upstream's new
waterfill slot case); everything else byte-stable (scaling 13P/25sub, hash 4P,
append 9S, padded 11S, jit 12S, smallm 28P/66sub, dispatcher 12P, b12x
67P/46sub, kernels_namespace 76P, trtllm-gen 8F class). ruff + format clean.

NUM_SHARED_TOPK_SLOT wiring at runtime (per-rank append widths on real
dispatch) stays GPU-window debt, same as the renorm pair above.

(cherry picked from commit 964ab2b)

Co-authored-by: port-moe-contracts subagent
(cherry picked from commit ad00ea48739c10c0345f572bed8d8aad818c0169)
detain added a commit to detain/sglang that referenced this pull request Oct 30, 2026
…ies 28,165,166,186,194,206,296,297,299,300

PER-MARKER TRUTH (census: B5 markers in this set now ZERO):
  kernels/aot topk.cu (28): VERIFIED-ABSORBED -- upstream's in-range
  legacy_radix_topk::select rewrite gates the threshold-bin population
  against the stash BEFORE staging (coarse_population >= StashEntries ->
  full-row byte-per-pass refinement), structurally fixing the overflow
  the fork patched after-the-fact; jit twin keeps its s_prefix_bins.
  kernels/ops/attention/utils.py (194): VERIFIED-ABSORBED -- the function
  re-sited to ops/attention/kv_indices.py (spec_kv_index_token_blocks with
  the 32768 gate) and every consumer imports it at that module; the saved
  utils re-export would be a dead hunk (zero importers).
  elementwise/elementwise.py (166): VERIFIED-PRESENT -- the batch-invariant
  num_warps clamp rides upstream's guard_or_false refactor ('or
  is_batch_invariant_mode_enabled()' intact).
  kvcache/__init__.py (186): VERIFIED-PRESENT -- the store_cache_fp8
  registry entry is the merge commit's own graft (entry 310), imports +
  capabilities in place.
  moe/hash_topk.py + moe/topk.py (296/297/299/300): the merged upstream
  canonicals of sgl-project#44901/sgl-project#44904/sgl-project#44912/sgl-project#44913 are IN RANGE (all four
  MERGED commits proven ancestors of 2a6c204); every fork symbol
  (fused_shared_expert_weight, is_routed_scaling_factor_fused_in_topk,
  num_shared_topk_slots x54, has_per_rank_fused_shared_slots,
  SharedExpertsConfig wiring) verified present. Markers stale.
  moe/flashinfer_trtllm.py (165): SUPERSEDED-BY-EQUIVALENT -- upstream's
  own in-range rewrite of the deferred block covers the routed triple
  (do_finalize=not defer + _make_deferred_finalize_output) fail-LOUDER
  (valid_deferred_input raise); the fork's sgl-project#6077 ignored-autotune-buffer
  targeted the OLD block shape and is deliberately NOT restored (GPU
  perf-only, defer+routed is not our production arm).
  moe/mega_moe.py (296): deep_gemm transform_sf redirect RE-PLANTED to the
  guarded wrapper (import-safety contract; sibling fix in dsv4 below).
  fused_moe_triton/layer.py (165/206/239): _built_runner_backend hunk
  ABSORBED by sgl-project#44993 (config carries the built runner); the entry-206
  supports_routed_deferred_finalize property RE-PLANTED (upstream's
  packed_nvfp4_routed_deferred covers TRTLLM only, NOT trtllm_routed --
  our runner arm), with the sgl-project#44993 2-arg fold predicate; the debug-log OR
  re-plant paired.
  models/deepseek_v2.py (296/297/300/236): fold chain verified; the
  entry-206 format-ternary consumer arm RE-PLANTED (BYPASSED ->
  supports_deferred_finalize, else supports_routed_deferred_finalize --
  restoring what sgl-project#44993's rewrite dropped).

Battery ACTUALS (CVD=9, prod venv, PYTHONPATH=python, import sglang OK):
  fused_shared_expert_scaling + mega_moe_deepgemm_api + moe_runner_
  extensions + topk_routing_inputs + model_overrides + kernels_namespace:
  GREEN (see B5-4 commit for full 43-file battery actuals)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants