> [!NOTE] > 本文档由 WeHub 基于上游 README 翻译整理,属于社区翻译,非官方中文文档。 > [English](./README.en.md) · [原始项目](https://github.com/Mininglamp-AI/cider) · [上游 README](https://github.com/Mininglamp-AI/cider/blob/HEAD/README.md) > 原作者、版权与许可证归属以原始项目及本仓库 LICENSE 文件为准。 # cider Cider 基于 MLX 为 macOS 开发。它提供: 1. MLX 所不具备的**在线激活量化**(W8A8/W4A8),配有自定义 INT8 TensorOps 内核,作为 MLX 自定义原语(custom primitives)构建,支持完整的惰性求值(lazy evaluation)。 2. 受 [FlashInfer](https://github.com/flashinfer-ai/flashinfer), 启发的**优化 SDPA 解码内核**,采用连续块 GQA 调度与寄存器分块(register tiling),以实现更快的自回归解码。 它还包含服务端扩展以及对 mlx_vlm 的非侵入式兼容性补丁(已在 mlx_vlm 0.4.3 上验证),包括修复与 RoPE 位置处理及分块预填充(chunked prefill)相关的 Qwen3-VL 多图推理问题。 ## 条件编译(M4 / M5) Cider 采用**条件编译**:INT8 TensorOps C++ 扩展仅在 Apple M5+ 上构建。 | Chip | `pip install -e .` behavior | `import cider` behavior | |------|----------------------------|------------------------| | **M5+** | 完整构建(CMake + Metal kernels) | 全部功能可用 | | **M4 及以下** | 跳过 C++ 构建,安装纯 Python 包 | `is_available()` → False,`convert_model()` 为警告空操作(warning no-op) | **通过环境变量覆盖:** ```bash CIDER_FORCE_BUILD=1 pip install -e . # Force build (e.g., CI) CIDER_FORCE_BUILD=0 pip install -e . # Force skip ``` ## 模式 | Mode | Weights | Activations | Compute Path | Status | |------|---------|-------------|--------------|--------| | **W8A8** | INT8 symmetric | INT8 per-token | TensorOps matmul2d | ✅ 已实现 | | **W4A8** | INT4 packed (uint8) | INT8 per-token | Unpack→TensorOps | ✅ 已实现 | | W4A16 | — | — | MLX built-in | 基线 | | W8A16 | — | — | MLX built-in | 基线 | **W4A16 和 W8A16 已由 MLX 原生支持** — 本 SDK 提供 MLX 未实现的 W8A8 与 W4A8 模式。 ## 优化 SDPA(解码注意力) Cider 包含面向自回归解码(`Q_seq=1`)的优化**缩放点积注意力**(Scaled Dot-Product Attention)内核,受 [FlashInfer](https://github.com/flashinfer-ai/flashinfer). 启发。关键技术: - **连续块 GQA 调度(Contiguous Chunk GQA Scheduling)**:不再让每个 Q 头独立派发(读取 KV `H_q` 次),而是由单个线程组处理共享同一 KV 组的所有 Q 头。这可减少最多 `gqa_factor`× 的 DRAM 带宽。 - **寄存器分块(Register Tiling,TILE=4)**:每个线程在寄存器中保存 4 个 Q 头的部分结果 — 无需共享内存,无需屏障同步 — 最大化分组查询解码的 ALU 吞吐。 - **两遍 FlashDecoding(2-Pass FlashDecoding)**:对于长序列(≥1024 tokens),两遍归约将 KV 序列划分到各线程组,以提升占用率。 - **AutoTune**:针对(架构、GQA 比率、头数、序列长度)自动扫描块大小,并缓存至 `~/.cider_sdpa_tune.json`。 ### 用法 ```python import cider # One-line patch — all downstream frameworks (mlx_lm, mlx_vlm) benefit automatically cider.patch_sdpa() # Optional: run AutoTune once to find optimal block sizes for your chip cider.autotune_sdpa() # Undo patch if needed cider.unpatch_sdpa() ``` 该补丁**完全透明**:预填充(`Q_seq > 1`)与带掩码注意力会自动回退到 MLX 原生实现。仅解码步骤(`Q_seq=1`,无掩码)会走 Cider 的优化内核。**无需修改** mlx_lm 或 mlx_vlm 代码。 ### SDPA 微基准(Apple M5 Pro,D=128) 相对 MLX `mx.fast.scaled_dot_product_attention` 的加速比: | Config | N=1K | N=2K | N=4K | N=8K | N=16K | N=32K | |--------|------|------|------|------|-------|-------| | MHA (H_q=32, H_kv=32) | 1.03x | 1.01x | 1.02x | 1.00x | 1.00x | 0.99x | | GQA4 (H_q=32, H_kv=8) | 1.04x | 1.06x | 1.06x | 1.12x | 1.16x | 1.21x | | GQA8 (H_q=32, H_kv=4) | 1.04x | 1.07x | 1.13x | 1.21x | 1.34x | **1.57x** | 加速范围:**0.99×–1.57×**。正确性:18/18 PASS(相对 MLX 最大差异 ≤ 0.000122,fp16)。14 WIN / 4 TIE / 0 LOSS — 零回退。 ### 启用 SDPA 的端到端解码速度(Qwen3-VL-4B,Apple M5 Pro) 使用 `benchmarks/bench_e2e_wxa16.py` 在真实多图 VLM 轨迹上测试(1–3 张截图,1334–3455 prompt tokens): | Config | Decode tok/s | vs Baseline | |--------|:-----------:|:-----------:| | FP16 | 32.0 | — | | FP16 + Cider SDPA | 32.6 | +1.9% | | W8A16 | 54.0 | — | | W8A16 + Cider SDPA | **58.0** | **+7.4%** | | W8A8 per-group | 51.0 | — | | W8A8 per-group + Cider SDPA | 52.3 | +2.5% | | W8A8 per-channel | 49.1 | — | | W8A8 per-channel + Cider SDPA | **52.6** | **+7.1%** | Cider SDPA 可叠加在 W8A8 预填充收益之上。收益随 GQA 比率与 KV cache 长度而放大 — GQA 比率更高(例如 GQA8 的 Qwen3-32B)且上下文更长的模型获益更大。 MLX 的量化是**仅权重量化(weight-only)**:QuantizedLinear 将权重反量化为 FP16 并使用 FP16 GEMM。虽然 MLX 的 Steel NAX 模板足够通用,可实例化为 INT8 类型(且能达到相同的原始 matmul 吞吐 — [参见我们的透明基准测试](benchmarks/mlx_native/cider_vs_mlx_int8.md)),但 MLX 并未提供实际 W8A8 推理所需的量化/反量化流水线。Cider 通过融合的 quantize-matmul-dequant 原语填补这一缺口。 本 SDK 为受支持的推理路径实现在线 INT8 激活量化,以及基于 INT8 TensorOps 的计算。 ### W8A8 量化粒度 | Granularity | Description | Speed | Precision | |-------------|-------------|-------|-----------| | **Per-channel** | 每个输出通道一个 scale | 最快(预填充 1.8x) | 略低 | | **Per-group (gs=128)** | 每 128 个元素一个 scale | 较快(预填充 1.5x) | 精度保留适中 | | **Per-group (gs=64)** | 每 64 个元素一个 scale | 中等(预填充 1.3x) | 精度更高 | ## 性能(Apple M5 Pro) ### 单算子延迟 Shape [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| Shape [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 | ### 端到端 VLM **Qwen3-VL-2B** | 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** | **Qwen3-VL-4B** 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 量化:精度与速度对比
Models Quantization Configuration wikitext2 PPL(↓) Prefill Time (s)(↓) Peak Memory (GB)(↓)
Qwen3-8B 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 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
## 说明 需要注意的是,cider 假定权重是 **量化友好(quantization-friendly)** 的。这意味着你需要确保模型已经经过某种训练后量化校准方法,例如 GTPQ、SmoothQuant 或 Quarot 等,以处理异常值(outliers),或者模型本身是通过 QAT(量化感知训练)训练的。当 cider 转换模型(例如使用 w8a8)并计算量化 scale 时,默认采用最简单的 **min-max** 方法,同时也支持第 99 百分位方法(clip_percentile=99.9)。如果遇到乱码输出,很大概率是模型受到了 **异常值(outliers)** 的影响。因此,某些预处理步骤是 **必要的**。 ## 环境要求 - **Apple M5+** 用于 INT8 TensorOps(M4 及以下:以纯 Python 方式安装,`is_available()` 返回 False) - Python 3.12+ - MLX >= 0.31 - nanobind >= 2.12(仅在 M5+ 上进行 C++ 编译时需要) - CMake >= 3.27(仅在 M5+ 上进行 C++ 编译时需要) ## 安装 ```bash pip install -e . ``` 在 M5+ 上,这会运行 CMake 编译 C++ 扩展,然后安装 Python 包。 在 M4 及以下设备上,仅安装 Python 包(不会出现编译错误)。 ## 快速开始 ### 一行命令完成模型转换(推荐) ```python from cider import convert_model, is_available model, proc = load("path/to/model") if is_available(): convert_model(model) # CiderLinear auto-detects: # seq_len > 1 → W8A8 INT8 TensorOps (faster prefill) # seq_len == 1 → INT8 MV kernel (near-native decode speed) else: pass # Falls back to standard MLX inference on M4 ``` **重要提示** 在量化视觉语言模型(Vision-Language Models,VLMs)时,视觉 Transformer(ViT)通常不会被替换。直接使用 convert_model 也会量化视觉模型的线性层,这通常会导致精度下降。对于 VLMs,我们建议调用 convert_model(model.language_model),仅对语言模型应用 GPTQ、SmoothQuant 和 AWQ 等现有量化方法。 已在选定的 MLX Transformer 模型上测试,包括 Qwen3、Qwen3-VL 和 Llama3 系列。其他架构可能需要适配。 ### 层级别 API ```python import numpy as np import mlx.core as mx from cider import W8A8Linear, W4A8Linear, is_available assert is_available(), "Requires Apple M5+" # Prepare weight W = np.random.randn(4096, 4096).astype(np.float16) # W8A8 linear (per-channel) from cider.ops import quantize_weight_int8 w_int8, scale = quantize_weight_int8(W) layer = W8A8Linear( w_int8=mx.array(w_int8), scale_w=mx.array(scale), group_size=0, in_features=4096, out_features=4096 ) x = mx.random.normal((32, 4096)).astype(mx.float16) y = layer(x) # lazy — builds MLX graph mx.eval(y) # GPU executes # W4A8 linear (half the weight memory) layer4 = W4A8Linear.from_weights(W) y4 = layer4(x) mx.eval(y4) ``` ## 底层 API ```python from cider import perchannel_linear, w4a8_linear, quantize_weight_int8, pack_weight_int4 # Quantize weights (numpy, offline) w_int8, scale = quantize_weight_int8(W_np) packed_w4, scale4 = pack_weight_int4(W_np) # Primitive calls (return lazy mx.array) y = perchannel_linear(x, mx.array(w_int8), mx.array(scale)) y4 = w4a8_linear(x, mx.array(packed_w4), mx.array(scale4)) ``` ## 项目结构 ``` cider/ ├── cider/ # Python package │ ├── __init__.py # Public API (conditional on is_available) │ ├── ops.py # Primitive wrappers + quantize helpers │ ├── nn.py # CiderLinear, W4A8Linear (nn.Module) │ ├── convert.py # convert_model() high-level API │ ├── attention/ # Optimized SDPA │ │ ├── __init__.py │ │ └── sdpa.py # patch_sdpa / unpatch_sdpa / autotune_sdpa │ └── kernels/ # Metal shaders (bundled) │ ├── w8a8_matmul.metal # W8A8 GEMM (prefill, M>1) │ ├── w8a8_int8_mv.metal # W8A8 per-channel MV (decode, M=1) │ ├── w8a8_quantize.metal # Per-token activation quantization │ ├── w4a8_matmul.metal # W4A8 GEMM (prefill) │ ├── pergroup_int8_gemm.metal # Per-group GEMM (prefill) │ ├── pergroup_int8_mv.metal # Per-group MV (decode) │ ├── cider_sdpa_vector.h # SDPA kernel header (1-pass & 2-pass) │ └── cider_sdpa_vector.metal # SDPA kernel template instantiations ├── csrc/ # C++ MLX primitives (nanobind, M5+ only) │ ├── include/ │ │ ├── w8a8_primitive.h │ │ ├── w4a8_primitive.h │ │ ├── pergroup_primitive.h │ │ └── sdpa_primitive.h │ └── src/ │ ├── w8a8_primitive.mm │ ├── w4a8_primitive.mm │ ├── pergroup_primitive.mm │ ├── sdpa_primitive.mm # SDPA 1-pass & 2-pass primitives │ └── prim_bindings.cpp ├── benchmarks/ │ ├── bench_e2e_wxa16.py # End-to-end VLM benchmark (W8A8 + SDPA) │ ├── bench_sdpa.py # SDPA micro-benchmark (correctness + speed vs MLX) │ ├── bench_full.py # Isolated kernel latency (per-channel/per-group vs MLX) │ ├── test_bitexact.py # Numerical correctness verification │ └── mlx_native/ # MLX native INT8 comparison ├── tutorial/ │ ├── how_to_write_efficient_int_gemm_m5_en.md │ └── how_to_write_efficient_int_gemm_m5_zh.md ├── tools/ │ ├── eval_ppl_all.py # Unified PPL eval (FP16/W8A16/per-channel/per-group) │ ├── convert_compressed_tensors_to_mlx.py │ └── smoothquant.py # SmoothQuant calibration ├── examples/ │ └── basic_usage.py ├── vlm_service/ # OpenAI-style VLM inference server │ ├── server.py # FastAPI server (streaming + non-streaming) │ ├── core_infer.py # HMInference engine (singleton) │ ├── custom_qwen3vl.py # Custom Qwen3-VL generation loop │ ├── config.py # Config loader │ ├── bench_client.py # Server benchmark client │ └── client.py # API client example ├── config/ │ └── config.yaml # Server & model configuration ├── experimental/ # ANE+GPU hybrid tensor parallelism (M4) │ ├── split_linear.py # SplitLinear + ANEBridge + patch_model() │ ├── bench.py # End-to-end benchmark │ ├── libane_bridge_v6.m # ANE private API bridge (Obj-C source) │ └── README.md ├── CMakeLists.txt ├── pyproject.toml ├── setup.py # Conditional build (M5+: full, M4: pure-Python) └── README.md ``` ## VLM 推理服务 `vlm_service/` 提供了一个开箱即用的 **OpenAI 风格** VLM 推理服务器,支持 W8A8 加速。 ### 快速开始 1. **配置** `config/config.yaml`: ```yaml model_name_or_path: /path/to/your/model # MLX VLM model (e.g., Qwen3-VL-2B W8A16) sampling: max_new_tokens: 1024 temperature: 1.0 top_p: 1.0 server: host: 0.0.0.0 port: 8341 ttl: 1800 w8a8: mode: 'off' # 'auto' | 'on' | 'off' ``` - `auto`:若硬件支持则启用 W8A8,否则回退到默认设置 - `on`:强制启用 W8A8(不支持时报错)。"选择 'on' 时,表示你的模型需要执行在线激活量化(online activation quantization)。在这种情况下,Cider 本身**不**保证量化精度,你需要自行应用某些量化算法,例如 SmoothQuant、QuaRot、GPTQ 或 QAT,以确保激活量化后精度不会明显下降。此选项只是让你在模型采用 W8A8 时能够利用硬件的计算优势,而不仅仅是模拟量化。" - `off`:禁用 W8A8,使用标准 MLX 推理 2. **启动服务器**: ```bash cd vlm_service python server.py --config ../config/config.yaml ``` 3. **发送请求**(OpenAI 风格 API): ```bash # Text-only curl http://localhost:8341/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "vlm", "messages": [{"role": "user", "content": "Hello"}], "stream": false }' # With image (base64) curl http://localhost:8341/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "vlm", "messages": [{"role": "user", "content": [ {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."} }, {"type": "text", "text": "What is in this image?"} ]}], "stream": true }' ``` ### API 端点 | Endpoint | Method | Description | |----------|--------|-------------| | `/v1/chat/completions` | POST | 对话补全(流式 / 非流式) | | `/v1/models` | GET | 列出可用模型 | | `/health` | GET | 健康检查 | | `/v1/queue` | GET | 请求队列状态 | ### 服务中 W8A8 的工作方式 当 `w8a8.mode` 为 `auto` 或 `on` 时,服务器在启动时会调用 `cider.convert_model()`,将所有 Linear 层替换为 `CiderLinear`。推理过程中: - **Prefill**(处理输入 token,seq_len > 1):使用 W8A8 INT8 GEMM 以加速计算 - **Decode**(逐 token 生成,seq_len == 1):使用 INT8 MV kernel(接近原生速度) 无需修改代码——切换会根据输入序列长度自动进行。 ## 架构 ### MLX 自定义原语 W8A8Linear 与 W4A8Linear 均实现为 `mlx::core::Primitive` 的子类。这意味着: 1. **惰性求值(lazy evaluation)**:`y = layer(x)` 构建图节点,而非立即计算 2. **图组合**:多个原语调用组合为单个 MLX 图 3. **流调度**:MLX 的调度器处理 GPU 派发顺序 ### Metal Kernel 流水线 每个原语都会派发 Metal 计算 kernel: **Prefill(M > 1):** 1. **quantize_per_token**:FP16 激活 → INT8 + 逐 token 缩放 2. **matmul_fused_dequant**:INT8 × INT8 → INT32 → FP16(融合缩放反量化) **Decode(M = 1):** - **int8_mv**:直接 INT8 矩阵-向量乘积,权重反量化即时进行(无需激活量化) 对于 W4A8,GEMM 步骤在 fragment load 中包含内联 INT4→INT8 解包。 ### TensorOps matmul2d INT8 GEMM 使用 Apple 的 `mpp::tensor_ops::matmul2d(16, 32, 16)`——通过 Metal 4 的 `cooperative_tensor` API,在 M5+ 上可用的硬件加速 INT8×INT8→INT32 矩阵乘法。这与 MLX 的 NAX 模板可用的硬件指令相同。Cider 的 kernel 在存储阶段加入融合反量化(INT32 × scales → FP16),避免额外的设备内存往返。详见 [kernel comparison](benchmarks/mlx_native/cider_vs_mlx_int8.md)。 ### Tile 配置 | Config | BM | BN | BK | SK | Threads | Use When | |--------|----|----|----|----|---------|----------| | Large | 128 | 128 | 512 | 32 | 512 | M > 64 | | Small | 32 | 128 | 512 | 32 | 128 | M ≤ 64 | 根据 M 自动选择。包含 L2 cache swizzle 派发。 ## ANE+GPU 异构张量并行(实验性) 我们发现,在 Mac 上进行推理时,仅利用了 GPU 与 CPU 两个硬件计算单元,而 Mac 上的 ANE(Apple Neural Engine)计算单元处于空闲状态。我们将此视为潜在的优化机会。受 [maderix/ANE](https://github.com/maderix/ANE), 启发,我们开展了 ANE+GPU 混合推理模式的实验工作。目前我们将该方案应用于张量并行计算。在 M4 芯片上,在仅同步的前向推理期间(MLX 原生使用称为惰性求值的技术以降低同步开销;在端到端测试中,混合推理目前尚未显示出优势,主要因为我们尚未基于 MLX 的惰性求值实现该方案——这仍是后续工作),与纯 GPU 推理在同步流水线(synchronize pipeline)下相比,我们观察到约 **3%~16%** 的性能提升。这仍属探索性工作,端到端收益目前受限于缺少与惰性求值兼容的实现。 在 LLM prefill 阶段,GPU 的矩阵单元已满载——但 **Apple Neural Engine 完全空闲**。ANE Split 利用这一点,沿输出通道拆分每个线性层的 GEMM: - **ANE** 计算约 65% 的输出通道(FP32,通过逆向工程的私有 `_ANEClient` API) - **GPU** 计算剩余约 35%(FP16,标准 MLX matmul) - 两者**并发**运行,结果拼接 这是一种**异构张量并行**——不是数据并行,也不是流水线并行——利用同一 SoC 上的两个不同计算单元。 ### 性能(Apple M4,Qwen3-VL-2B Prefill) | seq | W8A16 GPU | SplitLinear | Speedup vs W8A16 | |-----|----------|-----------|-------------| | 512 | 639.9 ms | **615.9 ms** | **1.039×** | | 1024 | 1348.6 ms | **1156.9 ms** | **1.17×** | 在测试的基准案例中,余弦相似度接近 1.0,top-1 token 一致率为 100%。 ### 关键设计选择 - **仅 Prefill**:Decode 回退到原始 GPU linear(零开销) - **共享输入准备**:Q/K/V 与 Gate/Up 投影通过 `_InputGroup` 共享单次输入转置+numpy 拷贝 - **自动路由**:Down 投影(IC > 2×OC)在 ANE 效率低下时保持仅 GPU - **短序列绕过**:序列 < 192 token 时跳过分拆(开销大于收益) 完整文档、用法、构建说明与限制见 [`experimental/README.md`](experimental/README.md)。 > **注意:** ANE Split 已在 M4 上测试。M5 引入了 ANE 架构变更,可能破坏私有 API 桥接——尚未在 M5 上验证。 ## 量化 | Component | Scheme | Granularity | |-----------|--------|-------------| | W8A8 weights | Symmetric INT8 | Per-channel or per-group (gs=64/128) | | W4A8 weights | Symmetric INT4 (zp=8) | Per-column | | Activations | Symmetric INT8 | Per-token | | Accumulation | INT32 | — | | Output dequant | `C_fp16 = C_int32 * s_act * s_weight` | Per-element | ## 限制 - **M=1 单算子**:逐通道 MV kernel 在独立 decode 调用时慢于 MLX W4A16。逐组 MV kernel 在端到端基准中与 MLX W8A16 decode 速度相差在 5% 以内。 - **INT8 TensorOps 仅支持 Apple M5+**:M4 及以下可安装但 `is_available()` 返回 False。 - **W4A8 慢于 W8A8**:INT4→INT8 解包 ALU 开销(Metal 4 matmul2d 无原生 INT4 操作数)。 ## 工具 ### 统一 PPL 评估 ```bash # Run all 5 configurations in one script python tools/eval_ppl_all.py --num-samples 50 # Evaluates: FP16, W8A16 (MLX native), W8A8 per-channel, per-group(gs=64), per-group(gs=128) # Outputs comparison table at the end ``` ## 路线图 - [x] 一行式模型转换 API(`convert_model`,自动 prefill/decode) - [x] 自动 dtype 处理(float16 / bfloat16) - [x] 逐通道与逐组 W8A8 量化 - [x] 专用 decode MV kernel(匹配原生 MLX 速度) - [x] 条件编译(M4 优雅回退) - [x] mlx_vlm 与 mlx_lm 集成示例 - [x] 优化的 SDPA decode kernel(受 FlashInfer 启发,支持 GQA) - [x] 一行式 `patch_sdpa()`,透明加速 decode - [x] SDPA AutoTune(按架构块大小扫描) - [ ] ANE 原语惰性求值 - [ ] 集成剪枝功能 - [ ] KVCache 量化 ## 作者 Multimodal Team, Mininglamp Technology 如有 bug 报告、功能请求或使用问题,请在本仓库提交 issue。 ## 引用 若您认为本工作有价值,请引用: ```bibtex @software{wang2026cider, author = {Multimodal Team, Mininglamp Technology}, title = {Cider: Exploiting Unused INT8 TensorOps for Faster LLM Prefill on Apple Silicon}, year = {2026}, howpublished = {https://github.com/Mininglamp-AI/cider} } ``` ## 许可证 MIT ## 致谢 - [MLX](https://github.com/ml-explore/mlx) by Apple — primitive API, NAXFrag kernel architecture - [FlashInfer](https://github.com/flashinfer-ai/flashinfer) — inspired contiguous chunk GQA scheduling and register tiling for SDPA decode kernel - Metal 4 MetalPerformancePrimitives for INT8 TensorOps - [maderix/ANE](https://github.com/maderix/ANE) — inspired and informed our ANE+GPU tensor-parallel implementation