[Attention] Overlap sparse MLA indexer on a native CUDA side stream - #47355
Draft
LucasWilkinson wants to merge 2 commits into
Draft
[Attention] Overlap sparse MLA indexer on a native CUDA side stream#47355LucasWilkinson wants to merge 2 commits into
LucasWilkinson wants to merge 2 commits into
Conversation
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
LucasWilkinson
force-pushed
the
codex/dcp-overlap-streams-main
branch
from
July 9, 2026 04:57
5012b43 to
3d91f64
Compare
LucasWilkinson
force-pushed
the
codex/dcp-overlap-streams-main
branch
9 times, most recently
from
July 9, 2026 20:54
23d7978 to
17eb1af
Compare
LucasWilkinson
force-pushed
the
codex/dcp-overlap-streams-main
branch
4 times, most recently
from
July 10, 2026 01:35
3d9dbe9 to
2cc45f5
Compare
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
LucasWilkinson
force-pushed
the
codex/dcp-overlap-streams-main
branch
from
July 27, 2026 15:19
2cc45f5 to
af9680b
Compare
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
The DSA indexer is independent of the q projection and rope that precede
attention, so run it on a shared high-priority stream and join just
before the attention call. Under CUDA graphs this lands the indexer on
its own graph branch, overlapping ~73% of its kernel time with the main
stream's path to attention.
Mechanics:
* Indexer owns the stream as a class attribute -- the join in
MultiHeadLatentAttentionWrapper and the compiled artifacts must all
reference the same object -- and hands it to the wrapper through
MLAModules.
* Fork/join are plain stream ops, so torch.compile traces them into
native stream nodes rather than a custom op.
* The fork and the join land in different compiled pieces, so a piece
carrying side-stream work cannot be captured as an independent
piecewise cudagraph (capture would end with unjoined work); such
pieces skip that capture and run inside the full graph instead.
* vLLM invokes compiled pieces without dynamo's registry-repopulating
bytecode, so stream external-object indices are resolved directly:
dynamo's reserved index means the current stream, anything else the
side stream.
Signed-off-by: Lucas Wilkinson <lwilkins@redhat.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…xists Installing at import patched torch._dynamo for every vLLM process, since side_stream is imported from backends.py. Layers register their stream during model construction, which is before anything is compiled or any AOT artifact module is exec'd, so registering is early enough to catch every binding of the resolver name. Signed-off-by: Lucas Wilkinson <lwilkins@redhat.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
LucasWilkinson
force-pushed
the
codex/dcp-overlap-streams-main
branch
from
July 31, 2026 04:38
af9680b to
7218d28
Compare
4 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.
Summary
Runs the sparse-MLA (DSA) indexer on a shared high-priority CUDA stream so it
overlaps the main stream's path to attention. The indexer is independent of the
q projection, kv layernorm and rope that precede attention; only
topk_indices_buffercouples it to attention, so a single join immediatelybefore the attention call is sufficient.
Rebased on current
main(PyTorch 2.13) and uses PyTorch's native compiledstream operations — no side-stream begin/end custom ops, and no per-graph
stream mappings.
Mechanics
Indexerowns the stream as a class attribute and hands it toMultiHeadLatentAttentionWrapperviaMLAModules. Class-level because thefork, the join and the compiled artifacts must reference the same object.
Indexer.forwardforks (wait_stream+with stream:); the wrapper joinswith
current_stream().wait_stream(side)just beforeself.mla_attn(...).Both are plain stream ops, so torch.compile traces them into native stream
nodes.
rope, so there is main-stream work left to overlap with.
splitting op), so a piece carrying side-stream work cannot be captured as an
independent piecewise cudagraph — capture would end with unjoined work. Those
pieces skip that capture and run inside the full graph instead.
so stream external-object indices are resolved by a small resolver: dynamo's
reserved index means "current stream", any other index means the side stream.
It is installed only once a layer registers a side stream.
non-default stream; PyTorch 2.13's compile-time autotune wrapper does not
initialize the user-stream handle.
Results
GLM-5.2-NVFP4, TP=4, B200,
torch.compile+FULL_AND_PIECEWISECUDA graphs(not eager, not piecewise-only).
Kernel overlap
Measured per steady-state decode iteration as
union(indexer) + union(other) − union(indexer ∪ other), at ~9.6k-tokencontext, batch 4. Stream IDs are unreliable for cudagraph-replayed kernels, so
this is time overlap, not stream attribution.
TP=4, no DCP — the main-stream row gaps open exactly where the indexer runs
on
main, and runs straight through it with the side stream:TP=4 + DCP=4 — lower, because the DCP-only tail of the indexer chain
(
pack_dcp_topk_candidates→ top-k all-gather → stable merge) overlaps poorly:the top-k all-gather is 0.330 ms at 2.7%.
Per kernel class, TP=4 without DCP:
End-to-end
vllm bench latency, 10 iterations at 4k / 5 at 32k, warmup excluded.This regresses short-context decode. Reporting it because it is real and
reproducible — the distributions do not overlap (main's slowest 4k run is faster
than this PR's fastest), sd ≤ 0.03 s.
Two contributing mechanisms are measured; a third is suspected:
main stream, take longer: 4.340 → 5.608 ms (+29%) without DCP, 0.975 → 1.014
ms with. On a saturated device concurrency partitions SMs rather than adding
throughput. You get 71% of a bigger number.
decode, so perfect hiding saves ≤4%; a 2–3% slowdown of the critical path
erases it. At 32k the indexer's share grows — it scans the whole KV while
sparse attention stays fixed at
topk_tokens.piecewise cudagraph capture. Decode uses full graphs so it is unaffected, but
prefill runs piecewise and now runs uncaptured. That cost is roughly fixed
per prefill while the decode benefit scales with context, which would explain
the sign flipping with the prefill:decode ratio rather than with DCP.
Accuracy
GSM8K, 300 questions, 5-shot, 0% invalid:
A 4-question spread on 300 is inside one standard error; no regression.
Not a duplicate
This updates the existing side-stream PR rather than opening another.
Related but distinct: #48196 is the DCP output-merge optimization; #45964
(merged) is DCP query replication.
Prior art worth naming: an earlier unlanded branch of mine
(
5012b439) did the same overlap usingdcp_indexer_stream_begin/restorecustom ops that call
torch.cuda.set_streamat runtime, scoped to DCP > 1, andplaced the join after the DCP query all-gather. This PR replaces the custom ops
with native traced streams. I re-tested that later join placement here and it
made no measurable difference (30.7% → 30.2% overlap, e2e slightly worse), so it
is not carried over.
Testing
test_aot_compile.pyincludes a new AOT save/reload round-trip of a graph thatuses a side stream, covering the index resolver.
Caveats
operating points — the overlap percentages do not transfer directly to the
end-to-end numbers.
above is confirmed, it is fixable rather than inherent.
AI assistance
AI assistance (Claude, and Codex for earlier revisions) was used to implement,
profile and test this change. The submitter has reviewed every changed line and
is responsible for the results reported here.