veloxquant_mlx.cache.nestedkv_cache module¶
NestedKV-adapted KV cache — multi-scale ensembled prefill eviction.
Inspired by “NestedKV: Nested Memory Routing for Long-Context KV Cache
Compression” (Chen, Liu, Gao, Fan, Wang, Chu, Lin, Hu; arXiv:2605.26678).
Documented as “NestedKV-adapted (VeloxQuant-MLX implementation)” — not a
faithful port. No verified peer-reviewed venue as of 2026-07-14 — a
one-time, user-directed exception to this repo’s venue-verification rule.
See paper/research/surveys/NEW_METHOD_SURVEY_V21.md.
Multi-scale ensembled eviction: at the end of prefill, each head’s tokens are
scored by three parallel continuum-memory anomaly signals (stable/global,
episodic/block-local, current/recent-window), combined via a head-adaptive
blend and a per-token surprise gate (see quantizers/nestedkv.py). Each
head then independently keeps its own top-nestedkv_budget scoring tokens
— UNIFORM per-head budget, like H2O/CurDKV (see “Uniform per-head budget”
below, issue #21, for why this class does not use the paper’s cross-head
budget competition even though quantizers/nestedkv.py still implements
it as a tested primitive). Decode tokens are appended unscored, never
evicted — this is a one-shot prefill compressor, not a per-step recurring
eviction loop.
This is the 15th eviction-family method in VeloxQuant-MLX and the first that ensembles multiple independent importance signals rather than committing to one:
H2O / CurDKV / KVzip / Keyformer / MorphKV : one signal, scored every step.
SnapKV : one signal (obs-window attention), scored once at prefill.
NestedKV : THREE signals, scored once at prefill, combined by a head-adaptive blend + per-token surprise gate.
Adaptation limitations (stated plainly — see quantizers/nestedkv.py for the full crux):
Unpublished preprint, no verified venue.
One-shot prefill compression; cache is NOT bounded during decode (grows with every decoded token, same as SnapKV-adapted’s decode-phase design).
Key-only scoring, no query/attention access at all (not even a proxy).
Gate/blend constants (beta=3.0, tau=0.60, kappa=10.0, prior=(0.4,0.4,0.2), safeguard_alpha=0.20) taken directly from the paper’s Appendix A.
Uniform per-head budget (issue #21). The paper’s cross-head budget competition (component 5,
nestedkv_allocate_head_budgets) can legitimately give different heads different token counts. An earlier version of this wrapper let each head keep its ragged, cross-head- competed count and zero-padded shorter heads (at the front) to stack them into one tensor. That was a real correctness bug, not just a shape accommodation: the padded rows became real (unmasked) cache entries that the downstream attention computation attended to — andmlx_lm’s attention forward computes ONE shared mask from the model’s first layer’s cache and reuses it for every layer, so a per-cache mask override cannot correctly express each NestedKV layer’s independent padding pattern. Rather than build mask machinery this architecture cannot actually support end-to-end, this class now keeps every head’s kept length uniform atnestedkv_budget— same convention as every other eviction method here (H2O, CurDKV, PyramidKV).nestedkv_score(the cross-scale anomaly ranking) still runs per head exactly as before; only the cross-head reallocation of how many tokens each head keeps is dropped, in favor of each head independently keeping its own top-nestedkv_budgetscoring tokens (sinks always included) — the same per-head-independent budget model H2O and CurDKV already use. No padding is ever needed, so there is nothing left to mask.
- Byte accounting:
nestedkv_kept_bytes — fp16 bytes for currently retained K + V tokens full_seq_bytes — hypothetical fp16 cost if all tokens were kept compression_ratio — full_seq_bytes / nestedkv_kept_bytes (> 1 = savings) tokens_seen — total token positions ever passed to update_and_fetch tokens_kept — tokens currently in the first (B=0, H=0) head’s cache
- class veloxquant_mlx.cache.nestedkv_cache.NestedKVKVCache(config)[source]¶
Bases:
DeferredEvictionMixin,KVCacheKV cache implementing NestedKV-adapted multi-scale ensembled eviction.
- Parameters:
config (
Any) –KVCacheConfig. Fields consumed:nestedkv_budget(int, default 512) — per-head budget(uniform across heads, see #21 — NOT per-head-equivalent summed into a cross-head-competed total as in the paper),
nestedkv_n_sink(int, default 4) — leading sink positions,nestedkv_window(int, default 64) — current-memory window W,nestedkv_beta(float, default 3.0) — head-adaptive blend temperature,nestedkv_tau(float, default 0.60)— surprise gate threshold,nestedkv_kappa(float, default 10.0)— surprise gate sharpness,nestedkv_safeguard_alpha(float, default 0.20) — parsed andstored for API stability but no longer consumed by this class (see #21): it configured the cross-head budget floor in
nestedkv_allocate_head_budgets, which this class no longer calls. Still a real, tested parameter of that quantizer-level function for direct callers.
Notes
No
.bitsattribute — stores and returns fp16 K/V directly. Eviction happens ONCE at prefill (S > 1), independently per head at a UNIFORM budget (see #21 — NOT the paper’s cross-head budget competition, which produced ragged per-head lengths that could not be safely stacked into one tensor without either corrupting attention with fake padding rows or requiring per-layer attention masks this architecture doesn’t support). Decode tokens (S == 1) are always appended, never rescored or evicted — same convention as SnapKV-adapted, NOT H2O’s/CurDKV’s per-step loop. Single-layer (no coordinator);KVCacheBuilder.for_model()propagates allnestedkv_*fields automatically viadataclasses.replace. Writes through to the basemlx_lmKVCache’sself.keys/self.values/self.offseton every call so.statestays valid (mlx_lm’sgenerate()reads it unconditionally during chunked prefill);is_trimmable()reportsFalsesince the internal per-token state can’t be rolled back by a base-classtrim()(see #83).- property merge¶
- update_and_fetch(keys, values)[source]¶
Absorb new K/V tokens; prefill compresses once, decode always appends.
- Parameters:
keys (
array) –[B, H, S, D]new key tokens (any dtype; cast to fp16 internally).values (
array) –[B, H, S, D]new value tokens.
- Returns:
Full prior kept rows plus all new rows, before eviction (#610). Only the state stored for the next call is compressed.
- is_trimmable()[source]¶
False: trim() would only roll back base-class offset bookkeeping, not the internal per-token eviction/compression state that actually determines what gets returned, silently corrupting future calls.
- Return type:
- property nestedkv_kept_bytes: int¶
Bytes currently stored across all heads (fp16 K + V, kept tokens only).
- property compression_ratio: float¶
full_seq_bytes / nestedkv_kept_bytes; > 1 means memory savings over fp16.