大模型量化可视化指南:从 FP32、INT8 到 GPTQ、GGUF 与 BitNet
用 60 张图理解大模型量化:数值表示、线性映射、校准、PTQ、GPTQ、GGUF、QAT、BitNet 与 1.58-bit LLM。
核心
量化的本质,是用更少的离散数值近似模型原本的高精度权重和激活,在显存、带宽、计算速度与模型精度之间寻找平衡。 位数越少,模型越容易部署,但可表示的数值越稀疏,因此量化技术的核心工作就是控制近似误差,并把误差留在模型不敏感的位置。
本文主要依据 Maarten Grootendorst 于 2024 年 7 月 22 日发布的 A Visual Guide to Quantization 翻译和整理,在保留原文直觉、例子与完整图解的同时,对 GGUF、动态量化和 GPTQ 等概念做了必要的准确化。除另有标注外,文中图片均来自原文。
- 核心
- 从“模型很大”追到每个数占多少位
- 量化把连续数值压进有限的格子
- 线性量化:比例尺与零点
- 离群值决定比例尺,校准决定舍弃什么
- 训练后量化:动态和静态处理激活的方式
- 进入 4 bit:GPTQ 与 GGUF 生态
- 量化感知训练:让模型在训练时适应格子
- BitNet:把低位约束写进 Transformer
- BitNet b1.58:零把乘法变成选择
- 评价
从“模型很大”追到每个数占多少位
大语言模型之所以“大”,首先是因为它包含数十亿乃至数千亿个参数,其中绝大部分是权重。推理时,输入与权重相乘还会产生激活值(activation,数据流过网络各层时生成的中间结果)。权重需要长期存储,激活则随输入不断产生;两者都要占用内存或显存,也都可能成为数据搬运和计算的负担。
如果要压缩数十亿个数,第一步不是立刻讨论某种量化算法,而是理解计算机怎样表示一个数。
浮点数怎样保存范围与精度
神经网络通常使用浮点数。按照 IEEE 754 的基本思路,一个浮点数由三个部分组成:
- 符号位(sign)决定正负;
- 指数位(exponent)决定数量级,也就是数值范围;
- 尾数位(fraction / mantissa)保存有效数字,决定精细程度。
忽略非规格化数、无穷大和 NaN 等特殊情况,一个二进制浮点数可以直观地写成:
其中 $S$ 是符号位,$E$ 是指数的编码值,$\text{bias}$ 是指数偏置,$F$ 是尾数表示的小数部分。
位数增加,通常意味着可以表达更多数值,并把相邻可表示数之间的距离压得更小。反过来,位数减少后,很多原本不同的数会落到同一个离散点上。
动态范围、精度与内存
一个数值格式能够覆盖的最小值到最大值称为动态范围(dynamic range);两个相邻可表示数之间的距离反映它的精度(precision)。指数位主要影响范围,尾数位主要影响精度,两者争用有限的总位数。
模型权重的理论存储量可以用一个很朴素的公式估算:
\[\text{memory(bytes)} = \frac{N_{\text{parameters}} \times b}{8}\]$N_{\text{parameters}}$ 是参数数量,$b$ 是每个参数使用的比特数。
图 8:8 bit 等于 1 byte,因此参数量乘位宽后还要除以 8。
以 700 亿参数模型为例,只计算权重,FP32 每个参数占 4 字节,总量就是约 280 GB;换成 16 bit 后约为 140 GB,8 bit 约为 70 GB,4 bit 则约为 35 GB。
图 9:700 亿个 FP32 权重,仅加载权重就约需 280 GB。
这个估算只覆盖权重。真实推理还要为 KV cache、激活、临时工作区、运行时开销和可能存在的多份权重副本留出空间;上下文长度和模型架构也会显著改变最终显存需求。
量化把连续数值压进有限的格子
量化(quantization)把较高位宽的数值映射到较低位宽的表示,例如从 FP32 映射到 INT8。低位格式只有有限个格子,所以映射通常伴随信息损失。
可以把它想成给图片减少颜色。原图拥有连续而丰富的色彩;若只允许使用 8 种颜色,主体仍然可辨认,但局部会出现明显色阶和颗粒感。模型量化面对的是同一个问题:怎样显著减少“颜色”,同时尽可能保留原有信息。
图 11:颜色数量减少后,整体内容得以保留,细节却变得粗糙。原图由 Maarten Grootendorst 基于 Slava Sidorov 的图片改编。
FP16、BF16 与 INT8 各自保留了什么
FP16 把 32 bit 浮点数缩短为 16 bit:1 个符号位、5 个指数位和 10 个尾数位。它拥有较多有效数字,但指数位较少,因此动态范围明显小于 FP32。
图 12:FP16 以更小内存换取更窄的动态范围和更低精度。
BF16 同样使用 16 bit,却保留了与 FP32 相同的 8 个指数位,只留下 7 个尾数位。它可以覆盖接近 FP32 的数量级范围,但有效数字更少。这种取舍对深度学习很实用:训练和推理经常更需要避免上溢、下溢,而不是为每个数保留很多小数位。
图 13:BF16 像是截短尾数后的 FP32,范围大而精度较粗。
继续压缩到 INT8 后,表示方式从浮点变成整数。一个有符号 8 bit 整数通常只有 256 个取值,即 [-128, 127]。它只占 FP32 四分之一的空间,但必须先建立浮点值与整数之间的映射。
整数计算是否更快取决于硬件指令、内核实现、批大小和数据搬运成本。更少位数通常能降低内存带宽压力,但模型变小并不自动等于端到端推理变快。
线性量化:比例尺与零点
量化不需要把 FP32 的全部理论范围映射到 INT8。模型权重实际只占其中很小的一段,因此只需找到一个比例尺,把这段数据范围投射到低位整数范围。最常见的基础方案是对称和非对称线性量化。
对称量化以零为中心
对称量化把浮点范围映射到以零为中心的整数范围。浮点数 0 仍然对应量化后的 0,实现简单,也能让许多硬件高效处理。
一种典型做法是绝对值最大量化(absolute maximum quantization,简称 absmax)。设输入中的最大绝对值为:
为了让正负两端严格对称,下面使用 [-127, 127],而不是完整的 INT8 范围 [-128, 127]。
若目标是有符号 $b$ bit 整数,令 $q_{\max}=2^{b-1}-1$,并采用“量化时乘比例”的记法:
\[s = \frac{q_{\max}}{\alpha}, \qquad x_q = \operatorname{clip}\left(\operatorname{round}(s x),-q_{\max},q_{\max}\right)\]这里 $s$ 是缩放因子,$x$ 是原始浮点值,$x_q$ 是量化后的整数。
图 17:先由位宽与最大绝对值计算缩放因子,再把输入映射到整数。
计算时如果需要恢复到浮点域,可以反量化(dequantize):
\[\hat{x} = \frac{x_q}{s}\]$\hat{x}$ 只是原数 $x$ 的近似值,不可能恢复已经被舍入丢掉的信息。
图 20:3.08 和 3.02 被映射为同一个整数后,反量化也无法再区分它们。
原值与反量化值之间的差就是量化误差:
\[e_i = x_i - \hat{x}_i\]一般来说,位数越低,整数格子越少,量化误差越大。但最终模型精度还取决于误差落在哪些权重和激活上,而不只取决于平均误差大小。
非对称量化为零设置偏移
现实数据不一定围绕零对称。非对称量化直接把浮点最小值 $\beta$ 和最大值 $\alpha$ 映射到整数范围两端,因此能更充分地利用所有整数格子。
图 22:当原始范围为 [-7.59, 10.8] 时,零不再位于区间正中。
设目标整数范围为 $[q_{\min},q_{\max}]$,仍使用“量化时乘比例”的记法:
\[s = \frac{q_{\max}-q_{\min}}{\alpha-\beta}\] \[z = \operatorname{round}(q_{\min}-s\beta)\] \[x_q = \operatorname{clip}\left(\operatorname{round}(sx+z),q_{\min},q_{\max}\right)\]$z$ 称为零点(zero-point),它表示浮点零在整数域中的位置。
图 24:浮点范围两端被映射到 INT8 的最小值和最大值。
反量化时同时撤销平移与缩放:
\[\hat{x}=\frac{x_q-z}{s}\]
图 26:对称量化固定零点,非对称量化用偏移换取更充分的范围利用。
非对称方案往往能更贴合偏斜分布,但多了零点运算与元数据。究竟选哪一种,要结合数据分布、硬件内核以及按张量、按通道还是按组量化等粒度判断。
离群值决定比例尺,校准决定舍弃什么
若一组数里存在极端离群值,简单的 absmax 会让它决定整个比例尺。其余大多数值因此挤在很窄的区间内,映射到低位整数后可能失去彼此差异。
图 28:为了容纳离群值,整数格子被浪费在大多数数据不会出现的范围。
解决办法之一是裁剪(clipping):主动选取较窄的动态范围,把超出上下界的数截到边界。假设把范围设为 [-5, 5],所有小于 -5 的数都映射到负端,所有大于 5 的数都映射到正端。
图 29:牺牲少数离群值的精度,换取多数普通值更细的量化步长。
这不是免费午餐:非离群值的误差减小了,离群值的误差却会显著增加。校准(calibration)就是选择量化范围与参数的过程,目标是在保留数据覆盖率和降低总体误差之间找到合适平衡。
权重和偏置是模型保存下来的规则
权重 $W$ 和偏置 $b$ 是训练学到并写入模型文件的参数。模型加载完成后,它们在普通推理过程中保持不变,因此可以提前完整读取和分析。量化权重时,也就能够直接观察每层或每组权重的分布。
常见范围选择方法包括:
- 用某个百分位数裁掉分布尾部;
- 最小化原权重与反量化权重之间的均方误差(MSE);
- 最小化原分布与量化分布之间的 KL 散度等统计距离。
激活是当前输入经过规则计算出的结果
激活值不是模型里另一组固定参数,而是模型处理当前输入时临时算出的中间结果。以最简单的一层神经网络为例:
\[z=Wx+b, \qquad a=f(z)\]$x$ 是当前层收到的输入,$W$ 是权重,$b$ 是偏置,$z$ 是线性计算结果;$f$ 是 ReLU 等激活函数,$a$ 是经过它处理后的激活值。
| 对象 | 在计算中的作用 | 普通推理时是否变化 |
|---|---|---|
| 输入 $x$ | 当前层正在处理的数据 | 随提示词、token 和上一层结果变化 |
| 权重 $W$ | 决定怎样组合输入 | 固定 |
| 偏置 $b$ | 对组合结果做整体平移 | 固定 |
| 激活 $z$ 或 $a$ | 这套规则处理当前输入后得到的中间状态 | 随输入和所在层变化 |
假设一个神经元的参数是:
\[W=[0.6,-0.4], \qquad b=0.1\]当输入为 $x=[2,-1]$ 时:
\[z=0.6\times2+(-0.4)\times(-1)+0.1=1.7\] \[a=\operatorname{ReLU}(1.7)=1.7\]如果换成输入 $x'=[0.2,3]$,仍然使用同一组 $W$ 和 $b$:
\[z'=0.6\times0.2+(-0.4)\times3+0.1=-0.98\] \[a'=\operatorname{ReLU}(-0.98)=0\]权重和偏置没有变化,激活却从 1.7 变成了 0。因此可以把 $W$ 和 $b$ 理解成固定的加工规则,把激活理解成这套规则处理当前输入后产生的中间产品。一层的激活会成为下一层的输入,于是中间状态沿网络逐层流动。
在 Transformer 量化语境里,“激活”通常是一个宽泛称呼,不一定只指紧跟 ReLU、SiLU 等函数之后的数值。各层的输入与输出、隐藏状态以及 Q、K、V 等运行时中间张量,经常都被统称为激活。
激活为什么也值得量化
激活虽然不像权重那样长期驻留,但计算当前层时,GPU 仍要同时容纳输入激活、输出激活和若干临时张量。单个激活张量的大小可以粗略估算为:
\[\text{activation memory} =B\times T\times H\times \text{bytes per value}\]$B$ 是 batch size,$T$ 是序列长度,$H$ 是隐藏维度。把激活从 FP16 的 16 bit 量化到 INT8,这部分张量的理论内存占用与搬运数据量都会减半。
量化激活通常有三个目的:
- 降低运行时峰值显存。 激活用完后可以释放或复用,但当前计算所需的中间张量仍会占显存;训练时还要为反向传播保留许多激活。
- 减少显存带宽压力。 GPU 计算 $Y=XW$ 时,必须把激活 $X$ 和权重 $W$ 搬进计算单元。低位数据更小,同一时间可以传输更多元素。
- 使用低精度矩阵乘法。 例如
W8A8同时使用 INT8 权重和 INT8 激活,在有对应指令与高效内核的硬件上,可以直接执行低精度计算。
不同推理阶段的收益并不相同。大 batch 或长序列的 prefill 会同时处理许多 token,激活可能形成可观的峰值显存;逐 token 解码时,普通临时激活相对较小,长期驻留的权重和随上下文增长的 KV cache 往往更加突出。
KV cache 保存的是由激活计算出来的 K、V 张量,但它会跨生成步骤长期保留,因此通常把 KV cache 量化作为一个独立问题讨论。激活量化主要处理当前层正在流动的中间数据,KV cache 量化主要压缩需要随上下文一起保存的数据。
因此,讨论“一个 4 bit 模型”时还不够明确。需要继续问:只量化权重,还是同时量化激活?缩放因子是每个张量一个、每个通道一个,还是每个小组一个?例如 W4A16 表示 4 bit 权重配 16 bit 激活,主要压缩模型权重;W8A8 则让权重与激活都进入 8 bit 计算。二者的显存收益、精度风险与硬件要求完全不同。
训练后量化:动态和静态处理激活的方式
训练后量化(Post-Training Quantization,PTQ)在模型训练完成后执行,不要求从头训练模型。权重是已知的,可以直接用对称或非对称方式量化;激活范围未知,则通常分为动态与静态两条路线。
动态量化在运行时观察当前数据
动态量化(dynamic quantization)在推理过程中,根据当前输入或当前激活张量的实际分布计算缩放因子和零点。
图 35:当前张量的范围决定本次量化使用的 $s$ 与 $z$。
这种方式更能适应输入变化,通常比一套固定参数稳健,但计算最小值、最大值、缩放因子以及执行量化本身都会增加运行时开销。具体实现可能按张量、token 或其他粒度更新参数,并不只是笼统地“每个隐藏层一个参数”。
静态量化提前用代表性数据校准
静态量化(static quantization)先准备一份有代表性的校准数据集,让样本通过模型,收集各处激活的典型分布,再提前确定量化参数。
图 36:部署前收集分布,部署时直接复用已经确定的量化参数。
静态方案省去了运行时估计范围的成本,更容易获得稳定吞吐;代价是它依赖校准数据的代表性。如果真实输入与校准集偏差很大,固定范围就可能频繁裁剪数据,或者浪费大量整数格子。
动态与静态并不是绝对的“精度高/速度慢”和“精度低/速度快”二分。最终表现仍取决于算子、量化粒度、后端内核、数据分布和部署硬件。
进入 4 bit:GPTQ 与 GGUF 生态
从 8 bit 继续降低到 4 bit 时,每组数可用的离散格子急剧减少,朴素舍入带来的误差更难控制。实践中常见两类名字是 GPTQ 与 GGUF,但它们不处于同一个概念层级:GPTQ 是量化方法,GGUF 是承载模型张量与元数据的文件格式。
GPTQ 用二阶信息补偿误差
GPTQ 是一种一次性的训练后权重量化方法。它逐层处理模型,并利用少量校准数据近似损失函数的二阶信息,估计某个权重发生变化时,对这一层输出会有多大影响。
二阶敏感度由 Hessian(损失对权重的二阶导数矩阵)及其逆矩阵近似表达。直觉上,模型对不同方向的权重扰动敏感程度不同,因此同样大小的舍入误差,造成的损失增长也不同。
图 38:图中用逆 Hessian 的数值帮助判断哪些权重更不能随意改变。
GPTQ 先量化一个权重,得到它与反量化近似值之间的误差。
图 39:当前权重被固定到低位格点后,会留下无法消除的局部误差。
图 40:同样大小的数值误差,对高敏感度权重和低敏感度权重影响不同。
关键步骤不是孤立地接受这个误差,而是依据逆 Hessian 中的相关关系,更新尚未量化的其余权重,让它们共同补偿当前误差,尽量维持这一层原来的输出。
论文实现还通过阻尼、批量更新、Cholesky 分解和缓存计算结果降低成本。GPTQ 能把大模型权重压到 3 或 4 bit,并在论文实验中保持很小的精度损失。不过,压缩后的权重能否真正加速推理,仍取决于是否存在匹配量化布局的高效 GPU 内核。
GGUF 是容器,Q4_K 才是量化编码
GGUF 来自 GGML / llama.cpp 生态。按照 GGUF 官方规范,它是一种面向推理的二进制模型文件格式,负责把运行模型所需的信息装进一个可快速读取的文件:
1
2
3
4
5
6
7
8
9
10
GGUF 文件
├── 模型元数据
│ ├── 模型架构、层数、隐藏维度
│ ├── tokenizer 与上下文长度
│ └── 量化版本等信息
├── 张量目录
│ ├── 张量名称与形状
│ ├── 数据类型:F16、Q4_K、Q6_K……
│ └── 张量数据在文件中的位置
└── 真正的张量数据
因此,GGUF 本身不是量化算法。同一个 GGUF 文件里可以同时保存 F16、Q4_K、Q6_K 等不同类型的张量;真正决定每组权重怎样压缩的是 Q4_K、Q6_K 等张量编码。
典型处理流程是先把 Hugging Face 等格式的模型转换成高精度 GGUF,再用 llama-quantize 量化其中的权重:
1
2
3
4
5
6
7
FP16 / BF16 原模型
↓ 转换格式
高精度 GGUF
↓ llama-quantize,例如选择 Q4_K_M
量化后的 GGUF
↓ llama.cpp
在 CPU / GPU 上加载并推理
GGUF 文件保存的是模型参数,不会预先保存尚未产生的运行时激活。激活量化和 KV cache 量化由推理运行时另行处理。
为什么 Q4_K 要把权重分块
假设一行权重中有两组分布相差很大的数:
1
[-0.9, -0.8, 0.1, 0.2 | 4.8, 5.0, 5.1, 5.2]
如果八个权重共用一个比例尺,它必须覆盖 -0.9~5.2。前半组会被挤在很窄的范围内,多个不同数值很容易落进同一个低位格子。
将它们拆成两个局部块后,每块可以使用自己的最小值和比例尺:
1
2
块 1:[-0.9, -0.8, 0.1, 0.2] → scale₁、min₁
块 2:[ 4.8, 5.0, 5.1, 5.2] → scale₂、min₂
对某个块,可以用非对称线性量化的形式把权重映射为 4 bit 整数:
\[q=\operatorname{clip}\left( \operatorname{round}\left(\frac{w-\text{min}}{\text{scale}}\right), 0,15 \right)\]反量化近似为:
\[\hat w=q\times\text{scale}+\text{min}\]不同实现对最小值的符号和公式写法有所不同,但共同思想不变:权重只保存低位整数,每个局部块再保存恢复数值所需的缩放与偏移信息。
图 43:分块让每一小组权重都能使用更贴近自身分布的比例尺。
Q4_K 使用两级分块
根据 llama.cpp 的 张量编码说明,一个 Q4_K 超块包含 256 个权重,并沿矩阵乘法的累加维度拆成 8 个子块,每个子块包含 32 个权重:
1
2
3
4
5
6
一个 Q4_K 超块:256 个权重
│
├── 子块 1:32 个权重 → 局部 scale₁、min₁
├── 子块 2:32 个权重 → 局部 scale₂、min₂
├── ……
└── 子块 8:32 个权重 → 局部 scale₈、min₈
每个权重被编码为 0~15 之间的 4 bit 整数。计算时,它和所在子块的局部比例尺、偏移共同近似原权重:
$j$ 是权重 $i$ 所在的子块。相比让 256 个数共用一套参数,32 个一组的局部映射更能贴合每段权重的实际分布。
比例尺本身也会被量化
小块改善了精度,却引入了新的存储开销:8 个子块各自需要保存 scale 和 min。如果这些参数全用 FP16,元数据会吃掉相当一部分压缩收益。
Q4_K 因此继续量化这 8 组局部参数:
- 每个权重使用 4 bit;
- 每个子块的局部
scale和min被量化成 6 bit; - 整个超块再保存两个 FP16 参数
d与dmin,用于恢复这些局部scale和min。
也就是说,它形成了两层比例关系:
1
2
3
4
5
超块 FP16 参数 d、dmin
↓ 恢复
8 个子块的 6-bit scale、min
↓ 恢复
256 个权重的 4-bit q
这正是图中 s_super 和 s_sub 的关系:s_sub 负责恢复一个小块里的权重,s_super 负责恢复已经被低位化的 s_sub。在 llama.cpp 的 block_q4_K 数据结构中,256 个权重的存储组成是:
| 内容 | 大小 |
|---|---|
| 256 个 4-bit 权重 | 128 字节 |
| 8 组 6-bit scale 与 min | 12 字节 |
FP16 的 d 与 dmin | 4 字节 |
| 总计 | 144 字节 |
平均每个权重占用:
\[\frac{144\times8}{256}=4.5\text{ bit/weight}\]所以 Q4_K 名称里的“4”表示每个权重主体使用 4 bit;算上恢复权重所需的分块元数据,实际平均约为 4.5 bit/weight。
图 46:2 bit、4 bit、6 bit 方案会为权重、缩放因子和最小值分配不同位宽。
更小的块通常更贴合局部分布、降低量化误差,但每个块都要保存比例尺等元数据;块过小又会让元数据占比升高。Q2_K、Q4_K、Q6_K 等编码正是在局部精度、文件体积和解码效率之间选择不同平衡。
Q4_K_M 是模型级混合预设
Q4_K_M.gguf 也不能简单理解成“文件中所有张量都是 Q4_K”:
Q4_K是一种具体的张量分块编码;Q4_K_M是模型级量化预设,可以让不同张量使用不同精度;M、S表示不同的混合方案,而不是给每个权重再增加一个字段;llama-quantize --pure才会关闭默认的 K-quant 混合策略,尽量把可量化张量统一为指定类型。
这种混合安排允许模型把大量普通权重压得更低,同时为敏感张量保留较高精度。因此,文件名描述的是主要量化档位,而不是每一个张量的绝对类型。
推理时按块解码,不会展开整个模型
加载 GGUF 时,运行时从张量目录知道每个张量的形状、位置和 Q4_K 等编码类型。量化权重通常继续以紧凑形式保存在内存或显存中;矩阵乘法内核读取一个块后,解析其中的 4-bit 权重和比例尺,并在点积过程中解码或反量化,不需要先把整个模型完整展开为 FP16。
1
2
3
4
5
6
7
读取 Q4_K 权重块
↓
取出 q、局部 scale/min、超块 d/dmin
↓
在计算内核中恢复当前块的近似权重
↓
立即参与当前点积
因此,模型的内存与显存压缩收益可以一直保留到推理阶段。至于哪些层放在 CPU、哪些层卸载到 GPU,是 llama.cpp 等运行时根据参数和设备能力执行的策略;GGUF 提供可快速读取和映射的文件布局,但不自动决定设备放置。
Q4_K 的关键取舍可以归结为两句话:用 32 个权重一组的局部比例尺换取精度,再用 256 个权重一组的超块比例尺压缩这些局部比例尺的开销。GGUF 则负责把量化结果、张量类型和完整模型信息稳定地装进同一个文件。
量化感知训练:让模型在训练时适应格子
PTQ 面对的是已经训练完成的高精度模型,只能在事后尽量减少压缩损失。量化感知训练(Quantization-Aware Training,QAT)把量化影响直接放进训练或微调过程,让模型学会在低精度约束下找到更合适的参数。
图 47:PTQ 在训练后压缩,QAT 在训练过程中模拟量化。
QAT 通常使用伪量化(fake quantization):前向传播时把高精度权重或激活量化到例如 INT4,随后再反量化回浮点数参与普通算子计算。这样既能让前向结果包含量化误差,又能继续使用成熟的浮点训练基础设施。
图 48:量化后立即反量化,数值仍是浮点格式,但已经落在低位格点上。
舍入函数几乎处处不可导,反向传播通常借助直通估计器(Straight-Through Estimator,STE),用近似梯度穿过量化操作。模型因此能更新参数,主动降低未来部署到低精度后产生的损失。
一个有用的几何直觉是寻找更宽的极小值。高精度下,一个狭窄谷底可能拥有最低损失,却对轻微权重扰动极其敏感;量化把权重推到附近格点后,损失会快速升高。更宽的谷底在高精度下未必最低,但对离散化扰动更稳健。
图 50:QAT 优化的是低精度部署后的效果,而不只是高精度权重的最低训练损失。
QAT 往往比 PTQ 更能保持低位精度,但代价也更高:需要训练数据、训练算力、可修改的模型流程和更复杂的工程验证。
BitNet:把低位约束写进 Transformer
BitNet 不再从一个训练完成的普通 Transformer 出发,而是直接设计适合 1 bit 权重的网络。Transformer 中绝大多数参数位于线性层,线性层也承担大量矩阵乘法,因此 BitNet 首先改造这里。
BitNet 用 BitLinear 替代普通线性层。接口仍然接收激活、执行权重与激活的线性变换,但前向计算使用二值权重,并对激活做低位量化。
图 52:Transformer 的整体结构不变,主要替换内部线性层。
图 53:权重约束为两个值,激活保留更高的 INT8 精度。
训练时仍保留用于优化的高精度潜在参数和优化器状态,在前向传播中执行伪量化;不能简单理解为整个训练过程只保存 1 bit 权重。
图 54:权重与激活分别量化,矩阵乘法后再按记录的尺度恢复输出。
权重量化
二值权重量化先让权重分布围绕零居中,再用符号函数把负数映射为 -1、非负数映射为 1。同时记录权重绝对值的平均值 $\beta$,用于恢复输出尺度。
激活量化
激活需要保留更多信息,因此使用 absmax 从较高精度映射到 INT8,并记录最大绝对值 $\alpha$。
图 56:激活量化保留 8 bit,并记录恢复尺度所需的最大绝对值。
输出反量化
矩阵乘法结束后,利用权重尺度 $\beta$ 与激活尺度 $\alpha$ 对输出重新缩放,得到高精度激活并传给后续计算。
图 57:前向路径虽然使用低位权重和激活,层输出仍可恢复到高精度域。
原始 BitNet 实验显示,模型规模增大后,1 bit 模型与 FP16 基线的差距会缩小;但小模型的差距仍然明显,而且论文结果不等于任意既有模型都能在训练后直接变成等价的 1 bit 模型。
BitNet b1.58:零把乘法变成选择
BitNet b1.58 在 -1 和 1 之外加入 0,把权重变成三值 {-1, 0, 1}。三个等概率状态所需的信息量是 $\log_2 3 \approx 1.58$ bit,这就是名称的来源。
普通矩阵乘法需要把每个输入乘以对应权重,再把结果相加。
三值权重则可以解释成三种操作:
1:把这个输入加进结果;0:忽略这个输入;-1:从结果中减去这个输入。
在专门优化的内核或硬件上,这种表示有机会用加减与跳过代替通用乘法,同时让 0 承担稀疏选择作用。
BitNet b1.58 的权重量化使用绝对均值量化(absmean)。它以权重绝对值的均值确定尺度,再把归一化结果舍入和裁剪到 -1、0、1。
激活量化仍沿用类似 absmax 的思路,但使用围绕零对称的整数范围。论文报告称,在其模型、训练规模和测量设置下,13B 的 BitNet b1.58 在延迟、内存与能耗方面可以优于 3B 的 FP16 模型。这个结果展示了从头训练低位模型的潜力,但真正获得理论上的乘法削减,仍需要匹配的数据布局、算子内核和硬件支持。
评价
写得好的地方
原文最出色的地方是用同一套视觉语言把抽象数值表示、线性映射和实际算法连成一条理解链。它没有一上来堆砌 GPTQ、GGUF、QAT 等名词,而是先从比特、动态范围、精度与内存讲起,再用颜色减少、数轴映射和离群值逐步建立直觉。60 张教学图并非装饰,而是在公式第一次出现时承担了解释任务,尤其是对称/非对称量化、误差补偿和三值矩阵乘法几组图,显著降低了理解门槛。
文章选择的贯穿矛盾也很清楚:位数越少,存储和计算潜力越好;格子越少,近似误差越难控制。 从裁剪、校准到 GPTQ 的误差重分配,再到 QAT 让训练主动适应误差,后续方法都能回到这个矛盾上,因此读者容易形成整体框架,而不是只记住一串算法名。
可以改进的地方
原文为了可视化直觉做了一些过度简化,阅读时需要补上边界:
- GGUF 不是一种量化算法。 它是模型文件格式,可以承载多种量化数据类型;CPU/GPU 分层卸载主要是
llama.cpp等运行时的能力。原文展示的超块、子块和嵌套比例尺更接近具体的 K-quant 方案,不能推广为所有 GGUF 文件的统一量化流程。 - 动态量化不只是“每个隐藏层计算一次参数”。 参数可能按张量、通道、token 或其他粒度计算,精度与开销也不能仅凭“动态/静态”标签判断。
- 低位并不天然加速。 模型文件更小、显存占用更低通常成立,但实际延迟还受反量化、内存带宽、批大小、算子融合和专用内核影响。没有匹配硬件时,理论上更少的乘法也未必兑现为端到端速度。
- GPTQ 与 BitNet 的适用阶段不同。 GPTQ 面向已有高精度模型的训练后权重量化;BitNet 则从训练阶段就改变线性层和数值约束。把两者只按“4 bit”和“1 bit”排列,容易忽略部署压缩与从头训练新架构之间的成本差异。
- 部分实现性描述不够严谨。 公式里的 $b$ 应是比特数而不是字节数;BitNet 训练也不能理解为只保存二值权重,训练所需的潜在参数、梯度和优化器状态通常仍使用更高精度。
把这些边界补齐后,这篇文章仍然是一份非常优秀的量化入门地图:它最适合帮助读者建立直觉,再以 GPTQ 论文、BitNet 论文、BitNet b1.58 论文 和具体推理框架文档继续深入实现细节。
































