perf: drop the indexer candidate stream's early-out test - #313
Merged
Merged
Conversation
`_TopKStream.push` returns without merging when `amax(tile) < amin(held)` and the buffer is already at width `k`. That test reads one boolean to the host, so it is a synchronization on every push, and it is written for the prefix level's geometry -- a 4096-key tile narrowed into a 512-wide buffer, where most tiles of most query tiles sit below the running k-th value. `_stream_candidates` builds its stream with `k = min(index_topk, width)`, which is exactly its own `span`: the field being narrowed is already the top-k's own width, so the running k-th value is drawn from the same distribution as an incoming tile's entries and sits inside their range rather than above it. The guard is not dead -- a real 4096-token chunk gave 1 hit in 896 pushes -- but one hit is one merge saved against a synchronization paid 896 times. Measured on the real fabric, four ranks, both cache widths, A-B-A-B with the read as one factor and the prefix path's depth-1 lookahead as the other (`/tmp/bench_indexer_cand_overlap.py`): the read is 174 and 162 us of a 1524 and 1543 us c-iteration, and with it gone the depth-1 lookahead goes from +3.9% against the shipped order to -21%. The read drains the compute stream at every push, which is what stops `_ReducePipeline` from deferring this path's collective, so the two are the same effect. In situ (`/tmp/probe_cand_skip_inproc.py --at 8192 --chunk 4096 --arms 0 1 1 0`, one process, state reset between arms) `stream_candidates` is 1.102 and 1.137 s with the test gone against 1.284 and 1.236 s with it, a ~0.14 s move of a ~1.12 s row with both brackets agreeing in sign -- 0.56% of a 25.08 s chunk. The default now answers `False`, which is also what a capture already builds: `_TopKStream` refuses that read inside one, so this makes the eager path agree with the recorded one rather than differ from it. `DEEPSEEK_V41_INDEXER_CAND_SKIP_TEST=1` restores the tested branch. The skip is exact in values and no further. The k largest of a union whose tile is strictly below the buffer are the k the buffer has, so the merge returns the same multiset; *which* member of an equal-valued group is named is `torch.topk`'s choice, and at this level's real arithmetic the k-th value is shared by 23 entries of a 12288-candidate union in the sample checked, so the boundary is routinely an equal group. That freedom is already in the shipped code across any change of tiling. The parity column of the in-situ probe did not meet its own determinism floor, so the exactness rests on the argument and on the tests rather than on that column. Testing: `tests/test_models_deepseek_v4_1_attention.py` 13 passed, including a new pair of streams at this level's geometry with the skip taken under a live `-inf` mask. The doc's section records the fabric, in-situ and tie-order measurements and the parity floor's failure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lvyufeng
added a commit
that referenced
this pull request
Sep 21, 2026
Master moved under this branch while it was open: #313 added the candidate stream's early-out as the page's fourth lever, #315 corrected the indexer row to be nested inside `compress_kv`, and #319 rewrote `make_all_reduce`'s docstring. Two files conflicted. `docs/performance/deepseek_v4_1_flash_chunked_prefill.md`: both sides appended a section at the same anchor. Master's early-out stays the fourth lever; the row split becomes the fifth, because the retile is already the third and the early-out now sits between them. 313's heading said the early-out "is the one that ships off", which is no longer exclusive -- the row split ships off too -- so it now reads "ships off by default", and the two cross-references inside the row-split section ("the third lever and the fourth do not add", "the fourth lever's own 91.6%") follow the renumbering. `docs/performance/index.md`: the chunked-prefill row keeps master's corrected framing -- `attn.compress_kv` with `attn.indexer` nested inside it, 7.7% of a 4096-token chunk at 32768 against 16.4% at 262144 -- and gains the row split as the fifth lever, with 313's early-out named beside the retile it now sits next to: five levers rather than two. Code merged clean: `attention.py` and `tp.py` hold both features (`INDEXER_CAND_SKIP_TEST` and `indexer_row_split`), and `tests/test_models_deepseek_v4_1_attention.py` with `tests/test_models_deepseek_v4_1_tp.py` pass 29 of 29. `mkdocs build --strict` is clean. Co-Authored-By: Claude Opus 5 (1M context) <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.
_TopKStream.pushreturns without merging whenamax(tile) < amin(held)and the buffer is already at widthk. That test reads one boolean to the host, so it is a synchronization on every push, and it is written for the prefix level's geometry -- a 4096-key tile narrowed into a 512-wide buffer, where most tiles of most query tiles sit below the running k-th value._stream_candidatesbuilds its stream withk = min(index_topk, width), which is exactly its ownspan: the field being narrowed is already the top-k's own width, so the running k-th value is drawn from the same distribution as an incoming tile's entries and sits inside their range rather than above it. The guard is not dead -- a real 4096-token chunk gave 1 hit in 896 pushes -- but one hit is one merge saved against a synchronization paid 896 times.Measurements
On the real fabric, four ranks, both cache widths, A-B-A-B with the read as one factor and the prefix path's depth-1 lookahead as the other (
/tmp/bench_indexer_cand_overlap.py, 255 pushes a stream). Every arm elementwise identical, every armreads 255 skips 0 merges 255:The read is 174 and 162 us of the shipped tile, and with it gone the depth-1 lookahead goes from +3.9% against the shipped order to -21%. They are the same effect: the read drains the compute stream at every push, which is what stops
_ReducePipelinefrom deferring this path's collective.In situ (
/tmp/probe_cand_skip_inproc.py --at 8192 --chunk 4096 --arms 0 1 1 0, one process, state reset between arms, reduce depth held at 0 so this stays one lever):INDEXER_CAND_SKIP_TESTstream_candidates~0.14 s of a ~1.12 s row on a 25.08 s chunk -- 0.56% of the wall -- with both brackets agreeing in sign.
Why the default is off
Falseis also what a capture already builds:_TopKStreamrefuses that read inside one, so this makes the eager path agree with the recorded one rather than differ from it.DEEPSEEK_V41_INDEXER_CAND_SKIP_TEST=1restores the tested branch.Exactness
The skip is exact in values and no further. The k largest of a union whose tile is strictly below the buffer are the k the buffer has, so the merge returns the same multiset; which member of an equal-valued group is named is
torch.topk's choice, and at this level's real arithmetic --relu(q.k)times a weight, summed over the 8 heads, left in bf16 -- the k-th value is shared by 23 entries of a 12288-candidate union in the sample checked, so the boundary is routinely an equal group. That freedom is already in the shipped code across any change of tiling, so nothing here can be bit-identical by construction and the selection is value-exact.The parity column of the in-situ probe did not meet its own determinism floor: arms 0 and 3 are the same setting with no knob between them and they disagree by 1.695, exactly what arm 1 came back with, while arm 2 came back 2.206. Every pairwise comparison differs, so the column attributes nothing to either setting. The leading explanation is the prefill MoE epilogue's plain
atomicAddinmoe_fp4_grouped_w2_wmma_scatter_kernel-- the deterministic-reduce default covers the single-token and multi-slot paths only. The exactness therefore rests on the argument and on the tests, not on that column.Testing
tests/test_models_deepseek_v4_1_attention.py-- 13 passed, including a new pair of streams at this level's geometry (k == span, bf16, a live-infmask, the skip asserted to have been taken) checked against each other and againstvalues.topk(k).mkdocs build --strictclean.docs/performance/deepseek_v4_1_flash_chunked_prefill.mdrecords the fabric, in-situ and tie-order measurements, and the parity floor's failure as a negative result.🤖 Generated with Claude Code