2、模型量化基础:FP32 vs FP16 vs INT8,量化原理与精度权衡
说到模型部署,内存占用永远是绕不开的坎。我见过太多团队,模型精度刷得漂漂亮亮,一到上线就傻眼——显存爆了,推理延迟高得离谱。这时候,量化往往是第一把手术刀。
量化说白了,就是用更少的比特数去表示模型的参数和激活值。你想想看,一个 7B 的模型,FP32 要 28GB,FP16 砍一半到 14GB,INT8 再砍一半到 7GB。这数字,谁看了不心动?
2.1 三种精度的本质差异
我们先看一张表,把 FP32、FP16、INT8 的底裤扒干净。
| 特性 | FP32 | FP16 | INT8 |
|---|---|---|---|
| 比特数 | 32 bit | 16 bit | 8 bit |
| 符号位 | 1 bit | 1 bit | 1 bit(有符号) |
| 指数位 | 8 bit | 5 bit | 无 |
| 尾数位 | 23 bit | 10 bit | 7 bit(数值) |
| 表示范围 | ~3.4×10³⁸ | ~6.5×10⁴ | -128 ~ 127 |
| 精度(最小可表示差值) | ~1.4×10⁻⁴⁵ | ~6.1×10⁻⁵ | 1 |
| 内存节省 | 基准 | 减少 50% | 减少 75% |
看到没?FP16 的指数位只有 5 bit,这意味着它能表示的范围比 FP32 小得多。我在项目中遇到过,有些模型的权重分布特别宽,FP16 直接溢出,loss 变成 NaN。嗯,那场面,调试起来真是欲哭无泪。
INT8 就更极端了——它根本没有指数位。整个数值范围就 256 个格子,你得想办法把 FP32 的整个分布塞进这 256 个格子里。怎么塞?这就是量化的核心问题。
2.2 量化原理:映射与截断
量化的数学本质,就是一个映射函数。把浮点数范围 [r_min, r_max] 映射到整数范围 [q_min, q_max]。
最常见的叫均匀量化,公式长这样:
// 量化:浮点 -> 整数
q = round((r - zero_point) / scale)
// 反量化:整数 -> 浮点
r = q * scale + zero_point
这里有两个关键参数:scale(缩放因子)和 zero_point(零点偏移)。
- scale:决定了每个整数格子代表多大的浮点范围。scale 越小,精度越高,但能覆盖的范围越小。
- zero_point:保证浮点 0 能精确映射到整数 0。这一点很重要,很多算子(比如 ReLU、Padding)依赖 0 的精确表示。
那 r_min 和 r_max 怎么选?这就引出了两种策略:
- 对称量化:r_min = -r_max,zero_point = 0。简单粗暴,适合权重分布比较对称的情况。
- 非对称量化:r_min 和 r_max 各自独立选取。能更好地适配激活值这种分布不对称的情况。
但这里有个坑——截断。如果原始浮点数的最大值超过了 scale × q_max,超出的部分会被直接截断。截断带来的误差叫截断误差,而格子不够细带来的误差叫舍入误差。
我曾经在一个 NLP 模型上试过 INT8 量化,结果精度掉了 5 个点。排查了半天,发现是某个 attention 层的激活值有极端 outlier,截断后信息全丢了。后来用了 per-channel 量化,给每个 channel 单独算 scale,才把精度拉回来。
2.3 精度权衡:你愿意牺牲多少?
量化不是免费的午餐。你省下来的内存和计算时间,是用精度换的。问题是:换多少合适?
我一般把量化方案分成三个等级:
| 等级 | 方案 | 精度损失 | 适用场景 |
|---|---|---|---|
| 保守 | FP16 量化 | < 0.1% | 高精度要求,如医疗、金融 |
| 激进 | INT8 量化(PTQ) | 0.5% ~ 2% | 大部分生产环境,如推荐、搜索 |
| 极限 | INT4 / 混合精度 | 2% ~ 5%+ | 移动端、边缘设备,内存极度受限 |
FP16 量化,说白了就是"白嫖"。大部分模型在 FP16 下精度几乎不变,但内存直接减半。我建议所有部署项目,第一步先把 FP16 跑通,这是最低成本的优化。
INT8 就有意思了。它有两种做法:
- PTQ(Post-Training Quantization):训练完的模型直接量化。快,但精度损失不可控。
- QAT(Quantization-Aware Training):在训练过程中模拟量化效果。慢,但精度损失可以压到 0.5% 以内。
2.4 量化误差的来源与缓解
量化误差主要来自三个地方:
- 权重量化误差:参数被压缩后,表达能力下降。
- 激活值量化误差:中间结果被量化,误差会逐层累积。
- 梯度量化误差:只在 QAT 中出现,影响训练稳定性。
怎么缓解?我总结了几条实战经验:
- 校准数据集要大:PTQ 需要一小批数据来统计激活值的分布。我一般用 500~1000 个样本,太少的话 scale 算不准。
- 敏感层保留 FP16:有些层(比如 attention 的 softmax)对精度特别敏感。可以做个混合精度方案,大部分层用 INT8,敏感层用 FP16。
- 用 KL 散度选阈值:截断阈值怎么选?我习惯用 KL 散度,找到让量化前后分布差异最小的那个截断点。
下面这张图,把量化的核心流程和权衡关系画清楚了。
2.5 实战建议:从哪开始?
如果你刚接触量化,我建议按这个顺序来:
- 先跑 FP16:改一行代码的事,白捡 50% 内存。大部分框架(PyTorch、TensorRT)都原生支持。
- 再试 INT8 PTQ:用 500 个校准样本跑一遍,看看精度掉多少。如果掉得不多(< 1%),直接上线。
- 精度不够就上 QAT:准备好训练脚本,花点时间重新训练。一般 10~20 个 epoch 就能收敛。
- 最后做混合精度:用 profiling 工具找出敏感层,单独保留 FP16,其他层用 INT8。
量化这件事,说白了就是"用空间换时间,用精度换空间"。你心里要有一杆秤:你的业务能容忍多少精度损失?你的硬件能提供多少算力?想清楚了,再动手。