[DSV4.1] Multi-stream prepare for ratio-1/2 layers, fused ratio-1 verify compression, PDL on _q_rope_store - #39445
Merged
Merged
Conversation
In compress-ratio 1/2 layers (38 of the 40 in V4.1-Flash) the fused KV-cache write ran on the main stream right behind the Q chain, so attention waited for it; only the compressor and indexer went to a side stream, and only in the layers that have them. Give these layers their own decode / target-verify prepare, _forward_prepare_low_ratio_multi_stream: the sources on one side stream, the KV write on another, the Q chain on the current stream. Every fork is followed by main-stream work and the side blocks are recorded right before their join, so CUDA-graph replay keeps the main chain on one lane. _forward_prepare loses the early_sources special case. bs=1 DSpark verify trace (TP0): the KV write leaves the Q lane in 40/40 layers and finishes ~6 us before the Q RoPE; attention-layer time 122.9 -> 121.0 us; distinct streams per verify cycle 19 -> 12. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The fused target-verify compression was gated on compress_ratio == 2, which only reflected that the verify variant had been written for c2. c1 is stateless (one latent per token, no pair state), its kernel takes any row count and suppresses padded rows, and c1_out_loc is built for verify too, so the ratio-1 KV-source layer needlessly fell back to the torch chain in verify: two rmsnorm_fp32, a rope_fake_quant_pack_indexer and a rope_tail_fake_quant_fp4 per cycle. Now it runs flash_c1_decode_kernel + flash_index_k_kernel like the ratio-2 layers. Also drop the int64 conversion of positions at the low-ratio sources entry: every consumer takes int32 or int64. Test: test_static_verify_dispatch_ratio_1 checks the dispatch and that the pool bytes equal the unfused chain on a verify-shaped batch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gdc_wait before the first load (the input is the wq_b output), launch_dependents after the store, launch_pdl on architectures that support it. Output is bit-identical. In a captured GEMM -> kernel chain the kernel-attributable time drops from ~2.0 us to 1.2-1.5 us per call (m = 1..8); the kernel itself is unchanged, the saving is the launch gap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
DarkSharpness
requested review from
HaiShaw,
HydraQYH,
celve,
hebiao064,
ispobock,
merrymercy and
yuan-luo
as code owners
September 14, 2026 17:37
Collaborator
Author
|
Full-branch check (all three commits) on 4x GB300, TP4/EP4, DSpark,
|
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
On DeepSeek-V4.1-Flash, 38 of the 40 attention layers have compress ratio 1 or 2. Their decode / verify prepare kept the fused KV-cache write (
fused_k_norm_rope_flashmla) on the main stream right behind the Q chain (wq_bGEMM +_q_rope_store), so attention waited for both; only the compressor / indexer went to a side stream, and only in the eight layers that have them. The multi-stream prepare (_forward_prepare_multi_stream) was excluded for these layers, and the special case lived inside_forward_prepareasearly_sources.Two smaller things showed up in the same trace: the ratio-1 KV-source layer still ran the torch compression chain in target-verify (the fused verify path was gated on ratio 2 although c1 is stateless), and
_q_rope_storesat behind a launch bubble on the critical path.Changes
_forward_prepare_low_ratio_multi_stream(deepseek_v4.py): the ratio-1/2 decode / target-verify prepare on three streams: compressor + indexer (forward_low_ratio_sources) on one side stream, the KV write on another, the Q chain on the current stream. Each fork is followed by main-stream work and the side blocks are recorded right before their join, so CUDA-graph replay keeps the main chain on one lane ([DSV4.1] Record side-stream work right before its join to keep CUDA-graph replay on one stream #39420)._forward_prepareloses theearly_sourcesbranches; dispatch is the formerearly_sourcescondition, now for every ratio-1/2 layer.deepseek_v4_backend.py):_low_ratio_compresstakes the fused path forcompress_ratio in (1, 2)in target-verify. c1 has no pair state, its kernel takes any row count and suppresses padded rows, andc1_out_locis built for verify. New testtest_static_verify_dispatch_ratio_1(dispatch + pool bytes equal to the unfused chain on a verify-shaped batch). The int64 conversion ofpositionsat the sources entry is dropped (every consumer takes int32 or int64)._q_rope_store(q_rope_store.py):gdc_waitbefore the first load,launch_dependentsafter the store,launch_pdlwhere supported. Output bit-identical; in a captured GEMM -> kernel chain the kernel-attributable time drops from ~2.0 us to 1.2-1.5 us per call (m = 1..8).Validation (4x GB300, TP4/EP4, DSpark, bs=1, 64K context, TP0 trace, one verify cycle)
fused_k_norm_rope_flashmlaon the_q_rope_storelane_q_rope_storespacing)_rmsnorm_fp32/_rope_fake_quant_pack_indexer/_rope_tail_fake_quant_fp4per cycleflash_c1_decode_kernelper cyclegsm8k (1319 questions, 64 threads) with (1)+(2): 0.9697, stop rate 1.0, no runaways; per-question agreement with the unchanged tree is at the launch-to-launch level (sampling). Graph capture succeeds; smoke outputs byte-identical. Unit tests:
test_dsv41_fused_compress4/4 (incl. the new case),test_dsv41_indexer_prefill2/2,test_dsv4_q_rope_store2/2; the split methods were also checked in isolation (eager, side stream, graph replay). A gsm8k run of the full branch including (3) is in flight and will be posted as a comment.🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ❌ Run #34875916558
Latest PR Test (Extra): ❌ Run #34875915808
Latest PR Test (AMD ROCm 10): ❌ Run #34875916470