Repository navigation
Resident decode: attention page arrays in the graph, sampler scalars in one copy, a row per KV pool - #1143
Draft
NSagan271 wants to merge 17 commits into
Draft
Resident decode: attention page arrays in the graph, sampler scalars in one copy, a row per KV pool#1143NSagan271 wants to merge 17 commits into
NSagan271 wants to merge 17 commits into
Conversation
A steady decode step only moves lengths by one and crosses a page now and then, so keep each request's block table row, length, GDN slot and position offset on the device. The captured step reads them by a row of slots and commits its own lengths; the host sends what changed and stays the truth. Mixed and eager steps keep the host plan and mark their rows. Behind knobs: resident_planning (auto|off), Qwen3.5 decode opts in, fa2 keeps its per-step plan.
Picks up #852. No textual conflicts.
The planner's stage runs before runner.plan sets plan_phase, so a fresh stage's laps landed under res.plan. A resident step doesn't read or write the recurrent addressing memo; the cached KV plan's np.insert still feeds fa2's attention plan on a resident step.
#980 has the runner drop its stage when an admit is refused, since the unwind gave back the pages it planned into. The planner's stage names the same pages in its crossing cells, so with no commit in between a retry over the same rows promoted cells pointing at a page the row no longer held. The KV pool now drops it wherever it gives a stage's pages back.
The knob lists in knobs.py and docs/serving.rst conflicted with #988's compile_deterministic entry; both sides kept.
#859 declares device loop-back (DeviceLoopback) and builds Qwen3.5's decode rotary tables in the graph. The engine enables the sampler's reader the way main does now and then builds the planner; Qwen3.5 keeps only resident_walks, since the loop-back hooks come from the declaration.
#820's LoRA lease (a base-only capture key when no row names an adapter) goes first, and the planner is matched to whichever lease it returns. Imports merged.
The scalar buffers' per-step rows become views of one byte block per graph slot, so gather_static stages every buffer into one pinned block and copies it once. A copy apiece cost ~14 us of host time each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a resident bucket whose FlashInfer decode wrapper plans the direct way, a kernel in the graph fills the wrapper's indptr, page indices and last page lengths from the block table and the gathered lengths. The KV plan then hands the attention plan the host lengths alone, from the tables' host mirror, and the attention plan runs FlashInfer's planner alone: no page lists are built or copied on the host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One token a row in the main stream, and preprocess reads no planned position or KV location, so the walk meets resident planning's terms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The planner kept one stream per row: one block table, one label, one stored length, one set of attention modes per bucket. Each is now keyed by resource key, so every KV pool of a node binds its own row, positions read the length of the pool they name, and an attention reads its own pool's table. A pool whose stream the step declares at span 0 is read only: the capture gathers its lengths and leaves its table, and it keeps its host plan, as Whisper's cross attention context does. Adjacent runs of a rewritten row are one copy, so a row of one pool is still one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The self-attention pool appends a token a row; the cross attention context pool is read at span 0, so it binds a read-only row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
Stacked on #1127 (resident decode planning). Two host-time cuts on resident decode steps that #933/#966 had and #1127 doesn't, a resident row per KV pool, and Qwen3-TTS and Whisper on resident tables. #933/#966 stay as drafts for reference.
Attention page arrays built in the graph
On Hopper, FlashInfer's paged decode reads an indptr, a flat page-index list and last-page lengths, not a block table, so a resident step still built the page lists on the host (the KV plan) and copied them (the attention plan).
FlashInferDecodeWrapper.gather_resident(mstar/engine/resources/attn/wrappers.py): one kernel per resident capture fills the wrapper's three arrays from Resident decode planning: decode rows on device tables #1127's block table and the lengths the KV gather already reads, through the step's slots.schedules_alone); the (bucket, pool) then joinsResidentPlanner.lengths_only, next tohost_plan_free.KVResidentTables.decode_indptrs), and the attention plan runs FlashInfer's planner alone (plan_schedule). A promoted step reuses its pre-plan's lengths.Sampler scalars in one copy
gather_staticstaged each scalar buffer (temperature, top-k, top-p, seed, penalty, and any optional ones) with its own H2D, ~14 µs of host time apiece. The per-step rows are now views of one byte block per graph slot (StaticRows), staged and copied once. This one doesn't depend on #1127.A resident row per KV pool
#1127's planner keeps one stream per row: one block table (
_sealrefuses a second), one label per step, oneResidentWrites.kv_len, and one block table, lengths buffer and attention mode per capture. Whisper's decoder has two KV pools, its self-attention cache and its cross attention context, so it couldn't run resident.mstar/engine/resources/resident.py): every KV pool binds its own block table and lengths, positions read the length of the pool theirPositionConfig.kv_cachenames, and an attention reads its own pool's table.bind_residenttakes the resource's key.Models
talker_decodewalk opts in (resident_walks).Results
2× H200; one GPU per server. Rust frontend and SHM arena for Qwen3.5. Each table is ABBA, back to back: #1127's head (863cf87), this branch, this branch, #1127's head.
Qwen3.5-0.8B, prefix cache off,
--ignore-eos, 256–512 output tokens, text tok/s:Phase timing (
benchmark/worker_phases), µs per iteration, before the per-pool commit (64085a9):pre_plan)Qwen3-TTS 0.6B (
benchmark.tts_speech_bench, 128 sentences, 5 repeats per concurrency), audio-s/s:Whisper large-v3-turbo (
benchmark.asr_eval, LibriSpeech test-clean, 640 requests at c=64, four passes per server; the first is warmup), req/s, mean of passes 2–4. The transcripts are greedy and score the same WER, so req/s compares like for like:Each pass is ~6 s; the twelve measured passes don't overlap (#1127 101.6–103.5, this 104.7–106.3).
Output checks:
benchmark.tts_speech_wer): 0.93–1.00% for this, 0.71–1.50% for Resident decode planning: decode rows on device tables #1127.Tests
ruff check .: clean.test/modular/test_resident_planning.py:test/modular/test_resident_planning.py,test/modular/test_sampler_*.py,test/modular/test_sampling_*.py,test/sampling_test, and the cross attention, Whisper and Qwen3-TTS modular tests: pass on H200.test_sampler_static_gather_skip.pynow counts the one packed gather.test_client_error_text,test_gpu_mla_attentionshared-prefix capture,test_kv_offloadOOM report,test_audio_watermark,test_bagel_cfg_positions,test_node_fp32_precision_gpu,test_qwen3_omni_encoders), excepttest_ragged_attention.py::test_piecewise_replay_matches_reference[one_empty], which failed only inside that run and passes alone on both.AI assistance was used for this PR.
🤖 Generated with Claude Code