Rust in Everything
基于 candle 0.11.0

量化

f32 和半精度都对上之后(搬一个 LLM),推理主线的最后一步是把权重变小。transformers 的量化入口大多依赖 CUDA 或额外的包,在 Mac 上能直接跑的只有 PyTorch 自带、已经弃用的动态量化;candle 的路线是 llama.cpp 的 GGUF:权重保持量化块,CPU 和 Metal 上都由量化内核直接计算。这一部分用 SmolLM2-135M-Instruct 量两边离 f32 有多远:不只比一个位置的 logits,还沿 270 个 token 逐步比 argmax,看生成从哪里分叉。

  1. GGUF:quantized_llama 读下载的 Q8_0 和 Q4_K_M;logits 差 0.42 到 0.72(Q8_0)和 2.4 到 2.5(Q4_K_M),teacher forcing 270 步里 argmax 相同 262 到 263 步和 230 到 238 步,自由生成在第 26 和第 5 个 token 分叉;CPU 多一层激活量化,所以和 Metal 也会分叉。
  2. 自己量化:从 bf16 safetensors 改名、重排 q 和 k、QTensor::quantize、gguf_file::write;F32 GGUF 对 transformers 差 4.4e-5,自己的 Q8_0 和下载的文件逐字节相同;K-quants 量化不了 576 宽的矩阵;torch.ao 的动态量化差 6.89,而且前面位置的 logits 会随后面的 token 变化。
flowchart TB
  st["bf16 safetensors"] --> own["自己量化"]
  dl["下载的 GGUF"] --> run["quantized_llama"]
  own --> run
  st --> hf["transformers f32"]
  st --> ao["torch quantize_dynamic"]
  hf --> ref["参照 logits"]
  run --> cmp["逐步对比"]
  ao --> cmp
  ref --> cmp

GGUF 的文件格式、块结构和量化矩阵乘的实现,见源码卷的 量化;quantized_llama 的一次前向见量化 llama 的一次前向。

💬 关联讨论

发起讨论
加载中...