veloxquant_mlx.cache.age_tiered_cache module¶
AgeTieredKV KV cache — position/age-driven three-tier precision (issue #256).
Every token is retained (compression-only, like AMC — never eviction). Each
token’s bit-width is set purely by how long ago it was written:
age = current_position - token_position. As a token ages past a tier
boundary, its stored slice is re-quantized at the coarser tier’s bit-width;
this mirrors KIVI’s “flush on boundary crossing” model rather than
quantizing once at write time and leaving the choice frozen — a token
written under this scheme is never coarser than its current age entitles,
which is what issue #256’s “recent -> high precision, older -> low
precision” framing calls for.
See veloxquant_mlx.quantizers.age_tiered for the tiering/quantize
primitives this class wraps around a per-(batch, head) token buffer, the
same storage shape AMC and KIVI both use.
- class veloxquant_mlx.cache.age_tiered_cache.AgeTieredKVCache(config)[source]¶
Bases:
KVCacheKV cache implementing position/age-gated three-tier quantization.
- Parameters:
config (
Any) –KVCacheConfig. Fields consumed:age_recent_boundary(int, default 128) — tokens withage < this stay at
age_bits_recent.age_mid_boundary(int, default 1024) — tokens with agein
[age_recent_boundary, age_mid_boundary)useage_bits_mid;age >= age_mid_boundaryusesage_bits_old.
age_bits_recent(int, default 8)age_bits_mid(int, default 4)age_bits_old(int, default 2)age_group_size(int, default 32) — token-axis group sizefor the shared min/max quantizer.
Notes
No
.bitsattribute — stores and returns fp16 K/V directly (same quantize-then-dequantize convention as every other method here). Both prefill (S > 1) and decode (S == 1) go through the identical per-step re-tiering pass. Writes through to the basemlx_lmKVCache’sself.keys/self.values/self.offseton every call, same as AMC (see #83) — without this,.statestays at its__init__default and mlx_lm’s chunked prefill crashes.is_trimmable()reportsFalsefor the same reason AMC’s does: the internal per-token tier state can’t be rolled back by a base-classtrim().Keeps a full fp16 copy of every token’s raw K/V alongside the quantized output (see #397’s fix), so this class’s own runtime memory footprint is closer to fp16 than the
compression_ratioimplies – the reported compression only describes what would be transmitted/stored if the quantized form were serialized on its own. This raw copy is what lets a group be re-quantized once per tier change without compounding error from its own prior quantize-dequantize output.- update_and_fetch(keys, values)[source]¶
Absorb new K/V tokens, re-tier the whole buffer by age, return all tokens.
- Parameters:
keys (
array) –[B, H, S, D]new key tokens (any dtype; cast to fp16).values (
array) –[B, H, S, D]new value tokens.
- Returns:
(K_out, V_out)both[B, H, n_seen, D]fp16 — AgeTieredKV never evicts, son_seenequals the total tokens passed so far.
- is_trimmable()[source]¶
False: trim() would only roll back base-class offset bookkeeping, not the internal per-token tier state, silently corrupting future calls (same rationale as AMC’s, see #83).
- Return type:
- property age_tiered_bytes: int¶
Actual bytes stored across all heads (fp16-equivalent K + V, tiered).
- property tokens_seen: int¶
Total token positions ever passed to update_and_fetch (all heads summed).
- property tokens_kept: int¶
Tokens currently in the (B=0, H=0) head’s cache — always == tokens per head seen.