Repository navigation
v2.6.4: SWA native decode, decode yield, SWA thin + 384 prefix tails, cone replay - #67
Merged
Merged
Conversation
…) on v2.6.3 6c424d0 (perf/v41n-on-v263b-20261102) moved onto 3305c29 without the v41n runtime: a plain cherry-pick conflicts because swan lives in the v41c module (runtime/flashinfer_v41c), which 3305c29 does not have. - runtime/flashinfer_v41c: byte for byte 6c424d0's directory (VENDORED.json, module tag 00505fe850b7, the one the one-GB10 microbench ran). Its v41c / v41n readers have no caller here: the canary's v41c blocks, adapter/v41c_pool.py, the sitecustomize hooks, the autotune_keep volatile keys and the b12x_next writer changes of the v41n merge are not taken. - runtime/flash_mla_sm120.canary.py: 3305c29's file + 6c424d0's swan block only (= 6c424d0's canary minus its two v41c blocks). - Dockerfile.canary, Dockerfile.canary-roce: build + verify the module after the K8 module. - tests: test_swa_native_decode.py, v41n_nvcc_check.sh, v41c_nvcc_check.sh from 6c424d0; test_prefill_native_extra.py strips the swan block for its base check. - CHANGELOG (Unreleased), NOTICE.
- .env.tp4.example: the production line ends with DSV41_SWA_NATIVE_DECODE=1; the switch documented next to the idle park; v2.6.4 note in the production block (unset it to return to v2.6.3). The module default stays off. - adapter/autotune_keep.py: DSV41_SWA_NATIVE_DECODE is volatile. Swan never tunes (tuning mode takes the stock split) and runs the tactic the stock call resolves for the same shape, so the tuned shapes and kernels are the same with it on or off; otherwise switching it on changes the launch fingerprint and re-draws the MoE tactics. - tests: test_autotune_keep (swan volatile), test_swa_native_decode Profile (production line, only that line, volatile key, module default off, both canary Dockerfiles build + verify the module). - Dockerfile.canary / .canary-roce: comment only (module default off, profile on, what the module compiles). - CHANGELOG v2.6.4 (candidate) with the GB10 microbench and the two TP4 in-boot A/Bs, release gate PENDING; README current state (intro, production table row, boot receipt, rollback, credit), docs/adapters.md, NOTICE.
…prompt prefills After a non-final chunk of a chunked prompt with decode rows running, the next prefill is deferred (the engine's own _should_defer_prefill path that --prefill-decode-interval already takes between chunks) until the streams have had decode time worth pct/(100-pct) of the chunk's modelled time. Integer cost model fitted to the 2026-11-03/04 production head log; no clock, every input is replicated scheduler state. Ends early when the streams finish or the chunked prompt is gone; never shorter than the configured interval, never longer than 128 steps. Off when unset (gate checked before the import). tests/test_decode_yield.py: the pinned get_next_batch_to_run, _should_defer_prefill and _arm_prefill_decode_interval lifted into a simulated event loop (stock trace, chunk identity, share, lone prompt, final chunk, solo pass, cancel, abort, rank invariance, pins, drift, env).
- .env.tp4.example: the production line ends with DSV41_DECODE_YIELD=33 (after the swan key); the switch documented next to the prefill bypass / --prefill-decode-interval block, v2.6.4 note in the production block (unset it to return to the stock prefill / decode cadence). The module default stays off; the rid arms stay test only. - adapter/autotune_keep.py: DSV41_DECODE_YIELD and DSV41_DECODE_YIELD_RID_ARMS are volatile. The yield wraps only Scheduler.get_next_batch_to_run / _should_defer_prefill and decides when the next prefill chunk runs, after boot; the autotune pass, the GEMM shapes it tunes and the kernels are the same with it on or off. Otherwise switching it on changes the launch fingerprint and re-draws every fused-MoE tactic on the next boot. - tests: test_decode_yield Profile (share 33 last on the production line and only there, rid arms in no profile, volatile keys, module default off, both canary Dockerfiles run the test on the image's scheduler source); test_autotune_keep (both keys volatile); test_swa_native_decode checks the swan key after v2.6.3's two keys instead of last on the line. - CHANGELOG v2.6.4 (candidate): the decode-yield entry moves from Unreleased into the candidate with the TP4 measurement of 2026-11-05 (run-20261105-064842-138505, verdict PASS: stream cadence during a cold 500K prefill 0.080 -> 0.393 at share 33, 0.524 at 50; that prompt's first token x1.47 / x1.94; speedbench c1 / c4 +0.72 to +0.99 % at share 50 vs mean(A, A')); release gate still PENDING. - README current state (intro, production table row, boot receipt, rollback, known limits, credits), docs/adapters.md.
…default) on v2.6.4 A cached prefix is reusable only at a node whose last sliding window still holds SWA slots. Every 8192-token prefill chunk and every agent turn leaves such a checkpoint, so in stock LRU order an idle session's only resume point is evicted after ~70-100K slots of other traffic and its next turn re-prefills the whole context although its full KV is still cached. With DSV41_SWA_THIN=1 a checkpoint its own request has moved past (chunk boundaries a chunked prefill has passed; the prefix-match node of a request that made no chunked step) goes to the evict-first end of the SWA LRU. Only the list order changes. Engine source pinned; receipt every 1024 hooked inserts; tests/test_swa_thin.py on the real UnifiedRadixCache (CPU), in the canary-roce image build and the offline CI. Port of perf/swafix-v262-20261104 faa55d8 + fb13292 + 2f49e42 (code, hook, test); the CHANGELOG entry moved into the v2.6.4 section, the .env.tp4.example and docs/README-upstream.md notes describe mode 1 as shipped in 2f49e42. The production profile change is a separate commit.
…ntext whose SWA resume point was evicted (off by default) on v2.6.4 When a request's prefix match stops early only because the sliding window at its resume point F was evicted while the full KV is resident, it matches on full KV at s = F - N and prefills [s, L): the resident latents / index keys of [s, F) are copied into the replay rows' fresh pages, their own latent / index-key writes go to the padding slot and their window is floored at s. Rows >= F compute what a prefix hit at F computes. The first pass must be a chunk covering the replay, else the stock match is kept. Port of perf/swa-cone-replay-20261104 35f36ac + 6ad65d3 + ee07120 (module, sitecustomize hooks, tests), with the review decision's ship conditions: - DSV41_SWA_CONE_TOKENS default 8192 (was 4096), the chunk the fleet windows ran and the production CHUNKED_PREFILL_SIZE; - one "DSV41_SWA_CONE plan" and one "copy" line per replay without the test switch (they were DSV41_SWA_CONE_TEST-only), so a release gate and an operator see every replay; the test switch stays test-only. Engine pins unchanged: v2.6.3 / v2.6.4 change no SGLang patch, and no v2.6.3 / v2.6.4 adapter wraps a pinned function (decode yield wraps Scheduler.get_next_batch_to_run / _should_defer_prefill, the fold and park Scheduler on_idle / run_batch / process_input_requests / __init__, swan is in the decode kernel module). The cone's PrefillAdder hook stays after the prefill bypass; swa_thin wraps cache_unfinished_req, which the cone pins through inspect.unwrap. tests/test_swa_cone.py: the hook test now runs with DSV41_SWA_THIN=1, the prefill bypass 256 + solo and DSV41_DECODE_YIELD=33 and checks all of them stay installed; the exact-chunk turn checks the plan / copy lines with the test switch off. 18 / 18 on the pinned SGLang tree (CPU).
…SWA_CONE_REPLAY=1 DSV41_SWA_CONE_TOKENS=8192 in the production profile - .env.tp4.example: the production line ends with DSV41_SWA_THIN=1 DSV41_SWA_CONE_REPLAY=1 DSV41_SWA_CONE_TOKENS=8192 (after the decode yield); the production EXTRA_SGLANG_ARGS carry --swa-prefix-tails 384 (was 128). Notes: the KV pool change (~5.1M -> ~3.6M tokens, the SWA pool +98,304 slots) and the config-only rollback (unset the keys, tails 128; 256 the middle step). DSV41_SWA_CONE_TEST stays test only. - adapter/autotune_keep.py: DSV41_SWA_THIN and the DSV41_SWA_CONE_* keys are volatile: they act on the prefix-cache tree and on replays of a cached prefix after boot; the autotune pass has no cached prefix, so no replay runs while tuning and no tuned shape or kernel depends on them. The tails change is a launch argument and re-tunes once. - tests: Profile classes in test_swa_thin (key once after the yield, tails 384 once, volatile, module default off, Dockerfile runs the test) and test_swa_cone (keys last on the line, code default 8192 = the production chunk, test switch in no profile, volatile, default off); test_autotune_keep (the five keys volatile); test_decode_yield checks the yield right after swan instead of last on the line. - CHANGELOG v2.6.4 (candidate): four switches, the KV pool trade-off and rollback, the fleet windows of both levers with run ids (run-20261105-005746-3350673, run-20261105-040948-3893907, run-20261105-082118-415058, run-20261105-033337-3800238), their verdicts and the review decisions; release gate still PENDING. - README current state (intro, production table rows, boot receipt, rollback, known limits, credits), docs/adapters.md, docs/README-upstream.md.
The release gate passed (run-20261105-170445-1455369: v2.6.3 / v2.6.4 / v2.6.3, 0 KILL, 0 INVALID). CHANGELOG records its numbers in place of PENDING; the README tables now show the v2.6.4 gate boot, with the KV pool trade-off (3,444,224 vs 4,997,632 / 5,101,824 tokens) and its config-only rollback.
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.
v2.6.4
v2.6.3 plus a decode attention that reads the sliding-window (SWA) cache in place, a decode share for running streams while a long prompt prefills, and two prefix-cache changes that keep idle agent sessions resumable without re-prefilling their whole context. Outputs are bit-identical for the decode attention; the other three change only scheduling, eviction order and how a lost resume point is rebuilt. The full KV pool is smaller (about 3.4-3.75M tokens instead of 5.0-5.16M); the rollback is config only. Full details are in CHANGELOG.md.
What's new
DSV41_SWA_NATIVE_DECODE=1, now in the production profile). Every decode attention call (up to 64 query rows: the DSpark verify and draft forwards) used to copy each touched 256-token SWA page into four 64-token pages first, 43 copy chains per c1 step, because FlashInfer's decode-dsv4 kernel was instantiated for 64-token pages only. Its address math is generic in the page size, so the same kernel body instantiated at 256-token pages reads the pool in place, with the same tactic and the same output bits. Calls it does not serve keep the copy and are logged once per reason. Two in-boot A/Bs on the four Sparks: greedy c1 +1.36 / +1.86 %, and T = 0 text identical on every prompt that was stable between control runs. The module (runtime/flashinfer_v41c) is built at image build with its.sosha256 checked at load.DSV41_DECODE_YIELD=33, now in the production profile). Between two 8192-token chunks of a long prompt the engine used to run only the--prefill-decode-intervalsteps, so the other streams fell to about 8 % of their decode rate for the whole prefill. After each non-final chunk the next one now waits until the running streams have had decode time worth 33/67 of that chunk's time. The decision uses an integer cost model, not a clock, so every rank decides alike, and chunk boundaries, batches and outputs are the engine's own. Four Sparks, two streams on 300K contexts beside a cold 500K prompt: the streams' decode rate during the prefill went from 0.080 to 0.393 of their rate before it, and the 500K prompt's first token came x1.47 later. A lone long prompt prefills exactly as before.DSV41_SWA_THIN=1with--swa-prefix-tails 384, was 128, now in the production profile). A cached prefix can only be resumed where its last sliding window still holds SWA slots. Every prefill chunk and agent turn leaves a checkpoint, so in stock LRU order an idle session lost its resume point after about 70-100K slots of other traffic and its next turn re-prefilled the whole context. Checkpoints that their own request has moved past are now evicted first, and the SWA pool holds 384 prefix tails. Four Sparks, two long sessions idle for minutes beside sub-agent turns and new 500K sessions: resumed 330K / 300K sessions answered in 3.9 / 6.5 s instead of 61-64 s, with no whole-session re-prefills (2 in each control). Only the eviction order and the pool split change.DSV41_SWA_CONE_REPLAY=1 DSV41_SWA_CONE_TOKENS=8192, now in the production profile). When a session's SWA window was evicted anyway but its full KV is still cached, the request matches on full KV 8192 tokens earlier and replays only that chunk. The cached latents and index keys are copied into the replay rows and their own writes are masked, so every row from the resume point on computes what a prefix hit there computes. Four Sparks, 16 prompts of 130-400K tokens: the replay's distance from a re-prefill was 1.01x that of a plain prefix hit, with 0 confident token flips, and its first token came in 1.6-2.1 s instead of 21.8-68.3 s. When the first chunk cannot cover the replay, the request falls back to the stock re-prefill and logs it.Release gate
v2.6.3 / v2.6.4 / v2.6.3: fresh boots in that order, production profile, abliterated 1017
517efab, GPU clock capped at 2200 MHz, 2026-11-05 (runrun-20261105-170445-1455369, 274 min including the restore of production). PASS, 0 KILL, 0 INVALID. No KV pin this time (earlier gates pinned 4,194,304), so each boot sized its own pool and the v2.6.4 boot ran with the pool it ships with. Speedbench 0.3 full mode, median of two runs for greedy cells, one run for sampled cells. The v2.6.4 boot ran the v2.6.3 gate image with this release's files overlaid and the swan module built in the image.At T = 0 the v2.6.4 outputs equal the controls' on all 74 of the 75 qeval prompts on which the two controls agree. KLD long 0.00677 and short 0.0045 (PASS). The T = 1 distribution check passed on 12,000 samples, and the 1M needle (969,071 tokens) was found in 195 s. The sampled cells are single runs; the two controls differ by 1.5 % between themselves at c1. Every rank of the v2.6.4 boot logged the swan module, SWA thin, the cone replay and the decode yield (share 33) as installed, with no fallback and no error, and every rank of all three boots logged the FlashInfer #7676 fence as present.
Evicted resume point (v2.6.4 boot): a 300,000-token document (prefill 49.7 s), then 800 distinct 1K-token prompts that pushed its SWA window out, then the document plus 2,048 tokens. Every node replayed one 8,192-token chunk with 0 reverts. The first token came after 2.07 s, with 291,328 of 302,048 prompt tokens cached and 0 errors.
Soak (issue #16 reproduction with a seeded document order plus a c2 companion load, 12 timed minutes): 256 / 318 / 263 requests with 0 failures, head MemAvailable low-water 9.55 / 8.37 / 8.73 GiB. v2.6.4 keeps the soak's documents cached where the controls re-prefill them, so it served more requests in the same time.
KV pool trade-off
The 384 prefix tails reserve 182,784 SWA slots instead of 84,480, and that memory comes out of the full KV pool: 3,444,224 tokens on the v2.6.4 boot against 4,997,632 / 5,101,824 on the controls (earlier A/B windows: 3.60-3.75M against 5.06-5.16M). That still holds three full 1M-token contexts, not four; the production peak in flight over a day was 1.91M tokens. If in-flight context needs more,
--swa-prefix-tails 256is the middle step (about 4.4M tokens).Upgrade / rollback
The "Production profile" line in
.env.tp4.examplenow ends withDSV41_SWA_NATIVE_DECODE=1 DSV41_DECODE_YIELD=33 DSV41_SWA_THIN=1 DSV41_SWA_CONE_REPLAY=1 DSV41_SWA_CONE_TOKENS=8192, and the production launch line carries--swa-prefix-tails 384. RebuildDockerfile.canary-roceto get the swan module. The rollback is config only, and each switch can be unset on its own:DSV41_SWA_THINand set--swa-prefix-tails 128to get back the v2.6.3 prefix cache and the ~5.0-5.1M-token pool;DSV41_SWA_CONE_REPLAY/DSV41_SWA_CONE_TOKENSto return a lost resume point to the stock re-prefill;DSV41_DECODE_YIELDto return to the stock prefill / decode cadence;DSV41_SWA_NATIVE_DECODEto go back to the 64-token page copy.All the new keys are volatile for
DSV41_AUTOTUNE_KEEP, so toggling them reuses the tuned MoE tactics; the tails value is a launch argument, so the first boot after changing it re-tunes once.Credits
--prefill-decode-intervalpath, with Shangming Cai's SGLang #44001 gate already backported. Christopher Owen's prefill yield in spark-ds41f was the starting point of the comparison.dsv4.1branch's decoder bounded replay, whoseswa_replay_startwindow floor the replay reuses. The eviction rule, the replay, the page copy and the write mask are ours.