4.2 W8A8 量化与 SmoothQuant:驯服 Activation Outlier
为什么直接 W8A8 会崩?SmoothQuant 如何用数学等价变换把量化难度从激活转移到权重,实现无损的 INT8 推理
上一节我们说过,W8A8 量化能让 GEMM 全面在 INT8 张量核上运行,算力收益最大。但这里有一个绕不开的拦路虎:权重好量化,激活难量化。直接把权重和激活都量化到 INT8,模型质量会断崖式下跌——OPT-175B 的平均任务准确率从 FP16 的 66.9% 掉到 35.5%,几乎等于模型失效。罪魁祸首是激活里的 Outlier(离群值):少数通道上的值比其它通道大 100 倍,把量化格子全抢走了。
SmoothQuant 是 MIT 韩松团队 2022 年提出的解法:通过一个数学上完全等价的变换,把量化的难度从激活端”搬”到权重端——激活的 Outlier 被抹平了,权重虽然变难量化一点,但权重的分布天生均匀,完全扛得住。这一节把它的洞察、变换公式和工程细节彻底讲透。
📑 目录
- 1. 直接 W8A8 为什么崩:Activation Outlier
- 2. 此前的解法:LLM.int8() 的混合精度路线
- 3. 核心洞察:把量化难度从激活搬到权重
- 4. 数学变换:SmoothQuant 的等价公式
- 5. 迁移强度 α:怎么把握”搬多少”
- 6. 工程实现:scale 如何零开销落地
- 7. 校准流程与效果数据
- 8. 现代视角:SmoothQuant 的遗产与 FP8 时代
- 总结
- 自我检验清单
- 参考资料
1. 直接 W8A8 为什么崩:Activation Outlier
1.1 Outlier 长什么样
先看数据。研究者对 LLM 每一层线性层的激活做了统计,发现三个规律:
- 激活比权重难量化得多:权重的分布均匀、平缓,像一片低矮的丘陵;激活则绝大多数值很小,但存在少数巨大的 Outlier,大出约 100 倍。
- Outlier 集中在固定通道:如果一个通道上出现了 Outlier,那么几乎所有 Token 在这个通道上都是 Outlier——不是随机冒出来的噪声,而是”这条通道天生就大”。
- Outlier 只占极少数通道:大约 1% 的通道贡献了绝大部分的数值范围。
1.2 Outlier 如何毁掉量化
回到第 4.1 节的公式:Per-tensor 量化时,整个张量共用一个 scale,而 scale 由最大值决定。设想一个通道的最大值是 100,其余通道只有 1:
- scale 被 outlier 通道拉大到能覆盖 100
- 对于其余通道,一个格子代表 ,而它们的数值大多在 之间——有效量化档位只有 2-3 个,等于把 8 位量化降级成了 1-2 位
数学上,通道 的有效档位数是 ( 是通道 的最大值, 是全局最大值)。当 时,有效档位少得可怜,量化误差巨大。
📌 关键点:激活量化难的根源不是”舍入误差”,而是截断误差的镜像——Outlier 稀释了格子。误差的分配极不均衡:少数大值抢走了几乎所有表示精度,绝大多数正常值被挤到几个粗格子里。
2. 此前的解法:LLM.int8() 的混合精度路线
在 SmoothQuant 之前,Dettmers 等人 2022 年的 LLM.int8() 已经注意到了同样的问题,并给出了一条路线:
- 检测出激活中的 outlier 通道(那些值明显大于阈值 的通道)
- 这些通道保持 FP16,其余通道量化到 INT8,分别做矩阵乘法再合并
LLM.int8() 的精度很好(OPT-175B 平均 66.7% vs FP16 66.9%),但它有一个致命缺陷:
- 混合精度意味着每层要分别跑一个 FP16 GEMM 和一个 INT8 GEMM,两个 Kernel 都要维护
- 两个 Kernel 之间需要同步、拼接、额外访存,张量核的利用率大打折扣
- 对硬件(尤其是张量核)非常不友好,实际加速远低于理论值
💡 提示:LLM.int8() 在 HuggingFace 生态里至今仍有使用(bitsandbytes 库),主要用于显存受限的本地部署,但它”保住精度靠混合精度”的思路,并不是硬件厂商想要的 W8A8 形态。
3. 核心洞察:把量化难度从激活搬到权重
SmoothQuant 的关键洞察建立在一条不对称性上:
- 权重是均匀分布,很好量化——此前已有大量工作证明 LLM 权重量到 INT8 甚至 INT4 都几乎不掉点(第 4.3 节的 GPTQ/AWQ 就是基于这一点)
- 激活有 outlier,很难量化——但激活的 outlier 是固定的少数通道
那么问题就变成了:能不能让激活在量化之前”变平滑”?SmoothQuant 的回答是——能,而且不用改变计算本身。
数学上,对一个线性层 ( 是激活, 是权重),我们可以在中间插入一个对角矩阵 及其逆:
这个变换数学上完全等价,但物理意义完全不同:
- 左边 :激活的每个通道被缩放,outlier 通道被压小,整体变得平滑,容易量化
- 右边 :权重对应的通道被放大,变得不那么均匀、难度增加
于是我们获得了对”量化难度”的自由度: 怎么选,决定了难度在激活和权重之间怎么分配。选得好,两边都不难量化。
🔑 核心概念:SmoothQuant = 通过数学等价变换,把激活的量化难度”搬”一部分到权重上,让两边都落在各自好量化的区间。 变换是精确的(无信息损失),损失的只有”难度分配”。
4. 数学变换:SmoothQuant 的等价公式
那么 具体怎么取?直觉上,每个通道 的 scale 应该正比于该通道激活的最大值 ——outlier 越大的通道被压得越多:
分析两个极端:
- :,等于把难度全部推给激活(激活被放大)——激活量化崩
- :,等于把难度全部推给权重(outlier 通道的权重被放大)——权重量化崩
- :按激活和权重的最大值等比分配难度,让变换后”激活通道与权重通道的尺度相当”,两边都好量化
📌 关键点: 叫做迁移强度(Migration Strength)。SmoothQuant 论文在 OPT/BLOOM 全系列上验证, 是一个普适的甜蜜点;对激活 outlier 特别严重的模型(如 GLM-130B),可以调到 0.75。 的有效区间是 0.4~0.6,超出这个区间两边会有一边崩掉。
5. 迁移强度 α:怎么把握”搬多少”
5.1 为什么 α 有甜蜜点
直观理解:激活的 outlier 通道需要被压小( 作用于激活),但权重被放大的部分如果超出了它自身的量化范围,权重端也会出现 outlier。所以:
- 太小 → 激活端仍难量化 → 激活误差主导
- 太大 → 权重端被”人为制造”出 outlier → 权重误差主导
- 附近 → 两端误差同时处于低位
5.2 实际怎么定
论文的实践是:从预训练数据(Pile)里抽 512 条句子做校准,在 上做一个快速的网格搜索(实验对比不同 在验证集上的困惑度),选最好的值。整个流程是一次性的离线步骤,搜索成本很低。
6. 工程实现:scale 如何零开销落地
确定了之后,还有一个工程问题:变换引入了额外的逐通道缩放,如果运行时每个 GEMM 前都乘一个对角阵,这本身就是开销。SmoothQuant 的处理非常巧妙——把 直接融进前一层。
看 Transformer 的结构:线性层的输入 通常来自前一个 LayerNorm 的输出(或前一个 GEMM 的输出)。以 LayerNorm 为例:
- 前一层:
- 变换后:
由于 LayerNorm 的输出再乘 ,等价于把 折进 LayerNorm 的权重()和偏置():
所有缩放都发生在离线阶段:重新计算一遍 LayerNorm 的 、,以及每层权重的 scale 融合,导出”平滑后的模型权重”。运行时 Kernel 完全是标准的 INT8 GEMM,一个额外的乘子都没有。
💡 提示:这一”变换免费融入前一层”的手法后来被反复复用——AWQ 的 scale 也是这么融的(第 4.3 节),包括 NVIDIA TensorRT 的 SmoothQuant 实现,套路完全一致。
7. 校准流程与效果数据
7.1 完整流程
- 加载 FP16 模型,跑 512 条 Pile 校准句子
- 统计每层激活的 per-channel 最大值 (激活统计只能靠校准数据估计,因为激活是动态的)
- 按公式计算每层平滑因子 ( 经网格搜索确定)
- 离线融合:更新 LayerNorm 权重,更新 GEMM 权重
- 导出模型:权重按 per-channel 静态量化,激活的 scale 用校准统计(静态)或在运行时动态计算(per-token)
论文提供了三个渐进的量化档位:O1(per-tensor 激活量化,最简)、O2(per-channel 激活量化,精度更好但 Kernel 要求高)、O3(+ 对 attention 的 BMM 也量化,最激进)。
7.2 效果数据(OPT-175B,7 个零样本任务平均准确率)
| 方案 | 平均准确率 | 说明 |
|---|---|---|
| FP16 | 66.9% | 基线 |
| 直接 W8A8 | 35.5% | 无 SmoothQuant 直接量化 → 崩 |
| LLM.int8() | 66.7% | 混合精度,精度好但硬件不友好 |
| SmoothQuant-O1/O2/O3 | 66.5% / 66.4% / 66.8% | 与 FP16 几乎无差 |
- 端到端推理 最高 1.56x 加速,显存占用减半(2x 节省)
- 首次让 530B(MT-NLG-530B)模型在单节点内完成服务
- WikiText 困惑度与 FP16 几乎持平(11.11 vs 10.99)
8. 现代视角:SmoothQuant 的遗产与 FP8 时代
SmoothQuant 发表于 2022 年,但它的思想至今是 W8A8 量化的基石,并且以两条线延续:
8.1 INT8 线:SmoothQuant 原封不动
llm-compressor(vLLM 官方推荐工具链)的 INT8 W8A8 方案、Intel/AMD 的 INT8 推理栈,基本都是 SmoothQuant 或其变体的工程化——平滑变换 + per-channel 权重 scale + per-token 动态激活 scale。
8.2 FP8 线:结构更松弛,但思想同源
FP8 的数值范围比 INT8 大得多(详见 4.5 节),很多模型直接做 W8A8 FP8(round-to-nearest,RTN)就能保住精度,不再需要显式的平滑变换。但 FP8 的**动态激活量化(per-token per-channel)**本质还是 SmoothQuant 那套”激活尺度不能按全局算”的思想。
8.3 一个重要的认识
SmoothQuant 的贡献并不只是那一条公式,而是确立了两个范式:
- 量化难度是可以”搬运”的——权重和激活之间可以通过等价变换重新分配表示负担
- 校准数据是激活量化的必要输入——激活是动态的,必须用统计方法估计其分布(这个思想贯穿后面 KV Cache 量化的 scale 校准)
💡 提示:想验证 SmoothQuant 的效果,可以直接用 llm-compressor 对模型做 INT8 W8A8 量化(
QuantizationModifier(scheme="W8A8-INT8")),再在 vLLM 里加载对比困惑度与吞吐。
📝 总结
- 激活 Outlier:约 1% 的固定通道数值大出 100 倍,per-tensor 量化时把格子稀释到 2-3 档,直接 W8A8 崩盘
- LLM.int8() 的路线:outlier 通道留 FP16,精度好但混合精度对硬件不友好
- SmoothQuant 的核心:,数学等价变换把量化难度从激活搬到权重
- 迁移强度 α:, 是普适甜蜜点(有效区间 0.4~0.6)
- 零开销落地:scale 离线融进 LayerNorm 的 γ/β 与前一层,运行时是标准 INT8 GEMM
- 效果:OPT-175B 平均 66.4-66.8%(FP16 66.9%),最高 1.56x 加速、2x 显存节省
- 遗产:难度搬运 + 校准统计,这两点贯穿了后面所有量化算法
🎯 自我检验清单
- 能不能解释”为什么权重好量化、激活难量化”?
- 能不能推导 SmoothQuant 的等价变换公式?
- α 的两个极端(0 和 1)分别会发生什么?
- 为什么 scale 融合进 LayerNorm 能做到”零运行时开销”?
- 直接 W8A8、LLM.int8()、SmoothQuant 三者的精度与硬件友好度对比?
- FP8 时代为什么很多模型不再需要显式平滑?
📚 参考资料
- Xiao et al., SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models(https://arxiv.org/abs/2211.10438)
- Dettmers et al., LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale(https://arxiv.org/abs/2208.07339)
- vLLM LLM Compressor 文档:INT8 W8A8(https://docs.vllm.ai/en/latest/features/quantization/llm_compressor/int8_w8a8/)