[SM90] Optimize FP8 MegaMoE: fused shared expert, interleaved scheduler, L2 epilogue, 4WG heuristic and decode swap-AB - #69
Conversation
|
@Fridge003 Could you please take a look when you get a chance? |
fix and add cache hint
Replay of PR sgl-project#69 onto the re-based `dev` branch (merge-base reset from the old deepseek-ai#364 lineage to current upstream `dev`). The upstream rewrite made SM90 keep a `LegacyMegaMoEScheduler` while adding the SM100 producer-warp `TaskInfo` scheduler; this commit re-integrates the SM90 work on that base. What this brings (the PR's original scope, unchanged): * SM90 MegaMoE moves from the wave-based scheduler to an SM100-style producer-warp interleaved scheduler. A dedicated producer warp claims L1/L2 tasks from two global atomic counters and interleaves them 1:1 after a warmup phase, so an early pool block's L2 tiles overlap the L1 compute of later blocks instead of waiting for a whole L1 wave to drain. * The `kNumExpertsPerWave` / ring-token heuristic is dropped from the SM90 config and host, since the interleaved scheduler needs no wave bookkeeping. * Fused shared experts (`kNumSharedExperts > 0`) on SM90: two extra phases (`SharedLinear1` / `SharedLinear2`) with their own task counters, wired through config, host descriptors, the TMA loader, the L1/L2 epilogues, and the python / FF buffers. Merge notes: * `scheduler/mega_moe.cuh`: kept upstream's `TaskInfo<>` / producer `MegaMoEScheduler` / `LegacyMegaMoEScheduler` (needed by the SM100 kernels) and appended the SM90 `SM90MegaMoETaskInfo` + `MegaMoEInterleavedScheduler`. The duplicated `get_num_l1_warmup_waves` helper resolves to the single upstream definition; `is_l1_phase` / `is_shared_phase` are added. * `sm90_fp8_mega_moe.cuh` + `heuristics/sm90_mega_moe.hpp`: taken from the PR side. The only upstream cuh delta vs the old base was a rename of the wave scheduler in a path the SM90 kernel no longer uses, so no behaviour is lost. Co-Authored-By: Claude <noreply@anthropic.com>
…ffer_for_mega_moe The three-way replay kept both copies of the kwarg that the rebase added in different positions (upstream placed it mid-signature; the PR placed it last), leaving a duplicated parameter and a SyntaxError at import time. Callers pass it by keyword, so keep only the last-position copy to match the SM90 variant (get_symm_buffer_for_sm90_mega_moe). Co-Authored-By: Claude <noreply@anthropic.com>
Both the rebase base and the PR branch carried the free function
`ptx::ld_volatile(const uint32_t*)`; applying one on top of the other left two
identical definitions (lines 190 and 202), which nvcc rejects ("already been
defined"). Remove the first copy; the uint64_t and uint32_t overloads remain.
Co-Authored-By: Claude <noreply@anthropic.com>
SM90 previously carried its own port of the interleaved scheduler
(`SM90MegaMoETaskInfo` + `MegaMoEInterleavedScheduler`, ~350 lines) that
duplicated the SM100 `MegaMoEScheduler` mechanism. This unifies them so both
archs share one scheduler, while guaranteeing the SM100 path is byte-identical.
Approach:
* Add `uint32_t kClusterSize = 2` and `typename WorkspaceT = layout::Workspace`
template parameters (inserted after the defaulted `kNumSharedExperts`, so the
existing SM100 12-positional-arg instantiation is untouched).
* Replace the hard-coded `... / 2` cluster arithmetic with `... / kClusterSize`
(`kNumL1Clusters`, `kNumL2Clusters`, warmup `kNumSMs / kClusterSize`,
`shared_mainloop` `kShapeN / BLOCK_N / kClusterSize`). With the SM100 default
`kClusterSize = 2` every formula reduces exactly to the pre-unification code.
* Guard the two cluster-dependent behaviours with `if constexpr`:
- `publish_task`: single-CTA writes the slot to local SMEM + arrive; the
2-CTA path keeps `st_async_cluster` (SM100, unchanged).
- consumer `get_next_task`: single-CTA releases the slot inline
(SM90 contract, empty barrier init `2 + kNumEpilogueWarps`); the 2-CTA
path keeps deferred release via `release_task_info()` (SM100, unchanged).
* Delete `SM90MegaMoETaskInfo` (byte-identical to `TaskInfo<kHasShared>`) and
`MegaMoEInterleavedScheduler`; the SM90 kernel now instantiates the unified
`sched::MegaMoEScheduler<..., kClusterSize=1, layout::SM90Workspace>` and
reads `task_info.n_cluster_idx` (equal to a block N index when the cluster
is a single CTA). `kNumRingBlocks` is assert-only on the SM90 path and is fed
the pool block count.
Net effect: the SM90 scheduler code shrinks by ~350 lines and future scheduler
improvements land on one struct instead of two.
Co-Authored-By: Claude <noreply@anthropic.com>
e4d6b08 to
b7fb616
Compare
…helpers Align the SM90 kernel with the SM100 pattern of asking the task-info struct about its phase instead of decoding a raw `BlockPhase` enum through namespace-level helper functions. * Add `TaskInfo::is_l1()` (true for both routed and shared L1 phases), next to the existing `is_shared()`. Both are simple `block_phase` comparisons. * Delete the now-redundant namespace free functions `is_l1_phase()` / `is_shared_phase()`; their semantics are exactly `is_l1()` / the gated `is_shared()` and they had no other callers. * The three SM90 epilogue loader/math lambdas now take `const auto& task_info` instead of seven unpacked fields, re-deriving only the members they use (`num_k_blocks` from `task_info.shape_k / BLOCK_K`, `n_block_idx` from `task_info.n_cluster_idx`, etc.) and `is_shared`/`is_l1` via the new members. Call sites collapse to `process_*(task_info)`. * Remove the now-dead `get_num_k_blocks` phase lambda and the four `kNum*BlockKs` constants it was the sole user of. Semantics are unchanged: `task_info.is_shared()` expands to `kHasSharedExperts ? (block_phase > Linear2) : false`, identical to the old `kHasSharedExperts and is_shared_phase(...)`. No SM100 code is touched. Co-Authored-By: Claude <noreply@anthropic.com>
After the SM90 kernel moved onto the unified producer-warp MegaMoEScheduler (cluster=1) and SM100 already used it, the wave-based LegacyMegaMoEScheduler had no remaining instantiations anywhere. Delete the struct and its comment to shed ~170 lines of dead code. Co-Authored-By: Claude <noreply@anthropic.com>
The four top-of-file device templates `sm90_fp8_mega_moe_clamp_gate`, `sm90_fp8_mega_moe_clamp_up`, `sm90_fp8_mega_moe_silu` and `sm90_fp8_mega_moe_swiglu` were never called anywhere. The kernel computes SwiGLU inline in the L1 epilogue with byte-identical logic (`__expf` + `fast_rcp` silu, `min`/`min(max(...))` clamps), so these were dead leftovers of an earlier implementation. Remove the unused cluster; `kFastMath` / `kActivationClamp` template params remain used by the kernel. Co-Authored-By: Claude <noreply@anthropic.com>
pure cleanup / build fixes on top of the rebase — no numeric or scheduling-semantics changes:
|
Summary
Relative to the dev branch, this PR optimizes the SM90 FP8 MegaMoE kernel and syncs its
scheduler with DeepGEMM's official July release. Main changes:
to SM90 (cluster = 1); a dedicated producer warp drives a 2-stage SMEM task-ring with global
atomic L1/L2 counters, replacing wave-based coarse scheduling.
SharedLinear1/SharedLinear2phases — onelaunch produces routed + shared, replacing the env-gated two-stream fallback; the symmetric buffer
grows to a 10-tuple and
transform_shared_weights_for_mega_moe_sm90is exposed.applied symmetrically on the STS write and LDS read sides (self-cancelling).
intermediate_hiddenand GPU SMcount: H20 pro/flash prefer 2-WG, and the 2-WG split-N
BLOCK_N=256selection is un-gated soflash picks
n=256.BLOCK_N=256. The swap-AB path supports 2-WG N-split (twom64n64slabs),with the L1-output TMA descriptor covering the full tile.
32 + 96; dead scheduler andOption-A/chunk paths removed; accuracy layer 6 (fused shared-expert) added.
Accuracy
All changes are performance-only and do not change numerical results:
BLOCK_N=256selection: only changes kernel shape/tile granularity; eachshape accumulates the same full-tile GEMM.
BLOCK_N=256: same GEMM with FP32 accumulation,m64n128split into twom64n64slabs.
the dev branch, no regression.
Benchmark results
Environment: 8× NVIDIA H20 (SM90), CUDA / nvcc 13.2. Every token point runs the fused kernel and
the DeepEP baseline on the same config. Timing is robust to per-run jitter: each implementation is
launched 10 times in a fresh 8-rank process group, the 8 per-rank kernel times are averaged per
run, and the median across the 10 runs is reported.
pr avg= fused-kernel time (kineto regionsm90_fp8_mega_moe_impl, fused single launch including the shared expert when SE is on);sgl avg= the sgl-deep-gemm counterpart time;
baseline avg= DeepEP dispatch + 2× grouped FP8 GEMM +SwiGLU + combine (CUDA-event wall-clock median). All are averaged over 8 ranks.
vs baseline speedup = baseline_avg ÷ pr_avg,vs sgl speedup = sgl_avg ÷ pr_avg.sgl (
sgl avg) SE-on runs the shared expert serially, out-of-kernel. sgl's SM90 MegaMoE has nofused shared expert, so with
num_shared_experts=1the shared expert is evaluated as a dense FP8w8a8 MLP (L1 GEMM → SwiGLU + FP8 quant → L2 GEMM) on the same stream, serialized with the routed
mega kernel, and the two outputs are summed (matching sglang's
forward_mega_moesingle-streampath). Its
sgl avgis the CUDA-event median of that combined sequence;vs sgl speedup = sgl_avg ÷ pr_avg(fused single launch vs serial shared + routed).Shapes:
pro(default): E=384, H=7168, IH=3072, topk=6flash(modified): E=256, H=4096, IH=2048, topk=6[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]SE off · pro
pro (default: E=384 / H=7168 / IH=3072 / topk=6)
SE off · flash
flash (modified: E=256 / H=4096 / IH=2048 / topk=6)
SE on (num_shared_experts=1) · pro
pro (default: E=384 / H=7168 / IH=3072 / topk=6)
SE on (num_shared_experts=1) · flash
flash (modified: E=256 / H=4096 / IH=2048 / topk=6)
DeepSeekV4Flash-Base Benchmark setup
--backend sglang)random-ids, fixed shape 32,000 input × 1,500 output tokens--seed 42Results
Fusing SE improves output token throughput by ~4.2% while also reducing mean
TTFT and mean TPOT by ~3–5% on a long-context (32k) decode workload.
DeepSeekV4Flash-FP8 sgl-eval End-to-End Evaluation
End-to-end accuracy and throughput measured with
sgl-evalon the same 8× H20machine.
--num-threads 32.--thinking,--temperature 1.0 --top-p 1.0.GSM8K
GPQA (Diamond)