KIVI: The Most-Cited KV Cache Baseline, Implemented in MLX
TL;DR — VeloxQuant-MLX 0.8.0 adds KIVI (Liu, Yuan et al., ICML 2024, arXiv:2402.02750), a faithful re-implementation of the most-cited KV-cache quantization baseline. It's asymmetric (per-channel keys, per-token values), keeps a small fp16 residual window, and is fully deterministic. On an Apple M4, across Llama-3.2-3B, Qwen2.5-7B, and Mistral-7B (4-bit, ~2.2–2.4k-token prompts), KIVI-2bit measured 5.8× key / ~4.0× full-KV compression at roughly fp16 throughput. On Apple Silicon the win is memory, not speed — and we report the throughput as measured, not as hoped. Every number below traces to a committed
results.json.
The wall, again
If you run local LLMs on a Mac, you've met the unified-memory wall. The CPU, GPU, and Neural Engine all share one pool of RAM, and the KV cache — the keys and values the model stores for every past token — grows linearly with context length until it crowds out everything else. Weight quantization (GGUF, GPTQ, AWQ) is an offline, one-time compression of the model's parameters; it does nothing for the cache, which is rebuilt token by token at inference time.
VeloxQuant-MLX exists to compress that cache. It already ships a suite of methods — TurboQuant, RVQ, VecInfer, SpectralQuant, RaBitQ, CommVQ, QJL, PolarQuant, RateQuant. With 0.8.0 we added the one method a reviewer asks about first, and which the library conspicuously lacked: KIVI.
What KIVI is
KIVI is "A Tuning-Free Asymmetric 2-bit Quantization for KV Cache" (arXiv:2402.02750, ICML 2024). Its central observation is that keys and values want different quantization layouts:
- Keys → per channel. Key distributions have a few high-variance channels. Quantizing along the token axis, one (scale, zero) per channel-group, keeps those channels accurate.
- Values → per token. Value distributions are flatter across channels but vary token to token, so the group runs along the channel axis instead.
- A residual window. The most recently generated tokens dominate attention and are cheap to keep exact, so KIVI holds the last
Rtokens in fp16 and only quantizes tokens once they age out of that window.
The quantizer itself is plain asymmetric min/max group quantization — no codebook, no calibration, no randomness:
for each group g (a slice along the quantization axis):
zero = min(g)
scale = (max(g) - min(g)) / (2**b - 1)
q = round((g - zero) / scale) # uint code in [0, 2**b-1]
g_hat = q * scale + zero # asymmetric dequant
Because there's no k-means and no RNG, KIVI is deterministic — same input, identical output, every run. That matters in this codebase: our vector-quantization methods train codebooks and can vary run to run; KIVI adds none of that.
Why we added it (and what we didn't invent)
To be clear about credit: KIVI is Liu, Yuan, and colleagues' algorithm. We ported it to MLX. There is no novelty claim here.
The value is comparative. KIVI is the field's reference baseline — nearly every KV-cache paper measures against it. Until 0.8.0, VeloxQuant-MLX couldn't answer "how does your method compare to KIVI?" Now every other method in the library has a recognized point of comparison, in the same framework, on the same Apple-Silicon hardware. (The full reasoning, including why we chose KIVI over KVQuant, GEAR, and ZipCache, is in paper/NEW_METHOD_SURVEY.md: KIVI was the highest-value missing baseline, deterministic, and a clean architectural fit that needs no RoPE or attention-score hooks.)
How it plugs in
Three lines, and mlx_lm.generate runs unchanged:
import mlx_lm
from veloxquant_mlx import KVCacheConfig, KVCacheBuilder
model, tokenizer = mlx_lm.load("mlx-community/Llama-3.2-3B-Instruct-4bit")
config = KVCacheConfig(
method="kivi",
bit_width_inlier=2, # KIVI's default 2-bit
kivi_group_size=32, # min/max group size (paper default)
residual_length=32, # recent tokens kept in fp16
)
caches = KVCacheBuilder.for_model(model, config)
model.make_cache = lambda *_a, **_k: caches
response = mlx_lm.generate(
model, tokenizer,
prompt="Summarize the attention mechanism in three sentences.",
max_tokens=300,
)
The cache quantizes the aged-out keys/values and immediately dequantizes them, so the downstream scaled-dot-product attention sees standard fp16 tensors — no model surgery, no custom attention path.
Results
All numbers below come from figures/kivi/results_summary.json (aggregated from the per-model figures/kivi/<model>/results.json). Conditions, identical across runs: Apple M4, 24 GB, 4-bit mlx-community models, group_size=32, residual_length=32, max_tokens=120, prompts of ~2.2–2.4k tokens (a long prompt is required for KIVI to actually exercise the quantized path rather than sit inside the fp16 residual window).
KIVI-2bit, across three models:
| Model | head_dim / KV heads | Key compression | Full-KV compression | Throughput vs fp16 | Tokens |
|---|---|---|---|---|---|
| Llama-3.2-3B | 128 / 8 | 5.79× | 3.98× | 16.3 vs 16.0 tok/s (102%) | 121/121 |
| Qwen2.5-7B | 128 / 4 | 5.78× | 3.98× | 7.6 vs 7.6 tok/s (100%) | 120/120 |
| Mistral-7B | 128 / 8 | 5.76× | 4.03× | 6.8 vs 6.5 tok/s (105%) | 122/122 |
Bit-width sweep (Llama-3.2-3B):
| Config | Key compression | Full-KV compression | Throughput |
|---|---|---|---|
| fp16 baseline | 1.00× | 1.00× | 16.0 tok/s |
| KIVI-2bit | 5.79× | 3.98× | 16.3 tok/s |
| KIVI-3bit | 4.34× | 3.24× | 15.9 tok/s |
| KIVI-4bit | 3.47× | 2.73× | 16.0 tok/s |
Two honest observations from this data:
- Throughput is flat, not faster. KIVI here runs at 100–105% of fp16 — i.e. it does not slow generation down at these sizes, but it also doesn't speed it up. The published KIVI speedups come from a fused CUDA kernel; that kernel does not port to Metal. On Apple Silicon the deliverable is memory compression (the 4× full-KV figure), and the throughput column is there so you can see it costs you nothing, not so you can claim a speedup.
- Full-KV compression is lower than key-only, and deliberately so. The full-KV figure includes the fp16 residual window. At
residual_length=32over a 120-token generation, a meaningful slice stays in fp16; that drags the end-to-end ratio below the key-only number. We report both rather than quoting the flattering one.
The implementation is covered by 25 passing tests (veloxquant_mlx/tests/quantizers/test_kivi.py and tests/cache/test_kivi_cache.py), including reconstruction-fidelity bounds, the per-token/per-channel asymmetry, the residual-window behavior, and a determinism check.
Honest limitations
- Memory win, not a speed win on Metal. As above — the CUDA kernel fusion from the paper isn't available here. If you need raw throughput, this isn't your lever.
- Quality is measured as reconstruction fidelity, not task accuracy. Our tests check cosine similarity / MSE against the fp16 cache on synthetic and real key distributions. We have not run LongBench or a perplexity sweep across configs, so we do not claim "no quality loss" on downstream tasks.
- 2-bit is genuinely lossy. On unit-norm synthetic keys, KIVI-2bit reconstruction cosine sits around 0.93 — which is exactly why KIVI keeps an fp16 residual window. If you push
residual_lengthto 0 you'll feel it. The defaults exist for a reason. - Single chip, short generations. Everything above is one M4 at ~120 generated tokens. Behavior across other M-series tiers and very long generations isn't characterized yet.
Where KIVI fits
- Reach for KIVI when you want a simple, deterministic, calibration-free 2-bit baseline — or when you specifically need to compare against the literature's reference point.
- Reach for RVQ when you want stronger compression-per-bit with zero calibration and near-fp16 throughput (its analytical codebooks do better than scalar min/max at low bit-rates).
- Reach for VecInfer when you want the most aggressive key compression (up to 16× key-only) and have ~2 minutes for codebook calibration.
KIVI's job in the suite isn't to win every axis; it's to be the honest yardstick the others are measured against.
Try it
pip install VeloxQuant-MLX==0.8.0
- Docs:
/docs/algorithms/kivi/ - GitHub: https://github.com/rajveer43/VeloxQuant-MLX
- Paper: KIVI, arXiv:2402.02750 (ICML 2024)
What was measured vs. not: all compression, throughput, peak-memory, and token-count figures are from committed figures/kivi/*/results.json on a single Apple M4 (24 GB) at ~120 generated tokens with ~2.2–2.4k-token prompts; correctness is from 25 passing unit tests. We did not measure downstream-task accuracy (e.g. LongBench, perplexity sweeps) — "quality" here means reconstruction fidelity against the fp16 cache.
