Repository navigation
STRATA_MTP_ROOM_MIN — a long prompt takes the MTP drafter's 512 experts (decode-only) as ring - #2830
gopinath87607 wants to merge 3 commits into
Conversation
ead3ca0 to
138436c
Compare
…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>
138436c to
67146ca
Compare
Measured: the room pays on an 8 GB Turing card (+17% to +23% prompt, decode unchanged, bitwise-identical logits)We ported this PR onto our 0.1.45-based branch and measured it on a card where the ring is the limit. It is now our live default. Setup
Is the ring the limit here? We ran
So the ring is a limit here. Taking it from the cache, though, shrinks the auto chunk (6656 -> 5632) and costs decode tok/s after short prompts. The room gets the ring without either cost.
Room cost and correctness
A fix we suggest: count the tokens the request reads, not the whole promptAs written, the room opens when With any threshold below the conversation length, every one of those turns pays the ~107 ms lend/reclaim round trip, 2 x 708 MB over PCIe, to read 15-350 tokens. The ring cannot help a read that small, so on these turns the room is pure overhead. Fix (our commit
Nothing else moves:
Effect, with
With diff (against our port; the code at both sites is the PR's)diff --git a/src/program/generate.cpp b/src/program/generate.cpp
index ea35be89..02e0e886 100644
--- a/src/program/generate.cpp
+++ b/src/program/generate.cpp
@@ -11107,19 +11107,6 @@ int main(int argc, char** argv) {
return 0;
};
if (mtp_room_open && room_close("left open") != 0) return 1;
- if (mtp_room_slots > 0 && room_path != nullptr && (int64_t) n >= mtp_room_min && mtp.experts_resident()) {
- const auto room_t0 = Clock::now();
- if (!mtp.experts_lend(err)) {
- std::fprintf(stderr, "strata serve: prompt room: %s: this prompt reads as it always did\n", err.c_str());
- } else {
- room_path->set_ring_room_active(true); // before the loan: a relayout re-carves with the room
- mtp_room_open = true;
- std::fprintf(stderr, "strata serve: prompt room: %lld tokens, the prompt path's ring is the drafter's "
- "%llu MiB (%d slots past its own, lent in %.1f ms)\n", (long long) n,
- (unsigned long long) (mtp.experts_bytes() >> 20), mtp_room_slots,
- std::chrono::duration<double, std::milli>(Clock::now() - room_t0).count());
- }
- }
if (o.prefill_head_alias && native_head.loaded()) {
const auto head_t0 = Clock::now();
native_head.unload();
@@ -11453,6 +11440,21 @@ int main(int argc, char** argv) {
tr("request", n, geni ? 1 : 0);
if (use_mtp) mtp.set_prompt_len(n);
const int64_t read_from = reread_to > 0 ? 0 : resume;
+ // STRATA_MTP_ROOM_MIN: opened here, once the reused prefix is known - the threshold counts the tokens this
+ // request reads (an agent turn on a long conversation reads a few hundred and gains nothing from the room)
+ if (mtp_room_slots > 0 && room_path != nullptr && n - read_from >= mtp_room_min && mtp.experts_resident()) {
+ const auto room_t0 = Clock::now();
+ if (!mtp.experts_lend(err)) {
+ std::fprintf(stderr, "strata serve: prompt room: %s: this prompt reads as it always did\n", err.c_str());
+ } else {
+ room_path->set_ring_room_active(true); // before the loan: a relayout re-carves with the room
+ mtp_room_open = true;
+ std::fprintf(stderr, "strata serve: prompt room: %lld tokens to read, the prompt path's ring is the drafter's "
+ "%llu MiB (%d slots past its own, lent in %.1f ms)\n", (long long) (n - read_from),
+ (unsigned long long) (mtp.experts_bytes() >> 20), mtp_room_slots,
+ std::chrono::duration<double, std::milli>(Clock::now() - room_t0).count());
+ }
+ }
conversations.limit_reuse(read_from);
pp_total = n;
pp_from = read_from; |
… 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>
hi thanks for testing this pr i applied your suggestion and also i will open different pr for tackling small prompt speeds up currently focusing on the deepseek implementation |
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Closed because the source fork was deleted. This PR was opened from the |
What this changes
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. Those experts are 708 MB (675 MiB) that only the decode reads —
record_restis their only reader; a prompt's passes run the draft layer's dense tensors and its K/V — so the promptpath's ring is pointed at the block itself:
slot i = experts_base + (i - rule) * MAXBLOB(135 slots on a Q8_0pack, 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 pathis 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 istwo 675 MiB DMAs around the read.
The room is opened after the reused prefix is known (
read_from) and immediately beforelend(), so a relayout stillre-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 draftersshare 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.From the Maya checkout (
project-maya), local commits7df369c(STRATA_GLM_ROOM_MIN),d4d0792(the tail'sslots rounded up to the largest blob) and
5ccf9a4(the in-place shape, for the MTP config's NextN block). Countingwhat a request reads is this port's change, made after the report below.
What it leaves alone
Unset (the default)
mtp_room_minis-1and not one line of the room runs; the prompt path is laid out exactly asbefore. The new code is reachable only behind that check. This is by construction, not by a measurement.
=0keepsthe behaviour that was measured: every request takes the rod, on 7-token continuations).
Measurements
room works and pays where the ring is the limit. Qwen3.8-Flash-Next IQ3_XXS,
--serve --mtp(Q2_0 drafter,675 MiB),
--resident-budget-gib 40(fully page-locked),--kv k8v4 --kv-resident 20480,--prefill auto; theauto chunk was 6,656 tokens with a 96-slot ring, the 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%); dec17 (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), so this is the case thn 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).
opened, the 675 MiB really went into the ring, the block came back before the first window and the decode ran
unchanged — but a deeper ring was not what the prompt waiens. That shape laid the
prompt path out again around the block and cost **3.4–4.0p of the read.
and its ~206 ms goes with it. Two engines from the sameainst this one), one start
each,
MIN=1024: one 5,042-token chat and three continuations that echoed the answer back. The parent lent theroom for the whole 5,096-token prompt on continuations thend ~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. A
still opened the room on the 7-token continuations (time to first token 0.432 / 0.415 the difference is the room, not the binary. The cold request still took the room in bver: more than one run per timing, HIP/SYCL,--prefill-head-alias`.7df369crecords the room as not yet measured, with its 23 tok/s) on itsbyte-identical 100,535-token read.
PPline againstPPline. The ring gain appears where GB card above) and is nilwhere it is not (our Q8_0 rig). The read threshold's gainend on the ring: it removes
the room's own DMA from turns that reuse a conversation, ~0.2 s per short turn on our rig.
Checks
-1and every room call is behind it).n - read_from >= TOKENS, afterread_fromis known and beforelend(),so the prompt path's relayout re-carves with the ring rooes the ring count alone; the
room's slot pointers are laid out by
carvewhether or nning it after the head-aliasrebuild still adds the slots.
sm_86 + sm_120) and ran on the Q8_0 4-way rig; the two binaries differ by this commit.
generate.cppcarries no per-backend code, so bothbuilds compile the same change.
this request reads (so a reused prefix is visible againstrafter's block ever moved, the
first shape said so — the in-place shape cannot.
🤖 Generated with Claude Code