3、量化精度与数值格式:FP32、FP16、INT8、INT4 精度对比,量化误差来源分析

聊到模型量化,第一个绕不开的话题就是:到底该用哪种数值格式?

FP32、FP16、INT8、INT4……这些名词你可能早就听过。但说实话,真正在项目里选型的时候,很多人还是会懵。我刚开始做端侧部署那会儿,也踩过不少坑。今天咱们就把这事彻底聊透。

3.1 数值格式的本质:精度与范围的博弈

先问一个问题:为什么要有这么多格式?

答案很简单——没有免费的午餐。你想省内存、省带宽、省算力,那就得牺牲点精度。反过来,你要高精度,就得接受更大的存储和更慢的速度。

我个人习惯把数值格式拆成三个维度来看:

  • 位宽:占多少比特,直接决定存储和带宽
  • 动态范围:能表示的最大值和最小值差距
  • 精度:相邻两个数值之间的最小间隔

这三个维度互相制约。你想想看,位宽固定了,你只能在这两个维度上做取舍。

3.2 主流格式逐个拆解

FP32(32位浮点)

这是深度学习的老祖宗格式。1位符号位,8位指数,23位尾数。动态范围大约 ±3.4×10³⁸,精度大概 1.18×10⁻³⁸。

说白了,FP32就是那个「啥都能干,但啥都不便宜」的选手。训练的时候用它,没问题。但部署到手机、IoT设备上?内存和带宽直接爆炸。

关键数据:一个FP32的权重占4字节。ResNet-50大约25M参数,光权重就100MB。手机端?想都别想。

FP16(16位浮点)

FP16是FP32的「瘦身版」。1位符号,5位指数,10位尾数。动态范围 ±6.5×10⁴,精度约 9.77×10⁻⁴。

嗯,这里要注意:FP16的动态范围比FP32小了很多。我在项目中遇到过,有些模型的梯度在FP16下直接溢出,训练都跑不起来。但推理场景下,FP16通常够用,尤其是GPU端(Tensor Cores原生支持)。

我的经验:如果模型里有很多极端值(比如某些层的输出特别大或特别小),FP16可能会出问题。这时候可以试试混合精度,关键层用FP32,其他层用FP16。

INT8(8位整型)

这才是端侧部署的「主力军」。8位整型,没有指数位,只有符号位和7位数值位。范围 -128 到 127。

你可能会问:范围这么小,能行吗?

答案是:靠缩放。我们把浮点数值映射到INT8的范围内,这个过程叫「量化」。说白了,就是用更少的比特去近似原来的数值。

INT8的优点是:

  • 存储只有FP32的1/4
  • 计算速度可以快2-4倍(取决于硬件)
  • 大部分端侧芯片(高通、联发科、苹果)都有INT8加速单元

避坑指南:我曾经在某个项目里,直接把FP32模型转INT8,结果精度掉了5个点。后来发现是激活值的分布太分散,量化后信息丢失严重。所以,量化前一定要做校准(calibration),看看数据的分布。

INT4(4位整型)

INT4是更极端的压缩方案。范围 -8 到 7,只有16个离散值。

说实话,INT4的精度损失通常比较大。我一般只在两种情况下用:

  1. 模型非常大(比如大语言模型),内存实在塞不下
  2. 任务本身对精度不敏感(比如某些分类任务)

但INT4也有它的价值。你想想看,一个INT4的权重只占0.5字节,比FP32省了8倍。对于百亿参数的大模型,这可能是「能不能部署」的关键。

3.3 精度对比:一张表说清楚

格式 位宽 动态范围 精度(最小间隔) 典型场景
FP32 32 bit ±3.4×10³⁸ ~1.18×10⁻³⁸ 训练、高精度推理
FP16 16 bit ±6.5×10⁴ ~9.77×10⁻⁴ GPU推理、混合精度训练
INT8 8 bit -128 ~ 127 1 端侧推理、边缘部署
INT4 4 bit -8 ~ 7 1 极致压缩、大模型部署

这张表很直观。你会发现,从FP32到INT4,动态范围和精度都在急剧下降。但为什么我们还能用?因为神经网络对数值误差有一定的鲁棒性。说白了,模型学到的特征分布,往往集中在某个小范围内,我们只需要把这个范围「照顾」好就行。

3.4 量化误差的来源分析

量化不是无损的。误差从哪来?我总结为三个主要来源:

3.4.1 截断误差(Clipping Error)

当原始数值超出量化范围时,会被强行截断。比如INT8只能表示-128到127,如果某个权重是200,那它就被截成127。

这种误差在「离群点」多的模型里特别明显。我记得有一次,某个层的激活值偶尔会飙到1000以上,INT8直接截断,导致那一层的输出完全乱掉。

怎么办? 可以用「饱和量化」,先统计数据的分布,然后选择一个合适的截断阈值。比如,把99.9%的数据都包进去,剩下的0.1%直接截掉。虽然也有损失,但比硬截断好得多。

3.4.2 舍入误差(Rounding Error)

量化本质上是把连续值映射到离散值。比如FP32的0.3,在INT8里可能被量化成0或1。这个「四舍五入」的过程,就是舍入误差。

单个舍入误差很小,但成千上万个参数累积起来,影响就大了。尤其是深层网络,误差会逐层放大。

3.4.3 精度损失(Precision Loss)

这个好理解。INT8的精度只有1,意味着两个相差0.5的数值,在INT8里可能被映射成同一个值。信息就这么丢了。

精度损失在「小数值」区域尤其严重。比如权重分布在0.001到0.01之间,INT8根本没法区分它们。

3.5 实战中的选择策略

说了这么多,到底怎么选?我个人的经验是:

  • 训练阶段:无脑FP32。别省这点内存,训练不稳定更麻烦。
  • GPU推理:优先FP16。Tensor Cores加持,又快又准。
  • 端侧CPU/GPU:INT8是首选。精度损失可控,性能提升明显。
  • 内存极度受限:试试INT4。但一定要做充分的精度验证。

一句话总结:能用INT8就别用FP16,能用FP16就别用FP32。但前提是——精度达标。

好了,数值格式这块就聊到这儿。下一章咱们深入讲讲量化的具体算法,包括对称量化、非对称量化、逐层量化和逐通道量化。到时候我会拿实际代码来演示,保证你看完就能上手。