Rust in Everything
基于 candle-core 0.11.0

量化

推理主线上的权重不是 Tensor,是 QTensor:从 GGUF 文件里原样读进来的量化块,乘法时由专门的内核直接在块上算。这一部分讲 candle-core/src/quantized/(约 1.1 万行,比整个 Metal 后端还大),也就是推理主线的前两步:读文件、建权重,以及每个投影层的那一次量化矩阵乘。格式和算法都照搬 llama.cpp,所以 candle 能直接读 llama.cpp 生态里的 GGUF 文件。

  1. GGUF 文件:魔数、版本、13 种元数据值、张量目录和对齐;SmolLM2 两个 GGUF 里有什么(Q4_K_M 文件大半其实是 Q5_0);按偏移读张量;0.11.0 加的分配上限挡住了哪些坏文件,又为什么让 BufReader 下的解析慢了 5 倍;还会 panic 的两种输入。
  2. 量化格式:GgmlDType 的 15 种类型,BlockQ8_0、BlockQ4K、BlockQ6K、BlockQ8K 的字段和每块字节数;量化和反量化;点积的另一边先量化成 VecDotType;NEON 和 sdot;实测往返误差、和 GGUF 文件逐字节对照、点积速度。
  3. QTensor 与 QMatMul:QStorage 在 CPU 和 Metal 上怎么存;QMatMul 三种形态由 dtype 和两个环境变量决定;CPU 先量化激活再逐行点积,Metal 一行用 mv、多行用 mm 内核;实测三种形态的速度,以及 Q8_0 和 Q4_K_M 生成时的速度和 CPU、Metal 结果的差别。
flowchart TB
  file["GGUF 文件"] -->|Content read| content["元数据 和 张量目录"]
  content -->|Content tensor| qt["QTensor 块数组"]
  qt --> qmm["QMatMul"]
  qmm --> cpu["CPU vec_dot"]
  qmm --> metal["Metal mv mm 内核"]
  qmm -.->|反量化| plain["普通 Tensor matmul"]

第一页是文件,第二页是块,第三页是计算。三页的实验都在 M2 Max、macOS 27、rustc 1.99.0 上跑,用本地的 SmolLM2-135M-Instruct-Q8_0.gguf 和 Q4_K_M.gguf。CUDA 上的量化内核(cuda.rs、fast_mmq.rs、fast_mmvq.rs)只读了源码,没有实测。量化模型怎么用这些权重搭出一次前向,见量化 llama 的一次前向;不依赖 candle 自己写一遍 Q8_0 和 GGUF 头部解析,见动手:mini-quant。

💬 关联讨论

发起讨论
加载中...