Repository navigation
Conversation
The RAM conversation cache drops a parked conversation when RAM pressure or --conversation-cache-slots evicts it. With --conversation-cache-spill-dir DIR it is written to DIR as an ordinary session file (the same format and model/config identity as the slot save/restore API) plus a small metadata sidecar, so a later request - or a restart - reads it back instead of re-reading the prompt. The spill reuses conversation_file.cpp rather than a second on-disk format: a spilled conversation and a hand-saved session file are interchangeable, and the same identity rules refuse a foreign file. The sidecar holds only the token and image lists, so best() matches a disk conversation without reading its K/V. --conversation-cache-similarity and --conversation-cache-n-min filter weak hits; a disk hit must fit the RAM budget and the min-free floor before it is read back. On a clean QUIT the parked conversations spill before exit. Single-GPU only: with --layer-split the disk tier stays off. conversation_spill_test covers spill/match/load round-trip, similarity/n_min, identity refusal, budget eviction, reopen reindex, and the RAM cache handing its evictions to the tier (make_room callback + spill_all).
|
May I ask is it possible to use disk to park conversation in the first place and skip RAM parking entirely? For a 64GB machine, really don't have much headroom after load IQ3_XXS. And I'm hitting a lot of park rejection in the first RAM parking gate. |
…disk # Conflicts: # include/strata/core/conversation_cache.hpp
|
The disk tier helped in a local v0.1.40.2 port on Windows, but I found a recovery case worth fixing before merging. With one RAM cache slot, a request can have a shorter RAM match and a longer disk match. I reproduced this using only synthetic snapshots: A and B share a system root, A is on disk, B is the sole parked RAM entry, and an unrelated conversation is active. After changing A's file header, requesting A logs Recomputing the RAM match after an unsuccessful disk attempt avoids the stale index: if (!incoming)
parked = conversations.best(ids, req_imgs, want_cvec,
o.conversation_cache_similarity,
o.conversation_cache_n_min);This belongs immediately before the RAM The normal disk path and restart checks passed separately. In a deliberately constrained one-slot seven-request sequence, newly read tokens fell from 37,345 to 18,690 and prompt processing from 127.18 to 67.61 seconds. Total wall time was 150.65 vs 93.44 seconds, with different sampled output totals (513 vs 550). An already-spilled conversation also restored after restarting the process. This demonstrates the eviction/reuse case, not a universal Hermes speedup or persistence of the live RAM-only conversation. Hardware: Ryzen 9 7940HS, RTX 4070 Laptop 8 GB, 64 GB RAM; IQ3_S, 64K context. Tests and the damaged file were isolated from production. Update after reviewing the current branch: commit |
…pt: make_room may have evicted the entry the stale index named
With the current code is not possible I'm afraid, this functionality it would need more extensive code changes worth another PR.
I added the fix to the code and applied upstream main to my branch. Thanks for the test and good catch! |
…disk # Conflicts: # include/strata/core/conversation_cache.hpp
…ot checkpoint, takes what RAM cannot hold On top of Niko1221#1271 / Niko1221#1269 / Niko1221#1480 (merged unchanged): - an evicted conversation is handed to a writer thread (spill_async) instead of written - and flushed - inside the request that evicted it; the file is indexed, so matched, only once wait() has joined the write (before a disk match, a streamed save and at shutdown). When the physical-RAM admission refuses a park or a disk hit while a write still holds an evicted image, the request waits for that write first. - a spilled file keeps the deepest checkpoint (as SAVE) and the shallowest one (the chain's root - the end of the system prompt a new chat resumes from); the ones between only serve edits further back. - with the RAM cache on, a conversation it cannot park (over its budget, or refused by the RAM admission) is streamed to the directory as --conversation-cache-disk-only writes it, and a disk hit over the RAM budget is streamed back as that mode reads it, instead of both being dropped; put() hands what it evicts to the tier too (it could evict past make_room's estimate without spilling). - --conversation-cache-disk-min-tokens N (default 0): shorter conversations are not written. - every spill write leaves --session-min-free-mib free on the disk. conversation_spill_test: 34 new checks (93 pass, also under ThreadSanitizer); the other conversation tests pass. End to end on an RTX 2080 Ti (docs/DETAILS.md): later turns 5.20 -> 4.51 s median with a 4 GiB RAM cache, and 14.83 -> 5.01 s with 256 MiB (0 -> 12 of 12 turns resumed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CMakeLists.txt # src/program/generate.cpp
Status line keeps both conversation_cache_min_tokens (Niko1221#1271) and conversation_cache_disk_only. The disk-only save/read admission now reads available_host_bytes() (upstream daa7655 replaced conversation_available_memory()).
The session writer's temporary beside a spill file is dot-prefixed
(".strata-conv-1.sess.<hex>.tmp", conversation_file.cpp), and the scan at open only matched names starting
with "strata-conv-", so a process killed mid-write left it behind forever: 6,056,574,976 + 687,865,856 bytes
stranded in one user's spill directory (a 198 GB partition at 56% used) while the log reported 0 stale files
wiped. The scan now takes ".strata-conv-" as well, and leaves anyone else's dot files alone.
Checked with conversation_spill_test (46 checks, new: an abandoned temp is wiped and counted, a foreign
.tmp is left, the temp is not indexed) and ctest -R 'conversation|conv_cache' (9/9) on Linux, sm_89.
|
Hi! We're building the same disk tier (our PR #2510): spill = sidecar + ordinary session file, same The one difference: ours is v2 of this format - it adds a stage count so We'd rather converge than race two copies of the same file: v2 is backward-compatible with v1, so your sidecars keep working unchanged. Want to coordinate so we land a single |
|
The call to make the spill file the same artifact as the API save/restore session file is a good one, ANBAL534 — Heads-up, so nobody is surprised when the maintainer picks one: our branches add the same three new paths Two things of ours that may be useful to you, offered as material rather than as a comparison. Our sidecar is The choice between the two routes is the maintainer's, and either one reaches the same place. |
|
Thanks for the pull request. We're resetting the tracker for 0.2.0-beta. There are more than 1,300 open issues and pull requests, too many for us to review properly, so we're closing everything that isn't being worked on right now. Closed here does not mean rejected. To reopen it takes about two minutes:
The rules are in CONTRIBUTING.md. |
The RAM conversation cache (
--conversation-cache-mib) parks whole conversations so a later request reuses their prefill instead of reading the prompt again. But a parked conversation is dropped the moment RAM pressure or--conversation-cache-slotsevicts it, and every parked conversation is gone when the engine restarts. On a small-RAM PC the budget is tight, so the conversations that matter are the ones evicted; after a restart the long prompt is read again from token 0.--conversation-cache-spill-dir DIRkeeps what the RAM cache evicts: the evicted conversation is written to DIR as an ordinary session file (the same format and model/config identity as the slot save/restore API) plus a small metadata sidecar, so a later request - or a restart - reads it back instead of re-reading the prompt.TL;DR
What changes
make_room) is handed to a spill callback that writes it to the directory; on a cleanQUITor stdin close the parked conversations spill before exit (spill_all).--conversation-cache-min-free-mibfloor.--conversation-cache-similarity Fand--conversation-cache-n-min Nfilter weak matches (least common-prefix fraction / token count); both default to 0, which keeps every exact prefix the RAM cache would have used.conversation_file.cpprather than adding a second on-disk format: a spilled conversation is byte-for-byte what the slot save/restore API would write, so the two are interchangeable and there is one format, one model/config identity rule, one checksum.Why it helps
--conversation-cache-miband let the disk tier hold the rest: the RAM cache stays a small hot working set, and the conversations it evicts stay resumable on NVMe instead of being recomputed. On a 12-16 GB card where the cache budget competes with the expert cache, this trades a little disk for prompt-read time.spill dir ready (N conversations, ...)at start, and the first request after a restart resumes from disk instead of reading the whole prompt again.Flags
--conversation-cache-spill-dir DIR--conversation-cache-disk-mib N--conversation-cache-similarity F--conversation-cache-n-min NTesting
conversation_spill_test(41 checks, wired intoSTRATA_BUILD_CONVERSATION_TESTS): spill/match/load round-trip with the K/V compared byte for byte, similarity and n_min filtering, refusal of a foreign model/config identity, disk-budget eviction, reindex on reopen, and the RAM cache handing its evictions to the tier (make_roomcallback +spill_all).generate.cppand the module compile in the CUDA build andstratalinks; the flags appear in--helpand bad values are refused.Scope
Single-GPU only: with
--layer-splitthe disk tier stays off, matching the existing parking limit. A disk hit is read into host RAM before restore, so it must fit the RAM cache budget and leave the min-free floor available, or the request reads the prompt normally.