Automatic KV-cache session persistence for llama-server: save the model's 'notes' to disk, restore them after a restart, refuse incompatible files.
Project description
stillwarm
Your local LLM re-reads everything after every restart. stillwarm fixes that.
llama.cpp's llama-server can save a conversation's KV cache (the model's
per-token "notes") to disk and load it back — but nothing does it automatically,
and nothing checks that the file you're loading matches the server you're
loading it into. stillwarm is a zero-dependency Python library + small CLI:
- restore-before-request, save-after-response — sessions survive restarts;
- a metadata sidecar per save — incompatible files are refused with a reason, never silently misloaded;
- a silently-ineffective guard — if a restore "succeeds" but the server
re-prefills anyway (the measured SWA-without-
--swa-fulltrap), you get a loud warning instead of silently paying cold-start cost; - LRU pruning under a disk budget (KV files are ~128 KiB/token for an 8B model — a 32K-token session is ~4.3 GB);
verify— an integrity mode that byte-checks a restored state against a probe captured at save time.
Measured on an M3 Max (llama.cpp b9871, Llama-3.1-8B Q4_K_M, same-session thermally controlled): resuming a session from disk instead of re-reading it is 71x faster at 8K tokens, 159x at 32K, 201x at 64K (restore + first-token time vs cold prefill), and ~50x cheaper in energy at 32K. Even with a purged page cache, restore wins for any document longer than ~300 tokens.
Quickstart
from stillwarm import LlamaServer, ServerProfile, Session
# llama-server ... -fa on --slot-save-path ~/.stillwarm/ (must be running)
server = LlamaServer("http://127.0.0.1:8080")
profile = ServerProfile(model_sha256="<sha256 of your .gguf>",
flash_attn="on", cache_type_k="f16", cache_type_v="f16",
swa_full=False, ctx_size=16384)
sw = Session(server, profile, cache_dir="~/.stillwarm",
server_build_tag="b9871", disk_budget_gb=50)
r = sw.ask("paper-review", document + "\n\nQ: Summarize.\nA:", n_predict=256)
# ... llama-server restarts ...
r = sw.ask("paper-review", document + "\n\nQ: And the weaknesses?\nA:", n_predict=256)
print(r.restored, r.prompt_n) # True, ~a few dozen tokens instead of thousands
cache_dir must be the directory passed to --slot-save-path. CLI:
stillwarm list ~/.stillwarm # sessions, sizes, ages
stillwarm inspect ~/.stillwarm/paper-review.bin
stillwarm prune ~/.stillwarm --budget-gb 50 [--dry-run]
Compatibility contract (every rule measured, not guessed)
From a 480-row benchmark matrix (stillwarm-bench, Block D portability taxonomy):
| you change... | what happens | stillwarm's response |
|---|---|---|
| flash-attention on<->off | restore fails (400) both directions | REFUSE up front |
| KV cache type (f16/q8_0/q4_0) | layout mismatch | REFUSE |
| model file | meaningless state | REFUSE |
--swa-full (SWA models) |
restore "works" but silently re-prefills | REFUSE + runtime guard |
| server ctx < saved tokens | restore fails (fit rule) | REFUSE with a clear message |
| server ctx >= saved tokens | works | OK |
-ngl (GPU layers) |
works (measured both directions) | OK — not part of the cache key |
| llama.cpp build (+-5 weeks measured) | works both directions | ADVISORY warning only |
verify — honest semantics
At save time, stillwarm writes the slot file first (the file is the
pre-probe state), then generates a 64-token pinned-greedy probe and stores its
hash in the sidecar. restore(..., verify=True) restores, re-runs the probe,
compares hashes, then re-restores so the served state is exactly the
restored state.
This is a determinism check (does the restored state reproduce the save-time continuation?), deliberately not a cold-equivalence check (does it match a full recompute?). Cold-equivalence measurably fails without any corruption: different prefill batch splits flip marginal tokens, and quantized caches amplify this to near-certainty (q4_0: 5/5 divergent at 8K in stillwarm-bench Block C) while both outputs stay coherent. A verify failure therefore means the file/model/config does not reproduce what was saved — a real integrity signal, not batching noise.
Cost: ~2x restore time + one 64-token decode (~1.5 s for an 8B model).
Non-goals (v1 fence)
- Not a serving framework — one machine, one llama-server.
- No multi-machine cache sharing; no vLLM (LMCache owns that space).
- llama-server only: Ollama and LM Studio do not expose the slot save/restore endpoints this needs.
- No compression research. Small and finished beats large and abandoned.
Platforms
- macOS (Apple Silicon) — tested; all measured numbers come from an M3 Max against llama.cpp b9871.
- Linux — expected to work (same llama-server HTTP API; the demo Space runs the identical mechanism on Linux/CPU); not part of the measured matrix.
- Windows — untested. The code is audited for portability (pathlib
throughout, atomic writes via
os.replace, no POSIX-only stdlib modules, and pruning toleratesPermissionErroron files Windows holds open) — reports welcome.
Tests
pytest — unit tests run anywhere; integration tests need a llama-server
binary and a tiny GGUF (defaults: SmolLM2-135M-Instruct Q8_0, ~145 MB; override
via STILLWARM_TEST_SERVER_BIN / STILLWARM_TEST_MODEL; they skip if absent).
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file stillwarm-0.1.1.tar.gz.
File metadata
- Download URL: stillwarm-0.1.1.tar.gz
- Upload date:
- Size: 23.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7cb931cb1579eb5fed622d057eb490951d96e8a11044c70ec1fab07de55a51a0
|
|
| MD5 |
6b47f0542076e54b057f4fef38f45807
|
|
| BLAKE2b-256 |
ff371288a0c4aa9c0d37014f507ffa5087a4c2eb0bef5edb237d9d5c73309cf9
|
File details
Details for the file stillwarm-0.1.1-py3-none-any.whl.
File metadata
- Download URL: stillwarm-0.1.1-py3-none-any.whl
- Upload date:
- Size: 18.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3031f7587dfd22b2b05aefc7e9e75df539e873df7a2112e45253dd0b1b0e54f7
|
|
| MD5 |
fe967ee590839fcd6902ec98c9e67d75
|
|
| BLAKE2b-256 |
5214946a6716f5e7a250f2f924cd8b9ee47a0087ac6e9a18170c8e318d371174
|