2. 内存基础

说到模型部署,内存占用永远是绕不开的坎。我见过太多团队,模型精度刷得漂漂亮亮,一到上线就傻眼——显存爆了,推理延迟高得离谱。这时候,量化往往是第一把手术刀。

量化说白了,就是用更少的比特数去表示模型的参数和激活值。你想想看,一个 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 怎么选?这就引出了两种策略:

  1. 对称量化:r_min = -r_max,zero_point = 0。简单粗暴,适合权重分布比较对称的情况。
  2. 非对称量化:r_min 和 r_max 各自独立选取。能更好地适配激活值这种分布不对称的情况。
我的习惯:权重用对称量化,激活值用非对称量化。权重分布通常以 0 为中心,对称量化省掉 zero_point 的计算;激活值经过 ReLU 后全是正数,非对称量化能把整个整数范围都用上。

但这里有个坑——截断。如果原始浮点数的最大值超过了 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% 以内。
避坑指南:我曾经在一个检测模型上直接跑 PTQ,mAP 掉了 3 个点。后来换成 QAT,只掉了 0.3 个点。如果你的模型对精度敏感,别偷懒,上 QAT。但要注意,QAT 需要重新训练,训练时间会增加 1.5~2 倍。

2.4 量化误差的来源与缓解

量化误差主要来自三个地方:

  1. 权重量化误差:参数被压缩后,表达能力下降。
  2. 激活值量化误差:中间结果被量化,误差会逐层累积。
  3. 梯度量化误差:只在 QAT 中出现,影响训练稳定性。

怎么缓解?我总结了几条实战经验:

  • 校准数据集要大:PTQ 需要一小批数据来统计激活值的分布。我一般用 500~1000 个样本,太少的话 scale 算不准。
  • 敏感层保留 FP16:有些层(比如 attention 的 softmax)对精度特别敏感。可以做个混合精度方案,大部分层用 INT8,敏感层用 FP16。
  • 用 KL 散度选阈值:截断阈值怎么选?我习惯用 KL 散度,找到让量化前后分布差异最小的那个截断点。

下面这张图,把量化的核心流程和权衡关系画清楚了。

量化核心流程与精度权衡 FP32 原始模型 选择量化方案 PTQ(训练后量化) QAT(量化感知训练) 校准数据集统计分布 计算 scale / zero_point 训练中模拟量化效果 反向传播更新权重 INT8 / FP16 量化模型 PTQ:速度快,精度损失大 QAT:速度慢,精度损失小

2.5 实战建议:从哪开始?

如果你刚接触量化,我建议按这个顺序来:

  1. 先跑 FP16:改一行代码的事,白捡 50% 内存。大部分框架(PyTorch、TensorRT)都原生支持。
  2. 再试 INT8 PTQ:用 500 个校准样本跑一遍,看看精度掉多少。如果掉得不多(< 1%),直接上线。
  3. 精度不够就上 QAT:准备好训练脚本,花点时间重新训练。一般 10~20 个 epoch 就能收敛。
  4. 最后做混合精度:用 profiling 工具找出敏感层,单独保留 FP16,其他层用 INT8。
一个小技巧:量化后的模型一定要做端到端的精度验证,别只看单层的误差。我见过一个 case,每层误差只有 0.1%,但 100 层累积下来,最终输出完全不对。用你的测试集跑一遍,跟 FP32 的结果做对比,这是最靠谱的。

量化这件事,说白了就是"用空间换时间,用精度换空间"。你心里要有一杆秤:你的业务能容忍多少精度损失?你的硬件能提供多少算力?想清楚了,再动手。