第4章:模型量化基础——对称量化与非对称量化、定点数与浮点数转换

各位同学,今天我们来聊聊模型量化。说实话,量化这个概念在芯片部署领域,就像炒菜离不开盐一样基础。我刚开始接触量化时,觉得不就是把32位浮点数变成8位整数嘛,有什么难的?结果第一次在FPGA上跑量化模型,精度直接掉了5个点,被领导叫去喝茶……嗯,从那以后我老老实实把量化原理啃了个透。

4.1 为什么需要量化?

先问大家一个问题:为什么我们要把好好的浮点数模型,硬塞进整数的“小房子”里?

原因其实很直白:芯片不喜欢浮点数。你想想看,一个32位浮点数乘法器,面积是8位整数乘法器的十几倍,功耗更是翻着跟头往上涨。在边缘设备上,比如手机、摄像头、IoT芯片,我们最缺的就是算力和电池。量化,说白了就是用精度换效率。

核心收益:

  • 模型体积缩小4倍(32位→8位)
  • 推理速度提升2-4倍(整数运算更快)
  • 功耗降低50%以上(少搬数据、少做计算)

我在一个智能门锁项目里遇到过这种情况:原本的浮点模型在服务器上跑得飞快,但移植到ARM Cortex-M4芯片上,一帧图像要处理3秒钟。量化到8位后,直接降到200毫秒。用户终于不用对着门锁等半天了。

4.2 浮点数与定点数的转换

在讲对称量化之前,我们先搞清楚一个基础问题:浮点数怎么变成定点数?

浮点数,比如3.14159,在计算机里是用科学计数法存的:符号位+指数位+尾数位。定点数呢?就是固定小数点位数的整数。比如我们用Q8.8格式,就是8位整数、8位小数。

转换公式其实很简单:

// 浮点数 → 定点数
float f = 3.14159f;
int fixed_point = (int)(f * (1 << 8));  // 乘以2^8,取整

// 定点数 → 浮点数
float f_back = (float)fixed_point / (1 << 8);  // 除以2^8

这里有个坑:精度损失。3.14159乘以256等于804.247,取整后变成804,再除回来是3.140625。你看,小数点后第三位就开始不准了。我曾经在一个音频处理项目里,因为定点数精度不够,导致输出波形失真,听起来像收音机没调好频道……后来把Q8.8改成Q12.4,问题就解决了。

我的经验:定点数的小数位越多,精度越高,但能表示的数值范围越小。选多少位小数,取决于你的数据分布。建议先统计一下模型参数的绝对值最大值,再决定Q格式。

4.3 对称量化

对称量化,名字听着挺唬人,其实核心就一句话:量化范围关于0对称

什么意思呢?就是浮点数的最大值和最小值,绝对值相等。比如你的权重在[-1.0, 1.0]之间,量化到8位有符号整数[-128, 127],那么映射关系就是:

  • 浮点-1.0 → 整数-128
  • 浮点0.0 → 整数0
  • 浮点1.0 → 整数127

公式长这样:

// 对称量化
float scale = max_abs / 127.0f;  // 缩放因子
int q = (int)round(f / scale);   // 量化
float f_deq = q * scale;         // 反量化

注意这里分母是127,不是128。因为8位有符号整数的范围是[-128, 127],但为了对称性,我们通常把最大值映射到127,最小值映射到-128。这样0点完美对齐。

避坑指南:我曾经在量化一个ReLU激活后的特征图时,发现所有值都是正数(0到6.8之间)。如果用对称量化,最大值是6.8,最小值是0,但对称量化会强制把范围变成[-6.8, 6.8]。这就浪费了一半的量化区间!因为负半区根本用不上。这时候就该用非对称量化了。

4.4 非对称量化

非对称量化,说白了就是不要求0点对齐。它用两个参数:缩放因子(scale)和零点(zero point)。

公式:

// 非对称量化
float scale = (max_val - min_val) / 255.0f;  // 8位无符号整数
int zero_point = (int)round(-min_val / scale);
int q = (int)round(f / scale) + zero_point;
float f_deq = (q - zero_point) * scale;

你看,这里用了无符号整数[0, 255],所以分母是255。零点可以不是0,比如你的数据范围是[2.0, 10.0],量化后零点可能是某个非零值。

非对称量化的好处是:不浪费量化区间。所有256个等级都用来表示有效数据。坏处呢?计算时多了一个零点偏移,硬件实现稍微复杂一点。

对比项 对称量化 非对称量化
0点对齐
适用场景 权重(通常对称分布) 激活值(通常非对称分布)
硬件复杂度
精度损失 数据分布不对称时较大 通常更小

4.5 量化误差分析

量化一定会带来误差,关键是怎么控制它。误差主要来自两方面:

  1. 截断误差:超出量化范围的值被强行截断。比如你的数据最大值是100,但量化范围只到10,那90以上的信息全丢了。
  2. 舍入误差:浮点数到整数的取整操作,会损失小数部分。

我在一个图像分类项目里,发现量化后精度掉了3%。排查了半天,原来是某个层的激活值分布有个长尾,最大值是正常值的10倍。用对称量化时,scale被这个异常值拉得很大,导致正常值的量化精度严重不足。后来我用了剪裁(clipping),把最大值限制在99.7%分位数上,精度损失降到了0.5%以内。

实用技巧:量化前先做数据分布分析。画个直方图看看你的权重和激活值长什么样。如果分布很集中,量化效果就好;如果分布很分散,考虑先做剪裁或者用非对称量化。

4.6 知识体系总览

下面这张图,是我自己总结的量化知识框架。你看一眼,就能把今天讲的内容串起来。

模型量化知识体系 模型量化 浮点数 → 定点数 对称量化 非对称量化 Q格式选择 精度 vs 范围 取整误差 0点对齐 适合权重 硬件友好 零点偏移 适合激活值 精度更高 核心目标:在精度损失可控的前提下,最大化推理效率 选择策略:权重用对称,激活用非对称,数据分布决定一切

4.7 实战建议

最后,给大家几条我在实际项目中总结出来的建议:

  • 先做校准:用一小批真实数据跑一遍推理,统计每层激活值的min/max。不要用训练集的理论范围,那往往偏大。
  • 逐层分析:不同层的分布差异可能很大。我见过一个模型,第一层激活值范围是[-1, 1],最后一层是[-100, 100]。统一量化方案肯定吃亏。
  • 混合精度:敏感层(比如第一层和最后一层)用16位,中间层用8位。我在一个语音识别模型上试过,精度几乎无损,速度还快了2倍。
  • 量化感知训练:如果量化后精度掉得厉害,试试在训练时加入伪量化操作(fake quantization),让模型自己适应量化误差。

记住一句话:没有最好的量化方案,只有最适合你数据的量化方案。动手之前,先花10分钟看看你的数据长什么样。


专注资料整理