量化数据类型:INT8、INT4、FP16、BF16 的区别与选择

做 NPU 部署,选量化数据类型是第一步。选错了,后面全白干。

我见过不少团队,一上来就上 INT4,结果精度崩了,回头查才发现模型对动态范围特别敏感。也有项目死守 FP16,功耗压不下去,NPU 带宽跑满,帧率就是上不来。

说白了,数据类型的选择就是一场「精度 vs 效率」的博弈。你得知道每种数据类型的脾气,才能用好它。

主流量化数据类型一览

目前 NPU 部署中,常见的量化数据类型就四种:INT8、INT4、FP16、BF16。它们的核心差异在三个维度:位宽、动态范围、精度。

数据类型 位宽 动态范围 精度(尾数位) 典型场景
INT8 8 bit [-128, 127] 均匀量化,无指数 卷积层、全连接层
INT4 4 bit [-8, 7] 均匀量化,无指数 激活值、权重压缩
FP16 16 bit ±65,504 10 bit 尾数 梯度、敏感层
BF16 16 bit ±3.4×10³⁸ 7 bit 尾数 大动态范围场景

嗯,这里要注意:INT8 和 INT4 是定点数,FP16 和 BF16 是浮点数。定点数计算快、功耗低,但动态范围窄;浮点数动态范围大,但硬件开销也大。

INT8:部署的黄金标准

INT8 是目前 NPU 部署的主力。为什么?因为大部分模型在 INT8 下精度损失可以控制在 1% 以内,而推理速度能翻倍。

我在项目中遇到过最典型的案例:一个 ResNet-50 分类模型,FP32 精度 76.3%,INT8 量化后精度 75.8%,只掉了 0.5%。但推理延迟从 12ms 降到了 6ms,吞吐量直接翻倍。

INT8 量化核心公式:

scale = (max_val - min_val) / 255
zero_point = round(-min_val / scale)
quantized_val = round(float_val / scale) + zero_point

其中 scale 是缩放因子,zero_point 是零点偏移。这两个参数决定了量化精度。

INT8 的坑在哪?我踩过。当模型某些层的激活值分布极度不均匀时,比如大部分值集中在 0 附近,少数离群值很大,这时候均匀量化会严重损失精度。

避坑指南:我曾经在一个检测模型上直接做 INT8 量化,结果 mAP 掉了 5%。排查后发现是某个特征层的激活值有长尾分布。解决办法是:对该层做 per-channel 量化,或者改用 KL 散度校准算法,而不是默认的 min-max 校准。

INT4:极限压缩的利器

INT4 是最近两年才火起来的。它能把模型体积压缩到 INT8 的一半,对带宽敏感的场景特别友好。

但 INT4 的精度损失明显更大。你想想看,INT8 有 256 个量化等级,INT4 只有 16 个。信息量差了 16 倍。

我个人习惯只在两种情况下用 INT4:

  • 权重量化:权重的分布通常比较规整,INT4 可以接受
  • 非敏感层:比如网络浅层的激活值,对精度影响较小

千万别在 attention 层或分类头用 INT4。我试过,精度直接跳水。

FP16 vs BF16:浮点数的两个流派

FP16 和 BF16 都是 16 位浮点数,但设计哲学完全不同。

特性 FP16 BF16
指数位 5 bit 8 bit
尾数位 10 bit 7 bit
动态范围 ±65,504 ±3.4×10³⁸
精度 高(尾数多) 低(尾数少)
适用场景 梯度、小数值 大动态范围

FP16 的尾数位多,精度高,但动态范围窄。训练时梯度容易下溢或上溢。BF16 正好相反,它保留了和 FP32 一样的 8 位指数,动态范围极大,但尾数位少,精度低。

我记得有一次做 BERT 部署,用 FP16 推理时某些层的激活值直接变成 NaN。排查后发现是 softmax 后的值太小,FP16 表示不了。换成 BF16 就没事了,因为它的动态范围大得多。

实战建议:如果你的 NPU 支持 BF16,优先用 BF16 做浮点推理。它的动态范围接近 FP32,基本不会出现溢出问题。精度损失通常可以忽略。

混合精度量化策略

单一数据类型很难满足所有需求。所以就有了混合精度量化——不同层用不同的数据类型。

我常用的混合精度策略分三步:

  1. 敏感度分析:逐层量化到 INT8,看精度损失。损失大的层标记为「敏感层」
  2. 分层配置:敏感层用 FP16 或 BF16,非敏感层用 INT8,极端压缩场景用 INT4
  3. 迭代调优:跑一遍验证集,调整分层策略,直到精度和性能都达标

下面这张图展示了混合精度量化的完整流程:

混合精度量化决策流程 原始 FP32 模型 逐层 INT8 量化 + 精度损失评估 精度达标? 是 全 INT8 部署 否 标记敏感层 敏感层 → FP16/BF16 非敏感层 → INT8/INT4 验证集评估 + 调优 迭代优化

实战中的选择建议

说了这么多,到底怎么选?我总结了一个快速决策表:

场景 推荐方案 理由
精度优先,带宽充足 全 FP16 或 BF16 几乎无损,部署简单
性能优先,精度容忍 全 INT8 速度翻倍,精度损失可控
极致压缩,边缘设备 INT8 + INT4 混合 体积减半,带宽友好
大模型,动态范围大 BF16 + INT8 混合 BF16 兜底敏感层
训练后量化(PTQ) INT8 为主 工具链成熟,校准方便

最后提醒一句:别信任何「万能量化方案」。每个模型、每颗 NPU 都有自己的脾气。我习惯的做法是:先跑一遍全 INT8,看精度;精度不行就做敏感度分析,上混合精度;还不行就换 BF16 兜底。迭代三次,基本能找到一个最优解。