2、深度学习模型量化基础:量化的数学原理(对称/非对称量化)、量化精度与性能的权衡、INT8与FP16对比

好,咱们正式开始聊量化。说实话,很多同学一听到「量化」就觉得是玄学,其实没那么复杂。你想想看,深度学习模型本质上就是一堆矩阵乘法,而矩阵里的数字,我们通常用 FP32 来存。但 FP32 太「胖」了,一个数占 4 个字节。我们能不能用更瘦的数据类型来存?这就是量化的核心动机。

2.1 量化的数学原理:对称 vs 非对称

量化的本质,就是做一个映射。把 FP32 的数值范围,映射到 INT8 的数值范围(-128 到 127,或者 0 到 255)。这个映射怎么做?有两种主流方案。

2.1.1 对称量化(Symmetric Quantization)

对称量化,说白了就是让零点对齐。FP32 的 0.0,映射到 INT8 的 0。正负范围是对称的。

公式很简单:

scale = max(|x_fp32|) / 127
x_int8 = round(x_fp32 / scale)

这里有个关键点:scale 是一个浮点数,也叫缩放因子。它决定了每个 INT8 步长代表多少 FP32 的值。

重要:对称量化不需要零点偏移(zero-point),实现起来更简单。硬件实现也更快。我见过很多部署框架,默认就用对称量化。

举个例子。假设你的权重范围是 [-2.0, 2.0],那么 scale = 2.0 / 127 ≈ 0.0157。FP32 的 1.0 就变成 round(1.0 / 0.0157) = 64。你看,计算很直接。

2.1.2 非对称量化(Asymmetric Quantization)

非对称量化,允许零点偏移。FP32 的 0.0 不一定映射到 INT8 的 0。它更适合那些数值分布不对称的情况,比如 ReLU 之后的激活值(全是非负数)。

公式稍微复杂一点:

scale = (max - min) / 255
zero_point = round(-min / scale)
x_int8 = round(x_fp32 / scale) + zero_point

这里 zero_point 就是零点偏移。它让量化范围可以「平移」,从而更精确地覆盖原始数据分布。

我的经验:我在项目中遇到过,对于权重,对称量化通常就够了。但对于激活值,尤其是经过 ReLU 后的值,非对称量化往往能多保留 0.5-1% 的精度。你想想看,ReLU 把所有负数截断成 0,数值分布全挤在正半轴,这时候对称量化会浪费一半的 INT8 范围(负数部分全空着)。非对称量化就能把整个 INT8 范围都利用起来。

2.2 量化精度与性能的权衡

量化不是免费的午餐。你省了内存和带宽,但可能会损失精度。这个权衡,是部署工程师每天都要面对的。

2.2.1 精度损失从哪来?

精度损失主要来自两个地方:

  • 截断误差:FP32 的数值范围比 INT8 大得多。超出 INT8 范围的值,会被截断(clamp)到边界值。比如 FP32 的 3.0,如果 scale 是 0.02,那么 3.0 / 0.02 = 150,但 INT8 最大是 127,所以只能截断成 127。这 23 的差值就丢了。
  • 舍入误差:量化后的值必须是整数。round 操作会引入舍入误差。比如 1.3 / 0.02 = 65,但 1.31 / 0.02 = 65.5,四舍五入后也是 65。这 0.5 的误差就丢了。

这两种误差叠加,就会导致模型输出和原始 FP32 模型有偏差。

2.2.2 性能提升从哪来?

性能提升是实打实的:

指标 FP32 INT8 提升比例
内存占用 4 字节/参数 1 字节/参数 4x
带宽需求 约 4x
计算吞吐 基础 2-4x 取决于硬件
功耗 显著降低

我建议你记住这个表。面试的时候经常被问到。

注意:INT8 的加速效果高度依赖硬件。在 NVIDIA T4 上,INT8 的 Tensor Core 吞吐是 FP32 的 4 倍。但在一些老旧的 GPU 上,可能只有 2 倍甚至更少。我曾经在 Jetson Nano 上踩过坑,它的 INT8 支持有限,加速比远不如预期。

2.3 INT8 与 FP16 对比

很多同学会问:既然 FP16 也是 2 字节,INT8 是 1 字节,为什么不用 FP16?

嗯,这里要分清楚。FP16 和 INT8 是两种完全不同的数据类型。

2.3.1 数值表示范围

数据类型 位数 范围 精度
FP32 32 ~±3.4e38 约 7 位有效数字
FP16 16 ~±65504 约 3-4 位有效数字
INT8 8 -128 到 127 整数精度

FP16 虽然只有 16 位,但它是指数+尾数的结构,能表示很大的范围(最大 65504)。INT8 只能表示 256 个离散的整数值。

2.3.2 什么时候用 FP16?

FP16 适合那些对数值范围敏感、但对精度要求不极端的情况。比如:

  • 训练时的混合精度训练(AMP),用 FP16 做前向和反向,用 FP32 做权重更新。
  • 推理时,如果模型对量化敏感(比如一些 NLP 模型),FP16 比 INT8 更安全。

2.3.3 什么时候用 INT8?

INT8 适合那些对带宽和延迟极度敏感的场景。比如:

  • 边缘设备部署(Jetson、手机等),内存和带宽都是稀缺资源。
  • 高吞吐服务,比如在线推荐系统,需要同时处理大量请求。

核心对比:FP16 是「降精度但保范围」,INT8 是「降范围但保速度」。你想想看,FP16 的计算单元和 FP32 是共享的(在 Tensor Core 里),而 INT8 有独立的计算单元。所以 INT8 的吞吐可以做到 FP32 的 4 倍,而 FP16 通常只有 2 倍。

2.3.4 我的选择策略

我个人习惯这样选:

  1. 先试 INT8。 如果精度损失在 1% 以内,直接上 INT8。省下来的内存和带宽,可以让你部署更大的模型。
  2. INT8 精度不够? 试试量化感知训练(QAT)。如果 QAT 还不行,再退回到 FP16。
  3. FP16 是保底方案。 它比 FP32 快,比 INT8 准。但说实话,在 TensorRT 里,FP16 的加速比往往不如 INT8 明显。

避坑指南:我曾经在一个目标检测模型上,INT8 量化后 mAP 掉了 3 个点。我以为是量化方法不对,折腾了两天。后来发现是某个卷积层的权重分布太宽,导致截断误差太大。解决办法很简单:对该层单独做 per-channel 量化,而不是 per-tensor 量化。精度就回来了。

好了,这一章的内容就到这里。量化不是银弹,但它是目前部署领域最实用的技术之一。下一章我们会聊量化感知训练(QAT)的具体实现,到时候会手把手带你写代码。


公众号:蓝海资料掘金营,微信deep3321