Repository navigation
Troubleshooting
First, look at three things:
bash scripts/budget-report.sh status # usage, namespaces, disk free
tail -n 100 "$KVP_CACHE_ROOT/lmcache-server.log" # cache service
docker logs --tail 100 mimo-pro-tp4-kvp # engine (kv-persistence launcher)(KVP_CACHE_ROOT comes from configs/generic/cache.env.local.)
-
KVP_MODEL_REVISION must be a full 40-hex SHA— paste the commit SHA, not a branch name (see Quick Start step 3). -
KVP_CHAT_TEMPLATE_DIGEST must be 64 hex— run preflight once withKVP_MODEL_DIR="$HOME/models/MiMo-V2.6-Pro-MOPD"set; copy the printed value.
ERROR: lmcache CLI missing — install per Quick Start step 4. If import lmcache
fails inside your environment with undefined-symbol errors, that pip wheel is built
for a different PyTorch/CUDA stack. Fallback: run the server from the production
image itself:
docker run --rm --entrypoint lmcache "$IMAGE" server --helpIf that works, adapt scripts/start-cache.sh's lmcache server line to run inside
docker run with the same arguments (ports/paths stay identical).
- Start order:
start-cache.shBEFORE the vLLM launcher (run-vllm-with-persist.shrefuses otherwise). - Inspect the service log's error line; both processes use the same host network — do not move the request port without updating both sides.
- With the recipe integration (
KV_PERSIST=1): the launcher printsKV_PERSIST=1: attaching LMCache connector …— if absent, the knob did not reach the launcher (checktp4.envand whether variables were exported).
Check in order:
-
cached_tokensin the response is > 0. -
grep -c Retrieved "$KVP_CACHE_ROOT/lmcache-server.log"grows during the request; entries after restarts are the disk-recovery proof. -
used_bytesdid NOT grow to re-create the same data (budget-report.sh statusbefore/after). - Identity unchanged — rerun
scripts/preflight.sh; a changed revision/dtype/template silently moves to a NEW namespace (by design). -
KVP_PERSIST_ENABLEDistrue(start-cache.shwarns otherwise).
free_bytes: 0 in the report, or the adapter logs rejections at the hard cap —
raise KVP_CACHE_BUDGET_GB or purge old namespaces explicitly. The hard cap is
max_capacity_gb in the L2 adapter: once usage reaches it, new stores are
rejected (serving continues without new cache writes). Eviction at the soft
threshold (90 %) usually reclaims space first, but it is NOT a guarantee against
filling the DISK itself — the real bound is the free space on the volume that
holds KVP_CACHE_ROOT (on the reference host that volume also holds Docker, the
model and the OS). Keep a free-space margin, and lower the budget if the disk
runs low. Never delete individual files inside the cache root while the service
runs.
Raise KVP_EVICT_TRIGGER_WATERMARK (start later) or tune KVP_EVICT_RATIO
(reclaim more per cycle). Verify with a tiny isolated budget before expecting
effects at 1.5 TB scale. evictions/reclaimed_bytes in the status report show
the actual regime.
Measure what part of the prompt is actually stable:
python3 "$REPO/implementation/prompt_probe.py" --tokenizer "$MODEL_DIR" \
--request-file a.json --request-file b.jsonwith two real request bodies exported from the client. A small
stable_prefix_tokens means dynamic values (timestamps, tool-call ids) break the
prefix — fix serialization at the source or accept the limit. Truncating history
to make caching work is forbidden.
Corruption is tolerated: a lookup that misses recomputes. To clean deliberately:
stop the cache service, bash scripts/budget-report.sh purge --all --yes, start
again. Never happens automatically. Frames written with the lossless codec also
carry a CRC32: a corrupt or truncated frame is detected at read time and
reported as a cache miss — never returned as wrong KV.
-
CodecError: zstandard/lz4 python bindings not installed— the SELECTED codec needs its Python package (zstd→zstandard,lz4→lz4). Install it in the lmcache venv, or fall back toLMCACHE_L2_SERDE=none(byte-exact raw, no extra dependency at all). The check runs before the server starts and never requires an unused codec: anonecache needs no compression package. -
LMCACHE_L2_SERDE=... is LOSSY and is not supported— expected: this project is lossless-only. Usenone,zstdorlz4. - Switched the codec and cache hits disappeared — expected and safe: the codec is part of the cache-identity namespace, so old chunks become a cache miss. Re-store them (or switch back) — never a wrong read.
- Compressed chunks not smaller on disk — the codec only shrinks what actually compresses; incompressible blocks are stored raw and padded to the 4 KiB page size. On tiny chunks the per-chunk header (28 B) can dominate.
After a hard kill (SIGKILL, OOM, power loss) the adapter can leave crash
leftovers: <final>.bin.tmp.<uuid> files it never got to atomically rename.
Upstream sweeps them only on a CLEAN shutdown, so they linger and consume disk
while staying invisible to the cache index (they are never returned as KV).
Check with bash scripts/budget-report.sh status (fields tmp_files and
tmp_files_bytes) and remove them with bash scripts/budget-report.sh purge --tmp --yes (keeps every real cache file) or purge --all --yes.
Both commands protect in-flight writes with an active-write registry
(kvpersist.tmp_guard): every store registers a marker before its tmp file is
created and clears it after the atomic rename. Liveness works in BOTH launch
modes (review round 18):
-
host mode — the marker records
(pid, process start-time)and the writer is alive while that exact process exists (/proc/<pid>+ start-time guard against PID reuse); -
docker mode (
KVP_LAUNCH_MODE=docker, no--pid=host) — the marker records the writer's PID-NAMESPACE id; when the marker belongs to a foreign namespace the host CLI cannot check its PID, so liveness falls back to the marker's own HEARTBEAT (a running store refreshes it every 30 s): a marker touched within 2 minutes is alive, older = crash leftover. If liveness cannot be determined at all (no /proc, unreadable marker) the marker is kept: safer to retain one tmp file too long than to delete a live store. Markers whose writer provably exited are swept after an hour. As a second net for writers that do not use the registry (e.g. a barelmcache server),*.tmp.*files modified within the last 15 minutes are skipped — that window is a HEURISTIC, not an absolute lock.
The monitoring stack is passive until the cache service pushes telemetry. Start
the cache with USE_OBSERVABILITY=1 (or KVP_OTLP_ENDPOINT=http://localhost:4320)
and check KVP_CACHE_ROOT/lmcache-server.log for the [kvp] banner. In docker
mode start-cache.sh rewrites localhost to host.docker.internal
automatically. All stack ports are bound to 127.0.0.1 only (loopback contract).
Changing ANY identity field rotates the cache namespace: the old cache entries are not deleted, they become unreachable — which looks like "the cache is broken" but is the isolation design working correctly. Every row below invalidates ALL previously cached KV for that deployment:
| You changed... | Effect on namespace | Reusable old cache? |
|---|---|---|
| model weights (content swap, even same file names) | SHA-256 of shard bytes rotates | NO |
| model revision (HF SHA) | rotates | NO |
| tokenizer (tokenizer.json / tokenizer_config.json) | rotates | NO |
| chat_template / chat_template.jinja content | digest rotates | NO |
--kv-cache-dtype (bfloat16 -> float16 etc) |
rotates | NO |
--max-model-len |
rotates | NO |
--tensor-parallel-size |
rotates | NO |
| KV block size / head config in model config.json | rotates | NO |
| vLLM build / LMCache connector version (KVP_CONNECTOR_VERSION) | rotates | NO |
--enforce-eager / other engine flags that change KV computation order |
may change block layout → rotates | NO |
| Docker image digest (different vLLM build) | rotates | NO |
Practical advice: pin KVP_MODEL_REVISION via setup-identity.sh output and
copy identity.env.local alongside each deployment; expect a namespace change
whenever you change engine args and treat "cache is cold after my tweak" as the
correct, expected outcome — not a bug.