B 当你连 scale 也量化了

当你连 scale 也量化了

May 30, 2025
ai

上次算到,160 亿参数的模型用 Q4_0 量化后,光 scale 和 $\alpha$ 这些"说明书"就要吃掉 2 GB。

2 GB 不小。一台 16 GB 显存的显卡,模型权重压缩后大概 8 GB,结果说明书自己占了四分之一。这就像你去宜家买了一张桌子,包装盒里一半是螺丝和安装图纸。

K-quants 要解决的就是这个问题。思路很直白——既然权重可以量化,那说明书也可以量化。

套娃

K-quants 的核心结构叫 super-block。做法是把 8 个普通 block 打包成一组,然后对 8 个 scale 再做一次量化——从 FP16(16 bit)砍到 INT8(8 bit):

super-block: 256 个 INT4 权重
├── block 0: scale = 0.10 (FP16 → INT8)
├── block 1: scale = 0.14 (FP16 → INT8)
├── block 2: scale = 0.11 (FP16 → INT8)
├── block 3: scale = 0.18 (FP16 → INT8)
├── block 4: scale = 0.08 (FP16 → INT8)
├── block 5: scale = 0.16 (FP16 → INT8)
├── block 6: scale = 0.13 (FP16 → INT8)
├── block 7: scale = 0.09 (FP16 → INT8)
└── super-scale = 0.00142 (FP16) ← 新加的,用来还原上面那些 INT8

来走一遍完整的计算。假设这 8 个 block 里 256 个权重已经量化完了——每个 block 的 scale 是上面那 8 个 FP16 值。现在的任务是把这 8 个 scale 自己也量化掉。

第一步,把这 8 个 FP16 scale 当作 8 个"小权重"。最大值 $d = 0.18$,对称区间就是 $[-0.18,\ 0.18]$。

第二步,确定 INT8 的桶。INT8 有 256 个桶——跟 Type 0 一样的对称套路,扔掉最左边的一个,用 $-127$ 到 $+127$,共 254 个桶。正中间 $0$ 当零点。

第三步,算这一层(meta 层)的 scale——我们就叫它 super-scale:

$$ S_{\text{meta}} = \frac{0.18 - (-0.18)}{127 - (-127)} = \frac{0.36}{254} \approx 0.00142 $$

第四步,把 8 个 scale 逐一量化:

scale(FP16)$s / S_{\text{meta}}$$\text{round}$ → INT8
$0.10$$0.10 / 0.00142 = 70.4$$70$
$0.14$$0.14 / 0.00142 = 98.6$$99$
$0.11$$0.11 / 0.00142 = 77.5$$78$
$0.18$$0.18 / 0.00142 = 126.8$$127$
$0.08$$0.08 / 0.00142 = 56.3$$56$
$0.16$$0.16 / 0.00142 = 112.7$$113$
$0.13$$0.13 / 0.00142 = 91.5$$92$
$0.09$$0.09 / 0.00142 = 63.4$$63$

第五步,验证还原精度。使用时把 INT8 乘以 $S_{\text{meta}}$:

INT8$\tilde{s}$原始 scale误差
$70$$70 \times 0.00142 = 0.0994$$0.10$$0.0006$
$99$$99 \times 0.00142 = 0.1406$$0.14$$0.0006$
$78$$78 \times 0.00142 = 0.1108$$0.11$$0.0008$
$127$$127 \times 0.00142 = 0.1803$$0.18$$0.0003$
$56$$56 \times 0.00142 = 0.0795$$0.08$$0.0005$
$113$$113 \times 0.00142 = 0.1605$$0.16$$0.0005$
$92$$92 \times 0.00142 = 0.1306$$0.13$$0.0006$
$63$$63 \times 0.00142 = 0.0895$$0.09$$0.0005$

误差在 $0.0003$ 到 $0.0008$ 之间——非常小,因为这些 scale 本身就小。

看看存储省了多少。这 8 个 scale,量化前后:

8 个 scalesuper-scale合计
量化前$8 \times 16 = 128\ \text{bit}$128 bit
量化后$8 \times 8 = 64\ \text{bit}$16 bit80 bit

每个 super-block(256 个权重)省了 48 bit。一个 block 有 32 个权重——上面这 256 个权重如果还用 legacy 方式,per-weight 是 $4 + 16/32 = 4.5\ \text{bit}$。K-quant 把说明书也压了,per-weight 降到 $4 + 80/256 = 4.3125\ \text{bit}$。

单看这 $0.1875\ \text{bit}$ 的差距不明显。放大到 160 亿参数。

$$ 16\text{B} / 32 = 5\text{ 亿个 block} $$

每个 block 省 6 bit 的 scale 开销($16 \to 10$),总共 $5\text{亿} \times 6 = 30\text{ 亿 bit} \approx 375\ \text{MB}$。如果用的是 Type 1(每个 block 还有 $\alpha$),$\alpha$ 的 FP16 也压缩到 INT8——每 block 再省 6 bit,又是 375 MB。加上 super-block 的额外开销抵消一小部分,常量的总开销从约 2 GB 砍到约 1 GB。

1 GB 对于消费级显卡来说,可能就是跑得动和跑不动的区别。

这套双重量化的方案最早来自 QLoRA 的论文。当时 QLoRA 用它来压缩 LoRA 适配器的存储——思路是一样的:辅助信息太多,那就把辅助信息也量化一遍。

顺带一提,网上说 K-quants 的"K"是指 K-means 聚类。不是的。K-quants 用的还是同样的仿射量化(把浮点数线性映射到整数),不是向量量化。K 来自开发者 Kawrakow 的名字,也可能是"kernel"——因为 super-block 结构需要新的 CPU kernel 来支持。

不只是省空间

super-block 带来的好处不止存储。还有一个更隐蔽的优势:内存访问。

CPU 读取内存的时候不是按需取几个字节——它一次读一整条 cache line,通常是 64 字节。256 个 INT4 权重连起来正好是 $256 \times 4 / 8 = 128$ 字节,刚好两条 cache line。CPU 一口气读完,不用满世界跳到不同的内存地址。

legacy quants 的 block 只有 32 个权重。每个 block 16 字节,散落在内存各处。读完 block 0 跳到 block 1,中间隔了很远——每次都是 cache miss,CPU 空转着等内存。

super-block 把 8 个 block 的权重在内存里排成连续的 128 字节。读一个 super-block 只需要两次 cache line 加载,而不是 8 次跳跃。一次推理要扫过整个模型——几亿个 block 的 cache miss 变成几分之一,这个差别在 CPU 推理上很明显。

这是一个典型的"顺便解决"型优化。当时做 super-block 是为了省存储,结果发现连推理速度也变快了。

重要的人多吃点

K-quants 还做了一件事:不是所有权重一视同仁。

以 Llama 7B 为例。32 层 transformer,每层 4 个权重矩阵(Q、K、V、O),加上 2 个 feed-forward 矩阵和 2 个 LayerNorm。总共大约 $32 \times (4+2+2) = 256$ 个权重矩阵。其中 LayerNorm 的 64 个权重矩阵(每层 2 个)加起来才几十万个参数——占模型总量的万分之一不到——但它们的精度直接影响每一层的输出缩放。

K-quants 给这些敏感层分配更高的精度:

  • LayerNorm 的权重保持 FP16,不量化。反正参数少,不差这点空间
  • 注意力层的 Q、K、V、O 权重用更高 bit(比如 Q5、Q6 替代 Q4),因为"看对"上下文比什么都重要
  • 输出层也用更高精度,直接决定下一个 token 到底是谁

这就是下载页面里 Q4_K_S、Q4_K_M、Q4_K_L 的含义——同一个基础精度(Q4),用不同的"敏感层保护策略":

后缀策略文件大小
S (Small)激进压缩,敏感层也尽量压最小
M (Medium)折中,大部分场景的最佳选择中等
L (Large)保守,敏感层给足精度最大

选 S 能跑但可能不太聪明,选 L 最聪明但可能跑不动。M 是大多数人的答案。

这不是什么理论突破。就是一个工程判断:既然有些层对误差更敏感,那就把有限的 bit 预算倾斜给它们。一个 7B 模型有约 70 亿个参数,其中真正"敏感"的可能只有几百万——给这几百万多配点 bit,其他几十亿省着用。跟团队里给核心成员配更好的电脑是一个道理——资源总是有限的,关键是放在哪里。

所以下载页那一排 K

回到最初下载页面让人愣住的那一排名字。Q4_0、Q4_K_M、Q5_1。

Q4_0 是 legacy 对称量化——桶少、空桶多、说明书占地方。

Q4_K_M 是 K-quant——两层套娃压缩了说明书,M 档给敏感层留了余量。同样的 4 bit 基础精度,效果比 Q4_0 好是应当的,因为桶用得更聪明、关键层吃得更好。

Q5_1 是 legacy 非对称量化换成 5 bit——基础精度更高了,但说明书还是一样膨胀。

搞懂了 legacy 和 K-quants,这一排名字就是一套密码。Q 后面的数字告诉你基础的桶有多大(bit 数),下划线后面的字母告诉你说明书怎么压缩、资源怎么分配。_\0 是朴素方案,_K_M 是套娃折中,_I 是更激进的重要性加权——但那是下一篇的事了。

理解 K-quants 记住一件事就够了:所有能被量化的东西,最后都会被量化。

diet 是没有尽头的。


参考资料

TouchingFish.top