2、模型量化基础:量化的数学原理、对称量化与非对称量化、量化参数(scale, zero-point)详解
各位同学,咱们今天聊聊模型量化。说实话,这玩意儿在工业界太重要了。我刚开始做模型部署那会儿,一个BERT模型在GPU上跑得飞快,结果一放到手机端,直接卡成PPT。后来才发现,量化就是那把钥匙。
说白了,量化就是把模型里那些32位浮点数(FP32)的权重和激活值,用更少的位数来表示。最常见的就是降到8位整数(INT8)。你想想看,内存占用直接缩到四分之一,计算速度还能翻倍,这不香吗?
2.1 量化的数学原理
量化的核心思想其实很简单:用一个低精度的数值范围,去近似表示一个高精度的数值范围。
假设我们有一组浮点数,范围在 [r_min, r_max] 之间。我们要把它们映射到整数范围 [q_min, q_max] 上。比如INT8,q_min = -128,q_max = 127。
这个映射关系,数学上就是一个线性变换:
r = S * (q - Z)
其中:
- r 是原始的浮点数值
- q 是量化后的整数值
- S 是缩放因子(scale),一个浮点数
- Z 是零点(zero-point),一个整数
反过来,从浮点转整数就是:
q = round(r / S) + Z
嗯,这里要注意,round操作会带来精度损失。我在项目中遇到过,有些层对精度特别敏感,比如分类任务的最后一层,量化后准确率直接掉了2个点。这时候就得考虑混合精度了。
核心公式记忆点:
量化:q = round(r / S) + Z
反量化:r = S * (q - Z)
2.2 对称量化与非对称量化
接下来咱们聊聊两种常见的量化策略。说白了,就是零点Z怎么选的问题。
2.2.1 对称量化
对称量化,顾名思义,就是量化范围关于原点对称。也就是说,Z = 0。
这时候公式就简化成:
q = round(r / S)
r = S * q
对称量化的好处很明显:
- 计算简单:少了Z的加减操作,硬件实现更友好
- 适合权重:我个人的习惯是,权重通常用对称量化。因为训练好的权重分布往往接近对称,比如均值为0的正态分布
但对称量化有个坑:如果数据分布不对称,比如ReLU后的激活值全是非负数,那对称量化就会浪费一半的表示范围。你想想看,INT8的[-128, 127]范围,结果你只用到了[0, 127]这一半,精度损失自然就大了。
我的经验:对称量化在权重上表现很好,但在激活值上,尤其是经过ReLU的,我建议用非对称量化。
2.2.2 非对称量化
非对称量化允许Z不为0。这样就能把浮点范围 [r_min, r_max] 完整地映射到 [q_min, q_max] 上。
计算S和Z的公式如下:
S = (r_max - r_min) / (q_max - q_min)
Z = round(q_min - r_min / S)
举个例子,假设激活值范围是 [0.0, 6.0],量化到INT8的 [0, 255]:
S = (6.0 - 0.0) / (255 - 0) = 0.02353
Z = round(0 - 0.0 / 0.02353) = 0
你看,这时候Z=0,其实变成了对称量化的一种特例。但如果范围是 [1.0, 7.0]:
S = (7.0 - 1.0) / 255 = 0.02353
Z = round(0 - 1.0 / 0.02353) = round(-42.5) = -43
这时候Z就是-43了,非对称量化就派上了用场。
避坑指南:我曾经在量化一个轻量级模型时,所有层都用了对称量化,结果推理结果完全不对。排查了半天,发现是某层激活值全是正数,对称量化把负半轴全浪费了,导致精度崩塌。后来改成非对称量化,问题就解决了。
2.3 量化参数详解:Scale 和 Zero-Point
这两个参数是量化的灵魂。咱们一个一个说。
2.3.1 Scale(缩放因子)
Scale决定了每个量化步长代表多大的浮点数范围。说白了,就是精度有多细。
计算公式:
S = (r_max - r_min) / (q_max - q_min)
Scale越小,量化精度越高。但Scale太小,意味着r_max - r_min范围窄,容易溢出。
我举个例子:
| 浮点范围 | 量化范围 | Scale值 | 量化误差 |
|---|---|---|---|
| [-1.0, 1.0] | INT8 [-128, 127] | 0.00784 | 较小 |
| [-100.0, 100.0] | INT8 [-128, 127] | 0.7843 | 较大 |
你看,范围越大,Scale越大,每个整数步长代表的浮点数变化就越大,精度自然就差了。
2.3.2 Zero-Point(零点)
Zero-point是浮点数值0对应的量化整数值。它的作用就是对齐。
为什么需要零点?因为浮点0在神经网络里太重要了。比如padding操作、ReLU的阈值,都依赖0的精确表示。如果没有零点,量化后的0可能对应一个非零的浮点数,那整个计算就乱套了。
零点有几个特点:
- 它必须是一个整数,且在 [q_min, q_max] 范围内
- 对于对称量化,Z = 0
- 对于非对称量化,Z 通常不为0
关键理解:Zero-point保证了浮点0在量化后能被精确表示。这一点在卷积和全连接层中尤其重要,因为零填充和稀疏计算都依赖它。
2.4 量化误差分析
量化必然带来误差。误差主要来自两个方面:
- 舍入误差:round操作导致的精度丢失
- 截断误差:超出量化范围的值被截断到边界值
我记得有一次,量化一个检测模型,发现小目标全丢了。分析后发现,是某些激活值出现了离群点,导致量化范围被拉得很大,大部分正常值的Scale变得很粗,精度不够。后来用了离群点裁剪,把极端值截断掉,效果就好了很多。
实用技巧:量化前先统计一下数据的分布。如果发现长尾分布,可以考虑裁剪掉两端1%的离群点,往往能显著提升量化精度。
2.5 实战:手动计算量化参数
咱们来手算一个例子,加深理解。
假设有一组浮点权重:
weights = [0.5, -0.3, 1.2, -0.8, 0.0, 2.5, -1.5]
我们要量化到INT8范围 [-128, 127]。
第一步:确定范围
r_min = -1.5
r_max = 2.5
第二步:计算Scale
S = (2.5 - (-1.5)) / (127 - (-128))
= 4.0 / 255
= 0.01569
第三步:计算Zero-Point(非对称)
Z = round(-128 - (-1.5) / 0.01569)
= round(-128 + 95.6)
= round(-32.4)
= -32
第四步:量化每个值
q(0.5) = round(0.5 / 0.01569) + (-32) = round(31.87) - 32 = 32 - 32 = 0
q(-0.3) = round(-0.3 / 0.01569) - 32 = round(-19.12) - 32 = -19 - 32 = -51
q(1.2) = round(1.2 / 0.01569) - 32 = round(76.48) - 32 = 76 - 32 = 44
... 以此类推
你看,整个过程其实不复杂。但在实际工程中,我们通常用框架自带的量化工具,比如PyTorch的torch.quantization,或者TensorRT的量化器。不过理解底层原理,能帮你更好地调试和优化。
总结一下:
- 量化就是用低精度近似高精度,核心是S和Z两个参数
- 对称量化适合权重,非对称量化适合激活值
- Scale决定精度,Zero-Point保证0的精确表示
- 量化误差不可避免,但可以通过裁剪、混合精度等手段优化
下一章咱们聊聊量化感知训练(QAT),那才是真正让模型在量化后不掉点的关键技巧。到时候我会分享一个我在项目里踩过的坑——直接训练后量化,结果模型直接废了,后来用了QAT才救回来。敬请期待!