大模型量化从0到1(六):AWQ 为什么更准——激活感知权重量化的巧思
上一篇我们讲了 GPTQ。
GPTQ 的思路可以粗略概括成一句话:
一个权重被量化以后产生了误差,就利用二阶信息调整后面的权重,让这些误差尽量不要传到层输出上。
这一篇换一条路线,讲 AWQ。
AWQ,全称 Activation-aware Weight Quantization,中文通常叫“激活感知权重量化”。
它同样要解决低比特权重量化的精度问题,但思考方式完全不同:
既然 INT4 一共只有十几个离散刻度,精度资源天然有限,那就别把这些刻度平均分给所有权重,而应该优先照顾真正影响输出的那些通道。
问题随之而来:
哪些权重才是真正重要的?
最直觉的答案是看权重本身:
- 绝对值大的权重重要;
- 范数大的权重通道重要;
- 离群权重重要。
AWQ 论文发现,这些标准并不好用。
真正更有效的判断依据,居然是权重另一边的输入——激活。
一个权重即使数值不大,只要它总是和很大的激活相乘,它的量化误差就会被明显放大;反过来,一个权重虽然很大,但它对应的输入通道几乎总是接近零,那么它对真实输出的影响可能并没有想象中那么大。
AWQ 就是从这个观察出发:
- 用校准数据观察各个输入通道的激活幅度;
- 找到那些容易放大量化误差的通道;
- 在量化前对权重做一个严格等价的通道缩放;
- 让共享的 INT4 量化网格把更多有效分辨率留给重要通道;
- 最后仍然把全部权重统一存成低比特格式。
论文将 AWQ 定位为一种硬件友好的低比特 weight-only PTQ 方法。论文中的动机实验显示,按照激活选出的少量显著通道对模型精度有非常大的影响;最终方法则用等价缩放代替混合精度,从而避免单独保存 FP16 权重。(ar5iv)
先把这篇最重要的一句话放在这里:
AWQ 不是简单地“找出大激活,然后把对应权重放大”。更准确地说,它利用激活统计构造通道缩放,在不改变浮点模型函数的前提下,重新分配 group-wise 量化中的共享分辨率,使最终的层输出误差更小。
读完这一篇,你应该能回答下面几个问题:
- 为什么权重的重要性不能只看权重?
- “保护 1% 权重”究竟是实验发现还是最终算法?
W·X = (W·s)·(X/s)明明是恒等式,为什么放到量化里就能提升精度?- 为什么 AWQ 必须放在 group-wise 量化的背景下理解?
- 为什么缩放得越大不一定越好?
- AWQ 搜索的到底是每个通道独立的 scale,还是一个更低维的参数?
- 为什么 AWQ 甚至可能让普通的权重 MSE 变大,却让模型输出更准确?
- AWQ 和 GPTQ 到底是竞争关系,还是可以组合?
目录
一、AWQ 到底在解决什么问题
二、先补一块地基:W4A16、量化粒度与 group scale
三、什么才叫“重要权重”
四、论文里的 0.1%~1% 实验究竟证明了什么
五、AWQ 的核心:等价缩放
六、从量化误差公式看缩放为什么有效
七、缩放系数不是拍脑袋定的:AWQ 如何搜索
八、用正确的代码验证 AWQ 核心思想
九、AWQ 与 GPTQ 横向对比
十、从论文原理到工程实现,中间还有哪些细节
十一、常见误区与避坑
十二、怎样用一分钟把 AWQ 讲给别人听
十三、小结
一、AWQ 到底在解决什么问题
1.1 低比特量化真正稀缺的是什么
假设我们有一个 FP16 权重矩阵。
FP16 中每个权重大致可以取非常多的数值;换成 INT4 后,一个权重通常只能落到 16 个整数编码中的某一个。
不管是有符号的:
-8, -7, ..., 0, ..., 6, 7
还是带零点的非对称编码:
0, 1, 2, ..., 15
本质都一样:
原来连续的权重空间,被压缩成了一张非常稀疏的离散网格。
因此,INT4 量化的核心矛盾不只是“数值有没有被表示出来”,而是:
有限的十几个刻度,应该怎样覆盖一组权重的动态范围?
假设一组权重位于:
[-0.7, 0.7]
用大约 15 个间隔覆盖,量化步长可能在 0.09 左右。
这意味着:
0.01、0.03、0.05
可能全都被量化成同一个值。
权重原本细微的区别消失了。
如果模型恰好非常依赖这些细微区别,输出就会明显变化。
1.2 RTN 的问题不只是“每个权重有误差”
最基础的量化通常是 round-to-nearest,简称 RTN:
q = round(w / scale)
w_hat = q * scale
它为每个权重找到最近的量化格点。
单看某一个权重,这个策略似乎很合理:
离哪个格点最近,就放到哪个格点上。
但神经网络从来不会孤立地使用一个权重。
在线性层中,真正被使用的是:
[
Y=XW^\top
]
其中:
- (X) 是输入激活;
- (W) 是权重;
- (Y) 是输出。
量化后变成:
[
\hat Y=XQ(W)^\top
]
输出误差为:
[
\Delta Y
\hat Y-Y
X\left(Q(W)-W\right)^\top
]
记权重量化误差为:
[
E=Q(W)-W
]
那么:
[
\Delta Y=XE^\top
]
这条式子揭示了一个非常重要的事实:
权重误差本身不是最终目标,权重误差经过激活放大以后形成的输出误差,才是模型真正承受的损失。
两个权重可能拥有完全相同的量化误差:
Δw₁ = 0.01
Δw₂ = 0.01
但如果对应激活是:
x₁ = 20
x₂ = 0.02
它们对输出造成的误差分别是:
Δy₁ = 0.01 × 20 = 0.2
Δy₂ = 0.01 × 0.02 = 0.0002
相差一千倍。
所以,低比特量化中最重要的并不是让每个权重的误差都一样小,而是:
把较小的量化误差分配给那些会被激活显著放大的位置。
这就是 AWQ 的出发点。
1.3 AWQ 的问题意识:量化精度不应该平均分配
可以把一个 group 的 INT4 网格想象成有限预算。
你只有十几个刻度,却要表示几十个甚至上百个权重。
普通 RTN 的态度是:
大家共用同一张网格,各自找最近的格点。
AWQ 的态度是:
既然有些通道对输出特别敏感,就先改变权重在这张网格中的相对位置,让关键通道获得更高的有效分辨率。
注意,这里说的是“有效分辨率”,不是凭空多创造几个 INT4 数值。
INT4 仍然只有原来的编码数量。
AWQ 做的是重新分配:
- 重要通道在共享范围里占据更显著的位置;
- 不重要通道可以承受稍粗的表示;
- 总体格式不变;
- 层输出误差下降。
这是一种非常典型的思想:
不追求让所有局部误差都最小,而是根据真实使用频率和敏感度,重新分配有限资源。
二、先补一块地基:W4A16、量化粒度与 group scale
AWQ 最容易被讲错的地方,不是激活,也不是缩放,而是很多文章没有先讲清楚:
权重到底按什么粒度共享量化参数?
如果忽略这件事,后面“放大权重为什么会更准”很容易变成似是而非的解释。
2.1 AWQ 通常是 weight-only,而不是 W4A4
常见的 AWQ 部署形式可以写成:
W4A16
含义是:
- Weight:4 bit;
- Activation:16 bit,通常是 FP16 或 BF16。
也就是说,AWQ 的重点是压缩权重。
它并不意味着:
权重 INT4
激活 INT4
累加 INT4
输出 INT4
真实推理中通常是:
- 权重以 4 bit 压缩形式存储;
- 内核读取并解包权重;
- 在融合内核中完成反量化和矩阵计算;
- 激活仍保持较高精度。
原始 AWQ 仓库明确把高效内核描述为 W4A16,即 4-bit 权重和 16-bit 激活。(GitHub)
这点非常重要,因为后面会看到:
x / s
这个操作只是等价变换的一部分。
它不代表 AWQ 把激活也量化成 4 bit。
2.2 量化不只有 bit 数,还有粒度
同样是 INT4,量化方式可能完全不同。
第一种:per-tensor
整个矩阵共享一个 scale:
整个 W → 一个 scale
优点是元数据少、实现简单。
缺点是不同区域的动态范围可能差别很大,一个离群值就可能抬高整张矩阵的量化步长。
第二种:per-output-channel
每个输出通道,也就是权重矩阵的每一行,共享一个 scale:
W[0, :] → scale₀
W[1, :] → scale₁
...
精度通常比 per-tensor 更好。
第三种:group-wise
每一行再沿输入维度切成多个小组,每组共享一个 scale 和 zero point:
一行有 4096 个权重
group_size = 128
第 0~127 个权重 → 一套量化参数
第 128~255 个权重 → 一套量化参数
...
这就是很多 INT4 LLM 量化格式中的 g128:
W4-g128
W3-g128
INT4-g128
AWQ 论文主要研究 group-wise weight-only 量化,实验中通常使用 group size 128。原始实现也会沿权重矩阵最后一维把权重 reshape 成固定大小的 group,再为每组计算 scale 和 zero point。(ar5iv)
2.3 为什么 group-wise 是理解 AWQ 的关键
假设一个 group 中有 128 个权重,它们共享同一个量化步长:
[
\Delta
]
这个步长通常由该 group 的最大值、最小值或者裁剪范围决定。
以简化的对称量化为例:
[
\Delta
\frac{\max |w|}{2^{b-1}-1}
]
4 bit 时:
[
2^{4-1}-1=7
]
于是:
[
Q(w)
\Delta\cdot
\mathrm{clip}
\left(
\mathrm{round}\left(\frac{w}{\Delta}\right),
-7,7
\right)
]
关键是:
一个 group 内的所有权重都使用同一个 (\Delta)。
因此,权重之间是在竞争同一张网格。
假设同一个 group 中有两个权重:
w₁ = 0.48
w₂ = 0.03
如果 group 的步长是:
Δ = 0.07
那么:
w₁ / Δ ≈ 6.86 → 7
w₂ / Δ ≈ 0.43 → 0
w₁ 还能被表示,w₂ 直接被压到零。
如果 w₂ 恰好对应一个非常大的激活,那么即使它看起来只是“小权重”,把它量化成零也可能对输出产生很大影响。
AWQ 想做的事情,就是在量化之前改变这种相对关系。
2.4 一个必须提前知道的反例
假如我们不是 group-wise,而是每一个输入通道单独拥有自己的量化 scale,会发生什么?
考虑某一列权重 (w),将它整体乘以正数 (s)。
该列的量化步长也会乘以 (s):
[
\Delta’ = s\Delta
]
于是:
[
Q(sw)
sQ(w)
]
再除以 (s):
[
\frac{Q(sw)}{s}
Q(w)
]
也就是说:
如果每一列都有完全独立的量化范围,先乘 (s) 再除 (s),结果可能和不缩放完全相同。
缩放没有重新分配任何共享资源,因为这一列本来就独占一张量化网格。
这也是原稿代码最需要修正的地方:
for c in range(W.shape[1]):
Wq[:, c] = quantize_int4_symmetric(W[:, c])
这段代码让每一列单独计算 scale。
在这种量化定义下,对整列做 AWQ 式缩放,scale 会同步变化,缩放前后理论上可能完全等价,不能用来演示 AWQ 收益。
真正能体现 AWQ 的实验,必须让多个输入通道的权重共享量化参数,例如:
每个输出行中的连续 32 个权重共享 scale
或者真实模型中常见的:
每个输出行中的连续 128 个权重共享 scale
请把这一点记牢:
AWQ 的“精度腾挪”发生在共享量化尺度的 group 内。没有共享,就没有重新分配。
三、什么才叫“重要权重”
3.1 从一个乘法开始
先看最简单的标量乘法:
[
y=wx
]
量化权重后:
[
\hat w=w+e
]
其中 (e) 是权重量化误差。
量化后的输出:
[
\hat y=(w+e)x
]
输出误差:
[
\hat y-y=ex
]
因此:
[
|\Delta y|=|e|\cdot|x|
]
这意味着:
- 权重误差越大,输出误差越大;
- 激活越大,同样的权重误差越危险。
这是 AWQ 最基本的数学直觉。
3.2 扩展到完整线性层
对某个输出通道 (j):
[
y_j=\sum_i w_{ji}x_i
]
量化后:
[
\hat y_j
\sum_i (w_{ji}+e_{ji})x_i
]
因此:
[
\Delta y_j
\sum_i e_{ji}x_i
]
这里的每个输入通道 (i),都对应:
- 一个激活 (x_i);
- 权重矩阵中的一整列 (W[:,i])。
当某个 (x_i) 经常很大时,这一列里的量化误差就更容易传到输出。
所以 AWQ 关注的不是孤立的一个权重,而是:
输入通道及其对应的权重列。
这也是论文中 “salient weight channels” 的含义。
假设权重矩阵形状为:
[out_features, in_features]
那么第 (i) 个输入通道对应的权重是:
W[:, i]
整列权重都会和激活通道:
X[..., i]
相乘。
3.3 从输出 MSE 看,激活天然是权重误差的加权系数
如果我们进一步观察平方误差:
[
(\Delta y_j)^2
\left(
\sum_i e_{ji}x_i
\right)^2
]
完整展开会出现不同通道之间的交叉项。
为了建立直觉,暂时作一个简化假设:
- 各通道量化误差近似不相关;
- 误差均值近似为零;
- 先忽略交叉项。
那么可以得到:
[
\mathbb E[(\Delta y_j)^2]
\approx
\sum_i
\mathbb E[e_{ji}^2]
\mathbb E[x_i^2]
]
这条式子特别重要。
它告诉我们:
输出误差并不是普通的权重 MSE,而是被激活能量加权过的权重误差。
普通权重 MSE 是:
[
\sum_{j,i}e_{ji}^2
]
它默认每个位置同等重要。
而近似的输出 MSE 是:
[
\sum_{j,i}
e_{ji}^2
\mathbb E[x_i^2]
]
它认为激活大的通道更重要。
这正是 AWQ “activation-aware” 的数学来源。
当然,真实 Transformer 激活之间并不独立,量化误差之间也不一定完全独立。
所以这不是一个严格描述所有情况的定理,而是一个帮助理解的近似。
AWQ 后面还会用真实校准输入计算层输出误差,正是为了把通道相关性、权重分布以及 group scale 的变化都纳入最终选择。
3.4 为什么只看权重大小不够
很多人会自然地认为:
|w| 越大,权重越重要
这句话不是完全错误,但它混淆了两个概念:
- 权重本身的数值大小;
- 权重误差对模型输出的敏感度。
一个大权重可能对应长期接近零的输入:
w = 3.0
x ≈ 0
它当前对输出的贡献仍然很小:
w × x ≈ 0
另一个权重可能只有:
w = 0.08
但它经常对应:
x = 30
贡献则是:
0.08 × 30 = 2.4
更关键的是,我们关心的不是原始贡献,而是量化误差的传播:
e × x
即使两个权重的误差相同,后者也危险得多。
因此,一个权重是否重要,并不是它与生俱来的、完全静态的属性。
更准确地说:
权重的重要性取决于它在什么输入分布下被使用。
同一个模型面对不同数据分布时:
- 某些通道可能频繁活跃;
- 某些通道可能很少出现;
- 某些专业领域特征可能突然变得重要。
这也是 AWQ 需要校准数据的根本原因。
3.5 AWQ 到底怎样统计激活
真实模型不会只观察一个激活向量。
AWQ 会拿一小批校准数据跑前向传播,收集某个线性层的输入:
[
X\in
\mathbb R^{N\times T\times C}
]
其中:
- (N) 是样本数;
- (T) 是 token 数;
- © 是隐藏维度,也就是输入通道数。
然后把样本和 token 维度展开,对每个输入通道计算平均绝对幅度:
[
a_i
\frac{1}{NT}
\sum_{n,t}
|X_{n,t,i}|
]
代码形式大致是:
act_scale = X.abs().reshape(-1, X.shape[-1]).mean(dim=0)
原始 AWQ 代码中的 get_act_scale 正是按最后一个维度保留通道,并对其他位置的绝对值取均值。(GitHub)
注意两个细节。
第一,AWQ 用的是一批数据上的通道统计量,不是单个 token 的瞬时值。
第二,论文和实现通常采用平均绝对值,而不是严格使用刚才推导中的二阶矩:
[
\mathbb E[x_i^2]
]
原因是平均绝对值简单、稳定,而且在实验中足以提供有效的显著性排序。
3.6 “看激活,不看权重”不能理解得太绝对
为了传播方便,我们经常说:
AWQ 判断重要权重时看激活,不看权重。
这句话适合作为标题,但技术上需要补全。
AWQ 的“通道显著性信号”主要来自激活。
但是最终量化过程当然仍然会使用权重:
- group 的 scale 由权重范围决定;
- 缩放后范围怎样变化取决于权重;
- 候选缩放的输出误差同时由 (X) 和 (W) 决定;
- clipping 搜索也会观察权重分布。
所以更准确的说法是:
AWQ 不用权重范数来直接定义通道重要性,而是用激活幅度构造候选缩放,再利用真实权重和层输出误差选择最终方案。
AWQ 不是“完全不看权重”。
它只是发现:
激活比单纯的权重大小,更适合作为显著通道的入口。
3.7 一个更准确的生活比方
还是用调音台来比喻。
每个推子可以看成一组权重,推子接收到的音频信号是激活。
两个推子的刻度误差都偏了 1 毫米:
- 第一个接的是主唱麦克风;
- 第二个接的是一条几乎静音的备用轨道。
物理上的偏差一样,但听感上的影响完全不同。
不过,AWQ 不只是“给主唱推子上锁”。
它做得更巧:
- 先观察哪些轨道长期信号更强;
- 调整推子和前级增益之间的配合;
- 保证理想状态下总音量完全不变;
- 让有限精度的数字推子更多地覆盖主唱附近的有效范围。
也就是说:
AWQ 不是简单地减少所有推子的机械误差,而是在有限控制精度下,优先保证最影响最终混音的轨道。
四、论文里的 0.1%~1% 实验究竟证明了什么
4.1 先做一个看似不实用的实验
AWQ 论文先没有立即提出缩放。
作者先做了一个“如果硬件完全不重要,我们最想怎样保护权重”的实验。
基本做法是:
- 大部分权重照常做 INT3 group-wise 量化;
- 选出少量输入通道;
- 这些通道对应的权重列保留 FP16;
- 比较不同通道选择策略。
三种选择方式包括:
- 按激活幅度选择;
- 按权重范数选择;
- 随机选择。
结果差别非常明显。
例如论文在 OPT-6.7B、INT3-g128 设置下报告:
FP16 模型 PPL 10.86
普通 RTN INT3-g128 PPL 23.54
按激活选 0.1% 通道保留 FP16 11.58
按激活选 1% 通道保留 FP16 11.39
按权重大小选 1% 通道保留 FP16 22.37
随机选 1% 通道保留 FP16 24.23
在这个实验里,按激活选择极少量通道就能显著恢复精度,而按权重范数选择的效果接近随机。论文在其他 OPT 模型上也观察到了类似现象。(ar5iv)
这组实验真正证明的是:
量化误差的破坏性高度集中在少数激活显著的输入通道上,而且这种显著性不能可靠地由权重范数替代。
4.2 “1% 权重”实际是怎样的 1%
这里也容易产生误解。
论文说保护少量 salient weights,但实验操作通常是按输入通道选择。
假设:
in_features = 4096
选择其中约 1% 输入通道,相当于选择约 41 个通道。
每个通道对应权重矩阵的一整列:
W[:, i]
因此,被保护的是这些列中的所有权重。
如果每一列长度相同,那么保护 1% 输入通道,也大致对应保护 1% 的矩阵权重。
所以它不是从整个矩阵里无结构地挑出零散的 1% 元素,而是带有明显的通道结构。
4.3 这个实验不是最终 AWQ
很多二手文章会写成:
AWQ 找出最重要的 1% 权重,然后保护它们。
这句话只讲了一半。
“保留 1% FP16”是论文用于验证核心观察的动机实验。
它不是最终部署方案。
原因很直接:
99% INT4 + 1% FP16
虽然平均 bit 数增加不多,但计算布局变得不规则。
一个矩阵乘法内核需要同时处理:
- 普通 INT4 权重;
- 稀疏分布的 FP16 权重;
- 两套加载方式;
- 两套计算路径;
- 最后的结果合并。
理论压缩率可能很好,实际硬件效率却不一定好。
论文明确把这种混合精度方案视为系统实现上的障碍,因此继续寻找一种不保留 FP16 权重、却能近似获得保护效果的方法。(ar5iv)
4.4 最终 AWQ 没有固定的“1% 阈值”
最终 AWQ 不会简单执行:
important = top_1_percent(act_scale)
important 保持 FP16
其他通道 INT4
它采用的是连续缩放:
[
s_i=f(a_i)
]
激活较大的通道通常获得较大的相对缩放,激活较小的通道获得较小的相对缩放。
所以最终方法更像是:
按照激活重要性连续地重新分配量化分辨率。
不是:
重要或不重要,二选一。
“1%”应该记成:
- 一个很有启发性的实验现象;
- 说明误差敏感度高度集中;
- 不是 AWQ 实现里的固定超参数。
五、AWQ 的核心:等价缩放
5.1 一个完全不改变模型输出的恒等式
设:
[
X\in\mathbb R^{N\times C_{\text{in}}}
]
[
W\in\mathbb R^{C_{\text{out}}\times C_{\text{in}}}
]
线性层输出为:
[
Y=XW^\top
]
现在为每个输入通道定义一个正缩放系数:
[
s_1,s_2,\ldots,s_{C_{\text{in}}}
]
组成对角矩阵:
[
D=\mathrm{diag}(s)
]
将权重矩阵的第 (i) 列乘以 (s_i):
[
W’=WD
]
同时将激活的第 (i) 个通道除以 (s_i):
[
X’=XD^{-1}
]
那么:
[
X’(W’)^\top
XD{-1}(WD)\top
]
由于 (D) 是对角矩阵:
[
(WD)\top=DW\top
]
所以:
[
XD{-1}DW\top
XW^\top
]
最终得到:
[
XW^\top
(XD{-1})(WD)\top
]
这不是近似,而是严格恒等式。
只要:
- 权重乘 (s_i);
- 对应激活除以同一个 (s_i);
浮点模型的数学输出完全不变。
5.2 缩放本身零损失
这一点必须强调。
AWQ 的误差不来自缩放。
在无限精度或者普通 FP32 运算中:
原模型输出 = 缩放后模型输出
缩放只是把同一个函数换了一种参数化方式。
类似于:
10 × 3 = 5 × 6
数字变了,乘积没有变。
因此:
AWQ 的等价变换本身没有精度损失。真正产生误差的是后面的权重量化。
5.3 量化让这个恒等式产生了价值
问题在于,量化不是线性运算。
量化后,我们计算的不是:
[
WD
]
而是:
[
Q(WD)
]
最终的近似输出变成:
[
\hat Y_{\text{AWQ}}
XD{-1}Q(WD)\top
]
也可以把它写成一个等效权重:
[
\hat W
Q(WD)D^{-1}
]
然后:
[
\hat Y_{\text{AWQ}}
X\hat W^\top
]
注意:
[
Q(WD)D^{-1}
\neq
Q(W)
]
一般情况下,两者并不相等。
这就是 AWQ 发挥作用的地方。
浮点世界中,(D) 和 (D^{-1}) 完全抵消。
但中间插入非线性的量化函数 (Q) 后,缩放会改变:
- 权重落在哪个整数格点;
- group 的最大值和最小值;
- group 的量化步长;
- 各通道相对于共享范围的位置;
- 最终误差怎样分配。
可以把它概括成一句话:
等价缩放不改变原函数,但会改变量化器看到的数值分布。
5.4 “放大权重就更精确”需要加一个限定
常见解释是:
把重要权重放大后,它在 INT4 网格里占据更多刻度,因此更精确。
这个说法作为直觉没有问题,但要加上三个限定。
限定一:刻度总数没有增加
INT4 仍然只有原来的编码数量。
AWQ 不是为重要通道额外增加 bit。
它改变的是重要通道相对于 group 共享范围的位置。
限定二:真正关心的是除回 (s) 后的有效误差
假设缩放后的权重被量化为:
[
Q(sw)
]
模型中对应的等效权重是:
[
\frac{Q(sw)}{s}
]
所以我们关心的误差是:
[
\frac{Q(sw)}{s}-w
]
而不是只看:
[
Q(sw)-sw
]
即使缩放后权重的绝对量化误差看起来没有下降,除以 (s) 还原到原参数空间后,重要通道的有效误差仍可能明显减小。
限定三:多个通道必须共享量化尺度
如果某一列拥有独立 scale:
[
Q(sw)=sQ(w)
]
那么:
[
\frac{Q(sw)}s=Q(w)
]
完全没有变化。
只有当多个通道共享 group scale 时,缩放才会改变它们对共享动态范围的占用比例。
所以更准确的说法是:
AWQ 通过通道缩放,改变重要权重相对于共享 group scale 的大小,从而降低其还原到原参数空间后的有效量化误差。
5.5 用一个小数值例子理解
假设某个 group 共享步长:
Δ = 0.1
其中一个重要权重:
w = 0.14
直接量化:
0.14 / 0.1 = 1.4
round(1.4) = 1
Q(w) = 0.1
误差:
0.1 - 0.14 = -0.04
现在将它放大 4 倍:
s = 4
sw = 0.56
假设这个缩放没有明显抬高整个 group 的步长,新的步长仍接近:
Δ' ≈ 0.1
那么:
0.56 / 0.1 = 5.6
round(5.6) = 6
Q(sw) = 0.6
除以 4:
Q(sw) / s = 0.6 / 4 = 0.15
有效误差:
0.15 - 0.14 = 0.01
原来误差是:
0.04
现在只有:
0.01
重要权重得到了更细的有效表示。
但这个例子隐含了一个关键假设:
Δ' 没有随着 s 同比例增长
如果缩放后的权重成为 group 中新的极端值,导致:
Δ' = 4Δ
那么收益可能被抵消。
这就引出了 AWQ 中最重要的权衡。
六、从量化误差公式看缩放为什么有效
6.1 直接量化的误差
用简化的对称量化表示:
[
Q(w)
\Delta\cdot
\mathrm{round}\left(\frac{w}{\Delta}\right)
]
定义取整误差:
[
\epsilon
\mathrm{round}\left(\frac{w}{\Delta}\right)
\frac{w}{\Delta}
]
那么:
[
Q(w)-w
\Delta\epsilon
]
乘以激活后,对输出造成的误差为:
[
\Delta y
x\Delta\epsilon
]
忽略符号,只看数量级:
[
|\Delta y|
\propto
|x|\Delta|\epsilon|
]
6.2 缩放后的误差
将权重乘以 (s),激活除以 (s)。
缩放后 group 的量化步长记为:
[
\Delta’
]
缩放权重的量化误差为:
[
Q(sw)-sw
\Delta’\epsilon’
]
除以 (s) 回到原始权重空间:
[
\frac{Q(sw)}s-w
\frac{\Delta’\epsilon’}s
]
对输出造成的误差:
[
\Delta y’
x
\frac{\Delta’\epsilon’}s
]
如果把缩放前后的取整误差分布近似认为相似,那么误差比大致为:
[
\frac{|\Delta y’|}{|\Delta y|}
\approx
\frac{\Delta’}{\Delta}
\cdot
\frac1s
]
这就是 AWQ 缩放分析里最核心的关系。
6.3 最理想的情况
如果缩放一个显著通道,并没有改变 group 的极值,那么:
[
\Delta’\approx\Delta
]
于是:
[
\frac{|\Delta y’|}{|\Delta y|}
\approx
\frac1s
]
假设:
s = 2
有效误差大致减半。
假设:
s = 4
有效误差可能降到四分之一。
这就是为什么放大重要通道能够保护它。
6.4 为什么不能无限放大
如果 (s) 太大,缩放后的显著权重可能开始决定整个 group 的动态范围。
这时:
[
\Delta’>\Delta
]
其他没有获得同等缩放的通道也必须使用更大的量化步长。
结果是:
- 显著通道误差下降;
- 普通通道误差上升;
- group 的总误差可能先降后升。
假设显著通道放大 8 倍,并让 group 步长增大 3 倍。
显著通道的误差比大约是:
[
\frac38
]
仍然受益。
但是普通通道没有除以 8 的补偿,其量化误差可能接近原来的 3 倍。
如果普通通道数量很多,总体输出误差就可能反而变差。
论文的缩放实验也观察到这种现象:继续增大显著通道的缩放,虽然这些通道的相对误差继续下降,但非显著通道受到更大 group scale 的影响,最终模型困惑度会在某个中间缩放处达到最好,而不是缩放越大越好。(ar5iv)
6.5 主要风险不是“自动溢出”
原稿中有一种常见说法:
缩放太大会把权重推出量化范围,导致 clip。
对普通 min-max 权重量化来说,这个表述不够准确。
如果量化器每次都根据缩放后的权重重新计算最小值和最大值,那么权重一般不会因为放大就自动溢出。
量化器会扩大 scale,把新的极值纳入范围。
真正的问题是:
范围变大以后,整个 group 的量化步长变粗。
当然,AWQ 还可能主动搜索 clipping 阈值。
那是另一件事:
- 不使用完整 min-max 范围;
- 故意裁掉少量权重极值;
- 换取更小的量化步长;
- 通过输出 MSE 判断是否值得。
论文在通道缩放之外也加入了基于 MSE 的权重 clipping 搜索。(ar5iv)
所以应当区分:
缩放过大导致 group scale 增大
和:
算法主动选择更窄范围进行 clipping
它们不是同一个机制。
6.6 AWQ 优化的不是普通权重 MSE
这一点非常有深度,也很容易被忽略。
普通权重 MSE 是:
[
|Q(W)-W|_F^2
]
它把所有权重位置看得一样重要。
AWQ 更关心:
[
|XQ(W)\top-XW\top|_F^2
]
也就是层输出 MSE。
因此完全可能出现这种情况:
普通权重 MSE:变大
层输出 MSE:变小
为什么?
因为 AWQ 可能把更多误差“挪”给激活较小的通道,同时减少激活较大通道的误差。
从参数空间看,整体权重误差变大了。
从模型真正使用这些权重的方式看,输出反而更准确。
这正是“activation-aware”的精髓:
重要的不是权重矩阵在欧氏距离上离原矩阵多近,而是它在真实输入分布下产生的函数离原函数多近。
七、缩放系数不是拍脑袋定的:AWQ 如何搜索
7.1 理论上可以为每个通道任意选 (s_i)
我们已经知道,每个输入通道都可以有独立缩放:
[
s_1,s_2,\ldots,s_C
]
最直接的优化问题是:
[
s^*
\arg\min_s
\left|
XD{-1}Q(WD)\top-XW^\top
\right|_F^2
]
其中:
[
D=\mathrm{diag}(s)
]
但这会带来一个问题:
如果隐藏维度是 4096,那么一层就有 4096 个待优化变量。
一个几十层的模型会产生大量参数。
而量化函数中包含 round 和 clip,不可微、分段常数,直接用普通梯度下降并不稳定。
AWQ 没有这样做。
7.2 用激活统计构造低维搜索空间
AWQ 先计算每个通道的激活尺度:
[
a_i=\mathrm{mean}(|X_i|)
]
然后将缩放向量参数化为:
[
s_i=a_i^\alpha
]
其中:
[
\alpha\in[0,1]
]
这样,本来几千维的缩放搜索,被压缩成了一个一维搜索。
当:
[
\alpha=0
]
有:
[
s_i=a_i^0=1
]
即完全不做 activation-aware 缩放。
当:
[
\alpha=1
]
有:
[
s_i=a_i
]
激活幅度被最直接地映射为通道缩放。
当:
[
0<\alpha<1
]
则是在“不缩放”和“激进缩放”之间折中。
论文使用这种由激活幅度和单个指数参数构成的搜索空间,并通过网格搜索选择最佳 (\alpha)。论文实验设置使用了 20 个网格点。(ar5iv)
7.3 原始代码实际怎样搜索
原始 AWQ 代码中的核心逻辑可以概括为:
x_max = mean(abs(x), dim=all_except_channel)
for ratio in grid:
scales = x_max ** ratio
scales = normalize(scales)
weight_scaled = weight * scales
weight_q = quantize(weight_scaled)
weight_effective = weight_q / scales
output_q = block(x, weight_effective)
loss = mse(output_fp, output_q)
选择 loss 最小的 scales
当前原始仓库代码使用 20 个候选比例,并按照:
ratio = 0 / 20
ratio = 1 / 20
...
ratio = 19 / 20
搜索,即:
0.00, 0.05, 0.10, ..., 0.95
代码还会将 (a_i^\alpha) 按最大值与最小值的几何均值做归一化,从而保留通道间的相对比例,同时避免整体数值尺度无意义地偏大或偏小。随后,代码将缩放后的权重做伪量化,再除回通道 scale,用真实模块输出 MSE 选择最优候选。(GitHub)
7.4 为什么只搜索一个 (\alpha) 仍然能产生每通道 scale
这里有个容易混淆的地方。
AWQ 是 per-channel scaling。
但搜索的通常不是:
通道 0 搜一个 α₀
通道 1 搜一个 α₁
通道 2 搜一个 α₂
...
而是一个模块搜索一个公共的指数 (\alpha),然后通过各通道不同的激活幅度产生不同 scale:
[
s_i=a_i^\alpha
]
所以:
- (\alpha) 是低维控制参数;
- (s_i) 仍然是逐通道不同的;
- 激活幅度越大,通常 (s_i) 越大;
- 通道间相对关系由数据决定;
- 整体保护强度由 (\alpha) 控制。
这种设计的好处是:
- 搜索空间小;
- 不需要反向传播;
- 不容易在少量校准数据上过度拟合;
- 仍能表达通道间显著性差异。
7.5 搜索目标为什么是模块输出,而不只是线性层权重
在 Transformer 中,有些线性层是组合工作的。
例如注意力模块中:
同一个 LayerNorm 输出
↓
q_proj、k_proj、v_proj
这些线性层共享同一份输入通道。
若要缩放 LayerNorm 输出,就需要同时调整 q、k、v 三个权重矩阵的对应列。
因此,原始实现会在某些位置一起搜索:
input_layernorm
→ q_proj
→ k_proj
→ v_proj
并计算整个 self-attention 模块的输出误差。
在 MLP 中,也可能一起处理:
post_attention_layernorm
→ gate_proj
→ up_proj
原始仓库针对 Llama、OPT、Qwen 等结构明确指定了哪些前置操作和后续线性层需要共同缩放。(GitHub)
这说明真实 AWQ 并不是机械地对每个 nn.Linear 完全独立处理。
它还需要理解模型拓扑中的等价变换位置。
7.6 逆缩放通常怎样消失在模型里
从公式看,推理时需要计算:
x / s
很多人会担心:
每层多一次逐元素除法,会不会抵消量化加速?
实际实现通常不会真的在每个 token、每个线性层前单独启动一次除法 kernel。
对于:
LayerNorm / RMSNorm → Linear
可以做:
Norm 的权重除以 s
Linear 的输入列乘以 s
因为 Norm 的输出通道会被相应缩小,后面的 Linear 权重再相应放大,整体函数不变。
对于:
Linear₁ → Linear₂
可以把前一个线性层对应的输出行和 bias 除以 (s),再把后一个线性层的输入列乘以 (s)。
原始 AWQ 代码中可以看到针对 LayerNorm 与多个全连接层、前后两个全连接层的参数融合操作。论文也指出逆缩放通常可以融合进前一个操作。(ar5iv)
“通常”二字很重要。
不同架构可能包含:
- 特殊激活函数;
- 并行分支;
- 非标准归一化;
- MoE 路由;
- 共享权重;
- 不规则残差路径。
这些情况需要实现针对模型结构正确放置缩放,而不是随便修改一个线性层。
7.7 AWQ 完整流程
把整个过程串起来,可以得到下面这条主线。
第一步:收集校准激活
拿少量、尽量有代表性的文本跑前向。
缓存各层线性模块的输入激活。
第二步:统计通道尺度
对每个输入通道计算:
[
a_i=\mathrm{mean}(|X_i|)
]
第三步:构造缩放候选
对每个候选 (\alpha):
[
s_i=a_i^\alpha
]
并做必要的数值归一化。
第四步:模拟缩放和量化
构造:
[
\hat W_\alpha
Q(WD_\alpha)D_\alpha^{-1}
]
第五步:计算输出误差
使用校准激活比较:
[
X\hat W_\alpha^\top
]
和:
[
XW^\top
]
选择 MSE 最小的候选。
第六步:搜索 clipping
在需要时,为权重量化范围搜索合适的裁剪比例,避免少数极值把 group scale 拉得过大。
第七步:应用等价变换
把逆缩放融合到前一个 Norm、Linear 或适配的激活模块中。
把正向缩放应用到目标线性层的输入列。
第八步:统一量化权重
所有目标权重最终仍然进入统一的低比特 group-wise 格式。
没有那种:
这一列 FP16
下一列 INT4
再下一列 FP16
的不规则混合布局。
八、用正确的代码验证 AWQ 核心思想
这一节不实现完整 Transformer AWQ。
我们只验证四件事:
- 相同权重误差落在大激活通道上更危险;
- 按激活保护通道比按权重范数保护更有效;
- 在 group-wise 共享 scale 下,等价缩放能降低输出误差;
- AWQ 可能增大普通权重 MSE,却降低真实输出 MSE。
8.1 先看一个错误实验为什么错
原稿按下面方式量化每一列:
for c in range(W.shape[1]):
Wq[:, c] = quantize(W[:, c])
这意味着每列单独计算 scale。
假设第 © 列整体乘以 (s_c)。
该列的最大绝对值也乘以 (s_c),量化 scale 同样乘以 (s_c)。
于是:
Q(W[:, c] × s_c) / s_c
会和:
Q(W[:, c])
相同。
用代码可以直接验证:
import numpy as np
def quantize_per_input_channel_int4(W):
"""每个输入通道,也就是每一列,单独计算量化 scale。"""
Wq = np.empty_like(W, dtype=np.float64)
qmin, qmax = -7, 7
for c in range(W.shape[1]):
col = W[:, c]
scale = np.max(np.abs(col)) / qmax
scale = max(scale, 1e-12)
q = np.clip(np.round(col / scale), qmin, qmax)
Wq[:, c] = q * scale
return Wq
rng = np.random.default_rng(0)
W = rng.normal(0, 0.1, size=(64, 128))
s = np.ones(128)
s[[10, 30, 77]] = 3.0
plain = quantize_per_input_channel_int4(W)
scaled_then_restored = quantize_per_input_channel_int4(W * s[None, :]) / s[None, :]
print(np.max(np.abs(plain - scaled_then_restored)))
输出为:
0.0
这不是 AWQ 无效。
而是量化器的定义让缩放完全被抵消了。
所以接下来必须实现一个真正的 group-wise 量化器。
8.2 实现一个简化的 group-wise INT4 量化器
为了更接近常见 AWQ 格式,下面使用:
- 权重矩阵形状
[out_features, in_features]; - 沿输入维度分组;
- 每个输出行中的连续 32 个权重共享 scale 和 zero point;
- 使用 4-bit 非对称量化;
- 整数编码范围为
[0, 15]。
真实模型中常见 group size 128。
这里使用 32,是为了让小矩阵中存在更多 group,方便观察。
import numpy as np
def quantize_groupwise_int4(W, group_size=32):
"""
简化版 INT4 group-wise 非对称伪量化。
参数
----
W:
二维权重矩阵,形状 [out_features, in_features]。
group_size:
每个输出行中,连续多少个输入权重共享一套 scale / zero point。
返回
----
Wq:
反量化回浮点数后的矩阵。
它用于模拟 INT4 量化误差,不包含真实 bit packing。
"""
W = np.asarray(W, dtype=np.float64)
if W.ndim != 2:
raise ValueError("W 必须是二维矩阵")
out_features, in_features = W.shape
if in_features % group_size != 0:
raise ValueError("in_features 必须能被 group_size 整除")
qmin, qmax = 0, 15
Wq = np.empty_like(W)
for row in range(out_features):
for start in range(0, in_features, group_size):
end = start + group_size
group = W[row, start:end]
min_val = group.min()
max_val = group.max()
scale = (max_val - min_val) / (qmax - qmin)
scale = max(scale, 1e-12)
zero_point = np.round(-min_val / scale)
zero_point = np.clip(zero_point, qmin, qmax)
q = np.round(group / scale) + zero_point
q = np.clip(q, qmin, qmax)
Wq[row, start:end] = (q - zero_point) * scale
return Wq
注意,这个函数返回的是:
量化 → 反量化
后的浮点矩阵。
它模拟量化误差,但没有真正:
- 压缩成 4 bit;
- 做 bit packing;
- 调用 CUDA kernel;
- 测量显存和速度。
8.3 构造一个带显著激活通道的线性层
这次不再只用一个激活向量。
我们模拟 512 个 token 的输入:
rng = np.random.default_rng(0)
out_features = 64
in_features = 128
n_tokens = 512
W = rng.normal(
loc=0.0,
scale=0.08,
size=(out_features, in_features),
)
# 大部分通道的激活标准差是 1。
act_std = np.ones(in_features)
# 人为埋入 3 个显著通道。
planted_salient = np.array([10, 30, 77])
act_std[planted_salient] = 20.0
X = rng.normal(
loc=0.0,
scale=act_std,
size=(n_tokens, in_features),
)
Y = X @ W.T
在这个实验中:
通道 10、30、77
的激活幅度大约是普通通道的 20 倍。
权重矩阵本身并不知道这三列特殊。
这正适合验证:
只看权重,能不能发现它们?
8.4 实验一:按权重保护,还是按激活保护
先把所有权重都做 group-wise INT4 伪量化:
Wq_all = quantize_groupwise_int4(W, group_size=32)
err_none = np.mean(
(Y - X @ Wq_all.T) ** 2
)
然后比较两种保护策略。
策略 A:保护权重范数最大的 3 列
weight_score = np.linalg.norm(W, axis=0)
protect_by_weight = np.argsort(weight_score)[-3:]
策略 B:保护平均激活幅度最大的 3 列
act_score = np.mean(np.abs(X), axis=0)
protect_by_act = np.argsort(act_score)[-3:]
所谓“保护”,在这个动机实验里就是:
- 先把全部权重量化;
- 再将被保护的列恢复成原始浮点权重。
def restore_columns(Wq, W_fp, columns):
restored = Wq.copy()
restored[:, columns] = W_fp[:, columns]
return restored
Wq_weight_protected = restore_columns(
Wq_all,
W,
protect_by_weight,
)
Wq_act_protected = restore_columns(
Wq_all,
W,
protect_by_act,
)
err_weight = np.mean(
(Y - X @ Wq_weight_protected.T) ** 2
)
err_act = np.mean(
(Y - X @ Wq_act_protected.T) ** 2
)
print("人为埋入的显著通道:", planted_salient)
print("按权重范数选出的通道:", protect_by_weight)
print("按激活幅度选出的通道:", protect_by_act)
print()
print(f"不保护任何通道: {err_none:.6f}")
print(f"保护权重范数最大通道: {err_weight:.6f}")
print(f"保护激活幅度最大通道: {err_act:.6f}")
在固定随机种子下,一次结果是:
人为埋入的显著通道: [10 30 77]
按权重范数选出的通道: [83 67 44]
按激活幅度选出的通道: [30 77 10]
不保护任何通道: 0.055171
保护权重范数最大通道: 0.055071
保护激活幅度最大通道: 0.005182
结果非常直观。
按权重范数选择的三列是:
83、67、44
它们没有找到真正的大激活通道。
输出 MSE 几乎没有变化:
0.055171 → 0.055071
而按激活选择准确找到了:
10、30、77
输出 MSE 降到:
0.005182
下降约 90%。
这个实验说明的不是“权重大小永远没用”。
它说明:
当量化误差最终要经过激活传播时,只根据权重范数选择保护对象,可能完全错过最敏感的通道。
8.5 实验二:不保留 FP16,用 AWQ 式缩放
接下来不再恢复任何浮点列。
全部权重都统一进入 INT4 group-wise 量化。
我们计算每个输入通道的平均激活幅度:
act_scale = np.mean(np.abs(X), axis=0)
对每个候选 (\alpha):
[
s_i=a_i^\alpha
]
然后执行:
[
\hat W_\alpha
Q(WD_\alpha)D_\alpha^{-1}
]
代码如下:
def awq_like_quantize(
W,
act_scale,
alpha,
group_size=32,
):
"""
简化的 AWQ 式缩放 + group-wise INT4 伪量化。
"""
scales = np.maximum(act_scale, 1e-4) ** alpha
# 保留通道之间的相对比例,
# 同时把整体尺度大致归一到几何中心附近。
scales = scales / np.sqrt(
scales.max() * scales.min()
)
W_scaled = W * scales[None, :]
Wq_scaled = quantize_groupwise_int4(
W_scaled,
group_size=group_size,
)
# 除回通道 scale,得到在原输入 X 下使用的等效权重。
Wq_effective = Wq_scaled / scales[None, :]
return Wq_effective, scales
搜索 20 个候选:
Wq_plain = quantize_groupwise_int4(
W,
group_size=32,
)
plain_output_mse = np.mean(
(Y - X @ Wq_plain.T) ** 2
)
records = []
for index in range(20):
alpha = index / 20
Wq_awq, scales = awq_like_quantize(
W,
act_scale,
alpha,
group_size=32,
)
output_mse = np.mean(
(Y - X @ Wq_awq.T) ** 2
)
records.append(
{
"alpha": alpha,
"output_mse": output_mse,
"Wq": Wq_awq,
"scales": scales,
}
)
best = min(
records,
key=lambda item: item["output_mse"],
)
print(f"直接 group-wise INT4 MSE:{plain_output_mse:.6f}")
print(f"最佳 alpha: {best['alpha']:.2f}")
print(f"AWQ 式缩放后 MSE: {best['output_mse']:.6f}")
print(
"误差变为原来的: "
f"{best['output_mse'] / plain_output_mse:.1%}"
)
一次固定 seed 的输出为:
直接 group-wise INT4 MSE:0.055171
最佳 alpha: 0.40
AWQ 式缩放后 MSE: 0.016408
误差变为原来的: 29.7%
也就是说,完全没有保留 FP16 权重。
仅仅通过:
激活统计
→ 通道缩放
→ group-wise INT4
→ 除回缩放
输出误差就下降了约 70%。
8.6 为什么最佳 (\alpha) 不是 1
在这个实验里,最佳候选是:
alpha = 0.40
而不是:
alpha = 1.00
原因正是前面讲过的权衡。
当 (\alpha) 增大时:
- 大激活通道获得更强保护;
- 权重列之间的数值差异也被进一步拉大;
- 一些 group 的范围可能被放大;
- 普通通道使用的步长变粗;
- 总误差可能再次上升。
所以 AWQ 不会简单执行:
scales = act_scale
然后结束。
它需要用真实输出误差搜索一个平衡点。
8.7 再验证一个反直觉现象:权重 MSE 可能变差
比较普通量化和最佳 AWQ 候选的权重 MSE:
best_Wq = best["Wq"]
plain_weight_mse = np.mean(
(W - Wq_plain) ** 2
)
awq_weight_mse = np.mean(
(W - best_Wq) ** 2
)
plain_output_mse = np.mean(
(Y - X @ Wq_plain.T) ** 2
)
awq_output_mse = np.mean(
(Y - X @ best_Wq.T) ** 2
)
print(f"普通量化的权重 MSE:{plain_weight_mse:.8f}")
print(f"AWQ 量化的权重 MSE:{awq_weight_mse:.8f}")
print()
print(f"普通量化的输出 MSE:{plain_output_mse:.6f}")
print(f"AWQ 量化的输出 MSE:{awq_output_mse:.6f}")
结果为:
普通量化的权重 MSE:0.00004094
AWQ 量化的权重 MSE:0.00005960
普通量化的输出 MSE:0.055171
AWQ 量化的输出 MSE:0.016408
AWQ 的普通权重 MSE反而更大:
0.00004094 → 0.00005960
但是输出 MSE 明显更小:
0.055171 → 0.016408
这组结果特别值得反复体会。
AWQ 不一定让整个权重矩阵在欧氏距离上更接近原矩阵。
它做的是:
- 减少大激活通道上的误差;
- 允许小激活通道承担更多误差;
- 让误差落在模型不那么敏感的位置;
- 最终使真实层输出更接近原模型。
一句话总结:
好的量化不等于权重误差最平均,而等于误差分布最符合模型的使用方式。
8.8 这个实验验证了什么,又没有验证什么
这个小实验验证了:
- 通道量化误差会被激活幅度加权;
- 激活统计可以定位敏感通道;
- group-wise 共享 scale 是缩放产生收益的必要背景;
- activation-aware scaling 可以降低输出误差;
- 最佳缩放需要在显著与非显著通道之间平衡;
- 输出 MSE 和普通权重 MSE不是一回事。
但它不是完整 AWQ,因为它没有:
- 真实 Transformer 模块;
- q、k、v 联合缩放;
- LayerNorm 或 RMSNorm 参数融合;
- clipping 搜索;
- 逐层传播量化误差;
- 真实 INT4 packing;
- CUDA、ROCm 或其他低比特 kernel;
- 语言模型困惑度评测;
- 下游任务评测。
因此,它适合验证核心原理,不适合直接作为生产量化器。
九、AWQ 与 GPTQ 横向对比
GPTQ 和 AWQ 经常被描述成两种完全相反的哲学:
GPTQ:量化后补偿
AWQ:量化前保护
这个比喻很好记,但不够完整。
先看更准确的对比。
| 维度 | GPTQ | AWQ |
|---|---|---|
| 基本目标 | 减小量化后层输出重构误差 | 用激活感知缩放重新分配量化误差 |
| 主要信息 | 校准激活形成的近似二阶信息 | 通道级激活幅度及候选输出 MSE |
| 核心操作 | 顺序量化,并更新尚未量化的权重以补偿误差 | 先做等价通道缩放,再统一量化 |
| 是否使用 Hessian 近似 | 是 | 否 |
| 是否需要校准数据 | 是 | 是 |
| 是否需要反向传播 | 通常不需要训练式反传 | 不需要 |
| 是否做层重构 | 是,核心就是近似重构 | 搜索候选输出误差,但不做 GPTQ 式权重补偿 |
| 最终权重格式 | 通常为统一低比特 group-wise 格式 | 通常为统一低比特 group-wise 格式 |
| 是否必须混合精度 | 否 | 否 |
| 主要风险 | 二阶近似、顺序与实现细节影响结果 | 缩放过强会抬高 group scale,校准分布也会影响通道统计 |
| 能否组合 | 可以 | 可以 |
GPTQ 原论文将其描述为基于近似二阶信息的一次性权重量化方法,通过高效算法把这种补偿扩展到大规模生成模型。(arXiv)
AWQ 则明确避免反向传播和 GPTQ 式回归重构,使用激活统计构造通道缩放,并通过小规模网格搜索最小化模块输出差异。(ar5iv)
9.1 GPTQ 更像“调整误差”
GPTQ 的核心问题是:
当我把当前权重量化到附近格点以后,怎样调整其他还没量化的权重,使整体输出变化最小?
它利用校准输入构造近似 Hessian,并根据二阶信息传播和补偿误差。
因此 GPTQ 更像在量化过程中不断回答:
这里已经产生了误差,
剩余自由度怎样配合才能补回来?
9.2 AWQ 更像“改变误差发生的位置”
AWQ 的核心问题是:
在量化之前,能不能先改变权重的参数化,让量化器天然把更小的有效误差分配给重要通道?
它不直接逐个修改剩余权重来抵消当前误差。
它先改变数值分布,让同一个 group quantizer 产生一种更有利的误差分配。
所以 AWQ 更像在回答:
既然误差不可避免,
怎样让误差优先落在不敏感的位置?
9.3 两者不是严格互斥
一个很重要但经常被忽略的事实是:
AWQ 缩放可以作为其他量化算法之前的重参数化步骤。
原论文在极低比特 INT2 实验中,将 AWQ 与 GPTQ 组合使用,并报告组合方法进一步改善了 GPTQ 的量化结果。(ar5iv)
所以更准确的理解是:
- GPTQ 是一种误差补偿和重构策略;
- AWQ 是一种 activation-aware 的通道重参数化策略;
- 两者作用位置不同;
- 在某些流水线中可以叠加。
9.4 谁的精度一定更好
没有脱离具体条件的绝对答案。
结果会受到很多因素影响:
- 模型架构;
- bit 数;
- group size;
- 对称或非对称量化;
- 是否启用 clipping;
- GPTQ 是否启用重排;
- 校准数据;
- 实现版本;
- 评测任务;
- 是否量化 MoE 专家、LM head 等模块。
AWQ 论文在其 LLaMA、Llama 2 和 OPT 等实验设置中报告了优于 RTN、并在多项结果上优于论文所用 GPTQ 基线的表现,但这些结果应理解为特定论文设置下的实验结论,而不是所有现代实现中的永久排名。(ar5iv)
因此,实际选型时不要只问:
AWQ 和 GPTQ 谁更强?
而应该问:
在我的模型、硬件、kernel、group size 和任务上,
哪个格式的精度、速度、显存与兼容性更合适?
9.5 谁量化得更快
从算法结构上看,AWQ 的核心搜索很简单:
- 统计激活均值;
- 构造少量 (\alpha) 候选;
- 做伪量化;
- 比较输出 MSE。
它不需要 GPTQ 那样的二阶矩阵处理和顺序误差更新。
因此从算法复杂度和实现负担看,AWQ 往往较轻。
但“实际量化多少分钟”仍取决于:
- 是否整模型同时驻留显存;
- 是否逐层 offload;
- 校准 token 数;
- 模型大小;
- GPU 和 CPU;
- 实现是否缓存激活;
- 是否同时搜索 clipping;
- 是否进行真实权重打包。
所以最好不要写成:
AWQ 一定比 GPTQ 快多少倍。
更稳妥的说法是:
AWQ 的优化形式通常更轻量,但实际墙钟时间由实现和硬件决定。
9.6 谁推理更快
量化算法决定“权重怎样得到”。
推理速度则主要由另外一组因素决定:
- 量化格式;
- 权重 packing 布局;
- kernel 是否支持;
- 解包与反量化是否融合;
- GPU 架构;
- batch size;
- prompt 长度;
- prefill 或 decode;
- 模型是否受显存带宽限制;
- 是否发生 CPU offload。
一个 AWQ 模型如果落到不合适的通用 kernel 上,可能并不快。
一个 GPTQ 模型如果使用高度优化的 Marlin、ExLlama 或其他内核,也可能非常高效。
所以:
AWQ 是量化算法,不是单独的速度保证书。
十、从论文原理到工程实现,中间还有哪些细节
10.1 激活仍然是高精度
再强调一次:
AWQ 中的:
x / s
不是在说激活也变成 INT4。
典型 AWQ 是 weight-only 量化。
激活通常仍保持 FP16 或 BF16。
缩放往往被融合到前一个操作中,不需要把每个激活张量永久保存成另一种低比特格式。
因此更准确的描述是:
低比特存权重
高精度保激活
用融合 kernel 完成反量化与计算
10.2 “统一 INT4”也不是所有模型参数一律 4 bit
工程实现中通常不会把模型里的每个浮点数都变成 INT4。
常见的例外可能包括:
- LayerNorm 或 RMSNorm 参数;
- bias;
- embedding;
- LM head;
- 很小的辅助模块;
- 路由器;
- 某些不受支持的层;
- 量化 scale 和 zero point;
- KV cache。
所以“AWQ 全程统一 INT4”应理解为:
被量化的目标线性权重采用统一低比特格式,不需要在同一权重矩阵里稀疏混入 FP16 显著权重。
不是说模型文件中的每一个参数和中间张量都是 4 bit。
10.3 模型大小也不会恰好缩小四倍
理想情况下:
FP16:每权重 16 bit
INT4:每权重 4 bit
权重主体可以缩小到四分之一。
但真实格式还需要保存:
- 每组 scale;
- zero point;
- 张量元数据;
- 对齐填充;
- 未量化参数;
- tokenizer 和配置;
- 可能的额外 permutation 信息。
因此真实模型大小通常不会严格等于:
原大小 ÷ 4
更准确地说,是:
权重主体接近四倍压缩,最终文件与显存节省会略低于理想值。
10.4 group size 是精度和元数据之间的交换
group size 越小:
- 每组权重更少;
- scale 更贴合局部分布;
- 通常精度更好;
- scale、zero point 数量更多;
- 元数据和处理开销增加。
group size 越大:
- 元数据更少;
- 格式更紧凑;
- 更多权重竞争同一个动态范围;
- 量化误差通常更大;
- activation-aware scaling 的作用也可能更明显。
因此:
g32
g64
g128
不是简单的“数字越大越高级”。
它们代表不同的精度、压缩和 kernel 权衡。
10.5 校准数据不需要很大,但不能完全乱选
AWQ 只统计通道级激活幅度,并进行低维网格搜索。
相比需要更强层重构的方法,它对校准数据量通常没有那么高的要求。
AWQ 论文在一项 OPT-6.7B、INT3-g128 分析中报告,AWQ 用 16 个序列就取得了优于使用 192 个序列的 GPTQ 结果;论文还在 PubMed 与 Enron 数据分布交叉校准实验中观察到 AWQ 的困惑度变化更小。(ar5iv)
但这不代表校准数据可以毫无关系。
如果你量化的是:
- 代码模型;
- 数学模型;
- 中文模型;
- 医疗领域模型;
- 超长上下文模型;
- 多模态模型;
- thinking 模型;
最好仍然让校准数据覆盖真实使用中的主要模式。
因为 AWQ 在估计的是:
哪些输入通道在实际分布下经常较大
如果校准分布和部署分布差距极大,这个排序当然可能改变。
10.6 大激活不等于单次离群点
AWQ 通常统计的是:
每个通道在校准数据上的平均绝对幅度
因此它关注的是通道级、分布级现象。
一个通道偶尔出现一次极端值,不一定会成为最显著通道。
一个通道即使没有惊人的单次峰值,但长期保持较大幅度,也可能拥有较高的平均激活尺度。
所以:
AWQ = 处理激活离群值
不够准确。
更完整的说法是:
AWQ 使用通道级激活统计估计权重量化敏感度;激活离群通道可能是其中的重要来源,但二者不是完全同义。
10.7 缩放因子不一定全部大于 1
为了方便解释,我们常说:
重要通道 s > 1
但实际实现可能会对整个 scale 向量做归一化。
例如:
[
s_i
\leftarrow
\frac{s_i}
{\sqrt{s_{\max}s_{\min}}}
]
归一化后:
- 一部分通道可能大于 1;
- 一部分通道可能小于 1;
- 关键是通道之间的相对比例。
所以“保护重要通道”的本质不是:
所有重要通道绝对值必须乘一个大于 1 的数
而是:
重要通道相对于其他通道,在共享量化范围中的权重被提高。
10.8 缩放不是额外存一份运行时向量那么简单
在理论表达中,我们写:
W × s
x ÷ s
工程实现中,最终变换可能被吸收到:
- RMSNorm 的 gamma;
- LayerNorm 的 weight 和 bias;
- 前一个 Linear 的输出行;
- 后一个 Linear 的输入列;
- 特殊的 scaled activation 包装;
- 已经重新参数化的权重文件。
所以打开一个 AWQ 模型时,你不一定会看到每层显式执行:
x = x / awq_scale
scale 可能已经“烘焙”进相邻参数。
10.9 当前工具链需要更新
原稿最后写:
下一篇用 autoawq 跑起来。
这一句在当前环境下需要修改。
AutoAWQ 仓库已于 2025 年 5 月 11 日归档,并明确声明不再维护;其说明将 vLLM 项目的 LLM Compressor 列为替代方向之一。(GitHub)
截至当前,vLLM LLM Compressor 提供了 AWQ 量化示例和文档,用校准数据生成缩放并导出可供 vLLM 使用的量化模型。(GitHub)
因此系列文章的下一篇更建议写成:
下一篇使用当前仍在维护的 AWQ 工具链完成真实模型量化,并验证显存、困惑度和推理兼容性。
可以选择:
- 原始
mit-han-lab/llm-awq; - vLLM LLM Compressor;
- 目标推理框架官方支持的其他 AWQ 工具。
若出于复现旧教程仍使用 AutoAWQ,需要明确标注:
该项目已归档
需要固定兼容版本
不建议作为新的长期生产依赖
十一、常见误区与避坑
误区一:AWQ 最终会把 1% 权重保留成 FP16
不对。
保留少量 FP16 权重是论文用来证明“显著通道确实存在”的动机实验。
最终 AWQ 使用等价缩放,让目标权重统一进入低比特格式。
误区二:AWQ 会先找出固定的 top 1%
不对。
最终 AWQ 通常使用连续通道尺度:
[
s_i=a_i^\alpha
]
不是简单的 top-k 二值选择。
1% 是论文观察,不是固定实现规则。
误区三:AWQ 的“activation-aware”表示激活也被量化
不对。
典型 AWQ 是 weight-only:
W4A16
激活通常仍是 FP16 或 BF16。
“activation-aware”是说量化权重时参考了激活统计。
误区四:看激活意味着完全不看权重
不对。
激活用于构造显著性和缩放候选。
但 group scale、量化误差、clipping、输出 MSE 都和权重有关。
误区五:权重大的通道一定重要
不一定。
真正的输出误差近似取决于:
[
e_{ji}x_i
]
权重大小只会间接影响量化误差。
对应激活是否经常较大,同样关键。
误区六:激活越大,就应该无限放大权重
不对。
缩放过强可能抬高整个 group 的量化步长,使大量普通通道变得更粗糙。
所以 AWQ 要搜索折中点。
误区七:缩放太大会自然把权重 clip 掉
不一定。
如果量化器重新根据缩放后的 min/max 计算范围,主要结果是 scale 变大,而不是自动溢出。
主动 clipping 是另一个经过搜索的操作。
误区八:缩放本身是有损近似
不对。
[
XW^\top
(XD{-1})(WD)\top
]
是严格恒等式。
误差只来自插入的量化函数:
[
Q(WD)
]
误区九:把重要权重放大,相当于增加了 INT4 的 level 数量
不对。
INT4 仍然只有原来的编码数。
AWQ 改变的是不同通道对共享量化范围的相对占用。
误区十:任何量化粒度下缩放都有效
不对。
如果每个被缩放的通道拥有独立 scale,缩放可能被严格抵消:
[
Q(sw)/s=Q(w)
]
AWQ 的收益必须结合 group-wise 共享尺度理解。
误区十一:验证 AWQ 时按每列单独量化就行
不行。
那会把 AWQ 最关键的共享量化网格拿掉。
正确的最小实验应至少实现:
多个输入通道权重共享一个 group scale
误区十二:AWQ 一定会降低普通权重 MSE
不一定。
AWQ 优化的是激活分布下的输出误差。
它可能主动把更多权重误差分配给小激活通道。
因此普通权重 MSE 可能上升,输出 MSE 却下降。
误区十三:AWQ 不需要校准数据
不对。
它需要校准数据来:
- 收集激活统计;
- 搜索 (\alpha);
- 计算候选输出误差;
- 搜索 clipping。
它属于 PTQ,不是 data-free quantization。
误区十四:AWQ 一定比 GPTQ 更准
不对。
论文结果、现代实现、模型架构、group size 和评测任务都会影响结论。
最可靠的方法仍然是在目标任务上实际评测。
误区十五:AWQ 一定比 FP16 推理快四倍
不对。
四倍是权重 bit 数的理想压缩比例,不是端到端速度保证。
实际速度取决于:
- kernel;
- packing;
- 硬件;
- batch;
- prefill/decode;
- 是否受带宽限制;
- 框架调度。
十二、怎样用一分钟把 AWQ 讲给别人听
可以直接这样复述:
AWQ 是一种激活感知的低比特权重量化方法。它发现权重的量化误差是否危险,不只取决于权重本身,还取决于这个误差会和多大的输入激活相乘。激活较大的输入通道,会把对应权重列的量化误差明显放大。
直接把这些显著权重保留成 FP16 虽然有效,但会产生硬件不友好的混合精度。AWQ 因此使用等价缩放:把重要通道的权重相对放大,同时把对应激活相对缩小。浮点模型输出完全不变,但在 group-wise INT4 量化中,这会改变各通道对共享量化范围的占用,让重要通道获得更小的有效量化误差。
缩放不是越大越好,因为过强缩放会抬高整个 group 的量化步长,伤害其他通道。所以 AWQ 根据校准激活构造 (s_i=a_i^\alpha),通过少量网格搜索选择输出 MSE 最小的 (\alpha),最后把缩放融合进相邻层,并对权重统一做低比特量化。
如果只能记住五个关键词,就记:
激活决定敏感度
通道级显著性
等价缩放
共享 group scale
输出误差搜索
再压缩成一句话
AWQ 根据激活幅度判断哪些输入通道会放大量化误差,再利用输出等价的通道缩放,重新分配 group-wise INT4 的有限分辨率,使重要通道更精确,而不引入混合精度。
十三、小结
这一篇真正需要吃透的,不是某一行代码,而是下面几层逻辑。
第一,权重量化误差不是最终损失。
真正影响模型的是:
[
\Delta Y=XE^\top
]
权重误差会被激活重新加权。
第二,“重要权重”不是完全静态的属性。
同一个权重是否敏感,取决于它在真实输入分布下对应的激活通道。
因此,AWQ 用校准数据统计通道级激活幅度。
第三,论文中的 0.1%~1% FP16 是动机实验,不是最终算法。
这个实验说明少量激活显著通道承担了不成比例的量化敏感度。
但直接混合 FP16 和 INT4 不利于高效内核。
第四,最终 AWQ 依赖等价缩放:
[
XW^\top
(XD{-1})(WD)\top
]
缩放本身完全无损。
只有在中间加入量化函数后:
[
XD{-1}Q(WD)\top
]
它才改变最终量化误差。
第五,AWQ 的收益依赖共享量化尺度。
如果每列完全独立量化:
[
Q(sw)/s=Q(w)
]
缩放可能没有任何作用。
AWQ 必须放在 group-wise 量化中理解:
多个通道竞争同一套量化参数,缩放才能重新分配共享分辨率。
第六,缩放过强会伤害其他通道。
显著通道得到保护的同时,group scale 可能变大。
所以 AWQ 不直接令:
s = activation
而是构造:
[
s_i=a_i^\alpha
]
再搜索输出误差最小的 (\alpha)。
第七,AWQ 优化的是函数误差,而不是普通权重误差。
因此完全可能:
权重 MSE 上升
输出 MSE 下降
这不是异常,而是它成功把误差移动到了不敏感位置。
第八,AWQ 和 GPTQ 不是简单的新旧替代关系。
GPTQ 利用近似二阶信息补偿量化误差。
AWQ 通过激活感知缩放改变误差分布。
它们的作用机制不同,甚至可以组合。
AWQ 最漂亮的地方,不只是用了一个小学数学恒等式。
真正漂亮的是它抓住了一个更深的原则:
模型压缩不应该平均地保护所有参数,而应该根据参数在真实数据分布中的作用,分配有限的数值精度。
从这个角度看,AWQ 做的并不是单纯“把某些权重变大”。
它是在回答一个更普遍的问题:
当表示能力有限时,哪些误差值得花资源消除,哪些误差可以被允许存在?
这也是理解低比特量化最重要的一步。
更多推荐




所有评论(0)