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