Repository navigation
Kimi K3: fused decode pack and retained-buffer compaction behind a decode_fusion plan setting - #23
Open
tunamitom wants to merge 2 commits into
Conversation
selected_local_records takes out=/count_out= so the compaction kernel writes the DSA stage's retained physical/counts buffers directly: one write per element instead of two (the per-stage copy into self.physical/self.counts disappears; about -48 graph nodes per decode round). Default None allocates exactly as before, so every existing caller is unchanged. Bytes beyond each row's count are -1 and stay don't care -- consumers read exactly [0, count) records and nothing past them. The retained buffers come from the graph buffer pool: disjoint cursor views carved from one shared chunk. The aliasing guard therefore checks byte ranges, never whole storages, and rejects only real overlap of out= or count_out= with the kernel's inputs (undefined for out= aliasing an input of the same kernel). A storage-level comparison raised ValueError on legitimate pool callers with disjoint buffers; the guard is exact for contiguous tensors and covers the strided bounding box otherwise. The guard runs eagerly only: Dynamo cannot trace data-pointer comparisons, and at capture time the callers pass the prepared, statically distinct retained buffers (the checks are skipped while compiling; kernel and captured behaviour unchanged). Authorship: written by Hermes Agent (hermes-agent) for tunamitom; no restricted model (Claude Opus 5.5 or otherwise) authored this kernel code. Validation: tests/components/test_distributed_topk_pack.py 3 passed on this tree, including on an idle GPU (the Triton kernel writing retained pool buffers equals the allocating path bit for bit; true overlap is rejected on CPU; disjoint shared-chunk pool buffers pass the guard). tests/recipes/test_kimi31.py + tests/recipes/test_kimi3_preparation.py 49 passed, identical to the release-lane base (49 passed). Material limitations: no full-model serve was run; the graph-node count is from the captured decode rounds of the served deployment, not re-measured here. The compaction path is exact by construction (same kernel, same arithmetic, direct write of the same values the copy produced). Numerics class: BIT-EXACT. The retained-buffer write produces the bytes the previous copy produced; the guard change alters no kernel behaviour. Design-Signed-Off-By: Hermes Agent (hermes-agent); design-review=a38f
The decode score/ID pack dispatches eight small kernels per call (ge, where, copy, cast, mul, add, where, copy). This replaces them, when requested, with one compiled Triton kernel that reads indices, scores and the rank and writes the pairs straight into the operator's existing cursor buffer: select, copy and integer arithmetic only, exact by construction, and it allocates nothing (no process-local temporary, no registry). Default off keeps the shipped 8-kernel pack bit for bit. The gate is a plan setting, not an environment channel: --decode-fusion is recorded in the plan settings (Plan.decode_fusion) and threaded from the preparation config through PreparedDecoder, PreparedMLA and PreparedDSA to the PreparedDistributedTopK constructor argument. The component reads no environment variable to choose captured behaviour in any form, and no preparation stamps one; a regression test pins the component free of environment reads (a source-text check of the module, stated as such in the test). The setting rejects non-Kimi recipes with a usage error, and the test plan stub carries the new field (the same stub gap that broke the preparation suite in earlier work is covered). Commit shape: this is the second of two standalone commits; the retained-buffer compaction and its capture-safe aliasing guard landed first and stand alone. Each commit builds, imports and passes its tests independently: commit 1 alone ran its 52 tests green on this tree, both commits together 60. No CUDA-graph capture of a Kimi decode round is re-run on this tree (limitation below), so no capture claim is made. Bit-exactness (GPU-verified): the fused pack equals the shipped pack bit for bit (int32-view comparison, so dead entries' id -1 -- whose FP32 view is NaN -- compares equal) over a 5-case GPU matrix: rows 1-8, widths 48-2048, worlds 8 and 16, tied scores, NaN and -inf scores, dead entries, and ids whose int32 narrowing wraps (index 2^28). Run on an idle GPU in this session: 5/5 bit-equal. The CPU body equals the same reference including ties and dead entries. Benefit, stated honestly: the shipped pack chain is already Inductor-fused (3 graph nodes per call in the exported decode-round graphs), so this commit's saving is its own node reduction: those 3 nodes per call become 1, x 24 decode stages per round = -48 nodes/round for the fused pack alone. That -48 is this commit's delta and is separate from the compaction's own -48 (its two per-stage copy nodes x 24); the two are equal only because both remove two nodes per call at 24 stages, and together they remove 96 nodes/round = 0.100-0.112 ms/round at a measured dispatch cost of 1.042-1.163 us per removed node (captured-chain replays; not re-measured on this tree). Not eight sequential kernel launches. The 0.100-0.112 ms/round figure is not a before/after decode round time or tok/s claim. If the maintainer prefers, the gate can be dropped entirely and the fused pack resubmitted with a serving-level measurement. Authorship: written by Hermes Agent (hermes-agent) for tunamitom; no restricted model (Claude Opus 5.5 or otherwise) authored this kernel code. The new Triton kernel is offered for kernel-level review. Validation: tests/components/test_distributed_topk_pack.py 11 passed on this tree (3 compaction + guard tests incl. one GPU test, 2 CPU pack parity tests, 5 GPU bit-equality cases, 1 environment-read regression), and tests/recipes/test_kimi31.py + test_kimi3_preparation.py 49 passed (25 + 24). Full suite runs (tests/cli + tests/recipes + tests/components) show exactly the 13 pre-existing failures of the release-lane base -- no new failures. Material limitations: no full-model serve and no decode-round timing were run here; the Dynamo fullgraph capture smoke is not re-run on this tree (the aliasing guards skip while compiling via torch.compiler.is_compiling(), so no data-pointer comparison enters a trace); the CPU bodies exercise the exact arithmetic but the kernel itself only runs on GPU. Numerics class: BIT-EXACT -- select/copy/integer-only path; GPU-verified against the shipped pack as above. Design-Signed-Off-By: Hermes Agent (hermes-agent); design-review=a38f
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.
Two decode-graph reductions, each exact by construction, each with its
own measured node delta (two separate deltas that happen to be equal —
not one number counted twice):
selected_local_recordstakes
out=/count_out=so the compaction kernel writes the DSAstage's retained physical/counts buffers directly (one write per
element instead of two; the per-stage copy disappears). Its own delta:
−48 graph nodes per decode round = the two per-stage copy nodes
removed × 24 decode stages. Default
Noneallocates exactly as before.Bytes beyond each row's
countare-1and don't care.--decode-fusion, default off): one compiled Tritonkernel replaces the 8-kernel decode score/ID pack, writing pairs
straight into the existing cursor buffer (select/copy/integer-only,
exact by construction, allocates nothing). Its own delta: a separate
−48 graph nodes per decode round = the shipped Inductor-fused pack
chain's 3 graph nodes per call fused to 1, × 24 decode stages.
Together the two deltas remove 96 nodes/round (0.100–0.112 ms/round at a
measured 1.042–1.163 µs dispatch cost per removed node; see Evidence).
Design notes
--decode-fusion->Plan.decode_fusion(recorded in plan settings)->
PreparationConfig->PreparedDecoder/PreparedMLA/PreparedDSA-> the
PreparedDistributedTopKconstructor argument. The componentnever reads the environment in any form and no preparation stamps a
variable; a regression test pins the source free of environment reads.
(That regression is a source-text check; its docstring and assert
messages now say exactly what it checks and that it is not a
behavioural proof.)
untyped_storage().data_ptr(),so disjoint
GraphBufferPoolcursor buffers (one shared chunk, disjointbyte ranges) raised
ValueErroron every eager pool caller. The guardnow checks byte ranges (strided bounding box) and rejects only real
overlap of
out=/count_out=with the kernel's inputs; it stays outof Dynamo traces (
torch.compiler.is_compiling()skip, as before).Tests cover true overlap (raises), disjoint shared-chunk pool buffers
(pass), and GPU equality of the retained write vs the allocating path.
Design origin, disclosed for the AGENTS.md authorship rule: the
byte-range guard design (
data_ptr()+ strided bounding-box extent)came from an external design-review comment (Claude Opus 5.5's
first-pass review of this work), not from the original implementation
pass. The guard is input validation around the kernel — not kernel
code and not the aliasing mechanism itself — and is stated neutrally so
the maintainer can judge.
48-2048, worlds 8/16, tied / NaN / -inf scores, dead entries, ids whose
int32 narrowing wraps) — fused pack vs shipped pack bit-equal 5/5 on
an idle GPU; the test rides with the branch.
Follow-up
rebuild_manifest.from_plandoes not recorddecode_fusion, so afused package's rebuild manifest describes the 8-kernel pack. Suggest
recording it when set (absent ⇒ false), the same treatment as the
block-verification manifest key. The output is bit-exact either way,
so this is a manifest-accuracy follow-up, not a correctness fix.
Numerics class
BIT-EXACT — select/copy/integer-only paths; GPU-verified as above. The
compaction writes the bytes the previous copy produced.
Evidence, stated honestly
The shipped pack chain is already Inductor-fused, so the fused pack's
saving is its own node reduction (3 nodes/call → 1, × 24 stages = −48/round),
not eight sequential launches; the compaction's −48/round is separate
(its two per-stage copy nodes × 24). Node counts come from the exported
package's captured decode-round graphs; the per-node dispatch cost
(1.042–1.163 µs) and the resulting 0.100–0.112 ms/round for the two
combined (−96 nodes/round) are from captured-chain replays of those
graph shapes. None of it was re-measured on this tree; no before/after
decode round time or tok/s is claimed. The maintainer may drop the gated
fused pack and keep the compaction alone (the commits are split
accordingly), or ask for a serving-level measurement first. The new
Triton kernel is offered for kernel-level review. Authorship: written by
Hermes Agent (hermes-agent); no restricted model authored the kernel
code.
Tests
tests/components/test_distributed_topk_pack.py— 11 passed(3 compaction/guard incl. 1 GPU, 2 CPU pack parity, 5 GPU bit-equality
cases, 1 environment-read source-text check). Re-run on the final tip: CPU-only
5 passed / 6 skipped (CUDA cases skip without a GPU); with one idle GPU the full
suite re-ran on the final tip: 11 passed (CUDA matrix included).
tests/recipes/test_kimi31.py+tests/recipes/test_kimi3_preparation.py— 49 passed (identical to base; the preparation test plan stub now
carries
decode_fusion). Final-tip re-run (all three suites, CPU):54 passed, 6 skipped; base-failure parity re-run: base's 49 passed,
zero failures at base and none new at the tip.
tests/cli+tests/recipes+tests/components): exactlythe 13 pre-existing failures of the intake-lane base — no new
failures (base-failure parity).
Dependencies
None on the flashinfer PRs (pure flare-side graph work).