Repository navigation
[Engine] a long prompt can borrow the MTP drafter's memory while it reads - #3307
Open
gopinath87607 wants to merge 3 commits into
Open
gopinath87607 wants to merge 3 commits into
gopinath87607 wants to merge 3 commits into
Conversation
…2 experts (decode-only) as ring A prompt of at least STRATA_MTP_ROOM_MIN tokens (0: every prompt) points the prompt path's ring AT the MTP drafter's 512 routed experts - 708 MB (675 MiB) that only the DECODE reads (record_rest; a prompt's passes run the draft layer's dense tensors and its K/V) - for as long as the prompt is read. The block is lent IN PLACE: slot i is experts_base() + (i - rule) * MAXBLOB (135 slots on a Q8_0 pack, whose largest blob is 4.98 MiB). Opening and closing a room moves the ring count alone (Prefill::set_ring_room_active): the block is never freed, never allocated again and never moves, so no prompt path is laid out again, the decode's captured round/step graphs stay valid (nothing is dropped), and what comes back before the first window is the decode's own bytes (MtpDrafter::experts_reclaim). Limits, refused with the reason at start: --serve only, --mtp needed, --batch not supported (its slot drafters share the solo drafter's weights). A layer split is fine (the room goes to the prompt path on the drafter's card), --pipeline-windows is fine (the room closes before the request's first window), and --prefill-head-alias is fine. Unset (the default, parsed as -1), not one line of the room runs and the prompt path is laid out exactly as before. Measured on the Q8_0 4-way rig in the first shape (copy out to pinned host, free the block, relayout; local commit 09f84d8 carried it): it works - the 675 MiB really went into the ring, the block came back before the first window, the decode ran unchanged - but it bought no time at 3,080 or 14,859 tokens (the ring was not what the prompt waited on) and cost 3.4-4.0 s per roomed request. The in-place shape in this commit is built and compile-clean but its runtime has not been run yet; its expected cost is the two 675 MiB DMAs (~0.12 s), an estimate. From the Maya checkout (project-maya), local commits 7df369c (the room for its DFlash drafter), d4d0792 (the tail's slots round up) and 5ccf9a4 (the MTP config's NextN block as the room's slots, in place). Co-Authored-By: Claude Code <noreply@anthropic.com>
… whole prompt An agent turn re-sends a long conversation and reuses almost all of it (one live log: 53,694 tokens sent, 53,594 reused, 100 read). The threshold was tested against the whole prompt, so every such turn lent the drafter's 675 MiB out and copied it to host and back (~107 ms) to read a few hundred tokens the room cannot help. The room now opens after `read_from` is known and compares `n - read_from` against TOKENS; `=0` still rooms every request, as measured. The open stays before `lend()`, so a prompt path relayout re-carves with the room active. set_ring_room_active only moves the ring count, and carve lays out the room's slot pointers whether the room is active or not, so opening after the head-alias rebuild still adds the slots. Also updates the start and per-request log wording, MTP_PROMPT_ROOM.md and DETAILS.md, and records merbanan's 8 GB RTX 2060 SUPER report (the in-place shape: +17%/+17%/+23% prompt tok/s at 4K/16K/32K, decode within noise, logits bitwise equal) with what was not tested. No measurement is claimed for the threshold change itself. Checked: generate.cpp -fsyntax-only with the build's own flags (one pre-existing unused-variable warning in unrelated code). HIP/SYCL not compiled here. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Thanks for the pull request. To keep the tracker readable, every item needs an area prefix in the title and the parts of the pull request template filled in. This one is missing:
Edit the pull request (title and description) and this note goes away on its own. If it is still missing parts in 5 days it will be closed, which is not a rejection: fix it and comment Details: CONTRIBUTING.md |
|
Thanks, this follows the format now. |
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.
Problem and result
A long prompt's ring is bounded by the VRAM the prompt path can borrow, so on a full card the read waits on the ring. The MTP drafter's 512 routed experts are 708 MB (675 MiB) that only the decode reads - a prompt's passes run the draft layer's dense tensors and its K/V - so while a request that reads enough tokens is being read, that block can become the prompt path's ring. Before: the ring is whatever the layout could free elsewhere. After:
STRATA_MTP_ROOM_MIN=TOKENS(opt-in) lends the block in place for such a request and takes it back before the first decode window; a turn that reuses a cached prefix and reads only a few hundred tokens does not take the room or pay its two 675 MiB DMAs.Related discussion
The original pull request and its discussion: #2830 (closed when its fork was deleted; this PR replaces it). The work comes from the Maya checkout (
project-maya), local commits7df369c(STRATA_GLM_ROOM_MIN),d4d0792(the tail's slots rounded up to the largest blob) and5ccf9a4(the in-place shape, for the MTP config's NextN block). Counting what a request reads, rather than the whole prompt, is this port's change, made after the report below.Approach
STRATA_MTP_ROOM_MIN=TOKENS(--servewith--mtp): a request that reads at least TOKENS prompt tokens (0: every request) uses the MTP drafter's 512 routed experts as the prompt path's ring while it is read. TOKENS counts the tokens this request reads, not the whole prompt: a turn that reuses a cached conversation prefix and reads a few hundred tokens gains nothing from the room. The prompt path's ring is pointed at the block itself:slot i = experts_base + (i - rule) * MAXBLOB(135 slots on a Q8_0 pack, whose largest blob is 4.98 MiB). Opening and closing a room moves the ring count alone (Prefill::set_ring_room_active): the block is never freed, never allocated again and never moves, so no prompt path is laid out again, the decode's captured round/step graphs stay valid (nothing is dropped), and what comes back before the first window is the decode's own bytes (MtpDrafter::experts_reclaim). In the measured shape's successor this is two 675 MiB DMAs around the read.The room is opened after the reused prefix is known (
read_from) and immediately beforelend(), so a relayout still re-carves with the room; a request that must be re-read from 0 (STRATA_CKPT_REREAD) counts all of its tokens.Limits, refused with the reason at start:
--serveonly,--mtpneeded,--batchnot supported (its slot drafters share the solo drafter's weights). A layer split is fine (the room goes to the prompt path on the drafter's card),--pipeline-windowsis fine (the room closes before the request's first window), and--prefill-head-aliasis fine.Unset (the default)
mtp_room_minis-1and not one line of the room runs; the prompt path is laid out exactly as before. The new code is reachable only behind that check. This is by construction, not by a measurement.=0keeps the behaviour that was measured: every request takes the room (the 2026-10-19 control did, on 7-token continuations).Validation
Tested revision:
41bff1d3(three commits on477ac13c; not rebased, and GitHub reports it merges cleanly with currentmaine88ee39d). Environment: Linux, CUDA 13.2,sm_86+sm_120; the Q8_0 4-way rig is 2x RTX 3060 12 GB and 2x RTX 5060 8 GB.Passed:
ninja -C build strataon the parent engine and on41bff1d3- both exit 0; the two binaries differ by this work.STRATA_MTP_ROOM_MIN=1024. One 5,042-token chat and three continuations that echoed the answer back. The parent lent the room for the whole 5,096-token prompt on continuations that read 61, 7 and 7 tokens (lend ~102 ms + reclaim ~104 ms); this commit lent nothing. Read time 2,054 -> 1,813 ms, 360 -> 153 ms, 344 -> 152 ms; time to first token 2.110 -> 1.869 s, 0.409 -> 0.203 s, 0.390 -> 0.199 s. AMIN=0control on this binary still opened the room on the 7-token continuations (time to first token 0.432 / 0.415 s), so=0is unchanged and the difference is the room, not the binary. The cold request still took the room in both arms.--serve --mtp(Q2_0 drafter, 675 MiB),--resident-budget-gib 40,--kv k8v4 --kv-resident 20480,--prefill auto. The auto chunk was 6,656 tokens with a 96-slot ring; the room added 303 slots (399 in all), chunk unchanged.STRATA_MTP_ROOM_MIN=0against the same build unset (paired A/B, 3 pairs, prompt tok/s): 4K 448.4 -> 527.2 (+17%), 16K 558.9 -> 658.1 (+17%), 32K 584.0 -> 719.7 (+23%); decode moved -0.27 / +1.42 / -0.17 (noise). The lend cost 107 ms per roomed request.STRATA_PREFILL_RINGalone showed the ring was the limit (192 slots: +9.8% / +6.2% / +2.3%, decode -3.1 to -3.6 tok/s). First-token logits after a 15,521-token prompt were bitwise equal to the unset run (two runs per arm, all four identical), the 48-token greedy replies matched, and the drafter's acceptance after reclaim was normal (27 of 34).Failed:
Not run:
generate.cppcarries no per-backend code, so both builds compile the same change.--prefill-head-alias.e88ee39d: this head is based on477ac13cand was not rebased; GitHub reports it merges cleanly.The measurements above are listed with what they were measured on. No blanket speedup is claimed: the knob is how you measure it, the same prompt with the variable set and unset,
PPline againstPPline.7df369crecords the room as not yet measured, with its standing baseline 89.5 s (1,123 tok/s) on its byte-identical 100,535-token read.Limits
--serve --mtponly;--batchis refused because its slot drafters share the solo drafter's weights.🤖 Generated with Claude Code