MLX Framework Port (Experimental)

June 12, 2026 · View on GitHub

TurboQuant KV cache compression is being ported to Apple's MLX framework for native Python/Swift inference on Apple Silicon.

Fork: TheTom/mlx feature/turboquant-plus

Results (M5 Max)

Qwen2.5-3B 4bit — delegated KVCache (5-run avg, 500 decode tokens, 7ad7500):

ConfigDecode tok/svs BaselinePPLPPL Delta
Baseline (f16 KV)172.6100%1.8764
Sym turbo4171.299.2%1.9083+1.70%
Asym (K=FP16, V=turbo4)171.099.0%1.8859+0.51%

Quality: Output text indistinguishable from baseline. KL divergence < 0.001, cosine similarity > 0.989.

35B MoE (Qwen3.5-35B-A3B 8bit):

ConfigPrefillDecodevs Baseline
Baseline11.495.7100%
turbo4 fused + boundary132.794.296%

Qwen3.5-27B Dense 8bit (16/64 KV layers):

ConfigPPLPPL DeltaDecodevs Baseline
Baseline1.480017.9100%
turbo4 asymmetric1.5082+1.91%15.587%
turbo4 symmetric1.5219+2.83%15.486%

Quality Validation (Qwen2.5-7B 8bit, dense, all 28 layers KV):

TestSymmetric turbo4Asymmetric (K=FP16)
KLD6.86 (broken)0.003
Top-1 match10.5% (broken)98.1%
NIAH0/15 FAIL15/15 PASS

Warning

Symmetric turbo is catastrophic on dense models. All K layers compressed → softmax error compounds across 28 layers. Asymmetric (K=FP16, V=turbo4) is mandatory for dense architectures. Hybrid models (Qwen3.5) with delta net layers are accidentally safe because only a fraction of layers use KV cache.

Dense models (short context, deferred compression):

ModelBaseline Decodeturbo4 asym DecodePPL Delta
Qwen2.5-7B 8bit64.264.10.00%
phi-4 8bit32.932.70.00%

M2 Pro — Qwen2.5-1.5B 8bit (dense, 28/28 KV layers, asymmetric):

TestResult
KLD0.004
Top-1 match96.8%
NIAH30/30 PASS
ContextBaseline DecodeTurbo Asymmetricvs Baseline
12834.835.2101%
409646.921.646%

M2 Pro shows more decode regression at long context — lower memory bandwidth amplifies turbo overhead.

M5 Max Context Scaling (Qwen2.5-7B 8bit, delegated KVCache, 7ad7500):

ContextBaselineSym turbo4vs BaselineAsym (K=FP16)vs Baseline
51263.663.6100%64.0101%
1K63.162.8100%62.699%
2K62.761.898%62.299%
4K61.060.299%61.0100%
8K58.256.998%57.799%
16K54.653.097%53.899%

Previous numbers (61-83%) were measured before the delegated KVCache optimization (7ad7500). Root cause was mx.concatenate allocating new arrays every decode step × n_layers. Fixed by delegating FP16 storage to an internal KVCache with pre-allocated buffers.

MLX Python vs llama.cpp (Qwen2.5-7B, M5 Max):

FrameworkPrefill (400 tok)DecodeMemory
llama.cpp (Q8_0)38720.97.5 GB
MLX (8bit)24321.28.5 GB

MLX decode matches llama.cpp. Prefill 37% slower (lazy graph vs pre-compiled).

MLX Python vs llama.cpp (M2 Pro, Qwen2.5-7B):

FrameworkPrefill (400 tok)Decode
llama.cpp38720.9
MLX24321.3

Note: Future benchmark logs should record Apple Silicon power mode (Low / Auto / High) when known, as it can materially affect throughput.

Quick Start (MLX Python)

import mlx_lm
from mlx.nn.layers.turbo_kv_cache import TurboKVCache

model, tokenizer = mlx_lm.load("mlx-community/Qwen2.5-7B-Instruct-8bit")
n_layers = len(model.model.layers)
cache = [TurboKVCache(bits=4, key_bits=4) for _ in range(n_layers)]
text = mlx_lm.generate(model, tokenizer, prompt="Hello!",
                        max_tokens=200, prompt_cache=cache, verbose=True)
pip install git+https://github.com/TheTom/mlx.git@feature/turboquant-plus
pip install mlx-lm

How it works

TurboKVCache is a drop-in replacement for mlx-lm's KVCache that adds TurboQuant 4-bit K+V compression. Compatible with mlx-lm and mlx-vlm — no framework changes needed.

Delegated KVCache architecture (7ad7500): During prefill, stores raw FP16. On first decode step, compresses to packed TurboQuant storage and seeds an internal KVCache with decoded FP16. Subsequent decode tokens go through the native KVCache (pre-allocated buffers, zero-alloc slice-assign). Packed storage updated in background via periodic batch recompression on CPU stream.

  • 97–100% baseline decode speed across 512–16K context (Qwen2.5-7B, M5 Max)
  • +0.51% PPL (asymmetric), +1.70% PPL (symmetric)
  • 99% answer agreement with baseline (520 multimodal samples)
  • Works with stock mlx-lm and mlx-vlm, no fork needed
  • All TurboQuant+ papers applied (beta centroids, dual SRHT signs, boundary layers)

Quick Start — mlx-vlm (multimodal)

from mlx_vlm import load
from mlx_vlm.models.cache import make_prompt_cache
from mlx_lm.models.cache import KVCache
from mlx.nn.layers.turbo_kv_cache import TurboKVCacheLite, compact_turbo_cache

model, processor = load("mlx-community/gemma-4-26b-a4b-it-bf16")

# Wrap KV layers with TurboKVCacheLite
cache = make_prompt_cache(model.language_model)
kv_indices = [i for i, c in enumerate(cache) if isinstance(c, KVCache)]
for idx in kv_indices:
    cache[idx] = TurboKVCacheLite(cache[idx], bits=4, key_bits=4)

# Generate as normal — prefill stores FP16
from mlx_vlm import generate
generate(model, processor, prompt="...", max_tokens=1, prompt_cache=cache)

# Compact: compress K+V to 4-bit TurboQuant
compact_turbo_cache(cache)

# Continue generating — native SDPA at full speed
generate(model, processor, prompt="Continue.", max_tokens=200, prompt_cache=cache)
pip install git+https://github.com/TheTom/mlx.git@feature/turboquant-plus
pip install mlx-vlm

Quick Start — mlx-lm (text)

import mlx_lm
from mlx.nn.layers.turbo_kv_cache import make_turbo_cache, compact_turbo_cache

model, tokenizer = mlx_lm.load("mlx-community/Qwen2.5-7B-Instruct-8bit")
cache = make_turbo_cache(model, bits=4)
mlx_lm.generate(model, tokenizer, prompt="Hello!", max_tokens=1, prompt_cache=cache)
compact_turbo_cache(cache)
mlx_lm.generate(model, tokenizer, prompt="Continue.", max_tokens=200,
                 prompt_cache=cache, verbose=True)

MM-NIAH Multimodal Benchmark (520 samples)

gemma-4-26b-a4b-it · BF16 · 4-bit TQ+ Compact · MM-NIAH (val) · M5 Max 128GB

BucketBL AccTQ+ AccAgreeBL DecodeTQ+ DecodeSpeedupBL KVTQ+ KVKV saved
~1K85%84%99%55.154.70.99x0.21G0.19G10%
~3K81%79%99%55.254.10.98x0.27G0.21G22%
~7K80%81%99%54.151.70.96x0.37G0.24G35%
~15K76%76%100%52.047.80.92x0.53G0.28G47%
~30K77%75%98%46.940.20.86x0.87G0.36G59%
~60K75%76%99%42.633.70.79x1.30G0.47G64%
Total79%78%99%51.147.20.92x0.58G0.29G50%

99% answer agreement with baseline across all context lengths — zero systematic quality degradation. KV savings of 10–64% where TQ+ is active. Decode speedup scales from 0.99x at ~1K to 0.79x at ~60K (dequant-once overhead on longer prefills).