☰
将 TurboQuant 4-bit KV 缓存压缩移植到 Apple MLX:TurboKVCache 架构、M5 Max 基准与对称/非对称压缩的工程取舍
2026/10/9 7:30:36 网站建设 项目流程

【免费下载链接】turboquant_plus

项目地址:https://gitcode.com/gh_mirrors/tu/turboquant_plus
点击查看免费下载

本文以 docs/mlx-port.md 为骨架,介绍 TurboQuant(turboquant_plus 项目的核心算法)在 Apple MLX 框架上的实验性移植:TurboKVCache如何以零框架侵入的方式替换 mlx-lm / mlx-vlm 的默认KVCache,delegated KVCache 架构如何把 decode 速度拉回基线的 97–100%,以及为什么在密集模型上"对称 turbo"会灾难性失败、而"非对称(K=FP16, V=turbo4)"是强制选择。读完本文,你将掌握 MLX 侧 KV 压缩的安装方式、两套 Quick Start 用法、对称/非对称配置的适用边界,以及如何在 M5 Max / M2 Pro 上复现质量与性能验证。

背景:TurboQuant 算法与 K/V 缓存的分工设计

TurboQuant 是一套面向 KV 缓存的 4-bit 量化压缩算法。在当前仓库的 Python 参考实现中,算法是两阶段的(见 turboquant/turboquant.py):

  1. PolarQuant 阶段(b-1 位):先做随机旋转,使各坐标近似独立同分布(Beta/Gaussian),再用按该分布标定的最优标量码本做 MSE 最优量化(见 turboquant/polar_quant.py 与 turboquant/codebook.py);
  2. QJL 阶段(1 位):对第一阶段的残差做 1-bit 量化 Johnson-Lindenstrauss 变换(随机正交投影 + 符号位),以无偏方式保留内积(见 turboquant/qjl.py)。

总计每坐标 b 位。更重要的是 KV 缓存层面的分工——turboquant/kv_cache.py 明确说明:

  • K 缓存:使用完整 TurboQuant(Algorithm 2),因为注意力分数依赖内积Q @ K^T,需要内积保持;
  • V 缓存:使用仅 MSE 的TurboQuantMSE(Algorithm 1,即纯 PolarQuant),因为attn_weights @ V的重建质量由 MSE 主导。

这个"K 管内积、V 管 MSE"的分工是理解下文"对称 vs 非对称"实验结果的算法根源。

TurboKVCache:mlx-lm / mlx-vlm 的无侵入替换

MLX 移植的核心交付物是TurboKVCache,定义在 fork 的mlx.nn.layers.turbo_kv_cache模块中。它被设计为 mlx-lmKVCache的drop-in 替换:既兼容 mlx-lm(文本),也兼容 mlx-vlm(多模态),不需要任何框架层修改。仓库中的 scripts/mlx_quality_suite.py 直接以from mlx.nn.layers.turbo_kv_cache import TurboKVCache方式使用,印证了这一点。

Delegated KVCache 架构(提交 7ad7500)

这是把 decode 速度从"61–83%"拉回"97–100%"的关键优化,工作流程如下:

  1. Prefill 阶段:缓存保持原始 FP16 存储,不做压缩;
  2. 首个 decode 步:将缓存压缩为打包的 TurboQuant 存储,并用解码出的 FP16 数据 seed 一个内部KVCache;
  3. 后续 decode:走原生 KVCache 的预分配缓冲 + 零分配 slice-assign,保证每个 token 的 decode 路径无额外分配;
  4. 后台:打包存储通过 CPU stream 上的周期性批量重压缩持续更新。

之前的性能瓶颈根因是:每个 decode 步 × n_layers 次mx.concatenate都会分配新数组,导致内存分配开销把压缩节省的带宽完全吃掉。delegated 架构把 FP16 存储委托给带预分配缓冲的内部 KVCache 后,decode 路径恢复原生速度,压缩数据则在后台"慢慢补写"。

快速开始:安装与两套用法

安装

pip install git+https://github.com/TheTom/mlx.git@feature/turboquant-plus pip install mlx-lm

多模态场景把mlx-lm换成mlx-vlm:

pip install git+https://github.com/TheTom/mlx.git@feature/turboquant-plus pip install mlx-vlm

前提是 Apple Silicon 环境(Python/Swift 原生推理)。

mlx-lm(文本)Quick Start

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)

注意这里的"两段式"写法:先max_tokens=1走 prefill(此时缓存仍是 FP16),调用compact_turbo_cache(cache)触发压缩,再继续生成——对应 delegated 架构中"首个 decode 步压缩"的时机。

mlx-vlm(多模态)Quick Start

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)

多模态路径用的是TurboKVCacheLite——它接收一个已有的KVCache实例做包装(TurboKVCacheLite(cache[idx], bits=4, key_bits=4)),同样遵循"prefill 存 FP16 → compact → 继续生成"的流程。

直接构造缓存

在质量测试脚本中还能看到更细粒度的用法:TurboKVCache(bits=4, key_bits=4)逐层构造,key_bits=0表示 K 保持 FP16(非对称模式),并支持min_compress_tokens参数控制压缩触发的最小 token 数(默认 256,见 scripts/mlx_quality_suite.py)。

对称 vs 非对称:密集模型上的关键决策

这是整个移植中最重要的一条工程结论,原文档给出了明确的警告:

对称 turbo 在密集模型上是灾难性的。所有 K 层都被压缩后,softmax 误差会在 28 层中累积放大。对密集架构而言,非对称(K=FP16, V=turbo4)是强制要求。混合模型(如 Qwen3.5,带 delta net 层)碰巧安全,因为只有一部分层使用 KV cache。

质量验证(Qwen2.5-7B 8bit 密集模型、全部 28 层 KV)给出了量级差异:

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

对称模式下 KLD 高达 6.86、Top-1 匹配率仅 10.5%、NIAH 全部失败——输出基本不可用;切换到非对称后 KLD 降到 0.003、Top-1 达 98.1%、NIAH 15/15 全过。这与仓库参考实现的分工逻辑完全对应:K 缓存走内积保持路径,一旦所有 K 都被 4-bit 量化,注意力 softmax 的分布误差在深网络中逐层放大;而把 K 留在 FP16、只压缩 V(MSE 主导的路径),误差就收敛在可忽略水平。

此外,mlx_quality_suite.py还实现了boundary layer 保护:默认把首、尾各 2 个 KV 层留在 FP16,避免边界层极端 V 范数引发 NaN(scripts/mlx_quality_suite.py)。

M5 Max 基准结果

以下数据均为移植作者在 M5 Max(128GB)上的实测,文档原文完整保留如下。

Qwen2.5-3B 4bit —— delegated KVCache(5 次运行平均,500 个 decode token,提交 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%

质量:输出文本与基线无法区分;KL 散度 < 0.001,余弦相似度 > 0.989。

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

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

Qwen3.5-27B Dense(8bit,16/64 KV 层)

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

密集模型(短上下文,deferred compression)

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

M5 Max 上下文缩放(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%

之前的数字(61–83%)是在 delegated KVCache 优化(提交 7ad7500)之前测得的。根因是mx.concatenate每个 decode 步 × n_layers 都会分配新数组;修复方式是把 FP16 存储委托给带预分配缓冲的内部 KVCache。

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 速度与 llama.cpp 持平;prefill 慢 37%(lazy graph 对比预编译路径)。

M2 Pro 结果与硬件差异

M2 Pro — Qwen2.5-1.5B 8bit(密集模型,28/28 KV 层,非对称):

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

M2 Pro 在长上下文下表现出更明显的 decode 回归——更低的内存带宽放大了 turbo 的额外开销。也就是说:压缩省下的显存/带宽收益,在带宽充足的高端芯片(M5 Max)上能接近完全兑现,但在带宽吃紧的芯片上会被解量化与后台重压缩的固定开销反超。这也提醒读者:同样配置在不同 Apple Silicon 上的速度结论不可直接外推。

MM-NIAH 多模态基准(gemma-4-26b-a4b-it · BF16 · 4-bit TQ+ Compact · M5 Max 128GB,520 样本)

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%,不存在系统性质量退化;KV 节省 10–64%(TQ+ 生效处);decode 速度比从 ~1K 时的 0.99x 降到 ~60K 时的 0.79x——原因是更长的 prefill 放大了"仅解量化一次"的固定开销。注意:约 1K 长度时 KV 节省只有 10%,因为短上下文中压缩/后台重压缩的边际收益尚未拉开。

用仓库脚本复现与验证

仓库中提供了可直接运行的 MLX 质量测试套件 scripts/mlx_quality_suite.py,它基于 mlx-lm 后端运行三类测试,且不修改任何 llama.cpp 测试脚本:

# 运行全部测试(kld + niah + context) python3 scripts/mlx_quality_suite.py --model mlx-community/Qwen3.5-2B-8bit # 只跑 NIAH / KLD / 上下文缩放 python3 scripts/mlx_quality_suite.py --model mlx-community/Qwen3.5-2B-8bit --test niah python3 scripts/mlx_quality_suite.py --model mlx-community/Qwen3.5-2B-8bit --test kld python3 scripts/mlx_quality_suite.py --model mlx-community/Qwen3.5-2B-8bit --test context # 指定位宽与非对称模式 python3 scripts/mlx_quality_suite.py --model mlx-community/Qwen3.5-2B-8bit --bits 4 --asymmetric

参数一览:--model(HF 模型 id 或本地路径,必填)、--test(niah/kld/context,缺省跑全部)、--bits(turbo 压缩位数,默认 4)、--asymmetric(K 保持 FP16、只压缩 V)、--verbose、--output-dir(默认 scripts/results/)。测试细节值得注意:

  • NIAH:在 1024/2048/4096 token 上下文、5 种深度(0/25/50/75/90%)下检索固定密码句BLUE TIGER 42,基线 15/15、turbo 也 15/15 才算通过;
  • KLD:用固定多主题文本逐 token 计算 baseline 与 turbo 的 KL 散度及 Top-1 匹配率,压缩阈值min_compress_tokens会调低到 32 以确保前向传播过程中真实触发压缩(scripts/mlx_quality_suite.py);
  • context:在 128–4096 token 上下文下测 decode 速度与峰值内存。

仓库 scripts/results/ 目录内已保存真实运行报告,例如mlx_quality_Qwen2.5-7B-Instruct-8bit_20260405_091833.md(turbo4_asymmetric 配置):NIAH 基线 15/15、turbo 15/15 全过,且各上下文下 prompt 速度基本持平。这些报告可直接作为复现结果的对照基准。

限制与注意事项

  1. 对称压缩仅适用于"碰巧安全"的架构:混合模型(Qwen3.5 的 delta net 层)只有部分层用 KV cache,因此对称 turbo 不会灾难性失败;密集模型必须使用--asymmetric(K=FP16, V=turbo4)。
  2. 长上下文 decode 有固定开销:TQ+ 的 decode 速度随上下文从 0.99x 降至 0.79x(dequant-once 开销被更长的 prefill 放大),KV 节省与速度之间需要按实际上下文权衡。
  3. 硬件带宽决定收益上限:M2 Pro 在 4096 token 上下文下 decode 降到基线的 46%,M5 Max 则在 16K 下仍保持 97–99%。
  4. prefill 较慢:MLX 惰性图对比 llama.cpp 预编译路径,prefill 慢约 37%。
  5. 基准记录建议:未来跑基准时应记录 Apple Silicon 电源模式(Low / Auto / High),因为它会实质影响吞吐量。
  6. 实验性状态:该移植目前是实验性的(fork 于 TheTom/mlx 的feature/turboquant-plus分支),核心压缩理论依据仍来自 turboquant_plus 仓库的 turboquant/ 参考实现与其对应论文(PolarQuant、QJL、TurboQuant 两阶段算法)。

综上,TurboQuant 的 MLX 移植验证了一个关键结论:在非对称(V-only)配置 + delegated KVCache 架构下,KV 缓存压缩可以在 M5 Max 上以 ≈0 的 PPL 代价(+0.51%)、≈99% 的 decode 速度保持,换来多模态场景最高 64%(平均 50%)的 KV 显存节省;而对称配置在密集模型上是不可用的。对于想在自己的 Apple Silicon 上尝试长上下文推理的开发者,推荐路径是:安装 fork 包 → 按 mlx-lm/mlx-vlm Quick Start 接入 → 始终使用非对称配置 → 用 scripts/mlx_quality_suite.py 在目标模型与目标硬件上复测质量与速度。

【免费下载链接】turboquant_plus

项目地址:https://gitcode.com/gh_mirrors/tu/turboquant_plus
点击查看免费下载
上一篇:猫抓:重新定义浏览器资源捕获的智能解决方案
下一篇:3步快速上手:B站会员购自动化抢票工具完全指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询