Repository navigation
[4/8] perf(kernels): per-arch DSA sparse attention dispatch with a FlashMLA forward - #3608
Merged
Merged
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
Zhichenzzz
added this pull request to stack #3611
September 22, 2026 23:16
This was referenced Sep 22, 2026
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
2 times, most recently
from
September 23, 2026 00:51
91c99cd to
1aa9c67
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
September 23, 2026 01:02
1aa9c67 to
47d0bf1
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
September 23, 2026 06:19
47d0bf1 to
c1cfca0
Compare
This was referenced Sep 23, 2026
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
September 23, 2026 22:08
c1cfca0 to
671d37b
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
2 times, most recently
from
September 24, 2026 01:05
7a89cd7 to
9e38836
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
September 24, 2026 04:58
9e38836 to
176c8e5
Compare
This was referenced Sep 30, 2026
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
October 2, 2026 22:31
5adc002 to
ae5dd36
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
October 3, 2026 07:43
ae5dd36 to
3a5ab7f
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
2 times, most recently
from
October 7, 2026 23:45
358ad9f to
f388879
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
October 8, 2026 00:03
f388879 to
c7192e9
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
October 8, 2026 00:21
c7192e9 to
9108623
Compare
yueming-yuan
approved these changes
Oct 8, 2026
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
October 8, 2026 02:17
9108623 to
1370eef
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
October 8, 2026 03:25
1370eef to
2dbb126
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
October 8, 2026 07:51
2dbb126 to
d31c21d
Compare
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
2 times, most recently
from
October 8, 2026 16:47
6e2f33e to
e48e5e2
Compare
One kernel pair for GLM-5 and DeepSeek-V4 / V4.1, tuned per arch and
TP-local head count.
- Forward: FlashMLA's sparse prefill kernel or the TileLang kernel, chosen
per shape. FlashMLA pads query heads to 64; it wins every GLM-5 shape and
every shape on Blackwell; the TileLang forward wins DeepSeek-V4.1 at 8-16
heads on H200. FlashMLA's natural-log lse is converted to the log2 lse with
the sink folded in that the backward takes.
- Padded -1 indices: the host passes kv_indices = indices.clamp(min=0);
the forward gathers KV through it unconditionally and builds the -inf mask
from the original indices, the backward does the same and atomic-adds dKV
through kv_indices. No out-of-bounds access, no per-element clamp (which
cost up to 2x at 8-16 heads on H200).
- The TileLang forward runs unpipelined. With the unconditional gather,
TileLang's software pipeline turned it into async copies that read the
wrong stage when the block count was stages + 1 (16 heads, topk 128 at one
stage / 192 at two): rows came back as 1e30. main's DeepSeek-V4.1 forward
has the same pattern and is wrong at 16 heads, topk 128 (rel err 5e-4).
- Backward takes batch and sequence lengths as dynamic shapes: a new
packed length no longer compiles a new kernel (main's V4.1 copy spends
6-8 s per new length on H200).
- SparseAttentionConfig: forward backend, forward threads, backward
stages/split_store. Measured rows keyed by (arch major, RoPE
tail, TP-local heads); B300 and GB300 pick identical winners, H200
differs (e.g. GLM-5 8 heads backward: stages 1 / split 2 on H200,
stages 2 / split 1 on Blackwell, 1.5x apart). Callers may pass a config.
- Indexer forward tiles: block_N 128, 2 stages, block_Q 256 / heads,
2x faster on GB300.
- Backward threads 256 when a block holds >= 32 heads on CUDA; ROCm keeps
32-head blocks above 64 heads and 1 stage.
fwd + bwd per layer vs main, 16k / 64k tokens:
H200 GB300
GLM-5 8 heads (TP=8) 3.40x / 3.28x 3.01x / 3.15x
GLM-5 16 heads (TP=4) 3.24x / 3.02x 3.01x / 3.15x
GLM-5 64 heads (TP=1) 9.41x / 10.06x 5.42x / 6.38x
V4.1 vs V4 kernel, 8 heads 1.12x / 1.18x 1.88x / 2.01x
V4.1 vs V4 kernel, 32 heads 1.32x / 1.51x 1.88x / 2.03x
V4.1 vs V4 kernel, 64 heads 3.27x / 2.96x 3.27x / 3.42x
V4.1 vs its copy, 8 heads 0.83x / 0.88x 1.60x / 1.67x
V4.1 vs its copy, 32 heads 1.03x / 1.03x 1.70x / 1.74x
V4.1 vs its copy, 64 heads 3.13x / 2.85x 3.57x / 3.71x
The H200 8-head V4.1 row is the copy's static-shape backward; at a fixed
length it is ~2 ms faster, at a new length it recompiles for 6-8 s.
Outputs within 1e-5 (TileLang) / 3e-3 (FlashMLA) of main, dkv within 1e-4
(fp32 atomic order). Tests cover every case on both forwards and on the
tuned default config, -1 padding, all-padded rows, sinks, odd lengths and
packed sequences (causal_ranges -> indexer -> top-k -> attention, 1-token
segments). A wider matrix (5 head counts x 6 topk x packed / causal / batch-2
x tail / shuffled padding x 8 configs, 3 repeats, fwd + bwd vs fp32) passes
on H200, B300 and GB300.
Zhichenzzz
force-pushed
the
zhichen/dsa-faster-kernels
branch
from
October 8, 2026 19:16
e48e5e2 to
d12726f
Compare
4 of 7 tasks
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.
What
kv_indices = indices.clamp(min=0); both kernels gather through it and mask from the original indices. No out-of-bounds access, no per-element clamp.stages + 1(16 heads, topk 128 / 192): rows came back as 1e30. main's DeepSeek-V4.1 forward has the same pattern and is wrong at 16 heads, topk 128; this PR replaces it.SparseAttentionConfig: forward backend, forward threads, backward stages/split_store, from a measured table keyed by (arch, RoPE tail, TP-local heads). B300 and GB300 pick the same winners; H200 differs. Callers may pass their own.tests/manual/bench_dsa.pybenchmarks GLM-5.3-Flash, DeepSeek-V4.1-Flash and GLM-5 at 4k–128k tokens.vs current main (2026-10-08, this push)
Forward + backward per layer; "main" is the kernel each model runs on main (
b7b2870daf). 16k / 64k tokens, idle GPUs.The H200 8-head V4.1 row loses to the copy's static-shape backward at a fixed length; at a new length the copy recompiles for 6–8 s.
Correctness
test_dsa_sparse_attention.py89 passed,test_dsa_indexer.py13 passed (H200, B300, GB300). New cases: odd lengths, 32 heads, packed sequences end to end (causal_ranges→ indexer → top-k → attention, 1-token segments).Summary of an earlier run (H200, before the rebase)
Forward + backward per layer, before this stack versus after:
Precision:
This branch (
91c99cd02)91c99cd02)test_dsa_sparse_attention.py, both forward backendstest_dsa_indexer.pytests/fast/test_dsv4_thd.pytest_dsv4_thd_cp_correctness.py, CP=2H200 results
8×H200 on the current
radixark/miles:devimage (pulled 2026-09-23): torch 2.13 + CUDA 13.0, sglang 0.5.21.dev55, tilelang 0.1.14, fla 0.5.2, FlashQLA 0.1.2, FlashMLA; Megatron-LM7a1f7fe3f(the image's); this stack atdd2cc6f6b.Forward + backward per layer, same columns as the GB300 table below.
GB300 results
8×GB300 (sm_103) on two 4-GPU nodes, GPUs otherwise idle; torch 2.13 + CUDA 13.0, tilelang 0.1.12, fla 0.5.2, FlashMLA
b7643bdbuilt for sm_100f; commits as pushed to this stack.Forward + backward per layer. "Before" is the merged kernel before this PR (#3607, bitwise equal to
main's kernels); "after" is this PR with the default forward backend (FlashMLA where it applies). The last column is the relative error against a dense fp32 reference (up to 8k tokens) and between the two forward backends.Stack
miles/kernelsmiles/kernels/attentionmiles_plugins/models/linear_attn.py)Rules for
miles/kernelsare in its README: only kernels we write, one directory per op, names say what is computed (never the backend), callers import the entry, a torch-reference test per op.