TurboQuant KV cache compression for MLX Swift — bringing 3-bit key and 3-bit value compression to on-device LLM inference on Apple Silicon.
Based on the TurboQuant algorithm from Google Research (Zandieh et al., ICLR 2026, arXiv 2504.19874), this repository provides:
- A benchmark CLI (
TurboQuantBench) that measures fp16 vs TurboQuant KV cache performance on identical workloads - Integration guide for adding TurboQuant to MLX Swift LLM applications
- Architecture documentation explaining the compression pipeline
TurboQuant compresses the KV (Key-Value) cache used during LLM autoregressive generation. During inference, the KV cache grows linearly with context length and dominates memory usage for long sequences. TurboQuant applies:
- PolarQuant (3-bit) — Lloyd-Max codebook quantization with Walsh-Hadamard Transform (WHT) rotation for uniform magnitude distribution
- QJL (Quantized Johnson-Lindenstrauss) — 1-bit residual correction for keys using random projection, recovering information lost in quantization
This achieves ~3.1-bit keys and 3-bit values while maintaining output quality, with a "hot window" design that keeps the most recent tokens in full precision.
┌─────────────────────────────────────────────────────┐
│ KV Cache (growing left to right) │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Compressed (3-bit) │ │ Hot Window (fp16) │ │
│ │ PolarQuant + QJL │ │ Last 256 tokens │ │
│ │ ~5× smaller │ │ Full precision │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ ← oldest tokens newest tokens → │
└─────────────────────────────────────────────────────┘
- Tokens in the hot window (configurable, default 256) remain in fp16 for maximum attention accuracy
- Tokens evicted from the hot window are compressed via PolarQuant + QJL
- During attention, compressed tokens are decoded and prepended to the hot window
- Compression activates only after
turboMinActivationTokens(default 2048) to avoid overhead on short contexts
Based on the TurboQuant paper (Table 1, Zandieh et al., ICLR 2026) and our MLX Swift implementation:
| Metric | fp16 Baseline | TurboQuant (3-bit) | Delta |
|---|---|---|---|
| KV Cache Size (8K context) | 100% | ~19% | -81% |
| Peak Memory (8K context) | baseline | -40 to -60% KV portion | significant |
| Throughput (tok/s) | baseline | ~0.95-1.0× | minimal overhead |
| Quality (perplexity) | baseline | +0.1-0.3 ppl | negligible |
| TTFT | baseline | ~1.0× | unchanged |
Note: Actual numbers vary by model architecture, hardware, and context length. Run
TurboQuantBenchon your hardware for precise measurements.
- Long contexts (4K+ tokens): Maximum KV cache savings
- Memory-constrained devices (8-16 GB): Enables larger contexts that wouldn't fit in fp16
- Batch inference: Memory savings multiply across batch dimension
- Short contexts (<2K tokens): Hot window covers the entire context, no compression occurs
- Already-quantized models (4-bit weights): Memory dominated by weight quantization overhead
The benchmark requires Xcode for Metal shader compilation:
# Open in Xcode (recommended)
open Package.swift
# Or build via xcodebuild
xcodebuild -scheme TurboQuantBench -configuration ReleaseNote:
swift build/swift runwill compile but fail at runtime because SwiftPM doesn't compile Metal shaders. Use Xcode orxcodebuildinstead.
# Default: Gemma 4 E2B, 256 max tokens, 3 measured runs
.build/release/TurboQuantBench
# Custom configuration
.build/release/TurboQuantBench \
--model mlx-community/gemma-4-e4b-it-4bit \
--max-tokens 512 \
--warmup 2 \
--runs 5 \
--output results.json
# fp16 only (baseline)
.build/release/TurboQuantBench --fp16-only
# TurboQuant only
.build/release/TurboQuantBench --turbo-onlyThe tool produces:
- Console summary table with averaged metrics per configuration
- JSON file (
benchmark_results.json) with per-run detailed results
| Workload | Prompt Size | Purpose |
|---|---|---|
short-256 |
~256 tokens | Verify no overhead below activation threshold |
medium-2k |
~2048 tokens | Measure compression activation point |
long-8k |
~8192 tokens | Full compression, maximum memory savings |
- tok/s — Generation throughput (tokens per second)
- TTFT — Time to first token (ms)
- Total generation time — End-to-end latency (ms)
- Peak memory — Resident set size (MB)
- Output snippet — First 80 chars for quality spot-check
TurboQuant is available as an opt-in feature through forked MLX Swift dependencies:
// Package.swift
.package(url: "https://github.com/joelnishanth/mlx-swift-lm", branch: "feature/turboquant"),This transitively pulls joelnishanth/mlx-swift (feature/turboquant) which contains the C++ TurboQuant primitives.
import MLXLMCommon
let session = ChatSession(modelContainer)
session.turboQuantEnabled = true // Opt-in to KV cache compression
for try await chunk in session.streamResponse(to: "Hello") {
print(chunk, terminator: "")
}When turboQuantEnabled is true:
- Each
KVCacheSimplelayer gets TurboQuant compression enabled - Compression activates after 2048 tokens (configurable via
turboMinActivationTokens) - A 256-token hot window (configurable via
turboHotWindowSize) stays in fp16 - Older tokens are compressed to ~3 bits using PolarQuant + QJL
| Component | fp16 (default) | TurboQuant |
|---|---|---|
KVCacheSimple.turboQuantEnabled |
false |
true |
| Cache storage | fp16 arrays | fp16 hot window + uint8 compressed |
attentionWithCacheUpdate() |
Direct SDPA | Decode compressed → concat → SDPA |
| Memory at 8K tokens | ~O(n) fp16 | ~O(256) fp16 + O(n) 3-bit |
mlx-swift-turboquant/
├── Package.swift # Swift Package with MLX dependencies
├── README.md # This file
├── Sources/
│ └── TurboQuantBench/
│ └── main.swift # Benchmark CLI tool
└── docs/
└── architecture.md # Detailed compression pipeline docs
Input tokens → KV Cache (fp16)
│
├─ offset < 2048 → standard fp16 path
│
└─ offset ≥ 2048 → evict cold tokens from hot window
│
├─ Keys: WHT rotate → Lloyd-Max 3-bit → QJL 1-bit residual
│
└─ Values: WHT rotate → Lloyd-Max 3-bit
│
└─ Store as uint8 packed arrays
│
During attention: │
Decode uint8 → fp16 → concat with hot window → SDPA
| File | Repository | Purpose |
|---|---|---|
turbo_quant.h |
joelnishanth/mlx-swift | C++ PolarQuant + QJL algorithms |
turbo_quant_ops.cpp |
joelnishanth/mlx-swift | Encode/decode implementations |
turbo_quant_bridge.cpp |
joelnishanth/mlx-swift | C bridge for Swift FFI |
MLXFast.swift |
joelnishanth/mlx-swift | Swift bindings |
KVCache.swift |
joelnishanth/mlx-swift-lm | Hot-window eviction logic |
AttentionUtils.swift |
joelnishanth/mlx-swift-lm | Decode path in attention |
This implementation is inspired by TurboQuant, a vector quantization algorithm developed at Google Research and Google DeepMind. TurboQuant was presented at ICLR 2026.
- TurboQuant: Amir Zandieh, Majid Daliri, Majid Hadian, Vahab Mirrokni. "TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate", ICLR 2026. arXiv 2504.19874
- PolarQuant: "PolarQuant: Polar Coordinate Quantization for Vector Compression", AISTATS 2026. arXiv 2502.02617
- QJL: "Quantized Johnson-Lindenstrauss Transform", AAAI 2025. ACM DL
TurboQuant was developed by Amir Zandieh (Google Research), Majid Daliri (NYU), Majid Hadian (Google DeepMind), and Vahab Mirrokni (Google Research), with contributions from Praneeth Kacham, Insu Han, Lars Gottesbüren, and Rajesh Jayaram. This Swift implementation adapts their algorithms for Apple Silicon via the MLX framework.
- ml-explore/mlx-swift — Apple MLX framework for Swift
- ml-explore/mlx-swift-lm — MLX Swift LLM library
Apache 2.0, matching the upstream MLX Swift license.