Reuses Pi's stable harness prefix (system-prompt sections, tool schemas,
chat_template_kwargs) in a llama-server KV-cache slot across sessions,
and can persist that prefix to disk so a restarted server (or a new Pi
install) resumes in seconds instead of re-ingesting the 5–20k-token
prefix.
warmup.ts.See README.html for full documentation.
| Name | Last modified | Size | Description | |
|---|---|---|---|---|
| Parent Directory | - | |||
| README.md | 2026-10-09 14:03 | 15K | ||
| old-sidecar-patch-version/ | 2026-10-09 14:02 | - | ||
| pi-prefix-cache.tgz | 2026-10-09 14:03 | 19K | ||
| pi-prefix-cache/ | 2026-10-09 14:03 | - | ||
Per provider request:
developer message (everything
before the earliest variable-section boundary). Three markers are
recognised, whichever comes first:
\n<project_context> — present only when the project has an
AGENTS.md/CLAUDE.md context file.\n<cwd> — pi >= 0.87 sectioned prompt (the cwd section is
rendered as <cwd>\n<path>\n</cwd>). Sections are joined with a
blank line, so the leading \n always matches.\nCurrent working directory: — pi < 0.87 plain-text cwd line,
kept for backward compatibility.chat_template_kwargs, stable prefix text).GET /slots, nonzero prompt counter). A server
restart clears the slot — the extension detects it and falls back
to restore/re-warm instead of a false in-process hit.pi-prefix-<fp16>.bin into the slot,
then verify in two stages:
cache_n > 0 verifies the checkpoint chain.
cache_n = 0 is logged loudly (usually a server-flags issue, e.g. prefix shorter than --checkpoint-min-step) and the
restore is kept — a rebuild would produce the same chain.n_predict: 0 request
carrying only the stable prefix + an empty user message).After each response, the extension polls the slot's last-prompt counters and logs the observed reuse of the normal request.
llama-server.-np 1 there is only one —
leave PI_PREFIX_CACHE_SLOT at 0.--ctx-checkpoints N should be large enough to hold the prefix chain.
--checkpoint-min-step is the minimum spacing between consecutive
checkpoints: a larger value stores fewer checkpoints (less RAM) but a
resume can only land on the last checkpoint before the divergence
point, so up to min_step prefix tokens are re-prefilled per session
start. The default 8192 is too coarse for typical 5–20k prefixes (no
intermediate checkpoint → full re-prefill); 0 gives maximum reuse,
an intermediate value (e.g. 4096) trades up to ~4k tokens of
re-prefill for less checkpoint RAM. -ub sets the reuse granularity
(last ubatch boundary ≤ divergence point) — a smaller -ub is finer
at higher overhead. These are per-deployment tradeoffs (e.g.
--ctx-checkpoints 32 -ub 384 on the 27B main server,
--ctx-checkpoints 8 -ub 128 on the 9B test server).PI_PREFIX_CACHE_PERSIST=1 on such models: a llama-server built
from mainline ≥ c811cb8f (includes upstream #26004, which
serializes the checkpoint chain into the slot save file). Older builds
restore the KV state but lose the checkpoint chain → full re-prefill
(see "Disk persistence — server requirement" below).Download and unpack the extension, then copy it into Pi's extensions directory:
wget https://store.piffa.net/lm/picache/pi-prefix-cache.tgz
tar xzf pi-prefix-cache.tgz
mkdir -p ~/.pi/agent/extensions
cp -r pi-prefix-cache ~/.pi/agent/extensions/
cp -r pi-prefix-cache ~/.pi/agent/extensions/
(or per-invocation: pi -e /path/to/pi-prefix-cache/index.ts)
export PI_PREFIX_CACHE_BASE_URL=http://localhost:8080/v1
export PI_PREFIX_CACHE_PERSIST=1
export PI_PREFIX_CACHE_SLOT_DIR=/home/eaman/llama/slot_caches
| Variable | Default | What it does |
|---|---|---|
PI_PREFIX_CACHE_BASE_URL | (unset) | llama-server API base, e.g. http://localhost:8080/v1. Unset → observe-only: the extension logs the fingerprint and skips all warm-up/restore/save. Must point at the server that serves the model Pi is using. |
PI_PREFIX_CACHE_SLOT | 0 | Slot id that holds the shared prefix. Warm-up, restore, save and erase all target this slot, and the normal Pi request must land on the same one. With -np 1 leave it 0. |
PI_PREFIX_CACHE_PERSIST | off | "1" enables disk save/restore. Off → runtime-only reuse (prefix lost when the server restarts). On → cold misses restore pi-prefix-<fp16>.bin (then verify) and save a new checkpoint after a cold build. Requires llama-server ≥ mainline c811cb8f for the restore to help on hybrid models. |
PI_PREFIX_CACHE_SLOT_DIR | (unset) | The server's --slot-save-path directory. The extension cannot discover it over HTTP, so it must be told. Only used for stale-checkpoint cleanup. Unset (or persistence off) → no cleanup. |
PI_PREFIX_CACHE_CLEANUP | 1 | "0" disables the one-time-per-process stale-checkpoint cleanup. Cleanup only ever deletes files matching pi-prefix-[0-9a-f]{16}.bin (+ their .ckpt sidecar) — manually named files are never touched. |
PI_PREFIX_CACHE_TTL_DAYS | 30 | Cleanup keeps any checkpoint whose lastUsed (from the metadata index) is within this many days, even if its fingerprint is no longer current. |
PI_PREFIX_CACHE_KEEP_RECENT | 3 | Cleanup always keeps this many most-recently-used checkpoints regardless of TTL — a margin so a Pi downgrade or A/B model switch doesn't force an immediate cold rebuild. |
PI_PREFIX_CACHE_MAX_GB | 0 (off) | Hard cap on total checkpoint storage (.bin + .ckpt). After the TTL/recent pass, oldest kept files are deleted (never the current session's checkpoint) until under the cap. |
PI_PREFIX_CACHE_SERVER_ID | (empty) | Optional llama-server build/runtime identity, included in the fingerprint (the server exposes no JSON build endpoint, so it is env-provided). Bump it when you deploy a new llama-server build, e.g. export PI_PREFIX_CACHE_SERVER_ID=ddddf03d-slckv2. Forces one clean rebuild after a server upgrade. |
PI_PREFIX_CACHE_DIR | ~/.cache/pi/llama-prefix/ | Where the metadata index (index.json) lives. Data only — policy comes from the env vars above. |
PI_PREFIX_CACHE_LOG | ~/.cache/pi/llama-prefix/debug.log | Diagnostics log file (timestamped [prefix-cache] lines). pi ≥ 1.0 runs the TUI fullscreen by default, so the extension no longer writes to stdout — raw console output would land on top of the input area. Empty string disables file logging. |
PI_PREFIX_CACHE_DEBUG | off | "1" also echoes log lines to stdout (for non-interactive runs). |
All numeric env vars fall back to their defaults when unset or invalid.
When PI_PREFIX_CACHE_BASE_URL is set, the extension acts only for models whose provider baseUrl in models.json (agent dir: $PI_CODING_AGENT_DIR or ~/.pi/agent) equals it — it can only warm its own server. Other models (remote providers, other local servers) are skipped with a log line: no warm-up, no restore/save, no slot polling. If models.json is missing or unreadable, all requests are skipped (safe direction: never warm a server the request cannot be attributed to).
Where logs go (v1.2.2+): all [prefix-cache] lines are written to the
log file (PI_PREFIX_CACHE_LOG), not stdout — pi ≥ 1.0's fullscreen TUI
renders raw console writes on top of the input area. In the TUI you get
exactly one notification per session (the first request's outcome),
rendered via ctx.ui.notify():
⚡ prefix-cache: WARM — restored 2335 tok from disk (1453 ms) # checkpoint restore verified
⚡ prefix-cache: WARM — slot already hot # slot warm in this process
prefix-cache: COLD — warmed 2335 tok, checkpoint saved # cold build (warning style)
prefix-cache: disabled this session (fail-open): <error> # on failure (error style)
All lines below are prefixed [prefix-cache] (log file). First run for a fingerprint:
[prefix-cache] fingerprint=16ba1104…
[prefix-cache] no checkpoint (restore HTTP 400: …) → cold build
[prefix-cache] cache=MISS → warming slot=0 (cold)
[prefix-cache] warm-up done: HTTP 200 prompt_n=1228 cache_n=0
[prefix-cache] SAVED pi-prefix-16ba1104….bin: n_saved=…
[prefix-cache] normal request proceeding (source=warm-up)
[prefix-cache] observed normal request: prompt_n=1360 cache_n=1096 (reuse 81%)
Later sessions (same fingerprint):
[prefix-cache] RESTORE pi-prefix-16ba1104….bin: n_restored=…
[prefix-cache] RESTORE verified: identical 1224/1228, divergent probe 1096/1240
[prefix-cache] normal request proceeding (source=restore)
Within one Pi process, subsequent requests log
cache=HIT (slot N already warmed for this fingerprint).
Verify server-side reuse with LLAMA_SERVER_SLOTS_DEBUG=1:
restored context checkpoint (pos_min = …, … n_past = …).
| File | Role |
|---|---|
index.ts | Extension entrypoint. before_provider_request → extract stable prefix, fingerprint, warm/restore slot on miss; after_provider_response → observed-reuse diagnostics. Fail-open; never mutates the payload. |
prefix.ts | Splits the developer message into stable content and variable tail at the earliest of \n<project_context> / \n<cwd> / \nCurrent working directory: (trailing whitespace stripped). The \n<cwd> marker covers pi >= 0.87, where the plain-text cwd line was replaced by a <cwd> section. |
fingerprint.ts | Canonical JSON (sorted keys, array order preserved) + SHA-256 over format tag, model id, pi version, extension version, tools[], chat_template_kwargs, stable prefix. |
warmup.ts | All llama-server HTTP details: warmSlot() (prefix-only request, n_predict: 0, slot: N), getSlotStats() (GET /slots), saveSlotFile() / restoreSlotFile() / eraseSlotFile() (POST /slots/{id}?action=save|restore|erase). All calls are timeout-bounded (120 s warm/save/restore/erase, 5 s stats). |
cache-store.ts | Metadata index (~/.cache/pi/llama-prefix/index.json) + cleanupStale() pruning pi-prefix-[0-9a-f]{16}.bin(+.ckpt) files whose fingerprint is no longer active. Keep set = current fingerprint ∪ index entries used within TTL ∪ N most recent. Manually named files are never touched. |
/slots save|restore on mainline ≥ c811cb8f is sufficient: upstream
#26004 ("server: preserve context checkpoints across slot save/restore")
appends the server-side slot.prompt.checkpoints chain to the slot save
file itself (the SCKP appendix) and rebuilds it on restore, with
size-bounded reads, draft-context mismatch handling, and a fallback to
full re-prefill when a restored checkpoint fails to load. No llama.cpp
patch is required — the extension is fully patch-free on such builds.
.bin + .ckpt
files) restore with a working KV state but no checkpoint chain on
a #26004 server: the identical-prompt gate passes, the divergent probe
logs cache_n = 0 loudly, and the next normal request re-prefills the
conversation tail. Bump PI_PREFIX_CACHE_SERVER_ID after deploying a
new server build to force one clean cold rebuild, which re-saves the
checkpoint in the native format..ckpt sidecar files are ignored by #26004 servers; the
extension's cleanup still removes them alongside their .bin.Legacy patches (only relevant for servers that cannot upgrade to ≥
c811cb8f) are no longer shipped in this repo — they were removed
from the tree and are recoverable from git history (commit 180ec5a:
kv_prefix_checkpoint_sidecar_9adc7f42.patch, SLCK v2 sidecar, base
9adc7f42; prompt_cache_best_match_9adc7f42.patch). An older
…_ddddf03d.patch (base ddddf03d) has an external copy at
/home/eaman/llama/bug/patches/. Do not apply any of these to mainline.
Full cold-save/restore-verify cycle, non-interactive Pi sessions:
| Binary | Cold build + save | Restore | Verify (identical / divergent probe) |
|---|---|---|---|
pure mainline c811cb8f, unpatched | SAVED 1712 tok (116 ms) | 85 ms | 1708/4 / probe cache_n = 1702 > 0 |
cumulative eaman, sidecar-stripped (b8531ac0; binary reports the stale 699d7417/build-2182 version string, discriminated by .so code strings) | SAVED 1712 tok (58 ms) | 84 ms | 1708/4 / probe cache_n = 1702 > 0 |
Server: --ctx-checkpoints 8 -ub 128 --checkpoint-min-step 1024
--slot-save-path ... + tools-first --chat-template-file. The divergent
probe cache_n > 0 on the unpatched binary is the acceptance criterion:
the checkpoint chain survives the disk round-trip via the native SCKP
appendix alone.
<project_context>
or the cwd line, extraction fails open (logged) and the cache is
simply unused.-ub 128). Tune -ub for tighter reuse vs
prompt-processing speed; ~51.5 MiB VRAM per checkpoint.-np 1).index.json disables cleanup for that run (logged);
checkpoint files are never deleted against an index that cannot be
trusted. A missing index is fine (treated as empty).piVersion() resolves via the pi CLI entrypoint realpath; falls back
to unknown (still deterministic within a machine).