1. 为什么模型部署绕不开量化这道坎
做过模型部署的人都有一个共同体会:训练出一个精度漂亮的模型只是万里长征第一步,真正把它塞进生产环境、跑出可接受的延迟和吞吐,才是折磨人的开始。我最早接触量化是在一个边缘设备项目上,当时手里有一个参数量不到 10M 的图像分类模型,FP32 权重文件大概 40MB,在服务器上跑得好好的,一放到端侧设备上推理延迟直接飙到 200ms 以上,内存占用也吃紧。那时候我第一反应是换更小的模型,但精度掉得厉害,后来才认真去研究量化这条路。
量化的本质其实很朴素:用更少的比特位来表示原本用 32 位浮点存储的权重和激活值。你可以把它想象成把一张高精度照片压缩成 JPEG——信息有损,但只要压缩策略得当,肉眼几乎看不出差别。深度学习模型里大量的参数其实存在冗余,FP32 的精度对于推理任务来说往往是过剩的,用 INT8 表示完全够用。这一压,模型体积直接变成原来的四分之一,内存带宽需求同步下降,而在支持 INT8 指令集的硬件上,矩阵乘的吞吐量能提升 2 到 4 倍。
但量化绝不是简单地把浮点数乘以一个缩放因子再取整就完事了。核心难点在于:如何在压缩表示的同时,把精度损失控制在可接受范围内。这就引出了量化领域几个关键问题——量化粒度怎么选、缩放因子和零点怎么定、校准数据怎么喂、要不要做量化感知训练(QAT),以及面对 LLM 这种超大模型时又该用什么策略。这篇内容我会把 INT8 矩阵乘的底层原理、校准流程、QAT 的实操细节,以及 LLM 量化的主流方案完整拆一遍,尽量把每个参数背后的“为什么”讲清楚,让你看完能直接上手做自己的量化实验。
适合阅读的人群:做模型部署的工程师、需要在端侧或边缘设备跑推理的开发者、对推理性能优化感兴趣的研究者,以及正在被 LLM 显存和推理成本困扰的同学。哪怕你之前没接触过量化,只要了解基本的神经网络推理流程,这篇内容都能帮你建立起完整的量化知识框架。
2. 量化基础与 INT8 矩阵乘的底层逻辑
2.1 从浮点到定点:量化的数学本质
量化的核心是一个仿射映射。给定一个浮点张量 $x$,我们想把它映射到 INT8 的整数空间 $[-128, 127]$,映射公式是:
$$x_{int} = \text{round}\left(\frac{x}{s}\right) + z$$
其中 $s$ 是缩放因子(scale),$z$ 是零点(zero point)。反量化的时候就是:
$$x_{float} = s \cdot (x_{int} - z)$$
这个 $s$ 和 $z$ 怎么来的?对于对称量化,$z = 0$,$s = \frac{\max(|x|)}{127}$,映射范围是 $[-127, 127]$。对于非对称量化,$z$ 不为零,$s = \frac{x_{max} - x_{min}}{255}$,$z = \text{round}(-x_{min} / s)$,映射范围是 $[-128, 127]$。
为什么要有对称和非对称两种?这跟数据分布有关。权重通常近似对称分布,用对称量化就够了,而且对称量化在计算上更简单,因为零点为零,矩阵乘的时候可以省掉一些加法项。激活值就不一样了,ReLU 之后的激活全是非负的,分布严重偏斜,这时候用非对称量化能更充分地利用 INT8 的表示范围,精度损失更小。
提示:实际工程中,权重几乎都用对称量化(per-channel),激活值用非对称量化(per-tensor)是常见组合。这个组合在 TensorRT、ONNX Runtime 里都是默认推荐。
2.2 INT8 矩阵乘到底怎么算的
理解了量化映射,再看 INT8 矩阵乘就清晰了。假设有两个矩阵 $A$ 和 $B$,量化后分别是 $A_q$、$B_q$,对应的缩放因子是 $s_A$、$s_B$,零点分别是 $z_A$、$z_B$。浮点矩阵乘 $C = A \cdot B$ 可以写成:
$$C_{float} = s_A s_B \cdot (A_q - z_A) \cdot (B_q - z_B)$$
展开后:
$$C_{float} = s_A s_B \cdot (A_q B_q - z_A B_q - z_B A_q + z_A z_B)$$
如果是对称量化,$z_A = z_B = 0$,那公式就退化成:
$$C_{float} = s_A s_B \cdot A_q B_q$$
这就是为什么对称量化在硬件上更受欢迎——INT8 的乘加运算可以直接做,最后统一乘一个缩放因子 $s_A s_B$ 就行。而非对称量化需要额外处理零点带来的交叉项,计算量更大。
实际硬件里,INT8 矩阵乘通常走的是DP4A(Dot Product of 4 8-bit integers and Accumulate)指令或者VNNI(Vector Neural Network Instructions)指令。以 DP4A 为例,它一次能算 4 组 INT8 乘加,累加结果存在 INT32 里。为什么累加器要用 INT32?因为 INT8 乘 INT8 的结果范围是 $[-128 \times 127, 127 \times 127]$,也就是大约 $[-16256, 16129]$,如果累加 256 次,结果可能到百万级别,INT16 根本装不下,必须用 INT32 才不会溢出。
2.3 量化粒度:per-tensor、per-channel 与 per-group
量化粒度决定了缩放因子和零点在多大范围内共享。粒度越细,精度越高,但元数据开销也越大。
| 粒度类型 | 缩放因子数量 | 精度 | 开销 | 适用场景 |
|---|---|---|---|---|
| per-tensor | 1 个/张量 | 最低 | 最小 | 激活值量化 |
| per-channel | 1 个/通道 | 中等 | 中等 | 权重量化(CNN) |
| per-group | 1 个/组 | 最高 | 最大 | LLM 权重量化 |
per-tensor 是整个张量共用一个 scale,实现最简单,但对分布不均匀的张量精度损失大。per-channel 是每个输出通道一个 scale,对卷积权重特别友好,因为不同卷积核的权重分布差异可能很大。per-group 是把通道再分组,每组一个 scale,这是 LLM 量化里的主流做法,比如 group size 取 128,就是每 128 个权重共享一个 scale。
我实测下来,对于 CNN 类模型,权重量化从 per-tensor 换成 per-channel,ImageNet top-1 精度通常能回升 0.5 到 1.5 个百分点,而元数据开销只增加了通道数那么多个 float,完全可以接受。所以只要硬件支持,权重量化优先用 per-channel。
2.4 对称与非对称的取舍实战
前面讲了对称和非对称的数学区别,实际选型时还要考虑硬件支持。很多推理加速器(比如某些 NPU)只支持对称量化,因为对称量化的矩阵乘可以直接映射到硬件指令,非对称需要额外的零点处理逻辑,会增加电路复杂度。
另一个考虑是校准难度。非对称量化需要同时确定 $x_{min}$ 和 $x_{max}$,对异常值更敏感。如果校准数据里混入了一个极端大的激活值,$x_{max}$ 会被拉高,导致整个张量的量化精度下降。对称量化只看绝对值最大值,虽然也受异常值影响,但至少零点固定在零,不会出现偏移。
注意:校准数据的质量直接决定量化精度。我踩过的坑是拿训练集的一个子集做校准,结果里面有几张标注错误的异常样本,激活值分布被带偏,量化后模型在正常样本上精度掉了 3 个点。后来换成从验证集里随机采样,并且做了异常值裁剪,精度就回来了。
3. 校准:量化精度的胜负手
3.1 校准到底在做什么
校准(Calibration)是训练后量化(PTQ)的核心步骤。它的任务是:用一批有代表性的数据跑一遍模型,统计每一层激活值的分布,然后根据这个分布确定量化参数(scale 和 zero point)。
为什么不能直接用权重的分布来定激活的量化参数?因为激活值是动态的,取决于输入数据。同一个模型,输入一张猫的图片和输入一张风景图片,中间层的激活分布可能完全不同。所以必须用真实数据去“探测”激活值的范围。
校准的流程大致是:
- 准备一批校准数据,通常 100 到 1000 个样本就够
- 把模型切换到校准模式,插入观测器(Observer)来统计激活值
- 前向传播校准数据,观测器记录每层激活的最小值、最大值或直方图
- 根据统计结果计算量化参数
- 把量化参数固化到模型里,切换到推理模式
3.2 校准算法怎么选
校准算法的核心问题是:如何从激活值分布中确定最优的 $x_{min}$ 和 $x_{max}$。直接取全局最小最大值是最简单的,但容易被异常值带偏。实际常用的算法有几种:
MinMax 校准:直接取观测到的最小值和最大值。实现最简单,但对异常值零容忍。如果校准数据里有一个极端值,整个量化范围就被拉大了,大部分正常值挤在很小的区间里,量化误差剧增。
Moving Average MinMax:对多个 batch 的最小最大值做滑动平均,平滑掉单 batch 的波动。比纯 MinMax 稳一些,但还是受异常值影响。
Entropy 校准(KL 散度):这是 TensorRT 默认的校准算法,也是我认为效果最稳的一种。它的思路是:不直接取最小最大值,而是找一个截断阈值,使得截断后的分布和原始 FP32 分布的 KL 散度最小。换句话说,它主动丢弃掉尾部那些极端值,用剩下的数据来确定量化范围。实测在 CNN 上,Entropy 校准比 MinMax 通常能提升 0.5 到 1 个点的精度。
Percentile 校准:取激活值的某个百分位数(比如 99.9%)作为最大值,直接砍掉尾部。比 Entropy 简单,效果也不错,适合快速实验。
| 校准算法 | 实现复杂度 | 抗异常值 | 精度表现 | 推荐场景 |
|---|---|---|---|---|
| MinMax | 低 | 弱 | 一般 | 快速验证 |
| Moving Average | 低 | 中 | 中等 | 数据分布稳定 |
| Entropy | 高 | 强 | 好 | CNN 生产部署 |
| Percentile | 中 | 强 | 好 | 通用场景 |
3.3 校准数据的准备技巧
校准数据不需要标注,但必须和真实推理数据同分布。我一般从验证集里随机采样 200 到 500 张,覆盖所有类别。如果模型有多个输入分支(比如多模态),每个分支都要有对应的数据。
一个容易被忽略的点是数据预处理必须和推理时完全一致。归一化参数、resize 方式、通道顺序,任何一个环节不一致,校准统计到的激活分布就是错的。我曾经因为校准数据用了 BGR 而推理用了 RGB,量化后精度掉了 5 个点,排查了大半天才发现是通道顺序的问题。
提示:校准样本数量不是越多越好。超过 1000 个样本后,统计分布基本收敛,再增加样本对精度提升微乎其微,反而浪费时间。200 到 500 个是性价比最高的区间。
3.4 校准实操:以 ONNX Runtime 为例
ONNX Runtime 提供了完整的 PTQ 工具链。下面是一个典型的校准配置流程:
from onnxruntime.quantization import quantize_static, CalibrationDataReader, QuantType, QuantFormat class MyCalibrationReader(CalibrationDataReader): def __init__(self, calibration_data): self.data = calibration_data self.index = 0 def get_next(self): if self.index >= len(self.data): return None batch = self.data[self.index] self.index += 1 return {"input_name": batch} def rewind(self): self.index = 0 quantize_static( model_input="model.onnx", model_output="model_int8.onnx", calibration_data_reader=MyCalibrationReader(calib_data), quant_format=QuantFormat.QDQ, activation_type=QuantType.QUInt8, weight_type=QuantType.QInt8, per_channel=True, calibrate_method=CalibrationMethod.Entropy )这里有几个参数值得说明。quant_format选 QDQ 还是 QOperator?QDQ 是在模型里插入 QuantizeLinear 和 DequantizeLinear 节点,兼容性好,方便调试;QOperator 是把量化算子融合成单个节点,性能更好但可读性差。生产部署我一般先用 QDQ 验证精度,确认没问题再转 QOperator。
activation_type选 QUInt8 还是 QInt8?激活值经过 ReLU 后非负,用 QUInt8(无符号)能多利用一位表示范围,精度更好。权重用 QInt8(有符号),因为权重有正有负。
per_channel=True对权重量化启用 per-channel,这个几乎必开。
4. QAT:当 PTQ 精度不够时怎么办
4.1 PTQ 的天花板在哪里
PTQ 的优点是快,不需要重新训练,几个小时就能搞定。但它有个硬伤:量化误差是在训练完成后才引入的,模型没有机会去适应这种误差。对于大模型或者对精度敏感的任务(比如检测、分割),PTQ 后的精度损失可能超过可接受范围。
我遇到过一个案例:一个轻量级分割模型,PTQ 后 mIoU 从 72% 掉到 65%,怎么调校准算法都回不来。这种情况下就得上 QAT。
QAT 的思路是:在训练过程中模拟量化误差,让模型在训练时就“感受”到量化带来的精度损失,从而调整权重去补偿。具体做法是在前向传播时插入伪量化节点(Fake Quantization),把权重和激活量化后再反量化,模拟量化误差,但反向传播时用直通估计器(STE)让梯度正常回传。
4.2 QAT 的实操流程
QAT 不是从头训练,而是在一个已经训练好的 FP32 模型上做微调。典型流程是:
- 加载预训练的 FP32 模型
- 插入伪量化节点,配置量化参数
- 用较小的学习率(通常是原始学习率的 1/100 到 1/10)微调几个 epoch
- 固化量化参数,导出量化模型
以 PyTorch 为例,用torch.quantization模块:
import torch.quantization as tq model = MyModel() model.load_state_dict(torch.load("fp32_model.pth")) model.eval() model.qconfig = tq.get_default_qat_qconfig('fbgemm') model_fused = tq.fuse_modules(model, [['conv1', 'bn1', 'relu1']]) model_prepared = tq.prepare_qat(model_fused, inplace=False) optimizer = torch.optim.SGD(model_prepared.parameters(), lr=1e-5, momentum=0.9) for epoch in range(5): for data, target in train_loader: optimizer.zero_grad() output = model_prepared(data) loss = criterion(output, target) loss.backward() optimizer.step() model_prepared.eval() model_int8 = tq.convert(model_prepared, inplace=False) torch.save(model_int8.state_dict(), "int8_model.pth")几个关键点:fuse_modules把 Conv+BN+ReLU 融合成一个模块,减少量化误差累积,这一步几乎必做。qconfig选 fbgemm 还是 qnnpack?fbgemm 面向 x86,qnnpack 面向 ARM,根据部署平台选。微调学习率不能太大,否则会破坏预训练权重,我一般从 1e-5 开始试。
4.3 QAT 的注意事项与踩坑记录
QAT 有几个容易翻车的地方。第一是伪量化节点的位置,不是所有层都适合量化。第一层和最后一层通常对精度影响大,可以考虑跳过量化,保持 FP32。第二是BN 层的处理,QAT 时 BN 的统计量会变化,需要在微调结束后重新校准 BN 的 running mean 和 var,否则推理时会有偏差。
第三是微调数据量,QAT 不需要全量训练数据,但也不能太少。我一般用训练集的 10% 到 20%,太少会导致过拟合,太多则浪费时间。第四是学习率调度,前几个 epoch 用正常学习率,最后一个 epoch 把学习率降到接近零,让模型收敛到稳定的量化参数。
注意:QAT 微调后一定要在验证集上完整评估,不要只看 loss。我遇到过 loss 下降但精度反而变差的情况,原因是伪量化节点的梯度估计有偏,导致权重更新方向不对。这时候可以尝试降低学习率或者减少微调 epoch。
4.4 PTQ 与 QAT 的选型决策
到底用 PTQ 还是 QAT?我的经验是:
- 如果 PTQ 后精度损失在 1 个点以内,直接用 PTQ,省时省力
- 如果损失在 1 到 3 个点,先尝试优化校准算法和数据,可能能救回来
- 如果损失超过 3 个点,或者任务对精度极度敏感,上 QAT
- 如果模型是 LLM,PTQ 的 weight-only 量化通常是首选,QAT 成本太高
| 对比维度 | PTQ | QAT |
|---|---|---|
| 训练成本 | 无 | 需要微调 |
| 时间开销 | 小时级 | 天级 |
| 精度表现 | 一般 | 好 |
| 适用模型 | CNN、小模型 | 检测、分割、大模型 |
| 实现难度 | 低 | 中高 |
5. LLM 量化:当模型大到装不下
5.1 LLM 量化的特殊挑战
LLM 的量化和小模型完全不是一个玩法。第一个挑战是激活值异常值。LLM 的激活值里存在极少数维度上的超大值,这些异常值比正常值大几十甚至上百倍。如果直接做 per-tensor 量化,这些异常值会把量化范围拉得极大,导致正常值全部挤在很小的区间里,精度崩溃。
第二个挑战是显存瓶颈。一个 7B 的模型,FP16 权重就要 14GB,INT8 降到 7GB,INT4 降到 3.5GB。量化不仅是加速手段,更是能不能跑起来的问题。
第三个挑战是量化粒度。LLM 的权重分布在不同通道间差异很大,per-tensor 量化效果很差,必须用 per-channel 甚至 per-group。但 per-group 的元数据开销又不能太大,所以 group size 的选择很关键。
5.2 主流 LLM 量化方案对比
目前 LLM 量化主要有几条技术路线:
GPTQ:基于二阶信息的逐层量化方法。它对每一层的权重做量化,用 Hessian 矩阵来指导量化误差的最小化。GPTQ 的特点是量化速度快,精度好,支持 INT4 甚至 INT3。缺点是只量化权重,激活值还是 FP16,所以推理时是 weight-only 量化。
AWQ:Activation-aware Weight Quantization。核心观察是:权重里只有一小部分(约 1%)对激活值影响大,这部分权重应该保留更高精度。AWQ 通过激活值分布来识别这些重要权重,对它们做保护。实测 AWQ 在 INT4 下比 GPTQ 精度略好,尤其是小模型。
SmoothQuant:解决激活值异常值问题的方案。它通过一个数学等价变换,把激活值的量化难度迁移到权重上。具体来说,对激活值除以一个平滑因子 $s$,对权重乘以 $s$,保持数学等价,但让激活值分布更平滑,更容易量化。SmoothQuant 支持 W8A8(权重和激活都 INT8),比 weight-only 方案加速更明显。
GGUF/GGML 量化:这是 llama.cpp 生态用的格式,支持从 2-bit 到 8-bit 的多种量化级别。它的特点是 CPU 推理友好,量化级别灵活,适合在消费级硬件上跑 LLM。
| 方案 | 量化对象 | 典型位宽 | 精度 | 加速比 | 适用场景 |
|---|---|---|---|---|---|
| GPTQ | 权重 | INT4/INT3 | 好 | 2-3x | GPU 推理 |
| AWQ | 权重 | INT4 | 很好 | 2-3x | GPU 推理 |
| SmoothQuant | 权重+激活 | INT8 | 好 | 3-4x | 高吞吐服务 |
| GGUF | 权重 | 2-8bit | 中等 | CPU 友好 | 本地部署 |
5.3 GPTQ 量化的实操细节
GPTQ 的量化流程大致是:加载 FP16 模型,准备校准数据(通常用 C4 或 WikiText 的几百条样本),逐层做量化。以 AutoGPTQ 为例:
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig quantize_config = BaseQuantizeConfig( bits=4, group_size=128, desc_act=False, damp_percent=0.01 ) model = AutoGPTQForCausalLM.from_pretrained( "meta-llama/Llama-2-7b-hf", quantize_config=quantize_config ) model.quantize(calibration_dataset) model.save_quantized("llama-2-7b-gptq-4bit")group_size是最关键的参数。128 是默认值,精度和开销的平衡点。降到 64 精度会好一点,但元数据翻倍;升到 256 精度会掉,但显存更省。desc_act控制是否按激活值重要性排序,开启后精度略好但推理速度会慢一些。damp_percent是 Hessian 矩阵的阻尼系数,防止数值不稳定,一般 0.01 到 0.1 之间。
提示:GPTQ 量化 7B 模型大概需要 20 到 40 分钟(取决于 GPU),13B 模型翻倍。量化过程中显存占用会比推理时高,因为要计算 Hessian。如果显存不够,可以减小 calibration 的 batch size。
5.4 AWQ 与 SmoothQuant 的选型建议
AWQ 的量化速度比 GPTQ 快,精度在 INT4 下通常略好,尤其是 7B 以下的小模型。它的实现也更简单,不需要计算 Hessian。如果你的场景是 INT4 weight-only 量化,我优先推荐 AWQ。
SmoothQuant 适合需要 W8A8 的场景,也就是既要量化权重又要量化激活。它的优势是能真正利用 INT8 矩阵乘的硬件加速,吞吐量提升比 weight-only 方案更明显。但 SmoothQuant 的实现复杂度更高,需要调整平滑因子,对校准数据也更敏感。
实际选型时,如果只是想在单卡上跑起更大的模型,AWQ 或 GPTQ 的 INT4 就够了。如果是做在线服务,追求高吞吐和低延迟,SmoothQuant 的 W8A8 更合适。
6. 常见问题与排查技巧实录
6.1 量化后精度掉得厉害怎么排查
精度下降是量化最常见的问题。排查思路是从粗到细:
第一步,确认校准数据是否正确。检查预处理是否和推理一致,样本是否覆盖了所有类别,有没有异常样本。这一步能解决 50% 以上的精度问题。
第二步,检查量化配置。权重量化是否开了 per-channel?激活量化用的是对称还是非对称?校准算法是 MinMax 还是 Entropy?把 MinMax 换成 Entropy 通常能救回 0.5 到 1 个点。
第三步,定位敏感层。逐层分析量化误差,找出哪些层的量化误差最大。通常第一层、最后一层、以及某些特殊结构(如 depthwise conv)对量化更敏感。这些层可以跳过量化,保持 FP32。
第四步,如果以上都不行,上 QAT。
6.2 量化模型推理速度反而变慢
这个问题听起来反直觉,但确实会发生。原因通常有几个:
一是硬件不支持 INT8 加速。如果部署平台没有 INT8 指令集,量化模型会先反量化成 FP32 再计算,反而多了一步转换开销。这种情况量化不仅不加速,还会变慢。
二是量化算子没有融合。Conv+BN+ReLU 如果没有融合,每个算子单独量化再反量化,中间会有大量的 Quantize/Dequantize 节点,开销很大。
三是内存带宽瓶颈。如果模型本身是 memory-bound 而不是 compute-bound,量化减少的内存访问可能被反量化的开销抵消。
排查方法是看推理时的算子耗时分布,确认瓶颈在哪里。如果 Quantize/Dequantize 节点占比高,说明算子融合没做好。
6.3 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 精度掉 3 个点以上 | 校准数据分布不对 | 对比校准和推理的预处理 | 重新准备校准数据 |
| 精度掉 1-3 个点 | 校准算法不佳 | 换 Entropy 或 Percentile | 调整校准算法 |
| 特定层误差大 | 该层对量化敏感 | 逐层分析量化误差 | 跳过该层量化 |
| 推理变慢 | 硬件不支持 INT8 | 查看算子耗时 | 换支持 INT8 的平台 |
| 推理变慢 | 算子未融合 | 检查计算图 | 做算子融合 |
| 显存没降 | 量化未生效 | 检查模型权重类型 | 确认量化配置 |
| 输出全零或 NaN | 量化范围溢出 | 检查 scale 和 zero point | 调整量化范围 |
6.4 几个容易被忽略的细节
第一个细节是量化模型的保存格式。不同框架的量化模型格式不通用,ONNX 的 QDQ 模型可以跨平台,但 PyTorch 的量化模型导出到 ONNX 时经常出问题。建议在量化前就确定好部署平台,选对应的量化工具链。
第二个细节是动态量化和静态量化的区别。动态量化只在推理时统计激活范围,不需要校准数据,适合 NLP 模型(如 LSTM、Transformer)。静态量化需要校准数据,但推理速度更快,适合 CNN。选错了类型,要么精度不够,要么速度上不去。
第三个细节是量化后的模型微调。有些框架支持在量化后做少量微调来恢复精度,这其实是 QAT 的简化版。如果 PTQ 精度差一点但不想做完整 QAT,可以试试这个。
第四个细节是多线程和 batch size 的影响。量化模型的性能表现和 batch size 关系很大。小 batch 时可能是 latency-bound,大 batch 时是 throughput-bound。调优时要根据实际场景选 batch size,不要盲目追求大 batch。
7. 我个人的量化实践体会
做了这么多量化项目,我最大的体会是:量化不是一锤子买卖,而是一个需要反复迭代的过程。同一个模型,换一个校准数据集、换一种校准算法、调整一下量化粒度,精度可能就差好几个点。所以不要指望一次配置就能达到最优,要留出足够的实验时间。
另一个体会是,量化的收益和硬件强相关。在支持 INT8 的服务器 GPU 上,量化能带来实打实的加速;在一些边缘设备上,量化更多是为了省内存而不是提速。做方案设计时,一定要先确认目标硬件的量化支持能力,否则可能白忙一场。
最后,LLM 量化和传统模型量化是两个不同的技术栈。传统量化那套 per-tensor、校准、QAT 的思路,直接搬到 LLM 上往往不work。LLM 量化要关注异常值处理、group-wise 量化、weight-only 策略这些专门的技术。如果你是从 CNN 量化转过来的,建议先把 GPTQ 和 AWQ 的论文读一遍,理解它们的核心假设和适用边界。
再分享一个小技巧:量化实验一定要做好记录。每次实验的配置、校准数据、精度结果都记下来,形成一个实验矩阵。这样当精度不达标时,你能快速定位是哪个参数的问题,而不是盲目试错。我一般用表格记录,列包括模型、量化位宽、粒度、校准算法、校准样本数、精度、推理延迟,一目了然。