Repository navigation
[Perf] minimax_m31: Run o_proj in the launch, route on the norm's scale, read B operands from LDS - #59
Merged
Conversation
…rows The router waited for XRDY: the norm stage's whole XN write and its drain. The norm now publishes each token's RMSNorm scale as soon as it has it, and a router CTA rebuilds the XN columns it reads from SRES with that scale, through the same xn_words the norm stores XN with, so the logits keep their bits. The routed up stage, which the router no longer orders XN for, waits for XRDY itself (by then it is set). Every launch output and scratch region (XN, logits, routes, MoE output, residual) is bit-identical at 1, 3, 8 and 16 rows. TP4, 8 rows, median of 5 x 200 launches, two runs each: 45.5-45.9 -> 43.4-43.9 us with 8 distinct experts, 51.5-51.7 -> 49.5-49.7 us with 15: the route stage ends ~2.6 us earlier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each lane loaded its own column's B operands from global memory, so a slot of cnt columns read its last column 16 - cnt more times. At 8 rows and 15-20 experts a slot has one or two columns, and those repeats were most of a CTA's vector memory traffic: an up unit's XN is 24 KB a wave against its 12 KB of weights, a down slot's MID the same. - XN goes to LDS once a CTA, after XRDY, for steps of up to 8 rows (16 do not fit); the up stage reads it there. - A down slot's wave first copies the slot's own MID columns to LDS, a 16-byte chunk a lane, then reads B there. The rows are the picks', in the LDS pool XN used. Outputs are bit-identical at 1, 3, 8 and 16 rows. TP4 MI350X, 8 rows, weights rotating through four copies past the MALL: 50.5 -> 48.0 us at 14 experts, 59.5 -> 57.2 us at 20. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fore With X in LDS, an up unit waits on its own weights alone: about 3 us of its 5.6 at 20 experts, while the CTA's loads sit idle as it computes, reduces and stores. A unit now issues its next unit's weights right after its X reads, and the first unit's go out after the table. Past the CTA's last unit they read through a zero-sized buffer. Outputs are bit-identical. TP4 MI350X, 8 rows, weights past the MALL: 48.0 -> 47.5 us at 14 experts, 57.2 -> 56.8 us at 20. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SGLang ran each sparse layer's o_proj as two kernels in front of the launch: AITER's per-token FP8 quant of the attention rows, then smallm_fp8_gemm on the rowwise-FP8 copy of the MXFP8 weight, about 8.8 us a layer at c1. The launch's push CTAs now run it. Each loads the rows and its 48 columns' weights, quantizes as AITER does, and multiplies with smallm's waves, K steps, accumulator chains and wave order, so the partial is SGLang's bit for bit (tested against smallm_fp8_gemm at 8 and 16 rows). The spec makes o_proj return its input while SGLang's attention runs. It refuses a layer whose index values add to o_proj's output, or whose o_proj has no rowwise-FP8 copy. TP4 MI350X, 8 rows, weights past the MALL; SGLang's quant + GEMM + the launch against the launch alone: 48.1 -> 44.9 us at 8 experts, 54.6 -> 51.9 us at 14, 61.2 -> 58.2 us at 18. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
At c1 with DSpark verifying 8 rows, MiniMax-M3.1-Flash's FFN launch (#56) takes about 60 µs a sparse layer at TP4 on MI350X, and SGLang runs o_proj in front of it as two more kernels (about 9 µs). Timing the launch's stages per CTA showed where the time went:
cntcolumns read its last column16 - cntmore times, and at 8 rows and 15-20 experts a slot has one or two columns. Those repeats were most of a CTA's vector memory traffic: 24 KB of XN a wave a unit against 12 KB of weights. Weights served from the MALL timed the same as from HBM.Modifications
All in
minimax_m31/(and the byte count ona16w4's loads). Every output is bit-identical tomainwith SGLang's o_proj in front of it, at 1, 3, 8, 9 and 16 rows.rstd(a pair) before it writes XN, and the router normalizes its own columns from SRES. The table no longer reaches XN through the router, so routed CTAs wait for XRDY after the table._ptpc_weight,_ptpc_scale): AITER's per-token FP8 quant (amax / 448,v_rcp_f32, clamp, RNE), thensmallm_fp8_gemm's waves, K steps, accumulator chains and wave order for the row count, so the partial is SGLang's to the bit. The spec makeso_projreturn its input while SGLang's attention runs, and refuses a layer whose index values add to o_proj's output or whose o_proj has no rowwise-FP8 copy.test_contractpins the attributes it reads.Accuracy tests
MI350X (gfx950), SGLang
af6d50a85e(as in #56) on a ROCm 10 image,MiniMaxAI/MiniMax-M3.1-Flash, TP4:outandres_outat 1, 3, 8, 9 and 16 rows (TP=1 build): identical tomainfed SGLang's o_proj output (AITER's quant +smallm_fp8_gemm), bit for bit.test/kernels/test_minimax_m31_ffn.py: 8 passed. New: the launch's o_proj partial equalssmallm_fp8_gemm's at 8 and 16 rows (its two wave splits), and is checked against a native reference before the existing stage checks.test/unit: 288 passed. One contract failure against this SGLang branch:deepseek_v41/spec.pyreadsmxfp8_aiter_preshuffled, which that branch lacks; unrelated (as on [Feat] minimax_m31: Run MiniMax-M3.1-Flash's decode FFN as one monokernel launch a layer #56).test/e2e/test_minimax_m31_server.py: 6 passed. Chat GSM8K past the 16-row width scores 0.98 (DSpark verify) and 0.975 (decode); 48,792 checked launches are all withinNUMERICS; a stalled rank still stops the server without tokens. The check's residual difference is the same distribution as onmain(median 0.0054, p99.9 0.021-0.024 over three runs of this branch), but its single worst launch varies from run to run (0.029, 0.047 and 0.077 against the 0.08 bound, in a different layer each time), so that bound is close to flaky on any revision.sglang.test.run_eval, 200 questions, greedy, DSpark verify server, real acceptance): 0.975 with this PR, 0.98 onmain(one question; std 0.156), acceptance 4.8-5.0 and 4.7-5.1 tokens per step.Speed tests
c1 decode on the same stack (as in #56): DSpark acceptance held at 3 tokens per step (
SGLANG_SIMULATE_ACC_LEN=3), 11K-token prompt, 1,024 greedy tokens, median of 4, arms alternated:mainThe launch with o_proj: TP4
FfnRunner, 8 rows, every rank routing alike, the router bias steering the rows to a set number of experts. Weights (o_proj's too) rotate through four copies, so they stream from HBM as a server's layers do (one copy stays in the 256 MB MALL).mainand the first three commits time SGLang's quant + GEMM + the launch; this PR times the launch alone. Median of 5 × 200 launches:mainmain's launch alone at 20 experts (62.8 µs) matches the 62.3 µs a launch measured in the server.Not covered
🤖 Generated with Claude Code