perf(dsfp4): pick the grouped-prefill tile from route density on sm_89 - #89
Open
gdevenyi wants to merge 1 commit into
Open
perf(dsfp4): pick the grouped-prefill tile from route density on sm_89#89gdevenyi wants to merge 1 commit into
gdevenyi wants to merge 1 commit into
Conversation
gdevenyi
force-pushed
the
perf/dsfp4-prefill-sm89-tiles
branch
2 times, most recently
from
August 23, 2026 16:18
392911f to
2f0cc8a
Compare
The shipped config is one static tile for every density, justified as "the
kernel is dequant-floor-bound, so per-expert padding at BLOCK_M=64 costs the
same as tighter tiles while keeping the wgmma-wide M tile on sm_90".
wgmma is Hopper-only. On sm_89 nothing pins M to 64, and without that floor the
moe_align padding BLOCK_M=64 pays stops being free: at DSV4's 256 experts and
1536 routes there are 6 routes per expert, so a 64-row M tile is ~75% padding.
Measured on main with this commit alone, on an RTX 6000 Ada (142 SM, 96 MiB L2,
960 GB/s) at DSV4-Flash TP=2 geometry -- 288 configs per shape, each checked
against the sm_90 config's output before its time counted (0 numeric rejections):
routes rows/expert shape shipped sm_89 best gain
1536 6 gate_up 4.233 ms BM16 BN64 BK128 w4 2.376 ms +43.9%
1536 6 down 2.298 ms BM16 BN128 BK128 w4 1.121 ms +51.2%
6144 24 gate_up 5.305 ms BM32 BN128 BK128 w8 3.375 ms +36.4%
6144 24 down 2.665 ms BM32 BN128 BK128 w4 1.444 ms +45.8%
24576 96 gate_up 11.098 ms BM128 BN128 BK128 w8 6.587 ms +40.6%
24576 96 down 5.648 ms BM128 BN128 BK64 w8 3.420 ms +39.5%
The optimum tracks routes-per-expert, so M is picked from it rather than fixed.
BLOCK_K goes 64 -> 128 everywhere (fewer scale loads per output element on a
dequant-bound kernel). num_stages stays 1: it barely moves the result on sm_89,
winning or tying at four of the six shapes, so the win is tile shape and not
pipelining.
sm_90 is untouched -- it returns the same constants it does today.
End-to-end on the box this was measured on, the change is not resolvable: that
deployment runs --moe-backend offload, where the GPU is 3% busy at one stream
and 20-24% at 16 concurrent, and prefill throughput came out 31/29 requests per
90 s window shipped vs 44/32 patched -- both patched runs above both baseline
runs, but n=2 with that spread quantifies nothing. The kernel win is for
GPU-resident (non-offload) sm_89 serving, where this kernel is on the
critical path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpGe2fQ5pDShGrnSuksfun
gdevenyi
force-pushed
the
perf/dsfp4-prefill-sm89-tiles
branch
from
August 23, 2026 16:23
2f0cc8a to
c4d403c
Compare
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.
The Hopper assumption
routed_experts_fp4_prefilluses one static tile config, and its comment says why:wgmmaexists only on sm_90. On sm_89 nothing requires M to be 64, and once that requirement is gone the padding a 64-row tile pays stops being free. DeepSeek-V4-Flash has 256 experts and top-6 routing, so at 1536 routes each expert receives about 6 rows. A 64-row M tile is then mostly padding.Measurements
RTX 6000 Ada, sm_89, 142 SMs, 96 MiB L2, 960 GB/s. DSV4-Flash geometry at TP=2. Measured on
main.288 configs per shape. Every config's output was checked against the sm_90 config's output before its time counted. No config was rejected on accuracy.
The best M tile tracks the rows per expert: 16, then 32, then 128. That is the padding term, so M is now chosen from route density rather than fixed.
BLOCK_Kmoves from 64 to 128 nearly everywhere, which halves the number of block-scale loads per output element on a kernel limited by dequantization.What is not the mechanism
I expected
num_stages=1to be the problem, because sm_89 has no TMA and setting one stage disables thecp.asyncpipeline that is its only way to hide load latency. It is not. One stage wins or ties at four of the six shapes. The gain comes from tile shape.num_stagesstays at 1.Scope
sm_90and above returns exactly the constants it returns today, so Hopper is unchanged.The new branch covers everything below sm_90, but the density rule is measured only on Ada. If you would rather gate it to
(8, 9)exactly until someone measures sm_80 or sm_86, say so and I will narrow it. I have no other pre-Hopper card to test.Effect on a running server
On the machine this was measured on, the change is not visible end to end. That deployment runs
--moe-backend offload, which puts routed experts on the CPU and leaves the GPU 13% busy, so this kernel is not the limiting factor there. A GPU-resident deployment spends a much larger share of its time in it: 57.9% of GPU kernel time in an nsys trace of 16-concurrent long-prompt serving onmain.Testing
tests/moeonmainwith this PR: 94 passed, 6 skipped, 1 failed. The failure istest_cpu_moe_q4_0.py::test_cpu_decode_q4_0_matches_ggml_mmvq, which also fails onmainwithout this PR.This PR adds no test. The config picker is a pure function of route count and expert count, and a small test that pins its three branches would be cheap. Tell me if you want it before merge.