跳到主要内容
推理优化

4.2 W8A8 量化与 SmoothQuant:驯服 Activation Outlier

为什么直接 W8A8 会崩?SmoothQuant 如何用数学等价变换把量化难度从激活转移到权重,实现无损的 INT8 推理

SmoothQuantW8A8量化Outlier激活量化INT8

上一节我们说过,W8A8 量化能让 GEMM 全面在 INT8 张量核上运行,算力收益最大。但这里有一个绕不开的拦路虎:权重好量化,激活难量化。直接把权重和激活都量化到 INT8,模型质量会断崖式下跌——OPT-175B 的平均任务准确率从 FP16 的 66.9% 掉到 35.5%,几乎等于模型失效。罪魁祸首是激活里的 Outlier(离群值):少数通道上的值比其它通道大 100 倍,把量化格子全抢走了。

SmoothQuant 是 MIT 韩松团队 2022 年提出的解法:通过一个数学上完全等价的变换,把量化的难度从激活端”搬”到权重端——激活的 Outlier 被抹平了,权重虽然变难量化一点,但权重的分布天生均匀,完全扛得住。这一节把它的洞察、变换公式和工程细节彻底讲透。

📑 目录


1. 直接 W8A8 为什么崩:Activation Outlier

1.1 Outlier 长什么样

先看数据。研究者对 LLM 每一层线性层的激活做了统计,发现三个规律:

  1. 激活比权重难量化得多:权重的分布均匀、平缓,像一片低矮的丘陵;激活则绝大多数值很小,但存在少数巨大的 Outlier,大出约 100 倍
  2. Outlier 集中在固定通道:如果一个通道上出现了 Outlier,那么几乎所有 Token 在这个通道上都是 Outlier——不是随机冒出来的噪声,而是”这条通道天生就大”。
  3. Outlier 只占极少数通道:大约 1% 的通道贡献了绝大部分的数值范围。

1.2 Outlier 如何毁掉量化

回到第 4.1 节的公式:Per-tensor 量化时,整个张量共用一个 scale,而 scale 由最大值决定。设想一个通道的最大值是 100,其余通道只有 1:

  • scale 被 outlier 通道拉大到能覆盖 100
  • 对于其余通道,一个格子代表 1001270.79\frac{100}{127} \approx 0.79,而它们的数值大多在 [0,1][0, 1] 之间——有效量化档位只有 2-3 个,等于把 8 位量化降级成了 1-2 位

数学上,通道 ii 的有效档位数是 28mim2^8 \cdot \frac{m_i}{m}mim_i 是通道 ii 的最大值,mm 是全局最大值)。当 mimm_i \ll m 时,有效档位少得可怜,量化误差巨大。

📌 关键点:激活量化难的根源不是”舍入误差”,而是截断误差的镜像——Outlier 稀释了格子。误差的分配极不均衡:少数大值抢走了几乎所有表示精度,绝大多数正常值被挤到几个粗格子里。


2. 此前的解法:LLM.int8() 的混合精度路线

在 SmoothQuant 之前,Dettmers 等人 2022 年的 LLM.int8() 已经注意到了同样的问题,并给出了一条路线:

  • 检测出激活中的 outlier 通道(那些值明显大于阈值 α\alpha 的通道)
  • 这些通道保持 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 的回答是——能,而且不用改变计算本身

数学上,对一个线性层 Y=XWY = XWXX 是激活,WW 是权重),我们可以在中间插入一个对角矩阵 diag(s)\text{diag}(s) 及其逆:

Y=XW=(Xdiag(s)1)(diag(s)W)Y = XW = (X \cdot \text{diag}(s)^{-1}) \cdot (\text{diag}(s) \cdot W)

这个变换数学上完全等价,但物理意义完全不同:

  • 左边 X^=Xdiag(s)1\hat{X} = X \cdot \text{diag}(s)^{-1}:激活的每个通道被缩放,outlier 通道被压小,整体变得平滑,容易量化
  • 右边 W^=diag(s)W\hat{W} = \text{diag}(s) \cdot W:权重对应的通道被放大,变得不那么均匀、难度增加

于是我们获得了对”量化难度”的自由度ss 怎么选,决定了难度在激活和权重之间怎么分配。选得好,两边都不难量化。

🔑 核心概念SmoothQuant = 通过数学等价变换,把激活的量化难度”搬”一部分到权重上,让两边都落在各自好量化的区间。 变换是精确的(无信息损失),损失的只有”难度分配”。


4. 数学变换:SmoothQuant 的等价公式

那么 ss 具体怎么取?直觉上,每个通道 jj 的 scale sjs_j 应该正比于该通道激活的最大值 max(Xj)\max(|X_j|)——outlier 越大的通道被压得越多:

sj=max(Xj)αmax(Wj)1αs_j = \frac{\max(|X_j|)^{\alpha}}{\max(|W_j|)^{1-\alpha}}

分析两个极端:

  • α=0\alpha = 0sj=max(Wj)1s_j = \max(|W_j|)^{-1},等于把难度全部推给激活(激活被放大)——激活量化崩
  • α=1\alpha = 1sj=max(Xj)s_j = \max(|X_j|),等于把难度全部推给权重(outlier 通道的权重被放大)——权重量化崩
  • α=0.5\alpha = 0.5:按激活和权重的最大值等比分配难度,让变换后”激活通道与权重通道的尺度相当”,两边都好量化

📌 关键点α\alpha 叫做迁移强度(Migration Strength)。SmoothQuant 论文在 OPT/BLOOM 全系列上验证,α=0.5\alpha = 0.5 是一个普适的甜蜜点;对激活 outlier 特别严重的模型(如 GLM-130B),可以调到 0.75。α\alpha 的有效区间是 0.4~0.6,超出这个区间两边会有一边崩掉。


5. 迁移强度 α:怎么把握”搬多少”

5.1 为什么 α 有甜蜜点

直观理解:激活的 outlier 通道需要被压小(sj>1s_j > 1 作用于激活),但权重被放大的部分如果超出了它自身的量化范围,权重端也会出现 outlier。所以:

  • α\alpha 太小 → 激活端仍难量化 → 激活误差主导
  • α\alpha 太大 → 权重端被”人为制造”出 outlier → 权重误差主导
  • α=0.5\alpha = 0.5 附近 → 两端误差同时处于低位

5.2 实际怎么定

论文的实践是:从预训练数据(Pile)里抽 512 条句子做校准,在 α\alpha 上做一个快速的网格搜索(实验对比不同 α\alpha 在验证集上的困惑度),选最好的值。整个流程是一次性的离线步骤,搜索成本很低。


6. 工程实现:scale 如何零开销落地

ss 确定了之后,还有一个工程问题:变换引入了额外的逐通道缩放,如果运行时每个 GEMM 前都乘一个对角阵,这本身就是开销。SmoothQuant 的处理非常巧妙——ss 直接融进前一层

看 Transformer 的结构:线性层的输入 XX 通常来自前一个 LayerNorm 的输出(或前一个 GEMM 的输出)。以 LayerNorm 为例:

  • 前一层:LN(x)W\text{LN}(x) \cdot W
  • 变换后:LN(x)diag(s)1(diag(s)W)\text{LN}(x) \cdot \text{diag}(s)^{-1} \cdot (\text{diag}(s) \cdot W)

由于 LayerNorm 的输出再乘 diag(s)1\text{diag}(s)^{-1},等价于把 s1s^{-1} 折进 LayerNorm 的权重(γ\gamma)和偏置(β\beta):

LN(x)jsj1=xjμσγjsj+βjsj\text{LN}(x)_j \cdot s_j^{-1} = \frac{x_j - \mu}{\sigma} \cdot \frac{\gamma_j}{s_j} + \frac{\beta_j}{s_j}

所有缩放都发生在离线阶段:重新计算一遍 LayerNorm 的 γ\gammaβ\beta,以及每层权重的 scale 融合,导出”平滑后的模型权重”。运行时 Kernel 完全是标准的 INT8 GEMM,一个额外的乘子都没有。

💡 提示:这一”变换免费融入前一层”的手法后来被反复复用——AWQ 的 scale 也是这么融的(第 4.3 节),包括 NVIDIA TensorRT 的 SmoothQuant 实现,套路完全一致。


7. 校准流程与效果数据

7.1 完整流程

  1. 加载 FP16 模型,跑 512 条 Pile 校准句子
  2. 统计每层激活的 per-channel 最大值 max(Xj)\max(|X_j|)(激活统计只能靠校准数据估计,因为激活是动态的)
  3. 按公式计算每层平滑因子 ssα\alpha 经网格搜索确定)
  4. 离线融合:更新 LayerNorm 权重,更新 GEMM 权重 W^=diag(s)W\hat{W} = \text{diag}(s) \cdot W
  5. 导出模型:权重按 per-channel 静态量化,激活的 scale 用校准统计(静态)或在运行时动态计算(per-token)

论文提供了三个渐进的量化档位:O1(per-tensor 激活量化,最简)、O2(per-channel 激活量化,精度更好但 Kernel 要求高)、O3(+ 对 attention 的 BMM 也量化,最激进)。

7.2 效果数据(OPT-175B,7 个零样本任务平均准确率)

方案平均准确率说明
FP1666.9%基线
直接 W8A835.5%无 SmoothQuant 直接量化 → 崩
LLM.int8()66.7%混合精度,精度好但硬件不友好
SmoothQuant-O1/O2/O366.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 的贡献并不只是那一条公式,而是确立了两个范式:

  1. 量化难度是可以”搬运”的——权重和激活之间可以通过等价变换重新分配表示负担
  2. 校准数据是激活量化的必要输入——激活是动态的,必须用统计方法估计其分布(这个思想贯穿后面 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 的核心Y=(Xs1)(sW)Y = (X \cdot s^{-1}) \cdot (s \cdot W),数学等价变换把量化难度从激活搬到权重
  • 迁移强度 αsj=max(Xj)α/max(Wj)1αs_j = \max(|X_j|)^\alpha / \max(|W_j|)^{1-\alpha}α=0.5\alpha=0.5 是普适甜蜜点(有效区间 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 时代为什么很多模型不再需要显式平滑?

📚 参考资料