|
| 1 | +# kv-canary |
| 2 | + |
| 3 | +**A canary for *silent* KV-cache-compression failures.** |
| 4 | + |
| 5 | +Lossy KV-cache compression (quantization, token eviction) is benchmarked almost entirely on |
| 6 | +**speed and memory** — and on token-level quality (perplexity). But token-level metrics can stay |
| 7 | +flat while **functional** outputs silently break: generated code stops passing its tests, tool |
| 8 | +calls stop being valid JSON. kv-canary measures that gap. |
| 9 | + |
| 10 | +> The wedge: everyone measures how *fast* KV compression is. Almost nobody measures whether it |
| 11 | +> silently breaks your code and tool calls while perplexity says everything is fine. |
| 12 | +
|
| 13 | +This is a measurement harness + methodology, motivated by VeriCache |
| 14 | +([arXiv:2605.17613](https://arxiv.org/html/2605.17613)), which showed lossy-KV bias accumulates |
| 15 | +linearly and functional tasks fail while token-level metrics look unchanged. kv-canary makes that |
| 16 | +effect reproducible and quantifiable across compression methods. |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## The idea in one chart |
| 21 | + |
| 22 | +For each compression method, kv-canary plots two lines against **KV memory retained**: |
| 23 | + |
| 24 | +- **functional accuracy** (solid) — does generated code pass its tests / is the tool call correct? |
| 25 | +- **perplexity-implied quality** (dashed) — the token-level metric, normalized to the full cache. |
| 26 | + |
| 27 | +The finding is the **divergence**: functional accuracy cliffing while the perplexity line hugs 1.0. |
| 28 | +That visual gap is the whole point — the token metric tells you nothing is wrong while real |
| 29 | +functional quality collapses. |
| 30 | + |
| 31 | +To make it a single number, kv-canary defines the **Silent Degradation Score (SDS)**: |
| 32 | + |
| 33 | +``` |
| 34 | +SDS = (relative functional drop) / (relative perplexity rise) |
| 35 | +``` |
| 36 | + |
| 37 | +High SDS = functional accuracy craters while perplexity barely moves (the metric "lies"). SDS ≈ 1 |
| 38 | +is graceful degradation (perplexity warns you in step). Perplexity *improving* while function |
| 39 | +breaks is the most deceptive case of all, and SDS reports it as maximally silent — by design. |
| 40 | + |
| 41 | +## What it measures |
| 42 | + |
| 43 | +| Compression family | Methods | How `budget` maps | |
| 44 | +|---|---|---| |
| 45 | +| baseline | full (fp16) | 1.0 | |
| 46 | +| quantization | int8 / int4 / int2 KV | bits / 16 | |
| 47 | +| token eviction | StreamingLLM, SnapKV | fraction of tokens kept | |
| 48 | + |
| 49 | +Against two objective, KV-eviction-sensitive functional tasks — **code execution** (pass@1) and |
| 50 | +**tool/JSON calling** (valid + correct-function + arg-match) — contrasted with **perplexity** on a |
| 51 | +held-out set. All on a shared *KV-memory-retained* x-axis, so quantization and eviction are directly |
| 52 | +comparable (and the fact that equal memory budgets degrade them differently is the finding). |
| 53 | + |
| 54 | +## Quickstart (CPU, no GPU) |
| 55 | + |
| 56 | +Verifies the whole pipeline end-to-end on a tiny model: |
| 57 | + |
| 58 | +```bash |
| 59 | +pip install -e ".[dev,ml]" |
| 60 | +python -m kvcanary run configs/smoke.yaml --out results/raw/smoke.jsonl |
| 61 | +python scripts/aggregate_and_report.py results/raw/smoke.jsonl |
| 62 | +# -> report/divergence.png, report/RESULTS.md |
| 63 | +``` |
| 64 | + |
| 65 | +> The smoke run uses `distilgpt2` and proves the harness runs. See the note on the v1 deferral below |
| 66 | +> for why the *finding* itself needs a GPU. |
| 67 | +
|
| 68 | +Run the fast test suite: |
| 69 | + |
| 70 | +```bash |
| 71 | +pip install -e ".[dev]" |
| 72 | +pytest -q # model-free unit tests |
| 73 | +pytest -q -m slow # + end-to-end smoke on distilgpt2 (downloads weights) |
| 74 | +``` |
| 75 | + |
| 76 | +## How it works |
| 77 | + |
| 78 | +Three small, independently-tested seams compose into a resumable experiment runner: |
| 79 | + |
| 80 | +- **`KVCompressor`** — `full`, `quantized`, `streamingllm`, `snapkv` behind one `budget` knob; the |
| 81 | + eviction selection logic is a pure function of attention scores (unit-tested on synthetic tensors). |
| 82 | +- **`Backend`** — `HFBackend` (HuggingFace causal LM: greedy generation + teacher-forced |
| 83 | + perplexity) and `FakeBackend` (a deterministic double, so the runner and tasks test with no model). |
| 84 | +- **`Task`** — `CodeExecTask` (sandboxed subprocess, pass@1), `ToolCallTask` (JSON parse + function |
| 85 | + + arg match), `PerplexityTask`. |
| 86 | + |
| 87 | +The runner sweeps `models × compressors × tasks`, writes one JSON line per sample, and **resumes** |
| 88 | +by skipping already-completed cells — so a killed spot-GPU run restarts cheaply. `metrics/` and |
| 89 | +`report/` turn the JSONL into the divergence chart and the SDS table. |
| 90 | + |
| 91 | +``` |
| 92 | +configs/*.yaml ─▶ runner ─▶ results/raw/*.jsonl ─▶ aggregate ─▶ report/{divergence.png, RESULTS.md} |
| 93 | + │ |
| 94 | + KVCompressor · Backend · Task |
| 95 | +``` |
| 96 | + |
| 97 | +## Status & honest caveats |
| 98 | + |
| 99 | +**v1 is the harness.** 16 build steps, ~38 tests, green CI. The real run is documented in |
| 100 | +[`docs/RUNBOOK.md`](docs/RUNBOOK.md) and needs a single 24GB GPU. |
| 101 | + |
| 102 | +- **The headline finding is not in this repo yet.** One piece is intentionally deferred to the GPU |
| 103 | + run: wiring the eviction selection into HuggingFace's live KV cache (a custom `DynamicCache` |
| 104 | + subclass). Until that lands, `HFBackend` records *how much* KV each method would retain but runs |
| 105 | + normal generation — so **all methods produce identical output and the divergence is NULL.** This |
| 106 | + is called out in the `HFBackend` docstring and the runbook. Any numbers you see in a freshly |
| 107 | + generated `report/` from the CPU smoke are pipeline-shape placeholders, **not** a result. |
| 108 | +- The gap kv-canary targets rests on a single recent paper (VeriCache); the contribution here is |
| 109 | + making it *measurable and reproducible*, not claiming novelty of the phenomenon. |
| 110 | +- The wedge is **functional-vs-token-level divergence**, not "reproducible benchmarking" — local |
| 111 | + inference benchmarking is fragmented (a coverage gap), but that is a different problem. |
| 112 | + |
| 113 | +## Layout |
| 114 | + |
| 115 | +``` |
| 116 | +kvcanary/ compressors/ · backends/ · tasks/ · runner/ · metrics/ · report/ |
| 117 | +configs/ smoke.yaml (CPU) · v1.yaml (GPU matrix) |
| 118 | +scripts/ aggregate_and_report.py |
| 119 | +docs/ RUNBOOK.md · superpowers/specs/ (design) · superpowers/plans/ (build plan) |
| 120 | +``` |
0 commit comments