跳转至

三、模型量化

三、 模型量化

1. 什么是训练后量化(PTQ)?它与量化感知训练(QAT)的区别?

训练后量化(PTQ, Post-Training Quantization)

是在模型完成全精度训练之后,直接对权重(Weights)和/或激活(Activations)进行低精度转换——例如从 FP32/FP16 转到 INT8。其核心在于不需要任何重新训练或反向传播,仅需要少量校准数据(通常几百至几千个样本)来统计激活值的数值范围,从而确定每个张量的量化参数(尺度因子 s 和零点 z)。PTQ 的整个流程通常可以在几分钟到几小时内完成,尤其适合大语言模型这种全量训练成本极高的场景。像 GPTQ、AWQ、SmoothQuant 等都属于 PTQ 的范畴。

量化感知训练(QAT,Quantization-Aware Training)

则完全不同。它在训练阶段就模拟了量化误差:在前向传播时,对权重和激活插入“伪量化”节点

(Quantize + Dequantize),让模型在优化过程中学会适应低精度表示;反向传播时,由于量化函数不可导,通常使用直通估计器(Straight-Through Estimator, STE)来近似梯度,从而更新全精度权重。QAT需要完整的训练流水线、标注数据和大量的计算资源,通常耗时几小时到几天,但最终可以得到精度几乎无损的低精度模型,尤其适合INT4或更低位宽的极端压缩场景。

核心区别一览表:

维度PTQQAT
时间成本分钟 ~ 小时(校准)小时 ~ 天(需完整训练)
数据需求少量无标签校准样本完整标注训练集
精度保持大模型通常良好,小模型/低位宽可能下降接近全精度,鲁棒性强
典型应用大语言模型快速部署移动端视觉模型、极端压缩
计算开销极低与重新训练相当
代表方法GPTQ, AWQ, SmoothQuantLSQ, PACT, QAT-BERT

大模型时代的选择哲学:由于 GPT-3、LLaMA 等大模型重训成本动辄百万美元,PTQ 成为工业部署事实上的标准。我通常给团队的建设是:先用 PTQ 快速出结果,如果精度损失在可接受范围内就直接上线;只有当业务对精度极度敏感、且 QAT 的资源投入能通过模型效果提升收回成本时,才考虑 QAT。实际中,90% 以上的场景 PTQ 都能满足需求。


Image

2. 对称量化与非对称量化的公式和优缺点是什么?

量化的本质是将浮点值 $ r $ 映射为整数 $ q $,核心参数是尺度因子 $ s $ 和零点 $ z $。对于 $ b $ 位量化,整数范围通常为 $ [0, 2^{b} - 1] $(非对称)或 $ [-2^{b-1}, 2^{b-1} - 1] $(对称)。

对称量化(Symmetric Quantization)

假设浮点分布关于零点对称,因此零点固定为0。公式为:

$$ q=round\left(\frac{r}{s}\right) $$

尺度因子 $ s = \frac{\max(|r|)}{2^{b-1}-1} $。反量化时 $ r' = s \cdot q $。

优点:计算简单高效——矩阵乘法中的零点项相互抵消,硬件实现友好(如 NVIDIA TensorRT 默认偏好对称量化);对于本身就是零均值分布的数据(如模型权重,通常呈正态分布),信息损失很小。

  • X 缺点:对于非对称分布(最典型的就是 ReLU 后的激活值,全为非负数),对称量化会浪费一半的量化范围;另外,对异常离群值(Outlier)极为敏感,一个极端值会撑大 $ \max(|r|) $,导致尺度因子变大,大量正常数据的量化分辨率变低,精度显著下降。

非对称量化(Asymmetric Quantization)

允许零点偏移,更灵活地贴合数据实际分布。公式为:

$$ q=round\left(\frac{r}{s}+z\right) $$

尺度 $ s = \frac{r_{\max} - r_{\min}}{2^{b} - 1} $,零点 $ z = \operatorname{round}\left(-\frac{r_{\min}}{c}\right) $。反量化时 $ r' = s(q - z) $。

优点:能精确覆盖偏斜分布(如激活值),量化区间利用率高,精度损失小。

  • ✗ 缺点:计算开销增加——矩阵乘法中需要补偿零点项(即多一次额外的矩阵运算),推理速度略慢;存储时需同时保存尺度和零点,参数开销略增。

大模型实践指南:在实际工程中,为了追求极致速度和硬件亲和,权重通常使用对称量化(因为它天然分布对称),激活值则视情况而定:如果追求精度且能接受轻微延迟,用非对称量化;如果要求低延迟,则统一用对称量化并配合校准技术(如KL散度校准)来尽量减少精度损失。我遇到过一个例子:某对话模型激活值使用对称量化后,回复质量明显下降,改为非对称量化后问题消失,但推理延迟增加了约5%,最后我们决定接受这个代价,因为用户体验更重要。


Image

3. 为什么大模型量化常用逐通道量化而不是逐张量化?

量化粒度决定了多少个参数共享同一个尺度因子。粒度从粗到细有三种:

· 逐张量(Per-Tensor):整个张量(例如一个权重矩阵的所有元素)使用一个尺度。

逐通道(Per-Channel):对权重矩阵的每个输出通道(即卷积核的每个filter或线性层的每行)分别计算一个尺度。

逐组(Per-Group):将通道分成若干组,每组使用一个尺度,是更细粒度的折中。

为什么大模型普遍采用逐通道量化?

主要原因是 Transformer 结构不同通道之间的权重分布差异巨大。例如,LLaMA-7B 的某个 FFN 层,其4096个输出通道的权值范围可能从 0.01 跨越到 0.5,相差数十倍。如果使用逐张量量化,一个统一的尺度会被幅度最大的通道所支配,导致小幅值通道的权值大部分被量化为 0 或者损失精细度;反过来,如果幅度最大的通道恰好是离群值,又会撑大尺度,造成信息丢失。而逐通道量化为每个通道独立设置尺度,就像为每个人量身定制衣服,能最大程度保留每个通道的独特信息。

为什么不是逐组量化?

逐组量化(比如 GPTQ 的 group size 128)精度更高,但需要存储更多的尺度因子(每 128 个权重一个尺度),会增加模型体积和推理时的小量计算。在权重上,逐通道以极低的额外存储(每个通道一个浮点数,对于 7B 模型只增加几 MB)换来了显著精度提升,是当前精度-效率的最佳平衡点。

实践经验:我曾在部署一个13B模型时尝试过逐张量量化,结果发现生成的文本包含大量无意义重复——这正是因为某些关键通道的权值被破坏。切换到逐通道量化后,生成质量恢复正常,显存占用几乎不变。所以现在我的默认配置就是权重逐通道量化。

Image

4. LLM.int8() 为什么要把离群特征单独用 FP16 计算?什么是离群特征?

LLM.int8() 是 Dettmers 等人在 2022 年提出的混合精度量化方法,专门解决大语言模型中激活值离群特征导致的量化灾难。


离群特征(Outlier Features)

在大模型的 Transformer 层中,研究发现某些隐藏维度(约占全部维度的 0.1%)的激活值会系统性地比其他维度大 20 倍以上,甚至 100 倍。这些离群值并非随机出现,而是固定出现在某些特定的特征通道上,并且往往承载着重要的语义信息(例如分隔符、标点、否定词等)。例如,某个 token 的第 2048 号特征几乎总是特别大,而其他特征值相对平坦。

为什么离群特征对量化是灾难性的?

如果对整个激活张量使用统一的量化尺度,离群值会迫使尺度因子变得很大,导致绝大多数正常特征在量化后被压缩到极小的整数范围,甚至全部被映射为0,造成灾难性的信息丢失。你可以想象一个房间里有一个2米高的人和一群1米高的人,如果按最高的人来定天花板高度,那么矮个子的人的活动空间就会被严重浪费。而如果把离群值直接截断(clamp),又会丢失这些维度上的关键信息,影响模型性能。

LLM.int8() 的解决方案:

  1. 分离:对输入矩阵 X,识别出包含离群特征的那些行(即对应那几个特殊的特征维度)。

  2. 混合精度:对绝大多数正常特征 (>99.9%) 使用 INT8 矩阵乘法,享受量化的速度与显存优势。

  3. 极小部分离群特征:保留 FP16 精度进行计算。由于这部分维度极少(通常只有几十个),额外的 FP16 计算开销几乎可忽略不计。

  4. 最后将两部分结果相加。

这一设计精妙之处在于以极小的额外计算代价,彻底规避了离群值对量化精度的破坏。LLM.int8()的提出,首次让千亿参数大模型能在消费级显卡上以可接受的精度运行,是量化领域的一个里程碑。我在自己的实验中用该方法对OPT-13B量化,困惑度下降几乎不可察觉,但显存占用减少了近50%。

5. GPTQ 是怎么做量化的?它基于 OBQ 做了哪些改进?

GPTQ (Frantar et al., 2023) 是针对超大规模生成式预训练 Transformer 的一种高效权重量化方法,能在几小时内完成 175B 模型的 3-bit/4-bit 量化,精度损失很小。

基础算法:OBQ (Optimal Brain Quantizer)


OBQ 将权重量化问题看作逐权重的剪枝:每次量化一个权重,然后利用该层的 Hessian 矩阵(二阶导数信息)精确计算出如何调整同一行中剩余未量化的权重,以补偿刚量化造成的输出误差。它通过解一个小型线性系统,最小化输出特征的平方误差,属于贪心算法。OBQ 的精度很高,但计算量巨大——它需要逐元素处理,无法直接用于大模型。

GPTQ 的关键改进:

批量量化(Block-wise Quantization):不再逐元素处理,而是将权重矩阵按列分成若干块(例如每128列一块)。块内所有列对应的权重同时被量化,然后利用该块内累积的量化误差,对块外尚未量化的权重进行一次性补偿更新。这大幅减少了迭代次数。

延迟更新与 Cholesky 分解:GPTQ 利用 Hessian 矩阵的结构特性(对称正定),预先计算其逆矩阵或进行 Cholesky 分解,使得补偿更新的步骤可以高效地批量执行,避免了 OBQ 中反复求解线性系统的高昂开销。

列重排(Column Reordering):在量化前,根据 Hessian 信息(例如对角线元素的大小)对权重矩阵的列进行重新排序,将“难以量化”的列(对输出影响大的)放到前面优先处理,以进一步降低量化误差。

分组量化(Group-wise Quantization):支持将权重通道分成若干组,每组独立计算尺度因子,提供更细粒度的压缩,提升低位宽(如3-bit)下的精度。

实践中,GPTQ能在单张GPU上用几小时处理完LLaMA-70B的量化,生成质量比之前的逐通道四舍五入方法好非常多。我们团队在部署70B对话模型时,用GPTQ4-bit量化后,模型回复的连贯性和准确性几乎与FP16持平,而显存需求降低到原来的四分之一左右。

6. GPTQ 中的“二阶信息”指的是什么?为什么能提升量化精度?

二阶信息在这里特指 Hessian 矩阵,即目标函数(通常是该层权重的输出误差)关于权重的二阶偏导数。具体来说,GPTQ 定义的目标是最小化量化前后该层输出特征的平方误差:

$$ E=|W X-\widehat{W}X|_{F}^{2} $$


其中 W 是原始权重, $ \widetilde{W} $ 是量化后的权重,X 是该层的输入(校准数据)。对这个目标关于权重进行泰勒展开,一阶项在最优权重处为零,主导的误差项就是二阶项,其系数恰好是 Hessian 矩阵 $ H = 2X X^{T} $。

为什么二阶信息能提升精度?

简单地将每个权重四舍五入到最近的量化值,是一阶或零阶的贪心方法——它只考虑单个权重的舍入误差,完全忽略不同权重之间的相关性。而二阶信息(Hessian)则刻画了权重之间的相互影响:改变一个权重 $ w_{i} $会对哪些输出维度产生多大影响,以及这种影响如何与另一个权重 $ w_{j} $的改变相互叠加。

GPTQ 利用这个 Hessian 矩阵,在每次量化一个(或一组)权重后,能够精确计算出应该如何微调同行的其他未量化权重,来抵消刚刚引入的量化误差。这就像调整精密天平:你不仅要知道放上一个砝码带来的偏差,还要知道如何调节其他砝码来保持平衡。这种全局补偿策略是 GPTQ 能以极低位宽保持高精度的核心原因。

直观理解:如果只用量级大小来决定量化的取舍(一阶信息),你可能会忽略那些数值虽小、但对输出影响很大的“关键小权重”。而 Hessian 捕捉的正是“参数重要性”,它会告诉你哪些权重的微小变动会引起输出剧烈变化,从而在量化时给予这些权重更精细的处理。

7. AWQ 的核心思想是什么?它是如何保护重要权重通道的?

AWQ (Activation-aware Weight Quantization) 的核心洞察是:并非所有权重都对模型输出同等重要。只有大约 1% 的权重通道(对应输入特征中激活值幅值特别大的那些通道)承载了绝大部分关键信息,如果这些通道的权重量化不当,会直接导致模型性能显著下降。而保护这些“重要通道”的量化精度,就可以用极低的成本换来整体效果的巨大提升。

如何保护重要权重通道?

AWQ 发现,可以通过一个数学上等价的缩放变换来实现保护。具体来说,对于输入 X 和权重 W 的矩阵乘法 Y = XW,如果你将权重 W 的某一列 i(对应输入通道 i)乘以一个缩放因子 $ s_{i} $,同时将输入 X 的该通道除以同一个 $ s_{i} $,则输出 Y 完全不变。

基于此,AWQ 的做法是:


  1. 统计激活值分布:使用少量校准数据,观察输入 X 每个通道的平均激活幅度(例如计算每个通道的绝对值的均值或最大值)。

  2. 确定重要通道:激活幅度最大的那部分通道(如1%)即为“重要通道”。这些通道的权重量化误差会被输入的放大效应加倍放大。

  3. 计算缩放因子:对这些重要通道,通过一个简单的搜索或启发式算法(如根据激活幅值比例),找到一个大于1的缩放因子 $ s_i $,将权重 $ W_{:,i} $除以 $ s_i $(缩小权重),同时将输入 $ X_i $乘以 $ s_i $(放大激活)。

  4. 效果:经过缩放后,该重要通道的权重量级变小,量化时相对误差显著降低;而输入激活虽然变大了,但激活本身是浮点计算的,可以承受更大的数值范围。这就将量化的“难度”从权重转移到了激活,从而在无需重新训练的情况下,大幅保护了关键信息的精度。

最终,AWQ 将权重被缩放的部分(即除以 $ s_{i} $ 后的值)进行量化,推理时再在激活端乘以相应的 $ s_{i} $,完成等效还原。这种方法简单、高效、无需反向传播,却能将 4-bit 量化的精度提升到接近甚至超越 GPTQ 的水平。

8. AWQ 为什么只需要观察激活值分布,而不需要反向传播?

因为 AWQ 的核心优化完全基于数学等价变换,而不是梯度优化。它不足义一个需要最小化的损失函数,不需要计算梯度,更不需要通过反向传播更新参数。

具体来说,AWQ 的整个流程是:

第一步:用校准数据跑一次前向,统计每个输入通道的激活幅值分布(例如求 mean(|X|) 或 max(|X|) 等)。这一步纯粹是数据统计,没有梯度。

第二步:基于统计结果,通过一个封闭形式的搜索算法(例如为每个通道在有限候选集中尝试不同的缩放因子,选择使量化后输出误差最小的那个)来确定缩放因子 $ s_{i} $。这个搜索过程虽然涉及前向计算,但同样不需要反向传播。

  • 第三步:将权重通过等效变换(乘/除 $ s_i $)后,应用标准的 PTQ 量化(如 min-max 量化)得到量化权重。推理时,只需在激活的对应通道上乘以 $ s_i $ 即可。

整个过程中,AWQ 的核心在于利用激活的幅值作为“重要性指标”,通过缩放将量化误差从敏感通道转移到不敏感通道。因为激活幅值是可以直接从校准数据获取的,不需要模型输出标签,也不需要计算损失,所以反向传播就成了多余。正是这种简洁性,使得 AWQ 的量化速度非常快(通常只需要几十分钟),同时对硬件和校准数据的要求极低,非常易于部署在自动化流水线中。

9. SmoothQuant 是如何通过数学等价变换来量化激活值的?写出核心公式。

SmoothQuant 的核心思想是将激活值的量化难度“平滑”到权重上。它观察到,在大模型中,激活值往往存在极端的通道级离群值,导致直接量化激活时误差极大;而权重分布相对平坦,更容易被量化。于是提出通过一个数学上等价的变换,重新分配激活和权重的数值范围。

核心公式推导:

对于一个线性层 $ Y = X \cdot W $,我们引入一个平滑向量 $ s \in \mathbb{R}^{C_{\text{in}}} $(每个输入通道一个平滑因子)。利用对角矩阵 $ \text{diag}(s) $ 进行等价变换:

$$ \boldsymbol{Y}=\boldsymbol{X}\cdot\boldsymbol{W}=(\boldsymbol{X}\cdot\operatorname{diag}(s)^{-1})\cdot(\operatorname{diag}(s)\cdot\boldsymbol{W}) $$

定义新的激活 $ \widetilde{X} = X \cdot \mathrm{diag}(s)^{-1} $,新的权重 $ \widetilde{W} = \mathrm{diag}(s) \cdot W $。

对于原来的 X,某些通道值极大(离群值),导致量化困难。

对于变换后的 $ \bar{X} $,由于除以了 $ s_{i} $(对应通道的平滑因子),离群值被压低了,整体分布变得更平滑,量化精度损失大大减小。

与此同时,新的权重 W 对应通道被乘以 $ s_{i} $,数值可能变大,但因为权重本身分布范围窄、更容易量化,这种“转移”对总体的量化损失影响很小。

这个变换是严格的数学等价,不改变任何层的输出。SmoothQuant 将量化激活的难点从“激活离群值”转移为“权重微调”,从而实现了对激活值的高效低位宽量化(如 INT8)。该公式简单优雅,已成为 LLM 推理加速的关键技术之一。我在实际应用中发现,对于某些激活值离群特别严重的模型(如 GLM-130B),SmoothQuant 几乎是唯一能让 INT8 推理保持可用性的方法。


Image

10. SmoothQuant 的“平滑因子”如何确定?它转移了谁的量?

平滑因子 \(s\) 的确定是 SmoothQuant 效果的关键。其原则是:让变换后激活 \(X\) 和权重 \(W\) 的量化难度尽可能均衡。直观上,我们想找到一个 \(s\),使得在迁移后,激活的离群值被充分抑制,而权重的数值又不至于被过度放大。

实践中,平滑因子通常通过以下公式计算:

$$ \boldsymbol{s}{j}=\max(|\boldsymbol{X} $$ }|)^{\alpha}\cdot\max(|\boldsymbol{W}_{j,:}|)^{(1-\alpha)

其中 \(j\) 表示输入通道索引,\(\alpha \in [0,1]\) 是一个超参数,称为“迁移强度”。当 \(\alpha = 1\) 时,\(s_j = \max(|X_{:,j}|)\),相当于把激活的所有量化难度全部转移给权重;当 \(\alpha = 0\) 时,\(s_j = \max(|W_{j,:}|)\),则是把权重的所有量化难度转移给激活。通常 \(\alpha\) 取 0.5 左右,即两者均匀分担。

它转移了谁的量?

平滑因子 s 的作用是重新分配数值范围:

对激活 X,除以 $ s_{j} $ 相当于压低了它的幅度,尤其是对于那些原本值很大的通道(离群值),被大幅降低,使得激活整体变得“温顺”,易于量化。

对权重 W,乘以 $ s_{j} $ 相当于放大了对应通道的权值。这种放大会稍微增加权重的量化难度,但因为权重通常没有极端的通道间差异,其量化原本就比激活容易,所以这部分的代价很小。

因此,SmoothQuant 实际上是把激活值通道间的“量化难度差异”转移了一部分到权重上,实现两边的平衡,让整体的量化误差最小化。

调参经验:我一般会在校准数据集上尝试几个不同的 $ \alpha $(如 0.3, 0.5, 0.7),用困惑度或下游任务得分来评估量化损失,选择最优的 $ \alpha $。对于大多数 Transformer 模型,默认的 0.5 就能工作得不错;但对于某些激活分布特别陡峭的模型,可能需要增大 $ \alpha $(如 0.7)来更强势地压制激活离群值。


11. 为什么激活值比权重更难量化?离群值对量化有什么影响?

激活值(Activations)是神经网络每一层的输出,它们会随着输入数据的不同而动态变化。在量化时,激活值远比权重难以处理,根本原因在于其数值分布的高度不规则性和离群值(Outliers)的普遍存在。

激活值与权重的分布差异

权重(Weights):通常是静态的、可事先获得的参数。经过训练后,权重的分布通常比较平滑,近似于高斯分布或拉普拉斯分布,数值范围相对集中。例如,一个卷积核的权重可能集中在[-0.5, 0.5]之间,极少出现极端值。

  • 激活值:是输入数据经过网络层层计算后的结果,其分布取决于输入数据和模型结构。激活值的分布可能是高度非对称的(例如 ReLU 之后所有值都 $ \geq 0 $)、长尾的,甚至在不同通道(Channels)之间差异巨大。更麻烦的是,激活值中经常出现比平均值大几十倍甚至上百倍的离群值。

$ ^{*} $ 离群值对量化的破坏性影响

离群值是激活值量化中最头疼的问题。想象一下,你有一个包含1000个数值的张量,其中999个值都在[0.0,1.0]范围内,但有一个值是100.0。如果你对这个张量做对称量化:

1. 尺度因子被离群值撑大

对称量化的尺度因子 $ s = \frac{\max |V|}{2^{b-1}-1} $。那个 100.0 的离群值会使得 s 变得很大,导致量化后的整数表示范围 [-127, 127] (INT8)被用来覆盖 [-100, 100]。

2. 绝大多数正常值被压缩到零点附近

对于 $ [0,0,1,0] $ 范围内的数值,除以巨大的尺度因子后,它们会被映射到极小的整数范围(例如 $ \theta $ 到 $ 1 $ 之间),甚至可能全部被舍入为 $ \theta $。这些值携带的细微信息就此彻底丢失。这就是量化噪声,它会严重破坏模型的精度,尤其对于那些依赖于精细激活值的 Transformer 层。

3. 非对称量化也无济于事

非对称量化虽然能更好地利用量化区间,但如果离群值与正常值之间的差距过大,它依然无法为正常值提供足够的分辨率。唯一的办法是采用逐通道量化(Per-Channel Quantization),为每个通道单独设置尺度,但即便这样,同一个通道内也可能存在离群token(例如某些特定token的特征值在所有通道上都异常大),这被称为逐token离群值。

离群值的成因与类型


通道级离群值(Channel-wise Outliers):在大语言模型的Transformer层中,某些特定的隐藏维度(例如第2048维)会系统性地出现比其他维度大几十倍的激活值。这些离群维度往往固定出现在少数几个特征通道上,并且承载着关键信息(如标点符号、否定词等)。

  • Token 级离群值(Token-wise Outliers):某些特定的 token(比如句首的 或分隔符)会在几乎所有通道上产生异常大的激活值。这些“注意力汇聚点”在 StreamingLLM 等研究中已被证实对模型至关重要。

工程中的解决方案

· LLM.int8(): 将离群特征通道分离出来,单独用 FP16 计算,其余部分用 INT8 量化。这是一种混合精度的方法,牺牲一点点额外的计算来换取无损的精度。

SmoothQuant:通过数学等价变换,把激活值的量化难度“平滑”一部分到权重上。它对激活值除以一个平滑因子(缩小离群值),同时对权重乘以相同的因子(放大权重),使得两者都更容易量化。

· KV Cache 量化:对于推理中的 KV Cache,由于其分布同样存在离群值,常使用逐通道量化或专门的校准策略(如 INT8 量化加异常值截断)。

实践直觉:权重量化就像是给一本厚度均匀的书设计包装盒,你很容易找到一个合适的尺寸。激活值量化则像是给一堆形状怪异的石头找盒子,总有一两块特别大的石头把盒子撑得很大,导致其他小石子在盒子里咣当,信息丢失。解决思路要么是把大石头单独拿出来(混合精度),要么是把大石头切小(平滑迁移)。

12. 什么是 NF4?它在 QLoRA 中如何和双重量化配合?

NF4(4-bit NormalFloat)是一种专门为神经网络权重量化设计的信息理论上最优的数据格式。它由QLoRA的作者Dettmers等人提出,核心思想是:既然模型权重通常服从零中心的正态分布,那么量化时应该让量化区间(即量化台阶)在概率密度高的区域更密集,在概率密度低的尾部更稀疏。换句话说,NF4是一种非均匀量化,它根据正态分布的累积分布函数(CDF)来确定量化值的位置,从而在相同位宽下最大化保留信息。


NF4 的构造原理

  1. 假设权重服从标准正态分布 $ \mathcal{N}(0,1) $。

  2. 将该分布的概率质量等分为 $ 2^{b} = 16 $ 个区间(对于 4-bit)。

  3. 计算每个区间的 CDF 分位数,这些分位数就是 NF4 的量化值。由于尾部概率密度低,区间更宽;中心概率密度高,区间更窄。

  4. 实际权重值被映射到距离最近的 NF4 量化值上。

这就像给一本书做索引时,经常被查阅的章节(高概率区)索引更密集,不常被查阅的附录(低概率区)索引稀疏。这样可以用有限的位数表达更多的有效信息。

与 INT4 的对比

· INT4:均匀量化,量化台阶是等距的。对于正态分布,这种均匀分配会在中心区域分辨率不足(因为大部分权重集中在这里),而在尾部浪费分辨率。

NF4:非均匀量化,以信息理论最优的方式分布台阶。在同样的4-bit位宽下,NF4的量化误差显著小于INT4,尤其当数据严格服从正态分布时。

QLoRA 中的双重量化

QLoRA 是一种结合量化与参数高效微调(PEFT)的技术。它首先将预训练模型的权重用 NF4 量化到 4-bit,从而将显存占用降至原来的约 1/4。然后,在量化后的模型上附加 LoRA 适配器(低秩矩阵),训练时仅更新 LoRA 的参数,保持量化权重冻结。

然而,NF4 量化本身需要存储量化常数(每个量化块需要独立的尺度因子和零点),当采用极小的块大小时(例如 64 个参数一个块),这些常数的总存储量不可忽视(对于大模型可能达到数 GB)。双重量化(Double Quantization)正是为了解决这个问题:它对第一次量化的量化常数本身再进行一次量化(通常使用 FP8 或 INT8),从而将这些常数的存储开销压缩到几乎可以忽略的程度。

Image

效果与价值

通过 NF4 + 双重量化 + LoRA,QLoRA 使得在单张 24GB 消费级显卡(如 RTX 3090)上微调 65B 参数模型成为可能。在我的一次实验中,用 QLoRA 微调 LLaMA-65B,显存峰值仅 21GB,而用 FP16 LoRA 会直接 OOM。虽然训练速度略有下降(由于量化/反量化的开销),但这是用有限资源撬动超大模型的唯一途径。


👎 直觉:NF4 就像是一个聪明的图书管理员,知道哪些书被借阅最多(高概率区),于是把那些书放在伸手可及、分类极细的书架上;而那些冷门书则堆在远处的仓库里,偶尔需要时再去取。双重量化则是连书架标签都做了缩写,进一步节省空间。

13. GGUF 格式中的 k-quant 是什么?它如何平衡精度和大小?

GGUF 是 llama.cpp 等 CPU/边缘推理框架使用的一种模型文件格式,专门为低资源、跨平台的推理设计。而 k-quant 是该格式中一种特定的混合精度量化策略,旨在在模型体积、推理速度和精度之间取得最佳平衡。

k-quant 的核心思想

k-quant 的全称是“基于块的量化”(block-wise quantization)。它将模型权重切分成固定大小的块(通常每 32 或 256 个参数为一个块),然后为每个块独立地计算量化尺度因子。这种逐块量化的粒度比逐通道更细,但又不像逐组量化那样产生过多的元数据开销。

关键点在于,k-quant 使用不同的位宽来量化不同类型的层和不同的参数组:

对于大多数权重:使用 4-bit 或 5-bit 量化,以最大化压缩率。

对于关键的少数权重(例如每个块的超级离群值)或特别敏感的层:使用6-bit甚至8-bit量化,以减少精度损失。

对于激活值:推理时通常保持 FP16 或 FP32,不量化。

这种“好钢用在刀刃上”的策略,使得 k-quant 在保持与全精度模型相近的困惑度的同时,将模型体积压缩到 FP16 的 1/4 到 1/5。

常见的 k-quant 变种

以 LLaMA 模型为例,常见的 k-quant 类型包括:


Q4_K_M:中等大小,平衡型。使用 4-bit 为主,关键层用 6-bit。这是最常用、推荐的选择。

Q4_K_S:小体积,牺牲少量精度换取更小模型。所有层强制 4-bit。

Q5_K_M:稍大,精度更高。使用 5-bit 为主。

Q6_K:更大,精度接近无损。

Q8_0:8-bit 量化,精度几乎无损,但体积减半。

每个变种背后,作者 TheBloke 等人通过大量的困惑度测试和人工评估,精细调整了哪些层用多少位、块大小如何设置、如何处理离群值等超参数。

平衡的艺术

k-quant 之所以能成功,是因为它认识到了模型内部参数的异质性:

  1. 注意力层 vs. 前馈层:注意力层的权重量化通常比前馈层对精度更敏感,因为注意力需要精确的 Q、K、V 投影。

  2. 嵌入层:通常保持较高精度(如 FP16),因为词嵌入对细微语义差异非常敏感。

  3. 输出层:同样需要较高精度,以免影响最终预测。

通过在模型的不同部分分配不同的“量化预算”,k-quant 在模型大小、推理速度和输出质量之间找到了一个sweet spot。对于普通用户来说,只需要选择合适的 k-quant 变体(如 Q4_K_M),无需手动调整任何参数,就可以获得一个“开箱即用”的高效模型。

实践建议:如果你不确定选哪个,就用 Q4_K_M。它几乎总是在所有维度上表现最好。只有当你的磁盘空间特别紧张时,才考虑 Q4_K_S;而如果你有足够的 RAM 且追求零精度损失,Q6_K 或 Q8_0 是更好的选择。

14. INT4 和 INT8 量化通常会带来多少模型体积和显存的缩减?

量化最直接的收益就是模型体积和推理时显存占用的下降。我们以 FP16(半精度浮点)为基准,来对比INT8和INT4量化的压缩效果。


模型体积缩减(磁盘存储)

FP16:每个参数占用2字节。

· INT8:每个参数占用1字节。体积缩减至原来的50%。

• INT4:理论上每个参数占用0.5字节,但实际中因为需要存储量化元数据(尺度因子、零点等),实际体积约为原来的25%-30%。例如,一个7B的FP16模型约14GB,INT8版约7GB,INT4版约3.5-4.5GB。

推理显存缩减

显存占用不仅要算权重,还要算 KV Cache、激活值等。但权重的压缩通常是最立竿见影的。

仅权重量化(W4A16 或 W8A16):权重被量化为 INT4/INT8,激活值保持 FP16。

INT8 权重:权重显存占用减半。总显存通常从权重 + KV Cache 变为(权重/2)+ KV Cache。如果 KV Cache 占比不大,总显存可降低约 40%-50%。

INT4 权重:权重显存占用降至 1/4。总显存从权重 + KV Cache 变为(权重/4)+ KV Cache。对于短文本,总显存可降低 50%-70%。

· 全量化(W8A8 或 W4A8):权重和激活值都量化。

W8A8(INT8 权重 + INT8 激活):理论上,不仅权重减半,激活值的显存和带宽需求也减半。在Decode 阶段,由于每次需要从显存读取整个 KV Cache 和激活值,A8 可以将该部分数据量减半,带宽压力下降,延迟降低。但对于自回归生成,激活值量化带来的额外精度损失可能更大。

W4A8:权重4-bit,激活8-bit。这是极致的压缩,但工程实现复杂,目前主要在部分边缘设备或专用ASIC上采用。

实际案例

以 LLaMA-7B、单张 A100 推理为例:


配置权重显存总显存 (短文本)总显存 (长文本)
FP1614 GB~16 GB~30 GB
INT8 (W8A16)7 GB~9 GB~23 GB
INT4 (GPTQ)~3.5 GB~5.5 GB~19.5 GB

可见,INT4 量化的权重显存降到了 FP16 的 1/4,但总显存降幅取决于 KV Cache 的大小。在长文本场景,KV Cache 才是大头,此时权重压缩的贡献相对变小,但仍至关重要——它为你腾出了更多空间来容纳更长的 KV Cache。

关键洞察:量化主要节省的是权重的存储空间和读取带宽,并不能直接减少激活值或KV Cache 的显存(除非你也量化它们)。但压缩后的权重占更少的显存,意味着你可以把宝贵的显存用在更大的 batch size 或更长的上下文上。

15. 量化后的模型推理速度一定会变快吗?什么情况下反而变慢?

直觉上,量化后模型变小了,推理应该更快。但现实远比这复杂:量化通常能提升吞吐、降低延迟,但并非在任何情况下都能加速,有时甚至会变慢。

为什么会变快(主要收益)

  1. 显存带宽压力降低:在Decode阶段(自回归生成),推理是显存带宽受限的。每次生成一个token,需要将整个模型权重从显存读入计算核心。以A100为例,显存带宽为2TB/s。一个FP16的7B模型(14GB)加载需要约7ms;而INT8版(7GB)只需要约3.5ms。这个时间直接构成了每token延迟的下限。因此,权重量化能直接降低Decode阶段的延迟。

  2. 更大的有效 batch size:压缩后的模型占用更少显存,可以在同一张 GPU 上同时服务更多的用户请求(更大的 batch)。由于 GPU 的并行计算能力强大,增加 batch size 可以显著提升整体吞吐(tokens/s)。

  3. 专用的整数计算单元:现代 GPU(如 NVIDIA 的 A100、H100)拥有专门用于 INT8 矩阵乘法的 Tensor Core,其吞吐量理论上是 FP16 的 2 倍以上。如果推理引擎(如 TensorRT)能够将量化的模型映射到这些硬件单元,计算速度本身也能大幅提升。


什么情况下反而变慢?

  1. 激活值未量化(W4A16)且 batch 很小:如果只量化了权重,激活值保持 FP16,那么在计算矩阵乘法时,需要将 INT4 的权重反量化回 FP16,然后与 FP16 的激活值相乘。这个反量化过程会引入额外开销。当 batch size 极小(如 batch=1)时,计算量很小,反量化开销占比变大,可能抵消甚至超过权重大小减少带来的带宽收益。这就是为什么有些用户发现 INT4 模型在单条查询时速度反而不如 INT8 模型。

  2. 硬件不支持低精度计算:老款 GPU(如 T4、V100)对 INT4 计算的硬件支持有限,无法充分发挥 INT4 的理论加速比。此时 INT4 推理完全依赖 FP16 反量化后的计算,速度提升有限,甚至可能变慢。

  3. KV Cache 成为新瓶颈:如果你量化了一个7B模型,使其权重显存降到3.5 GB,但你设置了一个超长的上下文(如32k),那么KV Cache可能会膨胀到16 GB。此时,推理延迟完全由KV Cache的读取时间主导(显存带宽瓶颈),权重的压缩对整体延迟的贡献变得微乎其微。

  4. 低质量的量化实现:如果你使用的是一个未优化的量化推理框架(例如简单的 PyTorch 自定义 kernel,而不是 TensorRT 或 vLLM),反量化操作可能没有被融合到算子中,导致大量的额外 GPU kernel 启动和内存搬运,严重拖慢速度。

实践对比(以 LLaMA-7B 为例,batch=1,A100)

配置每 token 延迟吞吐 (tokens/s)备注
FP16 (基准)~15 ms~65-
INT8 (W8A16, vLLM)~10 ms~100稳定加速,显存减少
INT4 (GPTQ, ExLlama)~8 ms~120更快,但批处理增大效果更佳
INT4 (原生 PyTorch)~20 ms~50反量化开销过大,反而更慢

所以我的建议是:不要想当然认为“量化 = 加速”。在正式部署前,务必在你自己的硬件上、用你最常使用的 batch size 和上下文长度,实测不同量化策略的延迟和吞吐。工具上,如果追求极致性能,优先选用 TensorRT-LLM 或 vLLM;如果只是本地玩玩,ExLlamaV2 和 llama.cpp 是不错的选择。


16. 如何评估量化模型的精度损失?只看困惑度(PPL)够吗?

困惑度(Perplexity,PPL)是衡量语言模型预测下一个 token 的不确定度的指标,通常越低越好。量化后 PPL 的上升幅度,常被用来快速衡量量化精度损失。但只看 PPL 是远远不够的,甚至会误导你。

✓ PPL 能告诉你什么

PPL 对模型输出概率的整体分布变化很敏感。如果量化导致大量 token 的概率被扭曲,PPL 会明显上升。因此,PPL 是一个很好的初步筛查工具:如果你量化后 PPL 暴涨(例如从 8 升到 80),说明量化肯定出了问题,不用再继续评估了。

× PPL 不能告诉你什么

  1. 下游任务的实际表现:PPL 是一个无监督的、内在的指标。它不能直接反映模型在特定任务上的能力变化,如阅读理解、代码生成、多轮对话、安全性等。你可能会看到 PPL 几乎不变,但模型在数学推理或事实回忆上明显变差。

  2. 长尾知识保持度:量化可能会微妙地“抹掉”模型在预训练中学到的某些长尾知识(例如某个冷门历史事件的细节)。这种损失在 PPL 上几乎不会体现,因为长尾 token 对整体困惑度的贡献极小,但对你需要精确事实的场景却是致命的。

  3. 生成文本的多样性与连贯性:量化可能使模型的输出分布变得更“尖锐”(低熵),导致生成文本重复、模板化、失去创造力。PPL 无法捕捉这种风格和多样性的变化。

  4. 安全对齐的退化:权重的微小变化可能影响模型的安全对齐效果,使其更容易被越狱或产生有害内容。PPL 完全无法衡量这一点。

更全面的评估体系

除了 PPL,你至少应该补充以下评估:

• 标准化基准测试:在与你业务相关的公开基准上跑分。例如,MMLU(综合知识)、HellaSwag(常识推理)、GSM8K(数学)、HumanEval(代码)。对比量化前后的得分变化。

特定任务的自建测试集:如果你用模型来做客服、摘要或翻译,构建一个你自己的黄金测试集,用自动化指标(如ROUGE、BLEU)和人工评估来对比。


生成样本的人工评估:这是最可靠的方法。随机抽取100-200个实际prompt,让量化模型和原模型分别生成回答,然后进行盲评。从有用性、流畅性、事实正确性、无害性等维度打分。

长文本与多轮对话测试:如果你的场景涉及长上下文或多轮交互,必须在这些条件下测试,因为量化对长程记忆的影响可能在短文本中不显现。

安全与对齐测试:用红队提示集(如 HarmBench)检查量化是否降低了模型的安全拒绝能力。

我的评估流程通常是:先看 PPL,如果上升超过 5%,直接排查量化配置;如果 PPL 正常,跑 MMLU 和自建测试集;如果都 OK,最后做一轮人工评估。不要因为 PPL 没变就掉以轻心。

17. 🔥 什么是“权重量化”和“权重+激活量化”?后者为什么更难?

权重量化(Weight-only Quantization):只将模型的权重(参数)从高精度转换为低精度,而激活值(Activations)在推理时仍然保持高精度(如 FP16)。这是目前大模型量化中最主流的做法,因为实现简单、精度损失小、对延迟的改善主要体现在显存带宽上。

权重+激活量化(Weight and Activation Quantization):同时将权重和激活值都进行低精度转换,例如W8A8(INT8权重 + INT8激活)。这需要在计算矩阵乘法时,直接从低精度的权重和低精度的激活值进行整数运算,理论上能够充分利用硬件(如Tensor Core的INT8单元)实现计算加速,并将带宽需求降到最低。

为什么“权重+激活量化”难得多?

1. 激活值更难校准

权重是静态的,你可以在量化前彻底分析其分布,并离线确定最优的量化参数。激活值是动态的,依赖于输入数据。你需要在推理前用校准数据集来统计激活值的范围,但校准数据毕竟只是真实数据的一个子集,无法保证覆盖所有可能的输入分布。一旦线上出现校准集未见过的新分布,激活值的量化参数可能不再合适,导致精度严重下降。

2. 激活值的离群值更棘手

如前所述,激活值中经常存在极端的离群通道和离群 token。权重的离群值通常可以通过逐通道量化缓解,但激活值的离群值往往需要更复杂的处理(如 SmoothQuant 的数学变换,或混合精度将离群 token 剥离)。在推理时动态处理这些离群值的开销也更大。


3. 累积误差与层间漂移

每一层的激活值都是下一层的输入。当所有层的激活值都被量化时,量化误差会在层与层之间累积和放大。这可能导致深层网络的实际输入分布与校准时的统计完全偏离,产生所谓的“量化漂移”。要缓解这种漂移,通常需要在量化后进行微调(QAT)或使用更复杂的校准策略(如逐层误差补偿)。

4. 硬件与算子支持有限

虽然 A100/H100 等现代 GPU 支持 INT8 的矩阵乘法,但生态尚不如 FP16 成熟。许多高效的 Transformer 算子(如 FlashAttention、PagedAttention)目前对低精度激活值的支持还不完善或仍在开发中。要实现 W8A8 推理,往往需要对模型代码做较大的适配,工程成本较高。

所以现状是:对于99%的大模型部署场景,W4A16或W8A16(仅权重量化)是性价比最高的选择。只有在极致的边缘计算、专用推理芯片或对吞吐有极端要求的场景下,才会投入巨大的工程努力去实现权重+激活的全球化。

18. 为什么大模型的量化常常只量化权重,而保持激活为 FP16?

这不仅是一个技术问题,更是一个工程经济学问题。

1. 精度损失与收益的不对等

权重量化(尤其是逐通道的 INT4/INT8)通常只会带来微小的困惑度下降(<0.5),对大多数下游任务的影响几乎不可察觉。而激活值量化,即使是简单的 INT8,也可能导致 PPL 上升 1-3 个点,并且在长文本生成中容易出现不稳定的重复、遗忘等问题。要弥补激活值量化带来的损失,通常需要 QAT 或大量微调,这动摇了 PTQ“快速、低成本”的根本优势。

2. Decode 阶段的瓶颈是带宽,而非计算

在自回归生成中,每生成一个 token,主要时间花在从显存读取模型权重和读取整个 KV Cache 上。权重量化直接减少了从显存读取权重的数据量,从而缓解了这一带宽瓶颈,带来可观的延迟下降。而激活值量化虽然能进一步减少计算量和部分数据搬运,但在 Decode 阶段,激活值本身的数据量(每次只产生一个新 token 的激活值)相对于权重和 KV Cache 来说非常小,量化激活值带来的额外带宽节省十分有限,却要承担上述精度风险。


3. 工程复杂性与生态成熟度

权重量化可以离线一次性完成,生成一个量化后的模型文件,推理时直接加载。而激活值量化需要在校准数据上统计激活分布,推理引擎必须支持动态量化/反量化,适配不同算子的低精度版本。目前 vLLM、TensorRT-LLM 等主流框架对 W4A16 和 W8A16 的支持已经非常成熟,但对 W8A8 的支持仍在快速迭代中(尤其在长上下文和动态批处理下)。对于大多数团队来说,优先使用成熟稳定的权重量化方案是降低风险、快速上线的明智之举。

4. 例外情况

当模型部署在算力极强、带宽相对较弱的硬件上(例如某些移动端 NPU 或边缘设备),或者模型权重本身已经压缩到极致(如 2-bit),而计算成为新瓶颈时,激活值量化的优先级就会上升。另外,对于非自回归的模型(如编码器 BERT),由于一次性处理整个输入,计算量巨大,此时激活值量化的收益也更明显。

一句话总结:在目前的大模型推理中,权重量化是“雪中送炭”,解决了最痛的显存和带宽问题;激活值量化是“锦上添花”,在特定硬件和场景下有价值,但对大多数通用场景来说,风险和成本暂时高于收益。

19. 量化时是否需要校准数据集?需要多少数据?怎么选?

绝对需要。校准数据集是 PTQ 的“眼睛”,它让量化算法能够看到真实世界中激活值和权重的分布,从而计算出合适的量化参数。

1 /3 需要多少数据?

经验上,100到500个样本通常就足够了。更多的校准数据(如几千个)并不会显著提升量化精度,反而会增加校准时间。关键是样本的代表性和多样性,而非绝对数量。

如何选择校准数据?

  1. 与部署任务匹配:如果你部署的是一个客服对话模型,就用真实的客服对话数据作为校准集;如果是代码生成模型,就用多样化的代码片段。校准数据的分布应与线上实际输入尽可能接近。

  2. 覆盖多种长度和格式:对于大语言模型,确保校准集中包含短问题、长文档、多轮对话等不同长度的样本,以及可能的多语言、特殊符号、代码块等。


  1. 避免高度重复或偏斜:不要只用某个特定主题的数据(例如全是科技新闻),否则模型在该主题之外的表现可能会变差。

  2. 数据质量:使用干净、无乱码的文本。如果校准数据中包含大量噪声或格式错误,会导致量化参数统计不准确。

校准的具体操作

对于权重量化(如 GPTQ、AWQ),校准数据被用来:

• GPTQ:运行一批校准数据,计算每一层的 Hessian 矩阵(二阶信息),用于指导权重的补偿更新。校准样本通常以 128 条为一批,一次输入。

• AWQ:运行校准数据,统计每个输入通道的激活幅度(如 mean(|x|)),用于确定缩放因子。通常需要几百条数据。

对于激活值量化(如 SmoothQuant),校准数据被用来:

统计激活值的最大值、最小值,以确定量化尺度。

· 计算平滑因子(迁移强度 $ \alpha $),平衡激活和权重的量化难度。

常见误区

“用训练集的一部分做校准”:这通常是可以的,但要确保这部分数据没有被用来更新过模型(即不是QAT),否则就失去了PTQ的意义。

“只用几条数据”:极少量数据(例如10条)可能会导致某些通道的激活值未被充分激活,统计出的范围过小,导致推理时出现溢出,输出乱码。

“校准集与测试集高度重叠”:这会导致你对量化精度的评估过于乐观,无法反映线上真实效果。

我的做法:通常从线上日志中随机采样256条真实的用户prompt,确保涵盖最常见的意图和长度,然后固定这个校准集,用于该模型所有版本的量化。这样可以保证实验的可复现性。


20. 如果量化后模型精度下降明显,你应该排查哪些因素?

量化后精度严重下降,意味着你的量化设置或数据存在严重问题。以下是我总结的排查清单,按优先级排序:

1. 检查量化位宽和粒度

位宽过低:如果你尝试2-bit或3-bit,大多数模型在没有专门训练的情况下会严重退化。先回到4-bit或8-bit,确认是否是位宽的问题。

粒度不匹配:确保权重使用的是逐通道量化(per-channel),而不是逐张量(per-tensor)。逐张量量化大模型几乎必然失败。

2. 检查校准数据集

校准数据量不足:尝试将校准样本增加到256或512条,看看是否有改善。

校准数据分布偏差:校准集是否与你的测试/实际使用场景匹配?如果校准集全是英文,而测试集是中英混合,量化参数可能不适合。

校准数据中包含异常值:是否有乱码、过长的 token 序列、特殊字符等?这些会破坏激活值统计。

3. 检查量化算法配置

  • GPTQ: 检查 group_size 是否设置得当(通常 128)。过大的 group_size 会降低精度。检查是否开启 desc_act(按 Hessian 排序),这对某些模型有帮助。

AWQ:确认缩放因子的搜索范围足够,且没有对重要通道的缩放造成负面效应。尝试微调 alpha 参数。

• SmoothQuant:确认 $ \alpha $(迁移强度)是否合适,如果激活值离群特别严重,需要增大 $ \alpha $(如 0.7)。

· LLM.int8(): 确认是否遗漏了对离群特征的处理。如果模型有大量离群值,混合精度策略是必须的。

4. 检查推理引擎与算子实现

  • 推理引擎版本:不同的 vLLM、TensorRT-LLM、ExLlamaV2 版本可能对同一量化格式的实现有细微差异。尝试用官方提供的推理脚本测试。

反量化精度:某些引擎可能在反量化时使用较低精度(如 FP16 而非 FP32),累积误差可能更大。尝试切换精度。

  • KV Cache 精度:如果你同时量化了 KV Cache(如 INT8),这可能是额外的误差来源。先保持 KV Cache 为 FP16,排除这个变量。

5. 人工抽查输出样本

看模型是否在生成重复的无意义 token?这通常意味着某些关键层的权重被过度压缩。

看模型是否在特定 token 上出现乱码?这可能是激活值离群值未被妥善处理。

模型是否丧失了多语言能力?可能校准数据语言单一。

6. ☒ 与原始模型对比,逐项排除

在同一个硬件、同一套推理代码上,跑原版 FP16 模型,确认不是推理环境本身的问题。

· 换一个已知表现良好的量化版本(如 TheBloke 发布的 Q4_K_M),如果他的版本没问题,说明问题在你自己的量化配置或流程上。

最后的大招

如果上述排查无果,可以尝试用更精细的方法——比如用 QLoRA 的思路,对量化后的模型进行极少量微调(几百步),让模型适应量化噪声。这通常能挽回大部分精度损失,但需要额外的算力。

记住:量化是一门工程手艺,不是魔法。耐心地调试每一个参数,用数据和实验说话,总能找到问题的根源。