TurboQuant KV Cache Compression¶
TurboQuant is a KV cache compression mode for long-context inference. It lets mlxcel store part of the attention cache in a compact quantized form, reducing memory use while keeping the model weights unchanged.
In practical terms, TurboQuant is for the moment when the model itself fits, but the memory used to remember a long conversation or several simultaneous requests does not. It makes that "remembered context" smaller.
Use TurboQuant when the model fits in memory but the context window, batch size, or number of concurrent requests makes the KV cache too large. Keep the default fp16 cache when memory is not constrained, when you are comparing against a baseline, or when the model family is not listed in the supported configuration table below.
The implementation follows the TurboQuant paper (Zandieh et al., ICLR 2026, arXiv:2504.19874) and the PolarQuant-only refinement validated by the independent TurboQuant+ project.
Table of Contents¶
- Why the KV Cache Matters
- What TurboQuant Changes
- Which Mode to Start With
- Cache Modes
- CLI Flags
- Examples
- Tuning
- Supported Configurations
- Known Limitations
Why the KV Cache Matters¶
During autoregressive generation, the model does not recompute every previous token from scratch. Each transformer layer stores two tensors for every token that has already been processed:
- K, or keys: used to compute attention scores for the next token.
- V, or values: mixed together according to those attention scores to form the next hidden state.
This stored state is the KV cache. It makes decoding practical, but it grows linearly with context length, layer count, the number of KV heads, the size of each head vector, and the number of active sequences. Here, an active sequence means one request or batch entry being decoded. At short context the cache may be small compared with model weights. At long context or high concurrency, it can become one of the main memory costs.
Think of the KV cache as the model's working notes for the current request. A longer prompt, a larger context window, or more simultaneous users means more notes. TurboQuant compresses those notes so a longer session can stay in memory.
What TurboQuant Changes¶
TurboQuant changes how mlxcel stores the KV cache. It does not change the model weights, the tokenizer, or the prompt. It does two things:
- Put the cache vector into a shape that is easier to compress.
- Store it with fewer bits per value.
- Expand it back when attention needs to read it.
The first step uses a Walsh-Hadamard transform, and the compact storage uses a PolarQuant codebook. You do not need to tune those pieces directly; choose the cache mode and boundary setting instead.
The default TurboQuant mode in mlxcel is fp16+turbo4. It keeps K in FP16 and compresses V to 4-bit TurboQuant storage.
This default is intentionally asymmetric. K is used to decide where the model attends. If K is distorted, the model may attend to the wrong tokens. V is the content mixed after attention has chosen where to look, so V can usually be compressed with less risk. That is why fp16+turbo4 is the recommended starting point for dense Q4KM models.
Boundary-V protection keeps the first and last few transformer layers in FP16 V. Those layers are often more sensitive to compression, so this policy trades some memory savings for better output quality.
Which Mode to Start With¶
| Situation | Start with |
|---|---|
| You want lower KV memory with the least operational risk | fp16+turbo4 |
| You are serving dense Q4KM models such as Llama, Qwen, or Mistral | fp16+turbo4 |
| You need symmetric K and V compression and the model family is allowlisted | turbo4 |
| You are experimenting with the delegated hot/cold cache path | turbo4-delegated |
| You need more V-side compression and can test quality locally | fp16+turbo3 |
For a first deployment, prefer fp16+turbo4 with the default boundary setting. Move to the more aggressive modes only after checking output quality on your own prompt distribution.
Cache Modes¶
--kv-cache-mode value | K side | V side | KV memory vs FP16 | Quality cost | Recommended for |
|---|---|---|---|---|---|
fp16 (default) | FP16 | FP16 | 1.00x | None | Baseline behavior |
int8 | INT8 per-token absmax | INT8 per-token absmax | about 0.50x | Small quantization error | Long context with simple INT8 cache storage |
fp16+turbo4 (alias turbo4-asym) | FP16 | 4-bit PolarQuant + WHT | about 0.74x before boundary layers | Low on supported models | Recommended TurboQuant default |
turbo4 (alias turbo4-sym) | 4-bit PolarQuant + WHT | 4-bit PolarQuant + WHT | about 0.27x before boundary layers | Model-dependent | Only model families on the symmetric allowlist |
fp16+turbo3 | FP16 | 3-bit PolarQuant + WHT | about 0.72x before boundary layers | Higher than Turbo4 V | More aggressive V-side compression |
turbo3 | FP16 | 3-bit PolarQuant + WHT | same as fp16+turbo3 | same as fp16+turbo3 | Alias for asymmetric Turbo3 |
turbo4-delegated | FP16 hot/cold K | FP16 hot tail + Turbo4 cold body | about 0.74x at long context | Similar to fp16+turbo4 | Delegated-cache decode experiments |
The simple memory formula is (K_bits + V_bits) / (2 * 16) plus small per-block norm sidecars. Boundary-V protection keeps selected V layers in FP16, so the actual memory footprint is higher than the table value for Turbo modes.
WHT in the table means Walsh-Hadamard transform. It is the rotation step that makes the cache vectors easier to store with a small codebook.
CLI Flags¶
mlxcel generate, mlxcel serve, and mlxcel-server share the same TurboQuant flags.
| Flag | Environment variable | Default | Description |
|---|---|---|---|
--kv-cache-mode <MODE> | - | fp16 | Unified KV cache mode. Accepts the mode values in the table above. |
--cache-type-k <TYPE> | LLAMA_ARG_CACHE_TYPE_K | fp16 | llama-server-compatible split flag for the K side. |
--cache-type-v <TYPE> | LLAMA_ARG_CACHE_TYPE_V | fp16 | llama-server-compatible split flag for the V side. |
--turbo-boundary-v <COUNT> | MLXCEL_KV_BOUNDARY_V_LAYERS | 2 | Number of first and last transformer layers whose V cache stays FP16 in Turbo modes. |
When a split flag and --kv-cache-mode are both supplied, the split flags take precedence and mlxcel logs a warning. If only one split flag is supplied, the other side defaults to fp16.
Supported K/V split combinations¶
--cache-type-k | --cache-type-v | Effective mode |
|---|---|---|
fp16 | fp16 | Fp16 |
int8 | int8 | Int8 |
fp16 | turbo4 | Turbo4Asym |
fp16 | turbo4-asym | Turbo4Asym |
turbo4 | turbo4 | Turbo4 |
fp16 | turbo4-delegated | Turbo4Delegated |
fp16 | turbo3 | Turbo3Asym |
fp16 | turbo3-asym | Turbo3Asym |
Other combinations are rejected at startup with a message listing the supported pairs. Symmetric Turbo3 (turbo3 / turbo3) is not exposed.
The split flags also accept f16, llama.cpp's spelling of unquantized FP16 storage, as fp16. Every other GGML cache type (q8_0, q4_0, q4_1, iq4_nl, q5_0, q5_1, f32, bf16) is rejected with an explanation, because none of them is numerically equivalent to an mlxcel mode. int8 here is per-token absmax, and the Turbo modes are PolarQuant with a Walsh-Hadamard rotation. fp16+turbo4-delegated is accepted as an alias of turbo4-delegated.
Examples¶
Offline generation¶
# Recommended TurboQuant default: FP16 K, Turbo4 V.
mlxcel generate -m models/<model> --kv-cache-mode fp16+turbo4 -p "Hello" -n 200
# Symmetric Turbo4. Use only for allowlisted model families.
mlxcel generate -m models/<model> --kv-cache-mode turbo4 -p "Hello" -n 200
# Delegated cache path.
mlxcel generate -m models/<model> --kv-cache-mode turbo4-delegated -p "Hello" -n 200
Server¶
# Unified mode flag.
mlxcel-server -m models/<model> --kv-cache-mode fp16+turbo4
# llama-server-compatible split flags.
mlxcel-server -m models/<model> --cache-type-k fp16 --cache-type-v turbo4
# Equivalent environment variables.
LLAMA_ARG_CACHE_TYPE_K=fp16 LLAMA_ARG_CACHE_TYPE_V=turbo4 mlxcel-server -m models/<model>
Tuning¶
Boundary-V layer protection¶
When a Turbo mode is active, the first and last --turbo-boundary-v transformer layers keep FP16 V caches. The default is 2, matching the LA-V7 policy used by the TurboQuant+ validation work.
mlxcel generate --kv-cache-mode fp16+turbo4 --turbo-boundary-v 2 \
-m models/<model> -p "Hello" -n 50
# Equivalent environment variable.
MLXCEL_KV_BOUNDARY_V_LAYERS=4 mlxcel generate --kv-cache-mode fp16+turbo4 ...
Special values:
0: disable boundary protection.2: default, suitable for typical 28- to 64-layer models.- Values larger than
n_layers / 2are clamped. For example, a 4-layer model with--turbo-boundary-v 8keeps all 4 layers at FP16 and does not error.
Sparse-V dequant threshold¶
For attention positions whose weight is below the threshold, mlxcel can skip V dequantization because the contribution to the output is negligible.
The default threshold is 1e-6. On macOS, the fused Metal kernel is enabled when the threshold is non-zero. Set MLXCEL_SPARSE_V_KERNEL=0 to compare against the graph reference path.
Delegated cache variants¶
The delegated cache keeps recent tokens in FP16 and stores older V tokens in Turbo4 form. In other words, the cache is split into a recent part that stays easy to read and an older part that is stored compactly. The variables below are advanced controls for experiments and performance comparisons.
| Environment variable | Effect |
|---|---|
MLXCEL_TURBO4_DELEGATED_DEQUANT_SDPA=0 | Use the older custom packed-V Metal paths instead of the default dequant-first SDPA path. |
MLXCEL_TURBO4_DELEGATED_FUSED=1 | With dequant-first SDPA disabled, use the cold-only fused composition kernel. |
MLXCEL_TURBO4_DELEGATED_FP16_FAST_PATH=1 | Keep an FP16 V working set in addition to packed sidecars. This is useful as a speed reference but does not meet the compressed-memory target. |
MLXCEL_TURBO4_DELEGATED_FP16_SIDECARS=lazy | With the FP16 fast path enabled, delay sidecar folding until cache preservation. |
Supported Configurations¶
| Model family | Recommended mode | Symmetric turbo4 |
|---|---|---|
| Llama 3.x, Qwen 2.5, Qwen 3, and Mistral dense Q4KM models | fp16+turbo4 | Not allowlisted by default |
| Qwen3.5 and Qwen3-Next family | fp16+turbo4; turbo4 when symmetric mode is needed | Allowlisted |
| Qwen 2-VL, Qwen 2.5-VL, and Aya-Vision VLMs | fp16+turbo4 for supported text/VLM cases | Not validated for symmetric turbo4 |
| Gemma 3 and Gemma 4 | Use fp16 unless you have tested the model locally | Not validated |
Current symmetric turbo4 allowlist entries are qwen3_5, qwen3_5_moe, and qwen3_next. Q8 and 70B+ Q4KM models are candidates for future allowlist entries, but they should not be documented as generally enabled until the runtime can identify the weight tier and the specific family has been tested.
Dense Q4KM models outside the allowlist can show large PPL regressions with symmetric turbo4 because K-side quantization affects attention scores. Use fp16+turbo4 for those models.
Known Limitations¶
Head-dimension constraint¶
TurboQuant requires a power-of-two head dimension, such as 64, 128, or 256. Models with head dimensions such as 80, 96, or 192 cannot use TurboQuant without padding and are rejected during cache initialization.
Symmetric Turbo3¶
Symmetric turbo3 is not exposed. --kv-cache-mode turbo3 and --cache-type-v turbo3 resolve to the asymmetric fp16+turbo3 mode.
Paged KV scheduler¶
--kv-cache-mode <turbo4-variant> --decode-storage-backend paged is wired through the server dispatch path. End-to-end validation on Apple Silicon for decode throughput and concurrent-prefix coherence is still in progress.
M5 Max delegated decode speed¶
The default compressed turbo4-delegated path on M5 Max has not consistently met the 0.97x FP16 decode-speed target. The MLXCEL_TURBO4_DELEGATED_FP16_FAST_PATH=1 variant can be used for speed experiments, but it keeps a full FP16 V working set and therefore changes the memory profile.
Pre-M3 hardware¶
turbo3 can be bandwidth-bound on M1 and M2 GPU microarchitectures. Prefer fp16+turbo4 for decode-bound workloads on M1/M2.
VLM coverage¶
Current measurements cover Qwen2-VL 2B as a stand-in for Qwen 2.5-VL 3B, and Aya-Vision 8B at contexts up to 4K. Other VLM families and multi-image prompts above 8K tokens still need separate validation.