Skip to content

perf: drop the indexer candidate stream's early-out test - #313

Merged
lvyufeng merged 1 commit into
masterfrom
perf/v41-indexer-cand-skip-test
Sep 21, 2026
Merged

lvyufeng merged 1 commit into
masterfrom
perf/v41-indexer-cand-skip-test

Conversation

@lvyufeng

Copy link
Copy Markdown
Owner

_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.

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 arm reads 255 skips 0 merges 255:

us a c-iteration width 16384 width 131072
serial, read on (what shipped) 1523.7 1543.2
serial, read off 1350.1 1381.3
lookahead depth 1, read on 1583.2 1609.8
lookahead depth 1, read off 1200.2 1233.5

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 _ReducePipeline from 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):

arm INDEXER_CAND_SKIP_TEST chunk s stream_candidates cand push/skip
0 0 25.08 1.102 896/0
1 1 -- 1.284 896/1
2 1 -- 1.236 896/1
3 0 -- 1.137 896/0

~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

False 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.

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 atomicAdd in moe_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 -inf mask, the skip asserted to have been taken) checked against each other and against values.topk(k).
  • mkdocs build --strict clean.
  • docs/performance/deepseek_v4_1_flash_chunked_prefill.md records the fabric, in-situ and tie-order measurements, and the parity floor's failure as a negative result.

🤖 Generated with Claude Code

`_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
lvyufeng merged commit 05b982e into master Sep 21, 2026
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>
@lvyufeng
lvyufeng deleted the perf/v41-indexer-cand-skip-test branch September 21, 2026 11:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant