Skip to content

serve: --prompt-cache-spread, checkpoints kept spread back from the conversation's end (opt-in) - #1959

Closed
xrip wants to merge 1 commit into
Niko1221:mainfrom
xrip:pr/prompt-cache-spread
Closed

xrip wants to merge 1 commit into
Niko1221:mainfrom
xrip:pr/prompt-cache-spread

Conversation

@xrip

@xrip xrip commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

What

Opt-in --prompt-cache-spread: when the conversation checkpoints are over --prompt-cache, the one that leaves is
chosen so that the kept ones stay spread back from the conversation's end, instead of the least recently used. Without
the flag nothing changes.

Why

The engine keeps 6 checkpoints and rotates them by least recent use. Every turn adds one at its start, so after a few
short turns all of them sit within the last 1-2K tokens (plus the pinned system-prompt root). That is fine while a
client only appends. Agent clients do not always: they trim an old tool output, drop a step, or rewrite an earlier turn
a few thousand tokens back. The new prompt then matches none of the recent checkpoints, the resume falls back to the
system prompt, and the whole conversation is read again.

Found with the pi coding agent (and the pi-blackhole extensions) on a long agent session, from the engine log:

conversation held new prompt same as held for resumed at read again
210,952 210,528 204,349 (6K before the end) 9,525 201,003 tokens, 100 s
206,572 205,253 163,508 (42K before the end) 0 205,253 tokens, 101 s

In both, a checkpoint a few thousand tokens before the edit would have saved most of the read; least recent use had
rotated those out.

How

  • conv_cache::spread_victim (include/strata/program/conv_cache.hpp): over the chain (ascending), the victim is the
    checkpoint whose removal leaves the smallest gap for its distance from the newest one,
    (len[i+1] - len[i-1]) / (len[newest] - len[i-1]). Dense recent points go first, so the kept ones thin out
    geometrically towards the root. Never the root, the newest or a pinned (strata_prefix) one; the
    --prompt-cache-tail checkpoint first, as in the existing policy; with no candidate, the existing policy.
  • generate.cpp: the flag, and the choice between the two policies at the one place a checkpoint is dropped.
  • The resume is unchanged: a checkpoint is used only when the prompt starts with exactly its tokens and pictures.
  • Same number of checkpoints, no more RAM.

In a simulation of 200 turns of 1,000 tokens after a 9,525-token system prompt (conv_cache_test):

6 checkpoints, kept (tokens before the end) edit 6K back edit 42K back
least recent use: 0, 1K, 2K, 3K, 4K and the root reads 200K reads 200K
spread: 0, 1K, 3K, 8K, 43K, 200K reads 8K reads 43K

Measured

2x CMP 50HX (Turing sm_75, --layer-split auto), 2x Xeon E5-2670 v2, 64 GB RAM, Qwen3.8-Flash-Next IQ3_XXS, CUDA 13.3,
GCC 14. An 82,593-token conversation (a 65K-token document, then 20 short turns sent 2 at a time, each request
continuing the previous one), then the same conversation with a turn ~6 steps back rewritten and a question; greedy,
thinking off:

resumed at time
--prompt-cache-spread 73,610 10.2 s
--prompt-cache 0 (read in full) 0 40.0 s

With the expert placement fixed (--adapt-swaps 0 --pcie-frac 0.17) the answer after the resume was the same text as
the full read. Without fixing it, two full reads already differ in wording (the placement moves with timing), so that
comparison needs the fixed placement.

Tests: conv_cache_test passes, with the new cases (the simulation above against least recent use; every candidate
pinned and one slot fall back to the existing policy). Not run on Windows or HIP.

Relation to #1937

Found while measuring the disk tier of #1937 on the same agent sessions, with a diagnostic that logs how many tokens a
request read again although a held conversation had them. It is independent of #1937: any long conversation whose
client edits its history benefits, with or without parked conversations.

How to try

Add "--prompt-cache-spread" to the config's args.

🤖 Generated with Claude Code

…onversation's end (opt-in)

Least recent use keeps the newest checkpoints and every turn adds one, so after a few short turns all 6 sit in the
last 1-2K tokens. An agent client that rewrites its history a few thousand tokens back then resumes from the system
prompt: on 2x CMP 50HX with the pi coding agent a 210,528-token prompt that was the same as the session for 204,349
tokens resumed at 9,525 and read 201,003 again (100 s).

With the flag the victim is the checkpoint whose removal leaves the smallest gap for its distance from the newest, so
the kept points thin out geometrically: after 200 turns of 1,000 tokens they sit 0, 1K, 3K, 8K, 43K and 200K before
the end, and an edit 6K back reads 8K again (least recent use: 200K), 42K back 43K. The same number of checkpoints, no
more RAM; never the root, the newest or a pinned one, the tail rule first, the plain policy without a candidate. The
resume is the usual exact-prefix match.

On the box: an 82,593-token conversation grown turn by turn, a turn ~6 steps back rewritten, resumed at 73,610 tokens
in 10.2 s against 40.0 s read in full; with the expert placement fixed (--adapt-swaps 0, --pcie-frac 0.17) the
greedy answer was the same text as the full read. conv_cache_test: the spread cases (a 200-turn simulation against
least recent use, the fallbacks). docs/DETAILS.md: a paragraph after the prompt cache's options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Niko1221

Copy link
Copy Markdown
Owner

Thank you, this is in for 0.1.45 as an opt-in with your authorship (docs included). The conv_cache_test cases pass on CUDA sm_86, sm_60 and HIP. Our engine run did not reach the case your flag changes: with appended turns and an edit 6 turns back both arms resumed at the root, so we have no engine measurement of our own; your 10.2 s against 40 s stands as your number.

@xrip

xrip commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

@Niko1221 Thank you for taking it in, docs and all!

@Niko1221

Copy link
Copy Markdown
Owner

Thanks for the pull request.

Your change is already in main for 0.2.0-beta, with your credit, so there is nothing left for you to do here. We're closing it because we're resetting the tracker for 0.2.0-beta: there are more than 1,300 open issues and pull requests, and we're closing everything that isn't being worked on right now.

If part of your work did not make it in and you want it considered, open a new pull request on top of main that follows CONTRIBUTING.md, or comment /reopen after fixing the title area prefix and the template.

@Niko1221 Niko1221 closed this Oct 19, 2026
DarthCY-K pushed a commit to DarthCY-K/Strata that referenced this pull request Oct 20, 2026
…h "parallel", the fork's changes and the measured costs

docs/BATCHING.md "This fork" and docs/NVFP4.md "Several dialogs at once": the switch cost of dialogs that take turns
without and with --conversation-cache-mib (8K / 32K / 100K), why "parallel" was unusable on the 262K config (14.6 GiB
of slots), --batch-max-context in the options table, the control-vector window fix, the conversation-cache PRs taken
(Niko1221#1503, Niko1221#1163, Niko1221#1164, Niko1221#1959, Xiaomi2008's STRATA_PARK_REWIND_MAX), the checks, one dialog's decode per config, and
the recommended setting (the conversation cache; "parallel": 2 with 64K slots when requests really overlap).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
DarthCY-K pushed a commit to DarthCY-K/Strata that referenced this pull request Oct 20, 2026
…gs, the rope warning, the final build's checks (81049f34)

What's new and "Upstream 0.1.43.1" add the EOS-literal fix ("stop_token_text"), the parallel-dialog fixes (upstream
PRs Niko1221#1503 Niko1221#1163 Niko1221#1164 Niko1221#1959; link to "Several dialogs at once") and the --max-context warning; the checks name the
release build 81049f34: 2K / 32K logits and tokens = 0.1.42-nvfp4.2's engine, the serve KL texts byte-identical,
ctest 116/119, serve 849, tools 821.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
DarthCY-K pushed a commit to DarthCY-K/Strata that referenced this pull request Oct 20, 2026
…3-way merge each (base v0.1.44.1, upstream v0.2.0-beta, fork rel/0.1.44)

The cherry-picks took HEAD's copy of these files at every stop (deferred: upstream changed generate.cpp by +2.6K lines,
35 conflict hunks on the whole file), so the commits that touched only generate.cpp were dropped as empty (114851a
34bb231 f482c2a a4f1c78 b2f564a 437947e b38dbaa 5cb54da 4c9b2d0 a0ec12c 7281f76 deb8265 6b04110); their
content is here.  generate.cpp, 27 hunks:
- both sides: the disk tier's options + --conversation-cache-park-gain; adapt_fetch() + upstream's evict-sync helpers;
  the slow-link stage check + Niko1221#2644's stage floors; the disk tier's idle sync + upstream's BYIELD-nodrain drain; the
  restore's ms + the lent slot reset; upstream's --prefill-pipe resume plan + the dynamic RoPE switch; the fetch line +
  the residency check; slot_kv_fit + upstream's opt-in STRATA_SLOT_COPY_DIRECT copy.
- upstream's: Niko1221#1959's spread_victim with the checkpoint pool; the Niko1221#669 lend warning (Niko1221#1683 keeps the lend region in the
  RAM budget now: the fork's "the budget keeps no copy" wording dropped, its auto:8192 hint kept).
- reworked: the card check the fork opens above the expert arena's thread is the tag's block (HIP: the hipBLAS warm-up
  thread); upstream's pinned-memory reserve (pin_budget.hpp) is taken before the fork's arena thread and handed back
  after the join; res_dirty is set where evict sync or the fork's fetch admission drains it (the fork set it always);
  STRATA_ADAPT_CHECKRES (upstream's name) and the fork's STRATA_ADAPT_FETCH_CHECKRES both read; give_way (upstream's
  BYIELD refactor) keeps the fork's --batch-max-context fit; the prompt chunk is Niko1221#2824's planned_chunk within the fork's
  STRATA_SPLIT_PF_CHUNKS chunk; parking: park-gain and STRATA_PARK_REWIND_MAX both.
- the usage text: the fork's auto up to 32768 + upstream's --prefill-pipe (the cherry-pick had left both auto texts).
serve/test_server.py: the clean merge, less the fork's old Niko1221#567 IncrementalPrompts class (upstream's f13a870 carries
it); upstream's Mac telemetry test resets the fork sampler's cache before its patched read.
tools/strata_mcp.py: upstream's Gyro entries; no nvfp4 fallback entries (setup maps --family nvfp4 onto this fork's
families, as since 0.1.44).
src/core/pinned.cu: the wddm-pin merge's hand edit (31bb176) again - the pin-by-need constructor calls
shared_pin_limit (sliced_pin_limit's name since extras/wddm-pin).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants