Cider:为什么 Apple Silicon 推理需要原生 INT8 激活量化
当 MLX 只做了一半量化,我们把另一半补上了。
背景:端侧 AI 的推理瓶颈
Apple Silicon 是目前最适合跑本地大模型的消费级硬件之一。统一内存架构让 72B 模型不再需要多卡互联,MLX 框架则提供了 PyTorch 级别的开发体验。
明略科技的 Mano-P 就是这条路线的产物——一个完全运行在本地 Mac 上的 AI Agent,72B 版本在 OSWorld 基准上拿到 58.2% 的成绩(当前第一),4B 轻量版在 M5 Pro 上也能跑到约 80 tok/s 的解码速度。数据不出本机,Apache 2.0 开源。
但即便硬件和模型都到位了,推理速度仍然是体验的硬约束。尤其是 prefill 阶段——处理长上下文、多轮对话历史、视觉输入时,这个阶段的延迟直接决定了用户感知的"反应速度"。
问题出在哪? MLX 目前的量化方案只做了"一半"。
MLX 量化的盲区
MLX 提供的量化选项是 W8A16 和 W4A16——权重被压缩了,但激活值(activations)始终保持 FP16。
这意味着:
- 计算依然是 FP16 GEMM:权重在计算前被 dequantize 回 FP16,实际矩阵乘法还是 16-bit 浮点
- 无法利用 INT8 TensorOps:Apple M5 芯片引入了专用的 INT8 矩阵运算单元(
mpp::tensor_ops::matmul2d(16,32,16)),但 MLX 的量化路径完全没有触及这些硬件能力 - 带宽节省有限:权重量化减少了内存占用,但 prefill 阶段是 compute-bound,真正需要的是计算加速
简单说:MLX 量化省了存储,没省计算。M5 的 INT8 硬件能力被浪费了。
Cider:补上另一半
Cider 是明略科技开源的推理加速 SDK,核心做的事情就一个:把激活值也量化到 INT8,让整个 GEMM 在 INT8 精度下完成。
两种模式:
| 模式 | 权重 | 激活 | 对比 MLX |
|---|---|---|---|
| W8A8 | INT8 对称 | INT8 per-token | MLX 只有 W8A16 |
| W4A8 | INT4 packed | INT8 per-token | MLX 只有 W4A16 |
技术实现上:
- 通过 Metal 4 的
cooperative_tensorAPI 调用 M5 原生 INT8 TensorOps - 以 MLX custom primitives 方式构建,完全兼容 MLX 的 lazy evaluation 机制
- prefill 阶段使用 INT8 GEMM,decode 阶段自动切换为 INT8 MV kernel
硬件要求:Apple M5 及以上(M4 会 fallback 到纯 Python 实现)。
性能数据
单算子延迟(M5 Pro)
矩阵形状 [N=10240, K=2560]:
| M | PC(ms) | PG(ms) | w8a16 | w4a16 | PC/w8 | PC/w4 | PG/w8 | PG/w4 |
|---|---|---|---|---|---|---|---|---|
| 1 | 0.27ms | 0.26ms | 0.26ms | 0.18ms | 0.96x | 0.67x | 0.99x | 0.69x |
| 128 | 0.34ms | 0.39ms | 0.49ms | 0.44ms | 1.43x | 1.28x | 1.26x | 1.13x |
| 1024 | 1.23ms | 1.52ms | 2.24ms | 2.04ms | 1.82x | 1.66x | 1.47x | 1.34x |
| 4096 | 4.41ms | 5.65ms | 8.12ms | 7.72ms | 1.84x | 1.75x | 1.44x | 1.37x |
| 8192 | 8.71ms | 11.40ms | 16.23ms | 15.09ms | 1.86x | 1.73x | 1.42x | 1.32x |
(PC = per-channel,PG = per-group)
矩阵形状 [N=2560, K=10240]:
| M | PC(ms) | PG(ms) | w8a16 | w4a16 | PC/w8 | PC/w4 | PG/w8 | PG/w4 |
|---|---|---|---|---|---|---|---|---|
| 1 | 0.25ms | 0.26ms | 0.26ms | 0.20ms | 1.03x | 0.78x | 0.98x | 0.75x |
| 128 | 0.39ms | 0.41ms | 0.55ms | 0.46ms | 1.43x | 1.19x | 1.35x | 1.12x |
| 1024 | 1.31ms | 1.65ms | 2.35ms | 2.14ms | 1.80x | 1.64x | 1.43x | 1.30x |
| 4096 | 5.37ms | 6.79ms | 8.54ms | 8.04ms | 1.59x | 1.50x | 1.26x | 1.18x |
| 8192 | 10.97ms | 12.94ms | 17.28ms | 16.23ms | 1.58x | 1.48x | 1.34x | 1.25x |
随着 M 增大,加速比稳步提升——更大的矩阵更能发挥 INT8 TensorOps 的吞吐优势。注意 M=1 时 per-channel MV kernel 略慢于 MLX W4A16(已知限制),但 M≥128 后优势即显现。
端到端 VLM 推理:Qwen3-VL-2B(M5 Pro)
| Prompt Tokens | FP16 Prefill (tok/s) | W8A16 Prefill (tok/s) | W8A8 PC Prefill (tok/s) | FP16 Decode (tok/s) | W8A16 Decode (tok/s) | W8A8 PC Decode (tok/s) |
|---|---|---|---|---|---|---|
| 1334 | 3010 | 2065 | 3242 | 70 | 107 | 104 |
| 2393 | 2868 | 1847 | 2983 | 69 | 97 | 100 |
| 3455 | 2777 | 1741 | 2796 | 66 | 90 | 95 |
端到端 VLM 推理:Qwen3-VL-4B(M5 Pro)
| Prompt Tokens | FP16 Prefill (tok/s) | W8A16 Prefill (tok/s) | W8A8 PC Prefill (tok/s) | FP16 Decode (tok/s) | W8A16 Decode (tok/s) | W8A8 PC Decode (tok/s) |
|---|---|---|---|---|---|---|
| 1334 | 1884 | 1786 | 2186 | 32 | 56 | 54 |
| 2393 | 1815 | 1700 | 2028 | 31 | 55 | 52 |
| 3455 | 1755 | 1603 | 1881 | 30 | 52 | 49 |
LLM 量化精度 vs 速度
Qwen3-8B:
| 量化方案 | wikitext2 PPL(↓) | Prefill Time(s)(↓) | Peak Memory(GB)(↓) |
|---|---|---|---|
| FP16 | 9.726 | 179.9 | 18.93 |
| W8A16 (mlx RTN) | 9.707 | 221.3 | 12.07 |
| W8A8 (per-channel) | 9.756 | 123.5 | 11.32 |
| W8A8 (per-group gs=64) | 9.744 | 179.1 | 11.83 |
| W8A8 (per-group gs=128) | 9.727 | 165.8 | 11.61 |
Llama3-8B:
| 量化方案 | wikitext2 PPL(↓) | Prefill Time(s)(↓) | Peak Memory(GB)(↓) |
|---|---|---|---|
| FP16 | 6.138 | 175.8 | 18.32 |
| W8A16 (mlx RTN) | 6.147 | 236.9 | 11.46 |
| W8A8 (per-channel) | 6.271 | 123.3 | 10.69 |
| W8A8 (per-group gs=64) | 6.269 | 178.7 | 11.19 |
| W8A8 (per-group gs=128) | 6.270 | 155.7 | 10.98 |
W8A8 per-channel 在两个模型上都实现了最快的 prefill 速度和最低的内存占用。PPL 增加极小(Qwen3-8B +0.049,Llama3-8B +0.124),精度损失可忽略。
W8A8 量化粒度选择指南
| 粒度 | 大致加速比 | 精度特点 |
|---|---|---|
| Per-channel | ~1.8x | 最快,精度损失极小 |
| Per-group gs=128 | ~1.5x | 平衡选择 |
| Per-group gs=64 | ~1.3x | 最接近原始精度 |
开发者可根据场景在精度和速度之间权衡。
使用方式
Cider 的设计哲学是"最小侵入"——一行代码完成转换:
from cider import convert_model, is_available
model, proc = load("path/to/model")
if is_available():
convert_model(model)
convert_model 会替换模型中所有 Linear 层为 Cider 的 INT8 实现。prefill 时走 INT8 GEMM,decode 时走 INT8 MV kernel,自动切换,无需手动干预。
Cider 同时提供了 OpenAI 兼容的 VLM 推理服务端,方便集成到现有工作流。
适用范围
需要强调的是:Cider 不是 Mano-P 的专属组件,而是通用的 MLX 模型加速基础设施。
目前已验证支持的模型家族:
- Qwen3 / Qwen3-VL
- Llama3
- 理论上所有基于
nn.Linear的 MLX 模型都可以通过convert_model一键转换
条件编译:M5+ 获得完整 INT8 TensorOps 加速,M4 自动 fallback 到纯 Python 实现。
已知限制:M=1 时 per-channel MV kernel 比 MLX W4A16 慢;完整 INT8 加速仅限 M5+。
此外,Cider 还包含实验性的 ANE+GPU 异构张量并行方案(针对 M4 设备,同步流水线下可获得 3-16% 额外加速)。
总结
MLX 生态在权重量化上做得很好,但激活量化是缺失的一环。M5 的 INT8 TensorOps 是明确的硬件信号——Apple 在芯片层面已经为 INT8 推理铺好了路,软件层面需要跟上。
Cider 做的就是这件事:让 Apple Silicon 的 INT8 硬件能力不再被浪费。
GitHub: github.com/Mininglamp-…
如果你也在 Mac 上跑本地模型,欢迎体验并⭐️