veloxquant_mlx.cache.kvtc_cache module¶
KVTC-adapted KV cache — local PCA + DP-optimal per-component bit allocation + entropy coding.
Inspired by “KV Cache Transform Coding for Compact Storage in LLM Inference” (NVIDIA, ICLR 2026, accepted poster, arXiv:2511.01815). Documented as “KVTC-adapted (VeloxQuant-MLX implementation)” — not a faithful port.
Modeled on PALUKVCache (both K and
V compressed, true-latent storage — not SVDq’s keys-only, reconstruct-fp16
scope). The mechanism this method adds over Palu/SVDq/SpectralQuant: a
DP-optimal, discrete, per-component bit allocation (may drop a
component entirely, i.e. assign it 0 bits) instead of a fixed hand-chosen
split, followed by a real, measured entropy-coding stage. See
quantizers/kvtc.py and allocators/kvtc_dp.py for the mechanism and
the honesty crux (local per-sequence PCA vs the paper’s pre-calibrated
global basis; analytic distortion proxy vs the paper’s real-activation-fit
rate-distortion model; order-0 Huffman coder vs the paper’s possibly more
sophisticated scheme; paper’s headline numbers are the paper’s, on trained
models, never reproduced here).
NOT PATH-DEPENDENT (contrast with the eviction family)¶
Unlike H2O/TOVA/MorphKV/KVzip (eviction, path-dependent keep-sets), KVTC is
in the Palu/SVDq/SpectralQuant family: the local PCA basis (V, mean)
and the DP-derived per-component bit allocation are fit once, at the
first update_and_fetch call with S > 1 (prefill), and reused
unchanged for every subsequent token (prefill continuation or decode).
Feeding the same sequence one token at a time after prefill vs. all at once
produces the identical stored basis/allocation/codes/decompressed output —
pinned by a determinism test in
tests/cache/test_kvtc_cache.py.
Design¶
- Prefill (first call,
S > 1): For keys and values independently: run local PCA (via
quantizers/kvtc.py::kvtc_compress) on the prefill batch, atkvtc_bit_budgettotal bits per token. Store the resulting projectionV,mean, and per-componentbit_allocationas fixed layer state.Store the (fixed-basis) latent codes for the prefill batch.
- Decode (subsequent calls, any
S): Project the new key/value into the already-fitted basis (
(x - mean) @ V), quantize each component at its already-fixed bit allocation, and grow the entropy-coded latent store.
Because true latent (entropy-coded) storage is not a fixed-shape tensor the
parent mlx_lm fp16 ring buffer cannot hold it — like
PALUKVCache, this class bypasses the parent buffer entirely and
manages its own offset, re-running kvtc_compress()
over the accumulated raw rows on every call (the DP allocation and PCA
basis are frozen after prefill; only the per-token codes grow). Every call
returns reconstructed fp16 [B, H, S_total, D] for the downstream SDPA
call; storage accounting is based on the realized entropy-coded bytes.
Byte accounting¶
kvtc_bytes / full_seq_bytes / compression_ratio — key + value
combined, mirroring Palu’s combined reporting style.
pre_entropy_bytes — fixed-width (pre-entropy-coding) size, for
comparison.
entropy_coding_gain — pre_entropy_bytes / kvtc_bytes. Since #77,
kvtc_compress guarantees the entropy-coded payload+table never exceeds
fixed-width packing (falling back to fixed-width when Huffman doesn’t pay
for itself — see quantizers/kvtc.py’s module docstring), but this ratio
divides by the FULL stored size (kvtc_bytes, which also carries the
fixed projection-basis overhead unrelated to entropy coding), so it can
still legitimately read < 1 when that basis dominates — it is not itself
guaranteed >= 1.
- class veloxquant_mlx.cache.kvtc_cache.KVTCKVCache(config)[source]¶
Bases:
KVCacheKV cache implementing KVTC-adapted local-PCA + DP-optimal bit allocation + entropy coding.
- Parameters:
config (
Any) –KVCacheConfig. Fields consumed:head_dim(D),kvtc_bit_budget(int, default4 * head_dim— total bitsper token across all principal components, for K and V independently; “4 avg-bits-per-component equivalent scaled by head_dim”),
kvtc_bit_choices(tuple[int, …], defaultallocators.kvtc_dp.DEFAULT_BIT_CHOICES),kvtc_beta(float, defaultallocators.kvtc_dp.DEFAULT_BETA).
Notes
Applies to both K and V independently (mirrors Palu’s scope, not SVDq’s keys-only scope) — see module docstring. No
.bitsattribute (this isn’t a fixed-bit-width quantizer in that sense, and it isn’t an eviction cache either). Single (batch, head) state per layer;BandHhandled by looping (mirrors PALUKVCache’s per-head loop). Bypasses the parent fp16 ring buffer (true latent storage), like PALUKVCache — manages its ownoffset.- property merge¶
- update_and_fetch(keys, values)[source]¶
Fit the local PCA basis + DP bit allocation on prefill, else append through the frozen basis; return reconstructed fp16 K/V.
- Parameters:
keys (mlx.core.array)
values (mlx.core.array)
- property offset: int¶
Number of tokens accumulated so far (own counter — parent ring buffer is bypassed).
- property state¶
Last reconstructed fp16 (keys, values), since the true-latent buffers bypass the parent ring buffer
mlx_lmreads directly.
- trim(n)[source]¶
Drop the
nmost-recently-added tokens, re-quantizing through the frozen basis; returns the number actually dropped.
- property kvtc_bytes: int¶
Realized total stored bytes (K + V, all heads/batches), including the entropy-coded payload, code table, projection basis, and quant params — never the pre-entropy-coding size.
- property compressed_key_bytes: int¶
Realized stored bytes for the compressed key cache (entropy-coded payload + table + basis, all heads/batches).
- property compressed_value_bytes: int¶
Realized stored bytes for the compressed value cache (entropy-coded payload + table + basis, all heads/batches).
- property entropy_coding_gain: float¶
pre_entropy_bytes / kvtc_bytes— realized entropy-coding gain.A modest, honestly-scoped secondary effect (see
quantizers/_entropy_coding.py): NOT the theoretical Shannon-entropy bound. Since #77, the entropy-coding stage itself (payload + table) is guaranteed to never exceed fixed-width packing — but this ratio’s denominator (kvtc_bytes) also includes the fixed projection-basis overhead, which is unrelated to entropy coding and can dominate at small survived-component counts, so this property can still read < 1 for that reason.