大模型量化从0到1(三):PTQ 和 QAT 到底怎么选,一篇讲透
PTQ vs QAT:为什么有的量化几分钟完成,有的却要重新训练好几天?
从量化公式、误差来源、校准、伪量化和 STE,一路讲到 AWQ、GPTQ、GGUF、QLoRA 与真实部署选型
模型量化看起来像一件很简单的事:把原本用 FP32、FP16 或 BF16 保存的数字,换成 INT8、INT4,甚至 2-bit,不就结束了吗?
可真正上手以后,你会发现同样都叫“量化”,工程流程却可能完全不同:
- 有的方法加载一个训练好的模型,喂几十到几百条校准样本,几分钟或几十分钟后就能导出低比特权重;
- 有的方法需要重新进入训练循环,准备数据、优化器、学习率、反向传播和检查点,耗时从几小时到几天不等;
- 还有一些方法明明使用了梯度,却仍然被论文归在 PTQ;另一些方法虽然在“量化模型上训练”,却不一定属于经典 QAT。
这背后的分界线,就是 PTQ 与 QAT。
但只记住“PTQ 不训练,QAT 要训练”还远远不够。这个说法适合第一分钟入门,却不足以解释下面这些更实际的问题:
- 为什么 AWQ 和 GPTQ 都属于 PTQ,但它们又需要校准数据?
- 为什么有些 PTQ 方法也会做局部优化,甚至调用反向传播?
- 为什么 GGUF 不能和 AWQ、GPTQ 并列成一种量化算法?
- 为什么 QLoRA 使用了 4-bit 模型训练,却通常不直接等同于 QAT?
- 为什么同一个“4-bit 模型”,有的只省显存不提速,有的却能明显加速?
- 为什么量化到 2-bit 时,QAT 往往更有价值,而 8-bit 时 PTQ 常常已经够用?
这篇文章会把这些岔路口一次讲清。你不需要先学过数值分析,也不需要懂硬件指令集。读完以后,你应该能做到三件事:
- 看懂 PTQ 与 QAT 在数学和训练流程上的真正差别;
- 正确判断 AWQ、GPTQ、SmoothQuant、GGUF、bitsandbytes、QLoRA 分别处在什么位置;
- 面对自己的模型、硬件和业务指标时,知道应该先试哪条路线,以及什么时候才值得为 QAT 付出训练成本。
目录
- 先看三个真实场景
- 一句话理解 PTQ 与 QAT,但不要停在这一句话
- 在比较路线之前,先把“量化”本身讲明白
- 量化误差从哪里来:舍入、截断与离群值
- PTQ 到底在做什么
- 动态量化、静态量化与权重-only:三个概念不要混用
- AWQ、GPTQ、SmoothQuant、GGUF 与 bitsandbytes 各自是什么
- QAT 到底在训练什么
- round 不可导,STE 为什么还能让模型学下去
- PTQ 与 QAT 并不是绝对二分,而是一条连续谱
- 成本、精度、速度与显存:真正的工程权衡
- 一个更公平、可复现的最小实验
- 如何正确解读实验结果
- 真实项目中的 PTQ 工作流
- 真实项目中的 QAT 工作流
- 一套可以直接使用的选型决策树
- 常见误区与避坑
- 横向对比总表
- 小结:先 PTQ,不迷信 PTQ;需要 QAT,也不必从头重训
- 附录:术语表与延伸阅读
一、先看三个真实场景
场景 A:把 7B 大模型塞进消费级显卡
你从 Hugging Face 下载了一个 7B 参数的指令模型,准备放到自己的消费级显卡上做本地推理。模型原始权重是 FP16,仅权重的理论体积就大约是:
[
7\times 10^9\times 2\text{ Byte}\approx 14\text{ GB}
]
这还没有算 KV Cache、临时工作区、CUDA 上下文、算子缓存和框架额外开销。模型也许勉强能加载,却很难留出足够空间跑长上下文或较大的 batch。
你的目标很明确:
- 不准备重新训练;
- 没有原始预训练数据;
- 最多能拿一些有代表性的文本做校准;
- 希望尽快得到一个能运行的 4-bit 模型。
这几乎就是标准的 PTQ 场景。你拿已有检查点做离线量化,评估质量,再交给支持相应格式和内核的推理框架。
场景 B:端侧模型必须在 INT8 或更低精度运行
你在做一个部署到 MCU、NPU 或移动端 DSP 上的小模型,例如语音唤醒、手势识别、关键词分类或轻量视觉模型。目标硬件只对 INT8 算子有高效支持,甚至要求权重与激活都压到 4-bit。
你先做 PTQ,发现:
- 浮点模型准确率 96%;
- INT8 PTQ 还能接受;
- 但 W4A4 或更低精度时,召回率明显下降;
- 某些少数类几乎完全失效。
与此同时,你拥有完整训练数据、损失函数、训练代码和部署测试集,也愿意用额外训练换回精度。
这时 QAT 才真正有发挥空间:训练阶段就模拟目标低精度,让参数主动适应舍入与截断误差。
场景 C:你用 QLoRA 微调一个 4-bit 模型
你把基础大模型以 NF4 形式加载,冻结基础权重,只训练 LoRA 适配器。训练显存显著下降,于是很自然地产生一个疑问:
我都已经在 4-bit 模型上训练了,这是不是 QAT?
不一定。
经典 QAT 的核心目标,是让最终要部署的量化权重或激活在训练中显式适应量化算子;QLoRA 的核心目标,则是借助冻结的 4-bit 基础模型降低微调显存,只把梯度更新到 LoRA 适配器。训练完以后,你可以保留适配器,也可以合并后重新量化。两者有交集,但不能因为都出现了“低比特训练”就直接画等号。
这三个场景揭示了一个重要事实:
判断 PTQ 还是 QAT,不能只看文件是不是 4-bit,也不能只看代码里有没有 backward;要看低比特误差是否进入了模型参数适应的训练闭环,以及最终部署对象是什么。
二、一句话理解 PTQ 与 QAT,但不要停在这一句话
最适合入门的定义是:
PTQ(Post-Training Quantization,训练后量化):模型已经训练完成,在不进行完整量化感知训练的前提下,把现有检查点转换成低精度表示。它可以完全不使用数据,也可以使用少量无标签校准数据,还可能执行逐层或逐块的局部优化。
QAT(Quantization-Aware Training,量化感知训练):在训练或微调阶段显式模拟目标量化行为,让模型参数在优化过程中适应量化误差,训练结束后再转换为真实低精度部署形式。
为了好记,可以继续使用“改衣服”的比喻:
- PTQ 像衣服做好以后再改尺寸。速度快,原版型基本不动,但修改空间有限;
- QAT 像在打版和试穿过程中不断按目标尺寸调整。成本高,却能主动修正不合身的地方。
不过这个比喻有两个容易误导的地方。
第一,PTQ 不一定只是“机械改小”。GPTQ 会根据近似二阶信息决定如何量化并补偿误差,AWQ 会利用激活统计识别重要通道,OmniQuant 甚至会在小规模校准数据上优化量化参数。它们仍然属于 PTQ,因为没有进行面向完整任务目标的端到端量化训练。
第二,QAT 不一定等于“从头训练”。现实中更常见的是从一个已经训练好的浮点检查点出发,进行较短的量化感知微调。对于大模型,还可以只更新低秩参数、量化尺度或部分模块,而不是把所有参数完整重训。
所以更准确的分界是:
PTQ 的重点是“在既有模型附近寻找更好的量化表示”;QAT 的重点是“把量化误差放进训练目标,让模型本身为低精度部署而适应”。
三、在比较路线之前,先把“量化”本身讲明白
很多人第一次接触量化,会把它理解成“把 float 转成 int”。这只说对了表面。
量化真正做的是:用有限个离散数值,近似表示原来更密集、范围更大的连续或高精度数值。
例如,一个 FP16 权重可能是:
-0.1372, -0.0428, 0.0061, 0.0934, 0.2817
如果你只允许 2-bit,那么最多只有 (2^2=4) 个编码。你必须让许多不同的浮点数映射到同一个离散格点。信息损失由此产生。
3.1 最常见的仿射量化公式
设浮点数为 (x),整数编码为 (q),量化步长为 (s),零点为 (z),整数范围为 ([q_{\min},q_{\max}])。常见仿射量化写作:
[
q=\operatorname{clip}\left(\operatorname{round}\left(\frac{x}{s}\right)+z, q_{\min},q_{\max}\right)
]
反量化得到近似值:
[
\hat{x}=s(q-z)
]
这里的三个动作分别对应三种直觉:
- 除以 (s):把浮点范围缩放到整数网格;
- round:把连续位置吸附到最近的格点;
- clip:超出可表示范围的值只能压到边界。
如果数据范围是 ([x_{\min},x_{\max}]),一种基础的 min-max 取法是:
[
s=\frac{x_{\max}-x_{\min}}{q_{\max}-q_{\min}}
]
[
z=\operatorname{round}\left(q_{\min}-\frac{x_{\min}}{s}\right)
]
这叫非对称或仿射量化,因为零点 (z) 不一定等于 0。它适合分布明显偏向一侧的数据,例如 ReLU 后非负激活。
3.2 对称量化
如果权重分布大致围绕 0 对称,可以设:
[
a=\max(|x_{\min}|,|x_{\max}|)
]
[
s=\frac{a}{q_{\max}},\quad z=0
]
这样实现简单,也更容易对应某些整数矩阵乘内核。
不过“对称”不代表所有实现都使用完全相同的整数范围。例如 INT8 有的实现使用 ([-127,127]) 的窄范围,有的使用 ([-128,127])。到了 2-bit,这个细节尤其重要:如果你简单令 (q_{\max}=1),再裁剪到 ([-1,1]),实际只得到 (-1,0,1) 三个级别,严格说更接近三值量化,而不是完整利用 2-bit 的四个编码。
这也是很多教学代码里隐藏的第一个坑:代码写着“2-bit”,实际量化级别却未必有四个。
3.3 scale 和 zero-point 不是附属品
低比特权重不能只保存整数本身。推理时还需要知道如何还原近似数值,因此通常还要保存:
- scale;
- 可选的 zero-point;
- 分组边界或张量形状;
- 打包布局;
- 某些格式中的重要性信息、混合精度标记或校验元数据。
所以 7B 模型从 FP16 量化到 4-bit,原始权重的理论下限是从约 14 GB 降到约 3.5 GB,但实际文件通常会高于 3.5 GB。原因不是“量化失败”,而是尺度、分组、对齐、元数据以及部分未量化张量都需要空间。
3.4 粒度决定精度与开销
scale 可以覆盖不同范围:
Per-tensor
整个张量共用一个 scale。开销最小,但如果张量内部各通道范围差异很大,一小撮极端值会迫使所有元素共用很粗的步长。
Per-channel
每个输出通道或输入通道使用独立 scale。更能适应通道差异,通常比 per-tensor 精度更好,但需要保存更多尺度,并要求内核支持相应布局。
Per-group
把通道再切成固定大小的小组,例如每 32、64 或 128 个权重共用一个 scale。它位于 per-tensor 与 per-channel 之间,也是大模型 4-bit 权重量化中非常常见的折中。
组越小,通常越容易降低量化误差;但尺度元数据更多、打包更复杂,某些硬件上的吞吐也可能受影响。
3.5 W4A16、W8A8 到底在说什么
量化配置常写成:
- W8A8:权重 8-bit,激活 8-bit;
- W4A16:权重 4-bit,激活仍用 FP16 或 BF16;
- W4A8:权重 4-bit,激活 8-bit;
- W4A4:权重和激活都 4-bit。
对大模型来说,AWQ、GPTQ 常见部署形态主要是低比特权重加高精度激活,也就是常说的 weight-only quantization。它最大的直接收益是压缩权重并降低读取权重的内存带宽压力。
而 W8A8、W4A8 或 W4A4 不仅压权重,还试图让矩阵乘的输入和计算路径进入整数或低精度内核。这样理论上的计算收益更大,但激活往往比权重更难量化,工程难度也更高。
3.6 不要忘了 KV Cache
大模型推理的显存不只有权重。长上下文生成时,KV Cache 可能成为显存大户。仅做 W4A16 能大幅压缩模型权重,却不会自动把 KV Cache 也压成 4-bit。
因此,同样是“4-bit 模型”,在短上下文下可能主要受权重读取影响,在长上下文或高并发下却可能转而受 KV Cache 和注意力计算影响。量化方案是否真的解决你的瓶颈,必须结合上下文长度、batch、并发和缓存精度一起看。
四、量化误差从哪里来:舍入、截断与离群值
要理解 PTQ 与 QAT,先要知道模型究竟在适应什么误差。
4.1 舍入误差
假设一个值落在两个量化格点之间,它只能被 round 到其中一个。若步长为 (s),且没有发生截断,那么最简单的均匀量化中,单个标量的舍入误差绝对值通常不超过约 (s/2)。
问题在于,神经网络里一次矩阵乘会累积大量这样的误差。单个权重偏一点看似无关紧要,经过多层传播、非线性和残差相加后,最终输出可能产生明显变化。
4.2 截断误差
为了让步长更细,我们常常不会覆盖所有极端值,而是选择一个较窄的量化范围。超出范围的值会被 clip 到边界。
这带来一个经典矛盾:
- 范围放得很宽:极端值保住了,但 scale 变大,大多数普通值的格点变粗;
- 范围收得很窄:主体分布表示得更细,但少数离群值被严重截断。
量化算法的许多技巧,本质上都在解决这个矛盾:怎样选择范围,怎样保护重要离群通道,怎样把难量化的分布变得更平滑。
4.3 权重通常比激活更“好管”
权重在训练结束后固定不变。你可以完整遍历它们,精确知道每个张量、通道或分组的最小值、最大值、分位数和误差。
激活则依赖输入:
[
a_l=f_l(x;W)
]
换一批输入,某一层激活的分布可能就变化。语言模型中,不同语言、代码、数学文本、长上下文和特殊格式都可能激活不同通道。
这就是校准数据存在的原因。你需要用一小批尽量代表真实使用场景的数据做前向传播,观察激活范围、离群值、通道重要性或近似二阶统计。
但“权重量化不需要数据”也只能算半句话:
- 最朴素的 per-tensor min-max 权重量化,确实只看权重即可;
- 现代高质量 weight-only PTQ,例如 AWQ、GPTQ,往往仍会利用校准输入来判断哪些权重更重要、怎样补偿误差。
4.4 误差大小不等于任务损失大小
另一个常见误区是:只要让 (|W-\hat W|_2) 最小,模型质量就一定最好。
并不一定。
同样大小的权重误差,落在不重要的方向上可能几乎没有影响;落在高敏感方向上,却可能显著改变输出。于是更先进的方法会尝试利用激活、Hessian 近似或层输出重建,让量化误差更贴近“模型实际会不会受伤”这个目标,而不只是机械追求每个权重的数值距离最小。
这正是 GPTQ 使用近似二阶信息、AWQ 使用激活统计的动机之一。
五、PTQ 到底在做什么
PTQ 的最简流程确实很短:
浮点检查点
↓
确定目标位宽、粒度和部署后端
↓
可选:用校准数据做前向统计
↓
计算 scale / zero-point / clipping / 分组策略
↓
离线生成量化权重并打包
↓
导出、部署、评估
但真正决定质量的,不是“有没有调用 quantize()”,而是中间几步做得多精细。
5.1 最朴素的无数据 PTQ
最简单的方法只看权重本身:
- 对每个张量或通道计算 min/max;
- 根据目标位宽计算 scale;
- round 和 clip;
- 保存整数编码和尺度。
优点是快、简单、完全不需要数据。缺点是它不知道某个权重在真实输入下是否重要,也不知道量化误差会怎样传递到层输出。
在 INT8 或本身非常稳健的小模型上,这种方法可能已经够用;在 LLM 的 4-bit、3-bit 或 2-bit 场景中,它常常不是质量最好的选择。
5.2 带校准数据的 PTQ
校准数据通常不需要标签,因为目标不是重新训练任务,而是观察模型内部统计。常见用途包括:
- 估计激活范围;
- 统计通道幅度或离群值;
- 构造近似 Hessian 或输入协方差;
- 比较不同 clipping 阈值的重建误差;
- 搜索分组尺度;
- 判断哪些层或张量应保留更高精度。
校准集的关键不在于“越大越好”,而在于覆盖真实输入分布。中文对话模型应包含中文对话;代码模型应包含代码;长文本应用应让校准序列长度接近部署长度;结构化输出任务应覆盖真实模板和特殊 token。
几百条高度同质的样本,可能不如几十条覆盖核心场景的样本有用。
5.3 PTQ 也可能包含优化
把 PTQ 理解成“绝对不能出现梯度”是不准确的。
有些 PTQ 方法会在小规模校准数据上优化 clipping、尺度或等价变换,甚至逐块最小化浮点输出与量化输出之间的重建误差。OmniQuant 就会在可微框架中优化量化参数,但仍被归类为 PTQ,因为它没有像 QAT 那样对完整模型进行面向训练目标的长期量化适应,而是在已有检查点附近做高效校准优化。
因此,“有没有 backward”不是唯一判据。更有用的问题是:
- 更新的是完整模型参数,还是少量量化参数?
- 优化目标是任务损失,还是局部重建误差?
- 使用的是完整训练数据,还是少量无标签校准样本?
- 训练过程是否让模型系统性适应目标低精度?
5.4 PTQ 的优点
- 不需要重建完整训练流程;
- 对开源检查点、第三方模型和无法获得原始数据的模型尤其友好;
- 时间成本低,适合快速尝试多个位宽、分组大小和后端;
- 便于把同一个模型导出为多种部署格式;
- 大模型场景中通常是第一选择。
5.5 PTQ 的局限
- 模型参数本身不会主动为量化误差让路;
- 位宽越低,可选格点越少,单靠校准越来越难;
- 激活离群值、特殊层和分布漂移可能造成明显退化;
- 校准集与真实业务不匹配时,离线评估好、线上表现差;
- 算法得到“数值上不错”的低比特权重,不代表部署后端一定有高效内核。
PTQ 的本质不是免费午餐,而是用算法设计、校准统计和硬件友好布局,换取“不重训模型”的巨大成本优势。
六、动态量化、静态量化与权重-only:三个概念不要混用
原始入门文章里最常见的一处表述是:
AWQ、GPTQ 基本都是静态量化。
这句话不够准确。
6.1 经典语境中的动态量化
在传统 CPU INT8 推理里,动态量化通常指:
- 权重提前量化;
- 激活的 scale 或范围在推理时根据当前输入动态计算;
- 计算前后进行必要的量化、反量化或整数累积。
它不依赖离线激活校准,部署简单,但运行时需要统计激活范围,存在额外开销。
6.2 经典语境中的静态量化
静态量化通常指:
- 权重提前量化;
- 激活的 scale 也通过校准数据提前确定;
- 推理时直接使用固定量化参数。
它更依赖校准质量,但可让推理路径更稳定,也更容易进入完整整数算子。
6.3 AWQ、GPTQ 更准确的标签是“离线 weight-only PTQ”
AWQ 和 GPTQ 的常见部署形式是:
- 权重离线压到 4-bit、3-bit 等;
- 激活仍保持 FP16/BF16,或者在内核内部按需要处理;
- 推理时读取打包低比特权重,解包或融合反量化后参与矩阵乘。
它们没有在经典意义上为“激活静态量化”预先固定一个 INT8 scale。因此,称它们为“离线权重量化”或“weight-only PTQ”通常比笼统说“静态量化”更清晰。
6.4 为什么这个术语区别重要
因为它直接决定你在期待什么收益:
- Weight-only 主要压缩模型权重与内存带宽;
- W8A8 静态量化还希望把激活和矩阵乘路径一起低精度化;
- 动态量化则在部署便利性和运行时开销之间折中。
如果术语混在一起,就很容易出现“我已经 4-bit,为什么 GPU 利用率没有明显提升”“为什么文件小了,prefill 却没快多少”之类的误解。
七、AWQ、GPTQ、SmoothQuant、GGUF 与 bitsandbytes 各自是什么
这一节非常关键,因为很多教程把算法、文件格式、运行库和训练方法混在一张表里。
7.1 GPTQ:利用近似二阶信息的 one-shot 权重量化
GPTQ 是面向生成式预训练 Transformer 的训练后权重量化方法。它的核心不是简单逐元素 round,而是利用校准输入形成的近似二阶信息,逐块或逐列量化权重,并把已经产生的误差补偿到尚未量化的权重中。
直觉上可以这样理解:
如果一个权重必须被推到邻近格点,GPTQ 不只是接受这次偏差,而是根据输入敏感方向,调整后续权重,让整层输出尽量接近原模型。
因此 GPTQ 仍然是 PTQ,但它比朴素 min-max 更“懂”量化误差对层输出的影响。原论文将其描述为基于近似二阶信息的 one-shot 权重量化方法,并展示了在超大模型上进行 3-bit、4-bit 量化的可行性。[1]
7.2 AWQ:激活感知的权重-only PTQ
AWQ 的出发点是:权重并非同等重要,某些与大幅激活通道相关的权重对输出更敏感。它利用少量校准数据统计激活分布,识别重要通道,再通过等价缩放等方式降低这些通道的量化误差,同时保持硬件友好的低比特权重布局。
AWQ 不是“把激活也量化了”。这里的 activation-aware,主要是说它借助激活统计来决定怎样量化权重。原论文将其定位为硬件友好的低比特 weight-only 方法,并强调不依赖反向传播或重建。[2]
7.3 SmoothQuant:把激活量化难题迁移一部分给权重
LLM 的激活常存在明显离群通道,直接 W8A8 容易受影响。SmoothQuant 通过数学等价的离线缩放,让激活分布更平滑,同时把一部分量化难度迁移到相对更容易处理的权重侧,从而支持训练后 W8A8 量化。[3]
它与 AWQ 都会谈到激活和缩放,但目标不同:
- AWQ 常见目标是高质量低比特 weight-only;
- SmoothQuant 重点是让权重和激活都能进入 INT8 路径。
7.4 GGUF:容器格式,不是量化算法
这是需要明确纠正的一点。
GGUF 是 ggml/llama.cpp 生态中用于保存模型张量和元数据的文件格式。一个 GGUF 文件可以保存较高精度权重,也可以保存多种量化类型。llama.cpp 的量化工具可以把 F32、BF16 等 GGUF 模型转换成量化 GGUF,但“GGUF”本身不等于某种具体的量化算法。[4]
更准确的说法是:
- AWQ、GPTQ 是量化方法或算法家族;
- Q4_K_M、IQ4_XS 等是特定生态中的量化类型或布局;
- GGUF 是承载张量、量化数据和模型元信息的容器格式;
- llama.cpp 是可以加载、量化和运行这类模型的推理生态。
把 GGUF 与 AWQ、GPTQ 并列为“PTQ 三大算法”,就像把 JPEG、某个降噪算法和 Photoshop 放在同一个算法分类里,层级不一致。
7.5 bitsandbytes:库与实现集合,不是一种单独算法
bitsandbytes 提供了多种低精度加载、矩阵运算和优化器能力。你可能通过它做 8-bit 推理,也可能用 4-bit NF4 加载基础模型进行 QLoRA 微调。
因此“bitsandbytes 属于 PTQ 还是 QAT”没有唯一答案,要看具体功能:
- 直接把预训练模型以 8-bit 或 4-bit 形式加载用于推理,更接近 PTQ/低精度推理工具链;
- 用 NF4 冻结基础模型、训练 LoRA 适配器,是 QLoRA 的训练范式;
- 它不是一个与 GPTQ、AWQ 完全同层级的单一算法名称。
7.6 QLoRA:低显存微调,不应自动等同于 QAT
QLoRA 把冻结的预训练模型以 4-bit 形式保存和计算,把梯度传给 LoRA 适配器,从而显著降低微调内存。原论文的重点是高效微调量化模型,而不是让基础权重通过 fake quant 主动学习目标部署误差。[5]
当然,工程上可以把 LoRA、低比特基础权重和量化感知目标结合,形成参数高效 QAT。但那需要看训练图里到底模拟了什么量化、更新了哪些参数,以及最终如何导出,不能只凭“用了 4-bit”判断。
八、QAT 到底在训练什么
QAT 的核心不是在训练时真的把所有参数永久改成整数,而是在前向传播中模拟目标量化误差,同时保留可更新的高精度参数副本。
经典流程可以写成:
高精度参数 W
↓
Fake Quant:量化 Q(W) 再反量化 D(Q(W))
↓
使用近似权重 W_hat 做前向
↓
计算任务损失
↓
通过 STE 等近似梯度更新高精度 W
也就是说,模型真正被优化的是“影子浮点权重”,但它每次前向看到的,都是受量化限制后的近似值。
8.1 伪量化为什么要“量化再反量化”
训练框架中的多数算子仍然使用浮点张量与浮点反向传播。如果直接把权重变成真正整数,很多优化器、梯度更新和算子都无法按普通训练方式工作。
于是 fake quant 做两步:
- 用目标整数网格模拟 round、clip 和有限位宽;
- 立刻反量化回浮点张量,继续交给普通矩阵乘。
数值已经被限制在量化格点上,但数据类型仍可保持浮点,方便训练。
要特别强调:
Fake quant 训练不等于已经获得真实整数推理速度。
训练时的“量化”主要是数值模拟。最终能否加速,仍取决于 convert/export 阶段是否生成了目标后端支持的低比特权重、整数算子和打包布局。
8.2 QAT 可以量化哪些对象
QAT 不一定只量化权重。常见配置包括:
- 只对权重 fake quant;
- 权重与激活同时 fake quant;
- 只量化部分线性层;
- 保留首层、末层、embedding、归一化或敏感模块为高精度;
- 对不同层使用不同位宽;
- 训练 scale、clipping 阈值或 zero-point;
- 连 KV Cache 或注意力中间量也纳入低精度设计。
目标硬件支持什么算子,往往比“理论上能量化到几位”更重要。
8.3 QAT 的训练起点
QAT 有两种常见起点:
从头做量化感知训练
适用于模型较小、训练流程完全可控、目标低比特非常明确的场景。训练全程都暴露于量化误差,但成本最高。
从浮点检查点做量化感知微调
这是更现实的做法:
- 先得到高质量浮点模型;
- 插入 fake quant;
- 用较小学习率做一段微调;
- 逐步稳定 observer、scale 和权重;
- 导出低比特模型。
对于大模型,QAT 更常是“量化感知微调”,而不是“把预训练重做一遍”。PyTorch 与相关工具链已经展示了面向 LLM 的端到端 QAT 流程;研究中也出现了低秩、参数高效的 QAT 方法,用更小的训练内存逼近传统全参数 QAT。[6][7]
8.4 QAT 真正学到的是什么
模型可能通过多种方式适应量化误差:
- 把权重从量化格点边界附近推向更稳定的位置;
- 调整通道尺度,减少少数离群值对整体步长的绑架;
- 改变冗余参数之间的分工,让某个权重的舍入误差由其他权重补偿;
- 学习更合适的 clipping 阈值或 scale;
- 让中间表示对低精度噪声更鲁棒;
- 在任务损失直接监督下,优先保护真正影响业务指标的方向。
这也是 QAT 相比纯 PTQ 最本质的优势:PTQ 只能在现有参数附近寻找更好的表示,QAT 则允许参数本身移动。
九、round 不可导,STE 为什么还能让模型学下去
QAT 最大的数学障碍来自 round。
9.1 round 的真实导数
round 是阶梯函数:
- 在每个台阶内部,输出不随输入小幅变化,导数为 0;
- 在台阶跳变点,函数不可导。
因此如果严格使用真实导数:
[
\frac{\partial\operatorname{round}(x)}{\partial x}=0
]
几乎处处成立。梯度经过量化节点后就会消失,前面的权重无法获得有效学习信号。
9.2 STE 的基本思想
Straight-Through Estimator 的思路是:
- 前向传播使用真实或模拟的离散操作;
- 反向传播使用一个更容易优化的替代导数。
最简单的 identity STE 直接假设:
[
\frac{\partial\operatorname{round}(x)}{\partial x}\approx 1
]
于是梯度像穿过恒等函数一样继续向前传。
这不是 round 的真实梯度,而是一个有偏的近似估计器。它之所以有用,不是因为数学上“突然可导了”,而是因为它给优化器提供了方向一致、方差较低且工程上可用的学习信号。STE 的经典表述可以追溯到对离散或随机神经元梯度估计的研究。[8]
9.3 一行 PyTorch 写出 identity STE
有一个非常直观的写法:
x_ste = x + (x_hat - x).detach()
前向看数值:
x + x_hat - x = x_hat
所以模型实际使用量化后的近似值 (x_{hat})。
反向看梯度:detach() 内部不参与求导,因此:
[
\frac{\partial x_{ste}}{\partial x}=1
]
这就实现了“前向用量化值,反向把梯度当作恒等映射穿过去”。
9.4 更真实的 STE 不一定处处传 1
教学代码常直接返回 grad_output,但实际实现可能更谨慎:
- 只对未超出 clipping 范围的值传梯度;
- 对被截断的区域设梯度为 0;
- 对 scale 使用单独的梯度缩放;
- 使用可学习步长,如 LSQ;
- 使用可学习激活截断,如 PACT;
- 在训练早期和后期采用不同 observer 或冻结策略。
所以“STE 就是把 round 的梯度永远设成 1”是最适合入门的版本,不是所有 QAT 算法的完整定义。LSQ 等方法还会直接学习量化步长,使 quantizer 本身也成为优化对象。[9]
9.5 STE 的代价
STE 给出的不是精确梯度,因此可能出现:
- 训练震荡;
- scale 更新不稳定;
- 低比特下梯度与真实离散目标偏差过大;
- 训练损失下降,但真正整数导出后效果不一致;
- fake quant 配置与部署内核不匹配。
这就是为什么 QAT 不只是“插个 round 再继续训练”。学习率、冻结时机、量化粒度、敏感层处理、数据分布和导出一致性都很重要。
十、PTQ 与 QAT 并不是绝对二分,而是一条连续谱
真实世界中的方法可以大致排成一条从“完全不训练”到“完整量化训练”的连续谱:
无数据 min-max PTQ
↓
带校准统计的 PTQ
↓
带误差补偿 / 二阶近似的 PTQ
↓
带局部重建与少量可学习量化参数的 PTQ
↓
量化感知微调
↓
参数高效 QAT / 低秩 QAT
↓
全参数 QAT
↓
从头低比特训练
10.1 为什么边界会模糊
因为“训练”本身有多个维度:
- 是否有反向传播;
- 是否更新原始模型权重;
- 是否使用任务标签;
- 是否优化整个模型;
- 是否只用少量校准样本;
- 是否以任务损失还是重建误差为目标;
- 是否持续多个 epoch;
- 最终部署量化误差是否进入训练闭环。
只看其中一个维度,很容易误判。
10.2 一个实用分类原则
可以用下面三个问题判断:
问题 1:模型主体参数是否为目标低精度部署而更新?
如果没有,只是计算尺度、做搜索或局部补偿,更偏 PTQ。
问题 2:优化目标是否是完整任务损失?
如果主要最小化层输出重建误差,更偏 PTQ 校准;如果直接用语言建模损失、分类损失或蒸馏损失让模型适应量化,更偏 QAT。
问题 3:最终部署时使用的量化行为,是否在训练前向中被持续模拟?
如果是,这是 QAT 的强信号。
10.3 参数高效 QAT 改变了“QAT 一定贵得离谱”的印象
传统全参数 QAT 确实成本高,但低秩 QAT、LoRA 化 QAT、只训练 scale 或部分敏感层等方法,可以显著降低训练内存和更新参数量。LR-QAT 等工作就尝试用低秩更新实现更轻量的 LLM 量化感知训练。[7]
这不意味着 QAT 从此“和 PTQ 一样便宜”,而是说明选项不再只有两个极端:
- 几分钟纯 PTQ;
- 几周完整预训练。
中间还有大量量化感知微调方案。
十一、成本、精度、速度与显存:真正的工程权衡
“PTQ 快、QAT 准”只是第一层结论。真实选型至少要同时考虑五个维度:模型质量、量化耗时、训练资源、推理内存和实际延迟。
11.1 模型体积:先算理论下限,再看实际开销
假设模型有 (N) 个参数,每个参数使用 (b) bit,纯权重理论体积约为:
[
M_{weight}=\frac{N\times b}{8}\text{ Byte}
]
以 7B 模型为例:
| 权重精度 | 理论纯权重体积 |
|---|---|
| FP32 | 约 28 GB |
| FP16/BF16 | 约 14 GB |
| INT8 | 约 7 GB |
| 4-bit | 约 3.5 GB |
| 2-bit | 约 1.75 GB |
但实际显存或文件体积还会包括:
- scale 与 zero-point;
- 分组元数据;
- 对齐和打包开销;
- 未量化的 embedding、归一化、输出头或敏感层;
- 运行时解包缓冲;
- 激活与临时张量;
- KV Cache;
- 框架和设备上下文。
因此,“4-bit 就一定正好是 FP16 的四分之一”只能作为粗略估算,不能当作部署承诺。
11.2 推理速度:位宽下降不等于延迟按比例下降
很多人会自然地推断:权重从 16-bit 变成 4-bit,速度就应该快四倍。实际通常没有这么简单。
一次低比特推理可能经历:
- 从显存读取打包的低比特权重;
- 在寄存器、共享内存或融合内核中解包;
- 反量化或转换为计算类型;
- 执行矩阵乘;
- 写回高精度累积结果。
最终速度取决于:
- 当前阶段是内存带宽瓶颈还是计算瓶颈;
- GPU、CPU、NPU 是否有原生低比特指令;
- 内核是否支持你的 group size、量化格式和模型结构;
- 解包与反量化能否和矩阵乘融合;
- batch、序列长度和并发大小;
- prefill 与 decode 哪一段占主导;
- KV Cache 是否仍是高精度;
- 是否发生频繁的数据类型转换。
所以量化首先几乎总能带来“权重更小”的收益,但能否带来“明显更快”的收益,需要实际 benchmark。
一个常见情况是:单 token decode 对权重带宽敏感,weight-only 量化很有价值;而大 batch prefill 更偏计算密集,未必按同样比例加速。另一个常见情况是模型文件缩小很多,但使用的后端没有匹配内核,只能先解包成 FP16 再算,于是显存节省明显,吞吐提升却有限。
11.3 QAT 的训练成本为什么高
普通全参数训练已经需要保存:
- 前向权重;
- 梯度;
- 优化器状态;
- 激活;
- 临时计算缓冲。
经典 fake-quant QAT 往往还需要:
- 高精度主权重;
- fake quant 节点;
- observer 或量化参数;
- 额外的转换与统计操作。
因此 QAT 不是“训练时直接用 INT4,所以训练显存自动变成四分之一”。传统 QAT 的低比特主要服务于最终推理数值模拟,训练更新通常仍依赖高精度参数和高精度优化器状态。
不过也不能把这句话绝对化。参数高效 QAT、低秩 QAT、冻结大部分参数、低精度优化器以及激活检查点,都可以降低训练成本。正确表述应该是:
经典全参数 fake-quant QAT 通常不会像低比特推理那样线性节省训练显存;现代参数高效方案可以缓解,但仍比纯 PTQ 更复杂。
11.4 精度优势不是只由 bit 决定
“8-bit PTQ 足够,2-bit QAT 更强”是常见趋势,却不是无条件定律。实际差异还受这些因素影响:
- 模型架构;
- 参数规模;
- 权重和激活分布;
- per-tensor、per-channel、per-group 粒度;
- group size;
- 对称或非对称量化;
- 是否量化激活、KV Cache、embedding 和输出头;
- 校准数据是否匹配;
- PTQ 算法强弱;
- QAT 数据量、学习率和训练轮数;
- 业务指标对少数错误是否敏感。
一个 4-bit weight-only 模型在通用困惑度上几乎不掉,不代表它在数学推理、代码补全、JSON 约束输出或罕见语言上也不掉。量化对某些“边缘能力”的影响,可能比平均指标更明显。
11.5 “精度”必须换成你的业务指标
分类模型可以看:
- Accuracy;
- Precision / Recall / F1;
- ROC-AUC;
- 少数类召回;
- 校准误差。
语言模型可以看:
- Perplexity;
- 任务 benchmark;
- 代码编译通过率;
- 数学题准确率;
- 结构化输出合法率;
- 工具调用成功率;
- 人工偏好;
- 长上下文一致性;
- 安全和拒答行为。
部署指标则包括:
- 首 token 延迟;
- 每 token 延迟;
- tokens/s;
- 峰值显存;
- 单请求和高并发吞吐;
- 能耗;
- 模型加载时间;
- 文件体积。
如果只比较一个公开 benchmark,就可能选出“论文上最好、业务里最差”的配置。
11.6 PTQ 与 QAT 的收益曲线
可以把位宽从高到低想象成不断收紧的瓶颈:
高位宽区间
例如 FP8、INT8 或较温和的 weight-only 8-bit。量化格点仍较密,模型冗余足够,优秀 PTQ 常能取得很小损失。QAT 的边际收益可能不够覆盖训练成本。
主流低比特区间
例如 4-bit weight-only。现代 PTQ 已经很强,很多大模型可以保持较好的整体质量,但对任务、模型和后端仍需实测。QAT 可能继续提升,是否值得取决于业务价值。
极低比特区间
例如 3-bit、2-bit、三值甚至二值。量化级别骤减,舍入和截断误差变得难以仅靠校准解决。模型参数若能在训练中重新组织,通常更有价值,因此 QAT 或量化感知微调的吸引力上升。
权重与激活同时极低比特
W4A4、W2A4 等不仅要处理权重,还要处理输入相关的激活分布。此时 QAT 的价值通常比单纯 W4A16 更明显,但硬件、算子和导出的一致性也更难。
结论不是“低于 4-bit 一律 QAT”,而是:
位宽越低、量化对象越多、业务容错越小,越应该认真评估 QAT;位宽越高、PTQ 算法越成熟、训练资源越有限,越应该优先 PTQ。
十二、一个更公平、可复现的最小实验
原始玩具实验有几个值得修正的问题:
- PTQ 模型和 QAT 模型从不同随机初始化开始,结果不完全可比;
- 只跑一次随机种子,偶然性很大;
- 简单的“2-bit 对称量化”实际可能只有三个量化级别;
- scale 为 0 时存在除零风险;
- 直接硬编码一组“典型输出”,容易让读者误以为结果必然如此;
- QAT 从头训练 200 轮,而实际项目更常从同一个浮点检查点做短程量化感知微调;
- 代码只模拟权重量化,却容易让人误以为同时量化了激活;
- fake quant 仍执行浮点矩阵乘,不能用来测试真实 INT4 速度和显存。
下面给出一个更严谨的教学实验。它仍然是玩具模型,但做了四件事:
- 所有 PTQ 和 QAT 都从同一个浮点检查点出发;
- 使用真正包含 (2^b) 个编码的仿射量化;
- 对 5 个随机种子取均值和标准差;
- 明确只模拟 weight-only 量化,不声称获得真实整数加速。
12.1 实验目标
我们构造一个三分类螺旋数据集。这个任务对非线性决策边界有一定要求,又能在 CPU 上快速完成。
流程是:
- 训练一个浮点 MLP;
- 把同一检查点直接做 fake-quant 评估,模拟 PTQ 数值误差;
- 从同一检查点开始,用 fake-quant 前向做短程微调,模拟 QAT;
- 比较 8、4、3、2 bit;
- 重复 5 个随机种子。
12.2 完整代码
from __future__ import annotations
import statistics
from dataclasses import dataclass
import torch
import torch.nn as nn
import torch.nn.functional as F
# 可选:避免小模型在某些 CPU 上因线程过多反而变慢
# 删除这一行也不影响算法含义
torch.set_num_threads(2)
# ---------------------------------------------------------
# 1. 仿射伪量化:前向使用量化近似值,反向使用 identity STE
# ---------------------------------------------------------
def fake_quant_per_tensor_affine(
x: torch.Tensor,
bits: int,
eps: float = 1e-8,
) -> torch.Tensor:
"""Per-tensor affine fake quantization with identity STE.
说明:
- 使用无符号整数编码 [0, 2**bits - 1];
- zero-point 允许把包含正负值的浮点范围映射到该编码;
- x_hat 是量化再反量化后的近似值;
- x + (x_hat - x).detach() 让前向等于 x_hat,反向 d/dx 等于 1;
- 这是教学实现,不等于某个生产后端的完整量化规则。
"""
if bits < 2:
raise ValueError("This demo expects bits >= 2.")
qmin = 0
qmax = 2**bits - 1
# 量化参数不通过这个简化版本学习,因此 detach
x_min = x.detach().amin()
x_max = x.detach().amax()
# clamp_min 避免全零张量导致除零
scale = ((x_max - x_min) / float(qmax - qmin)).clamp_min(eps)
zero_point = (qmin - x_min / scale).round()
zero_point = zero_point.clamp(qmin, qmax)
q = (x / scale + zero_point).round()
q = q.clamp(qmin, qmax)
x_hat = (q - zero_point) * scale
# identity STE
return x + (x_hat - x).detach()
# ---------------------------------------------------------
# 2. 只对权重做 fake quant 的线性层
# ---------------------------------------------------------
class QuantLinear(nn.Linear):
def __init__(
self,
in_features: int,
out_features: int,
bits: int,
bias: bool = True,
) -> None:
super().__init__(in_features, out_features, bias=bias)
self.bits = bits
def forward(self, x: torch.Tensor) -> torch.Tensor:
w_hat = fake_quant_per_tensor_affine(self.weight, self.bits)
return F.linear(x, w_hat, self.bias)
# ---------------------------------------------------------
# 3. 浮点网络与量化模拟网络
# 两者参数名和形状一致,方便加载同一 state_dict
# ---------------------------------------------------------
class FloatNet(nn.Module):
def __init__(self) -> None:
super().__init__()
self.fc1 = nn.Linear(2, 64)
self.fc2 = nn.Linear(64, 64)
self.fc3 = nn.Linear(64, 3)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = torch.tanh(self.fc1(x))
x = torch.tanh(self.fc2(x))
return self.fc3(x)
class QuantNet(nn.Module):
def __init__(self, bits: int) -> None:
super().__init__()
self.fc1 = QuantLinear(2, 64, bits=bits)
self.fc2 = QuantLinear(64, 64, bits=bits)
self.fc3 = QuantLinear(64, 3, bits=bits)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = torch.tanh(self.fc1(x))
x = torch.tanh(self.fc2(x))
return self.fc3(x)
# ---------------------------------------------------------
# 4. 三分类螺旋数据
# ---------------------------------------------------------
def make_spiral(
seed: int,
n_per_class: int = 400,
noise: float = 0.18,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
generator = torch.Generator().manual_seed(seed)
xs: list[torch.Tensor] = []
ys: list[torch.Tensor] = []
for class_id in range(3):
radius = torch.linspace(0.05, 1.0, n_per_class)
angle = torch.linspace(
class_id * 4.0,
(class_id + 1) * 4.0,
n_per_class,
)
angle = angle + torch.randn(n_per_class, generator=generator) * noise
points = torch.stack(
[radius * torch.sin(angle), radius * torch.cos(angle)],
dim=1,
)
xs.append(points)
ys.append(torch.full((n_per_class,), class_id, dtype=torch.long))
x = torch.cat(xs, dim=0)
y = torch.cat(ys, dim=0)
permutation = torch.randperm(len(y), generator=generator)
x = x[permutation]
y = y[permutation]
train_size = int(0.8 * len(y))
return x[:train_size], y[:train_size], x[train_size:], y[train_size:]
# ---------------------------------------------------------
# 5. 训练和评估
# ---------------------------------------------------------
def fit(
model: nn.Module,
x_train: torch.Tensor,
y_train: torch.Tensor,
epochs: int,
learning_rate: float,
) -> None:
optimizer = torch.optim.AdamW(
model.parameters(),
lr=learning_rate,
weight_decay=1e-4,
)
model.train()
for _ in range(epochs):
optimizer.zero_grad(set_to_none=True)
logits = model(x_train)
loss = F.cross_entropy(logits, y_train)
loss.backward()
optimizer.step()
@torch.no_grad()
def accuracy(
model: nn.Module,
x_test: torch.Tensor,
y_test: torch.Tensor,
) -> float:
model.eval()
prediction = model(x_test).argmax(dim=1)
return (prediction == y_test).float().mean().item()
@dataclass
class Result:
seed: int
bits: int
float_acc: float
ptq_acc: float
qat_acc: float
def run_one_seed(seed: int) -> list[Result]:
torch.manual_seed(seed)
x_train, y_train, x_test, y_test = make_spiral(seed)
# A. 先训练一个浮点基线
float_model = FloatNet()
fit(
float_model,
x_train,
y_train,
epochs=600,
learning_rate=3e-3,
)
float_acc = accuracy(float_model, x_test, y_test)
rows: list[Result] = []
for bits in (8, 4, 3, 2):
# B. PTQ 模拟:加载同一浮点检查点,不做训练,直接 fake quant 评估
ptq_model = QuantNet(bits)
ptq_model.load_state_dict(float_model.state_dict())
ptq_acc = accuracy(ptq_model, x_test, y_test)
# C. QAT 模拟:从同一检查点出发,小学习率做量化感知微调
qat_model = QuantNet(bits)
qat_model.load_state_dict(float_model.state_dict())
fit(
qat_model,
x_train,
y_train,
epochs=150,
learning_rate=3e-4,
)
qat_acc = accuracy(qat_model, x_test, y_test)
rows.append(
Result(
seed=seed,
bits=bits,
float_acc=float_acc,
ptq_acc=ptq_acc,
qat_acc=qat_acc,
)
)
return rows
# ---------------------------------------------------------
# 6. 多随机种子统计
# ---------------------------------------------------------
def mean_std(values: list[float]) -> str:
mean = statistics.mean(values)
std = statistics.stdev(values) if len(values) > 1 else 0.0
return f"{mean:.4f} ± {std:.4f}"
def main() -> None:
all_results: list[Result] = []
for seed in range(5):
all_results.extend(run_one_seed(seed))
print("bits | float | PTQ | QAT")
print("-----|----------------|----------------|----------------")
for bits in (8, 4, 3, 2):
current = [row for row in all_results if row.bits == bits]
float_values = [row.float_acc for row in current]
ptq_values = [row.ptq_acc for row in current]
qat_values = [row.qat_acc for row in current]
print(
f"{bits:>4} | "
f"{mean_std(float_values):<14} | "
f"{mean_std(ptq_values):<14} | "
f"{mean_std(qat_values):<14}"
)
if __name__ == "__main__":
main()
12.3 一次参考输出
在一组 CPU 环境中运行上述固定代码,可以得到类似结果:
bits | float | PTQ | QAT
-----|----------------|----------------|----------------
8 | 0.9983 ± 0.0037 | 0.9983 ± 0.0037 | 0.9983 ± 0.0037
4 | 0.9983 ± 0.0037 | 0.9983 ± 0.0037 | 0.9983 ± 0.0037
3 | 0.9983 ± 0.0037 | 0.9983 ± 0.0037 | 0.9983 ± 0.0037
2 | 0.9983 ± 0.0037 | 0.9575 ± 0.0252 | 0.9983 ± 0.0037
不同 PyTorch 版本、硬件、线程数和随机实现可能造成轻微差异,所以不要把这组数字当成固定答案。真正值得观察的是:
- 8-bit、4-bit 和 3-bit 下,这个简单模型对 weight-only PTQ 很稳健;
- 2-bit 时,直接 PTQ 的平均准确率下降且方差增大;
- 从同一检查点做短程 QAT 后,模型重新适应了粗糙量化网格,恢复明显。
这个结果比“预先写死 8-bit 差 0.2%、4-bit 差 3%、2-bit 差 20%”更有教学价值,因为它同时说明:
QAT 的优势不是每个位宽都会自动出现;当 PTQ 本来就没有造成明显损失时,QAT 没有什么可恢复的。
12.4 这段代码到底模拟了什么
它模拟的是:
- 每个线性层权重做 per-tensor 仿射 fake quant;
- 激活保持浮点;
- bias 保持浮点;
- 前向使用量化再反量化的权重;
- QAT 通过 identity STE 更新浮点主权重。
它没有模拟:
- per-channel 或 per-group 量化;
- 静态激活 observer;
- 真正 INT4 矩阵乘;
- 权重打包和硬件内核;
- GPTQ 的二阶误差补偿;
- AWQ 的激活感知缩放;
- scale 学习;
- 混合精度敏感层;
- LLM 的注意力、归一化和 KV Cache。
因此它只能证明 QAT 的机制,不代表真实大模型上的绝对数值。
12.5 为什么这个实验比原版更公平
同一浮点起点
PTQ 与 QAT 都加载同一个 state_dict,差异来自是否进行量化感知微调,而不是随机初始化运气。
多随机种子
不仅看均值,还看标准差。2-bit PTQ 的方差变大,本身就是稳定性下降的信号。
完整使用量化编码
仿射量化使用 ([0,2^b-1]) 的全部编码,2-bit 真正对应 4 个整数级别。
不声称真实加速
fake quant 仍然执行浮点 F.linear,只模拟数值误差。要测速度,必须换成真实部署后端。
QAT 是微调而非从头重训
这更接近大多数现实项目:先有一个好模型,再让它适应目标低精度。
十三、如何正确解读实验结果
13.1 “2-bit QAT 恢复了”不等于所有 2-bit 大模型都能无损
玩具网络参数少、任务单一、训练集完整,QAT 很容易重新组织决策边界。大模型要同时保留语言、知识、推理、指令跟随、安全和长上下文能力,难度高得多。
极低比特大模型的效果还受到模型规模、训练 token、量化结构、蒸馏、架构设计和专用内核影响。不能用一个螺旋分类实验推出“2-bit LLM 都能回到 FP16”。
13.2 “4-bit 没差”也不等于 QAT 永远没用
这个实验只量化权重,而且任务简单。真实 W4A4、特殊小模型、边缘设备模型或严格业务指标下,4-bit QAT 仍可能有价值。
更重要的是,现代 PTQ 在大模型 4-bit weight-only 上已经非常强。如果你的对手是 AWQ、GPTQ、OmniQuant,而不是朴素 per-tensor min-max,QAT 的相对优势可能比玩具实验更小,也可能在特定任务上更大。必须实测。
13.3 不要只看平均准确率
假设平均准确率只下降 0.3%,但下降全部集中在高价值客户、唤醒词漏检、金融实体或安全拒答上,业务仍可能不能接受。
评估至少要分层:
- 常见样本与长尾样本;
- 短输入与长输入;
- 中文、英文、代码和符号;
- 普通任务与高敏感任务;
- 平均质量与最坏情况;
- 离线数据与真实线上流量。
13.4 量化评估必须固定解码设置
语言模型输出具有随机性。比较浮点和量化模型时,应尽量固定:
- prompt;
- tokenizer;
- chat template;
- max tokens;
- temperature;
- top-p / top-k;
- random seed;
- stop token;
- 系统提示;
- 上下文截断方式。
否则你看到的差异可能来自采样,而不是量化。
13.5 同一个模型要同时测质量与系统指标
一个完整表格至少应该长这样:
| 配置 | 质量指标 | 文件体积 | 峰值显存 | 首 token 延迟 | tokens/s | 备注 |
|---|---|---|---|---|---|---|
| FP16 | 基线 | 大 | 高 | 基线 | 基线 | 原模型 |
| INT8 PTQ | 轻微变化 | 中 | 中 | 实测 | 实测 | 后端 A |
| W4A16 GPTQ | 实测 | 小 | 低 | 实测 | 实测 | group 128 |
| W4A16 AWQ | 实测 | 小 | 低 | 实测 | 实测 | kernel B |
| W4A4 QAT | 实测 | 更小 | 更低 | 实测 | 实测 | 目标 NPU |
没有系统指标,无法判断压缩是否真正转化为部署价值;没有质量指标,速度再快也可能失去业务意义。
十四、真实项目中的 PTQ 工作流
下面给出一套比“加载模型—点一下量化—结束”更可靠的 PTQ 流程。
14.1 第一步:先写清部署目标,不要先选算法
至少回答这些问题:
- 部署在 NVIDIA GPU、AMD GPU、CPU、Apple Silicon、手机 NPU,还是 MCU?
- 目标后端真正支持哪些位宽和算子?
- 更缺显存、内存带宽,还是算力?
- 主要优化首 token、decode 吞吐,还是批量 prefill?
- 上下文多长,KV Cache 占比多大?
- 可接受的质量损失是多少?
- 模型要不要频繁切换或动态加载?
量化不是脱离硬件的纯数学题。一个质量极好的格式,如果目标设备没有高效内核,可能不如稍弱但硬件原生支持的格式。
14.2 第二步:建立浮点基线
在量化之前,先固定并记录:
- 模型版本和权重哈希;
- tokenizer 版本;
- chat template;
- 推理框架版本;
- 解码参数;
- 测试集;
- 质量指标;
- 延迟和显存基线。
没有浮点基线,后面就无法区分:问题来自量化、框架变化、模板变化,还是模型本身。
14.3 第三步:选择量化对象
不要一上来就“全模型 4-bit”。先确定:
- 只量化 Linear 权重?
- embedding 是否量化?
- 输出头是否保留高精度?
- attention 与 MLP 是否同位宽?
- 激活是否量化?
- KV Cache 是否量化?
- 是否允许混合精度?
在很多项目中,少量敏感层保留高精度,带来的文件增量很小,却能显著降低质量风险。
14.4 第四步:构造校准集
校准集不一定需要标签,但要有代表性。
对于大语言模型,可以考虑覆盖:
- 真实用户问题长度分布;
- 中文与英文比例;
- 代码、数学、表格或 JSON;
- 系统提示和 chat template;
- 长上下文片段;
- 特殊 token;
- 业务高价值场景;
- 容易出现离群激活的格式。
不要直接把公开通用语料当作万能校准集。你的线上请求如果高度结构化,校准也应该反映这种结构。
14.5 第五步:从保守配置开始
合理的试验顺序通常是:
- INT8 或 8-bit weight-only;
- 4-bit per-channel / per-group;
- 调整 group size;
- 比较对称与非对称;
- 比较 AWQ、GPTQ 或目标后端原生方案;
- 再尝试 3-bit、2-bit 或激活量化。
先建立一条稳定的 Pareto 曲线,比一上来追求“最低 bit”更有意义。
14.6 第六步:逐层定位敏感点
如果整体质量下降,不要只得出“4-bit 不行”。可以做层级消融:
- 一次只量化一层;
- 恢复某个模块为 FP16;
- 比较 attention 和 MLP;
- 比较前层、中层和后层;
- 观察输出头、embedding、归一化附近;
- 比较不同 group size;
- 记录每层重建误差与任务指标变化。
常见结果是:并不是所有层都同样敏感。混合精度往往能用少量体积换回大量质量。
14.7 第七步:在真实后端 benchmark
量化完成后,必须在最终后端测试:
- 模型是否能正确加载;
- 是否触发预期低比特内核;
- 是否出现隐式反量化到 FP16;
- batch 1 与高 batch;
- 短 prompt 与长 prompt;
- prefill 与 decode;
- 冷启动与热启动;
- 峰值内存;
- 多并发稳定性。
只看模型文件大小,无法判断系统收益。
14.8 第八步:做线上分布验证
离线集通过后,再用匿名化、合规处理的真实业务样本做影子评估或灰度测试。重点关注:
- 长尾错误;
- 格式失败;
- 语义漂移;
- 拒答变化;
- 事实性下降;
- 工具参数错误;
- 特定语言退化;
- 线上延迟尾部。
PTQ 的速度优势很大,但质量风险常隐藏在平均指标之外。
十五、真实项目中的 QAT 工作流
QAT 的成本更高,因此更需要有纪律的流程。不要在 PTQ 还没充分调优前,就直接投入昂贵训练。
15.1 第一步:证明 PTQ 确实不够
先回答:
- 最强可用 PTQ 方法做过了吗?
- 校准数据匹配真实业务吗?
- group size、clipping、敏感层保留调过了吗?
- 质量损失来自权重还是激活?
- 后端是否存在格式转换问题?
- 失败是否只集中在少数层或少数任务?
如果 PTQ 只是因为配置粗糙而失败,QAT 会白白增加复杂度。
15.2 第二步:冻结目标部署规格
QAT 前向必须尽量模拟最终部署。需要提前确定:
- 位宽;
- 对称或非对称;
- per-tensor、per-channel 或 per-group;
- group size;
- clipping 规则;
- zero-point;
- 哪些算子量化;
- 累积精度;
- 权重打包限制;
- 后端是否支持混合精度。
如果训练模拟的是 A 量化器,导出后却运行 B 量化器,QAT 学到的鲁棒性可能无法兑现。
15.3 第三步:从浮点检查点开始,而不是默认从零训练
多数项目可以先做:
- 加载高质量浮点模型;
- 插入 fake quant;
- 低学习率 warm-up;
- 短程微调;
- 观察质量恢复;
- 再决定是否扩大训练。
这能快速判断 QAT 是否值得。
15.4 第四步:选择训练参数范围
不一定要全参数更新。可以依次尝试:
- 只训练 scale / clipping;
- 只训练 LoRA 或低秩参数;
- 只训练敏感层;
- 训练 attention 或 MLP 的部分模块;
- 全参数 QAT。
原则是用最小训练自由度解决问题。参数越多,恢复潜力越大,但训练成本、过拟合和稳定性风险也越高。
15.5 第五步:设计量化启用日程
直接从第一步开始对所有层施加强量化噪声,可能使训练不稳定。常见策略包括:
- 先以浮点或较高位宽 warm-up;
- 逐步启用 fake quant;
- 先量化权重,再量化激活;
- 先训练 observer,再冻结统计;
- 后期冻结 scale 和 zero-point;
- 位宽逐步降低;
- 对敏感层延迟启用。
并非所有模型都需要复杂日程,但低比特和激活量化越激进,训练计划越重要。
15.6 第六步:监控的不只是训练损失
训练过程中同时看:
- 浮点 shadow 权重上的损失;
- fake-quant 前向损失;
- 导出模拟后的验证指标;
- scale 分布;
- clipping 比例;
- 饱和元素比例;
- 梯度范数;
- 敏感层误差;
- 量化前后输出 KL 或重建误差;
- 任务指标和长尾指标。
如果训练损失在降,而饱和比例不断上升,模型可能在“钻 fake quant 的空子”,导出后未必稳定。
15.7 第七步:必要时使用蒸馏
QAT 可以结合教师模型:
- 教师使用浮点模型;
- 学生使用 fake-quant 模型;
- 同时优化标签损失与教师输出差异;
- 必要时匹配中间层表示。
蒸馏能给低比特模型更密集的学习信号,特别适合标签有限或极低比特场景。但它会进一步增加训练复杂度。
15.8 第八步:真正 convert 后再评估
QAT 模型在 fake-quant 模式下表现好,不代表导出的整数模型一定一致。最终必须:
- 冻结量化参数;
- 执行 convert/export;
- 使用目标内核;
- 重新跑完整质量测试;
- 对比 fake-quant 与真实部署输出;
- 检查算子回退和精度混用。
这一环节叫 deployment parity,也就是训练模拟与真实部署一致性。它是 QAT 成败的最后一道关。
15.9 第九步:保留可回退方案
生产系统中可以同时保留:
- FP16 基线;
- 稳健的 PTQ 版本;
- 激进 QAT 版本;
- 不同上下文或任务的路由策略。
当 QAT 版本在某些长尾任务不稳定时,可以按场景回退,而不是把整个系统押在单一低比特模型上。
十六、一套可以直接使用的选型决策树
下面这套判断顺序比“能 PTQ 就 PTQ”更完整。
第一个问题:目标硬件到底支持什么?
只支持或擅长 FP16/BF16
量化可能主要用于节省存储,未必能加速。先确认后端是否有低比特权重内核。
支持 INT8,但不支持高效 INT4
优先尝试 INT8 PTQ、SmoothQuant 类 W8A8 或动态/静态 INT8。不要为了文件更小而选一个最终会回退的 INT4 格式。
支持高效 4-bit weight-only
优先 AWQ、GPTQ 或后端原生 4-bit PTQ,比较质量与内核吞吐。
必须 W4A4、W2A4 或更低
先做最强 PTQ,若质量不达标,QAT 的优先级显著上升。
第二个问题:你有没有训练数据和训练能力?
没有训练数据或无法复现训练流程
基本锁定 PTQ。可以改进校准集、采用更强 PTQ、混合精度和敏感层保护。
有少量领域数据,但没有大规模预训练资源
尝试量化感知微调、LoRA 化 QAT、蒸馏或只训练量化参数。
有完整训练数据和训练管线
可以系统比较 PTQ、短程 QAT、全参数 QAT,按收益决定成本。
第三个问题:PTQ 的质量差距有多大?
几乎无差
直接部署 PTQ。QAT 没有明显恢复空间。
有轻微下降,但业务可接受
优先调校准、group size、混合精度和敏感层。不要急着增加训练复杂度。
明显下降,且集中在少数层或任务
尝试混合精度、局部 QAT、LoRA QAT、蒸馏。
全面崩溃
检查配置是否错误:量化格式、tokenizer、导出、内核、激活范围。确认不是实现 bug 后,再考虑更高位宽或完整 QAT。
第四个问题:瓶颈是权重、KV Cache 还是计算?
权重加载与显存是瓶颈
Weight-only PTQ 往往最划算。
长上下文 KV Cache 是瓶颈
仅量化权重可能不够,应评估 KV Cache 量化、分页缓存、注意力实现和上下文策略。
大 batch prefill 计算是瓶颈
需要关注激活量化、低精度矩阵乘和硬件原生计算,而不只是模型文件大小。
第五个问题:模型是否会频繁更新?
每周或每天都换检查点
PTQ 自动化更有优势。每次都做完整 QAT 可能无法承受。
模型版本稳定、部署规模巨大
一次 QAT 成本可以被长期推理收益摊薄。即使只提升几个百分点质量或吞吐,也可能值得。
一句话决策版
可以把它压缩成下面这段:
先看硬件支持
↓
用最强、匹配后端的 PTQ 建立基线
↓
若质量达标:部署 PTQ
↓
若不达标:先调校准、粒度、混合精度和敏感层
↓
仍不达标且有数据:量化感知微调
↓
极低比特或严格指标:参数高效 QAT / 全参数 QAT / 蒸馏
十七、常见误区与避坑
误区 1:QAT 一定比 PTQ 好,所以有资源就直接 QAT
QAT 提供更大的适应空间,但不是自动胜利。训练配置不当、fake quant 与部署不一致、数据分布偏移或过拟合,都可能让 QAT 不如成熟 PTQ。
正确做法:先建立强 PTQ 基线,再衡量 QAT 的增量收益。
误区 2:PTQ 完全不需要数据
最朴素权重量化可以无数据,但激活量化、AWQ、GPTQ 和许多高质量 PTQ 都会使用校准输入。
正确说法:PTQ 不需要完整训练数据和训练标签,但可能需要少量代表性校准数据。
误区 3:只要用了校准数据,就不算 PTQ
校准正是 PTQ 的常见组成部分。是否使用数据不是 PTQ/QAT 的唯一分界。
误区 4:代码里出现 backward,就一定是 QAT
局部重建、量化尺度优化和 clipping 搜索也可能使用梯度,仍被归为 PTQ。
关键看是否让模型主体针对最终量化行为进行任务级适应。
误区 5:AWQ、GPTQ 是静态激活量化
它们常见形态是离线 weight-only PTQ。激活通常仍是 FP16/BF16,不应与经典 W8A8 静态量化混为一谈。
误区 6:GGUF 是一种和 GPTQ 并列的算法
GGUF 是模型容器格式。量化模型可以存进 GGUF,但格式本身不是某一个量化优化算法。
误区 7:QLoRA 就是 QAT
QLoRA 通过冻结 4-bit 基础模型并训练 LoRA 适配器降低微调显存。它可以与 QAT 思想结合,但不能自动等同于经典 QAT。
误区 8:4-bit 模型一定比 FP16 快四倍
位宽只决定部分存储与带宽成本。实际速度还受低比特内核、解包、反量化、batch、序列长度、KV Cache 和算子回退影响。
误区 9:量化后的文件能加载,就说明量化成功
能加载只说明格式兼容。你还需要检查质量、实际内核、速度、显存和长尾行为。
误区 10:校准数据越多越好
校准更依赖覆盖性。大量重复样本既增加耗时,也可能把量化参数过度偏向某一分布。
正确做法:优先覆盖真实场景、长度、语言、格式和难例。
误区 11:困惑度几乎不变,聊天质量就一定不变
Perplexity 是重要信号,但无法覆盖结构化输出、工具调用、代码、数学、安全和人类偏好。应做多维评估。
误区 12:QAT 能节省训练显存,因为模型已经低比特
经典 QAT 通常仍保留高精度主权重、梯度和优化器状态。fake quant 的主要目标是模拟推理误差,不是自动把训练变成纯 INT4。
误区 13:STE 是 round 的正确导数
STE 是替代梯度,是有偏近似,不是数学上的真实导数。它工程上有效,但需要接受训练不稳定与部署不一致的风险。
误区 14:所有 2-bit 实现都有四个有效级别
不一定。若使用窄范围对称量化并简单裁剪到 ([-1,1]),可能只有三值。阅读代码时要检查 qmin、qmax、zero-point 和实际格点。
误区 15:只看权重误差最小,就能选出最好量化方案
模型对不同方向的误差敏感度不同。任务损失、层输出重建、激活统计与二阶信息往往比单纯 (L_2) 权重误差更有意义。
误区 16:量化一次,就能适用于所有业务分布
校准集和线上分布变化会影响激活与敏感通道。模型从通用聊天切到代码、长上下文或特定语言时,需要重新验证,必要时重新校准。
十八、PTQ vs QAT 横向对比总表
| 维度 | PTQ | QAT |
|---|---|---|
| 全称 | Post-Training Quantization | Quantization-Aware Training |
| 核心时机 | 浮点模型训练完成后 | 训练或微调阶段 |
| 核心目标 | 在既有检查点附近找到低误差量化表示 | 让参数主动适应目标量化误差 |
| 是否一定无梯度 | 不一定,局部优化可用梯度 | 通常需要反向传播 |
| 是否更新模型主体 | 通常不做任务级更新 | 可更新全部、部分或低秩参数 |
| 数据需求 | 无数据或少量无标签校准数据 | 训练/微调数据,通常更多 |
| 标签需求 | 通常不需要 | 取决于训练目标,可有监督、无监督或蒸馏 |
| 时间成本 | 秒到小时,取决于模型与算法 | 小时到天,取决于规模与训练范围 |
| 工程门槛 | 较低 | 较高 |
| 适合位宽 | INT8、4-bit 等主流区间尤其常见 | 极低比特、权重激活同时低比特更有价值 |
| 高比特精度 | 强 PTQ 往往已很好 | 可能略好,但边际收益有限 |
| 极低比特精度 | 更容易退化 | 通常更有恢复空间 |
| 对原训练流程依赖 | 低 | 中到高 |
| 对校准数据依赖 | 视方法而定 | 训练数据与验证数据更重要 |
| 典型风险 | 校准偏移、离群值、敏感层、后端不匹配 | 训练不稳、成本高、fake/real mismatch、过拟合 |
| 典型方法 | GPTQ、AWQ、SmoothQuant、OmniQuant | 经典 fake-quant QAT、LSQ、PACT、低秩 QAT |
| 大模型常见策略 | 优先选择 | PTQ 不达标后再做量化感知微调 |
| 最适合的使用者 | 只有检查点、追求快速部署的人 | 有训练数据、管线和严格目标的人 |
再补一张“容易混淆的名词表”:
| 名称 | 更准确的身份 | 通常与 PTQ/QAT 的关系 |
|---|---|---|
| GPTQ | 近似二阶、one-shot 权重量化算法 | PTQ |
| AWQ | 激活感知的 weight-only 量化算法 | PTQ |
| SmoothQuant | 面向 W8A8 的训练后平滑方法 | PTQ |
| OmniQuant | 优化量化参数的校准方法 | PTQ,虽可用可微优化 |
| GGUF | 模型张量与元数据容器格式 | 可承载 PTQ 量化结果,本身非算法 |
| llama.cpp quantize | 量化与转换工具 | 常用于离线 PTQ 工作流 |
| bitsandbytes | 低精度算子与加载工具库 | 视具体功能而定 |
| QLoRA | 冻结 4-bit 基础模型、训练 LoRA 的高效微调方法 | 不自动等同于 QAT |
| Fake Quant | 量化再反量化的训练模拟操作 | QAT 核心组件 |
| STE | 离散操作的替代梯度估计 | QAT 常用技术 |
十九、小结:先 PTQ,不迷信 PTQ;需要 QAT,也不必从头重训
现在可以回到开头那个问题:
同样是模型量化,为什么有的方法几分钟完成,有的却要重新训练好几天?
因为它们解决误差的方式不同。
PTQ 接受“模型已经训练完”这个前提,不再大规模改变模型,只利用权重统计、校准数据、误差补偿、等价变换或局部优化,寻找一个尽可能好的低比特表示。它把成本压得很低,因此特别适合大模型和第三方检查点。
QAT 则把量化误差放进训练前向,让模型在优化过程中亲自适应。权重可以移动,通道可以重新分工,scale 可以学习,敏感方向可以被任务损失直接保护。它因此更有机会在极低比特或严格指标下恢复质量,但需要数据、算力、训练管线和部署一致性。
全文最重要的结论可以压缩成十条:
- PTQ 的本质不是“完全不用数据”,而是“不做完整量化感知训练”。
- QAT 的本质不是“真的用整数训练”,而是训练时模拟最终量化误差。
- 权重比激活更容易量化,因为权重固定,激活依赖输入。
- 量化误差主要来自舍入和截断,离群值会恶化两者的权衡。
- AWQ 和 GPTQ 属于 PTQ,但会使用校准输入;它们常见形态是 weight-only。
- GGUF 是容器格式,不是与 AWQ、GPTQ 同层级的量化算法。
- QLoRA 是低显存微调方法,不应只因“4-bit 训练”就自动归为经典 QAT。
- STE 不是 round 的真实导数,而是让离散前向可训练的替代梯度。
- 低 bit 不自动带来等比例加速,最终效果取决于内核、硬件、KV Cache 和工作负载。
- 正确选型不是死记“能 PTQ 就别 QAT”,而是先用匹配硬件的强 PTQ 建立基线;质量仍不达标且有训练条件时,再逐级增加 QAT 强度。
最实用的一句话仍然可以保留,但要加上边界:
大模型部署先做 PTQ;PTQ 在真实后端和真实业务指标上不达标,再考虑量化感知微调。端侧极低比特、权重激活同时量化或严格精度场景,应更早把 QAT 纳入方案。
二十、附录:术语表与延伸阅读
20.1 术语表
Quantization,量化
用有限离散级别近似高精度数值的过程。
Dequantization,反量化
根据 scale 和 zero-point,把整数编码还原成近似浮点值。
Scale
量化网格的步长。scale 越小,格点越密,但可覆盖范围越窄。
Zero-point
浮点 0 在整数编码中的对应位置。非对称量化中常不为 0。
Calibration,校准
用代表性数据统计激活、敏感度、范围或重建误差,以确定量化参数。
Observer
训练或校准时收集 min/max、直方图、移动平均等统计信息的模块。
Fake Quantization,伪量化
前向执行量化再反量化,让数值承受量化误差,但保持浮点张量便于训练。
STE,Straight-Through Estimator
对不可导或离散操作使用替代梯度的技术。最简单版本在反向把量化节点近似成恒等映射。
Weight-only Quantization
只把权重压到低比特,激活通常保持 FP16/BF16 或其他较高精度。
Activation Quantization
把中间激活也映射到低比特。通常比权重量化更依赖输入分布和校准。
Per-tensor
整个张量共享量化参数。
Per-channel
每个通道使用独立量化参数。
Per-group
每一小组权重共享量化参数,是 LLM 低比特权重常见折中。
Group Size
每组包含的权重数量。更小通常更精细,但元数据和内核复杂度更高。
Clipping
把超出量化范围的数值压到边界。
Saturation,饱和
数值超出可表示范围并被截断到 qmin 或 qmax 的现象。
Mixed Precision,混合精度
不同层、通道或算子使用不同位宽,以在质量和成本间折中。
W4A16
权重 4-bit,激活 16-bit。常见于 LLM weight-only 推理。
W8A8
权重与激活均 8-bit,更有机会进入完整整数矩阵乘路径。
KV Cache
自回归 Transformer 为历史 token 保存的 Key/Value 状态。长上下文下可能占用大量显存。
Deployment Parity
训练或仿真量化行为与真实部署内核之间的一致性。
20.2 延伸阅读
[1] Elias Frantar, Saleh Ashkboos, Torsten Hoefler, Dan Alistarh. GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers. arXiv:2210.17323 / ICLR 2023.
[2] Ji Lin 等. AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration. arXiv:2306.00978.
[3] Guangxuan Xiao 等. SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models. arXiv:2211.10438.
[4] ggml-org/llama.cpp. tools/quantize/README.md,关于从高精度 GGUF 转换到量化格式的工具说明。
[5] Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, Luke Zettlemoyer. QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314 / NeurIPS 2023.
[6] PyTorch. Quantization-Aware Training for Large Language Models with PyTorch. 介绍 torchao 与 torchtune 中的 LLM QAT 流程。
[7] Peiyu Liao 等. Low-Rank Quantization-Aware Training for LLMs. arXiv:2406.06385.
[8] Yoshua Bengio, Nicholas Léonard, Aaron Courville. Estimating or Propagating Gradients Through Stochastic Neurons for Conditional Computation. arXiv:1308.3432.
[9] Steven K. Esser 等. Learned Step Size Quantization. arXiv:1902.08153.
[10] Benoit Jacob 等. Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference. arXiv:1712.05877.
[11] Wenqi Shao 等. OmniQuant: Omnidirectionally Calibrated Quantization for Large Language Models. arXiv:2308.13137.
[12] Jungwook Choi 等. PACT: Parameterized Clipping Activation for Quantized Neural Networks. arXiv:1805.06085.
最后一张记忆卡
PTQ
= 已有模型 + 量化/校准/局部优化
= 快、便宜、适合大模型第一轮部署
QAT
= Fake Quant + 任务训练 + STE/替代梯度
= 让模型适应量化误差
= 低比特、激活量化、严格精度时更有价值
AWQ / GPTQ
= PTQ 的 weight-only 方法
SmoothQuant
= 面向 W8A8 的 PTQ 思路
GGUF
= 容器格式,不是量化算法
QLoRA
= 冻结 4-bit 基础模型训练 LoRA
= 不自动等于经典 QAT
选型
= 先看硬件 → 建立强 PTQ 基线 → 调校准与混合精度
→ 仍不达标且有训练条件 → QAT
更多推荐




所有评论(0)