一、什么是模型量化
模型量化(Model Quantization) 是一种深度学习模型优化技术,核心是将模型中高精度的浮点数(如 FP32/FP16)参数和激活值,转换为低比特位宽的整数(如 INT8/INT4)或低精度浮点数(如 FP8),以压缩模型体积、加速推理、降低功耗,同时将精度损失控制在可接受范围。
其核心原理是:数值映射。
本质是连续高精度浮点 → 离散低精度整数的线性映射,通过两个关键参数实现:
- 缩放因子 (Scale, S):控制映射粒度,决定整数间的实际数值间隔
- 零点 (Zero Point, Z) :确保原始 0 值被精确映射(避免填充 / 偏置误差)
将高精度的浮点数,映射为整数或低精度的浮点数,从而实现压缩模型体积、加速推理、降低功耗的问题,
为什么数值上可扩大,反而能缩小功耗?
一句话先破题
你看到的 **"数值扩大" 只是数学上的计算 **,真正决定功耗的,是硬件里存储和计算用的比特位数(bit 数),位数越少,功耗越低,跟数值本身大小没关系。
从硬件上看:
硬件层面:bit 越少,电路越轻松
- FP32:32 根线 / 32 个寄存器一起工作
- INT8:只需要 8 根线 / 8 个寄存器
位数越少:
- 翻转的晶体管更少
- 读取的数据量更少
- 内存带宽压力更小
- 运算单元更简单、更快
这些全部直接导致功耗降低。
二、常见的量化方案
1. 按精度位宽
| 精度 | 位宽 | 存储 | 典型用途 |
|---|---|---|---|
| FP32 | 32bit | 4 字节 | 训练基准 |
| FP16/BF16 | 16bit | 2 字节 | 半精度训练 / 推理 |
| INT8 | 8bit | 1 字节 | 主流部署(W8A8) |
| INT4 | 4bit | 0.5 字节 | 大模型极致压缩 |
| 1bit (二值) | 1bit | 1 比特 | 极端低功耗场景 |
2. 按映射方式
- 对称量化 :以 0 为中心对称映射(如
-max ↔ -128、max ↔ 127),适合权重(分布对称) - 非对称量化 :按实际
min/max范围映射,带零点偏移,适合激活值(分布偏正)
3. 按实施阶段
- 训练后量化 (PTQ) :模型训完直接量化,简单快速,但精度损失较大(尤其低位宽)
- 量化感知训练 (QAT) :训练中模拟量化误差,让权重适应低精度,精度几乎无损,但需重训
三、什么是原始0值?
在模型量化里说的原始 0 值 ,就是:模型里本来是 0 的那个浮点数值,不是你自己随便定义的 0。
1. 它具体指什么?
模型里有很多浮点数,比如:0.0、0.000123、-2.456、123.45 ......
其中那个等于 0 的浮点值,就是 "原始 0 值":x=0.0......
2. 为什么要专门提它?
量化是做映射:浮点数 x→整数 Xq
我们希望:
原来的 0.0,映射后也要是一个整数里的 "0 位置"
不然会出问题:
- 很多卷积、矩阵乘法里,0 代表 "无信号、无权重"
- 如果 0 映射歪了,整个计算偏移会很大,精度掉得离谱
3. 用零点(Zero Point)保证原始 0 映射准确
公式里你见过:Xq=round(Sx+Z)
当 原始 0 值 代入:x=0⇒Xq=Z
所以:
- 零点 Z 就是:原始浮点 0.0 对应的量化整数
- 反量化时:x=(Z−Z)×S=0能精确还原回 0.0,没有误差.
四、会不会同时有多个0值?
1. 模型里确实有很多 0
比如:
- 权重里的 0
- 激活值经过 ReLU 后变成的 0
- 残差连接里的 0
- padding 填充的 0
这些都是原始 0 值,数量成千上万。
2. 量化时它们会变成同一个值
同一个量化层(同一个 scale、同一个 zero point)下:
- 所有浮点
0.0 - 都会被映射成 同一个整数 Z
公式再看一眼:Xq=round(x/S+Z)当 x=0.0:Xq=Z不管有多少个 0.0,算出来都是 同一个 Z。
3. 会不会出现 "多个零点"?
不会。
在标准对称 / 非对称量化里:
- 一个 tensor / 一个量化组
- 只有 一个 scale S
- 只有 一个 zero point Z
所以:
- 所有原始 0.0 → 统一映射到 Z
- 反量化时 Z 又统一还原成 0.0
五、量反化是怎么操作的?
1. 反量化公式(通用版)
Xfloat=(Xquant−z)×s
- xquant:量化后的整数(比如 INT8)
- z:zero point(零点)
- s:scale(缩放因子)
- 结果 Xfloat:还原出来的近似浮点数
2. 一步一步看它在干嘛
-
先减去零点把整数 "挪回" 以零点为中心的位置xquant−z
-
再乘以 scale把小整数区间放大回原来的浮点区间(...)×s
就这两步。
3. 举个超简单例子
假设:
- scale s=0.1
- zero point z=10
- 量化后整数 Xquant=15
反量化:xfloat=(15−10)×0.1=0.5
4. 最关键的:原始 0 还原回去还是 0
当量化值刚好是零点时:xfloat=(10−10)×0.1=0.0
这就是为什么要设计 zero point:保证原来的 0,反量化后一定精确是 0,不会有误差。
六、缩放因子是怎么计算的?
缩放因子 scale,就是用来把「浮点数的真实范围」和「整数的可用范围」对齐的那个比值 。它不是猜的,是根据你这一组数据的最大最小值算出来的。
1. scale 到底是干嘛的?
浮点数范围很大,比如:[-2.4, 1.8]
但 INT8 整数只有固定范围:[-128, 127]
我们要做的就是:把 [-2.4, 1.8] 压缩 / 拉伸到 [-128, 127] 这个拉伸倍数 ,就是 scale。
2. scale 怎么算?(最常用对称量化)
先找这组数据的最大绝对值 :max_abs = max(|min|, |max|)
然后除以整数能表示的最大绝对值:scale=max_abs/127
举个例子
浮点数据范围:[-2.4, 1.8]max_abs = 2.4
scale = 2.4 / 127 ≈ 0.0188976
这就是最终的缩放因子。
3. 非对称量化(带零点那种)怎么算 scale?
更通用的公式:
scale=max_float−min_float/max_int−min_int qaAQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
- max_float, min_float:这组数据的最大、最小浮点值
- max_int, min_int:整数范围(比如 INT8 就是 127 和 -128)
这样算出来的 scale 能刚好把整个浮点区间塞满整数区间,不浪费精度,也不溢出。
4. 一句话记住
scale = 浮点数据的跨度 ÷ 整数能表示的跨度 它的作用就是:把浮点数 "缩" 到整数能装下的范围里,反量化时再 "放" 回去。
七、多个浮点数值,可能会映射到同一个整数值上吗?
1. 答案:一定会多个浮点 → 同一个整数
举个最容易懂的例子:
假设:
- 缩放因子
scale = 0.1 - 零点
zero_point = 0
量化公式(简化):xq=round(scalex)
那么:
- 浮点
0.05→ round(0.05/0.1) = round(0.5) → 0 - 浮点
0.06→ round(0.6) → 1 - 浮点
0.14→ round(1.4) → 1
看到没:0.06 和 0.14 两个不同的浮点数,最后都变成了整数 1
这就叫:多个浮点数值,被映射到同一个整数值
2. 本质原因:精度 "变粗" 了
- FP32:小数点后很多位,非常精细
- INT8:只有 256 个离散值,很粗糙
就像:
- 尺子原来刻度到 0.1mm(精细)
- 现在刻度只到 1cm(粗糙)
那 0.3cm、0.5cm、0.7cm 都会被当成 0cm 或 1cm多个值挤到同一个格子里,就重合了。
3. 这会带来什么?
这就是量化的精度损失:
- 原本不同的数值,量化后变得一样
- 模型细节信息丢失
- 但换来模型更小、更快、更省电
八、好,我们现在完整走一遍流程
场景设定
假设我们有一小段浮点数值 (模拟模型里的权重或激活值):[-1.2, -0.5, 0.0, 0.3, 1.0]
我们要把它量化成 INT8 。INT8 的整数范围是:[-128, 127]
第 1 步:计算浮点的 min 和 max
浮点数据:[-1.2, -0.5, 0.0, 0.3, 1.0]
- min_float = -1.2
- max_float = 1.0
第 2 步:计算缩放因子 scale
公式(非对称量化):scale=max_float−min_float/max_int−min_int
代入:
- max_float - min_float = 1.0 - (-1.2) = 2.2
- max_int - min_int = 127 - (-128) = 255
scale=2552.2≈0.008627
第 3 步:计算零点 zero_point
公式:zero_point=round(min_float/−scale+min_int)
代入:min_float/−scale=−(−1.2/0.008627)≈139.1
zero_point=round(139.1−128)=round(11.1)=11
所以:
- scale ≈ 0.008627
- zero_point = 11
第 4 步:量化(浮点 → INT8 整数)
公式:Xq=round(x/scale+zero_point)
我们逐个算:
-
x = -1.2 −1.2/0.008627+11≈−139.1+11=−128.1→−128
-
x = -0.5 −0.5/0.008627+11≈−57.96+11=−46.96→−47
-
x = 0.0 0/0.008627+11=11→11👉 原始 0 精确映射到 zero_point
-
x = 0.3 0.3/0.008627+11≈34.77+11=45.77→46
-
x = 1.0 1.0/0.008627+11≈115.9+11=126.9→127
量化结果(整数): [-128, -47, 11, 46, 127]
第 5 步:反量化(整数 → 近似浮点)
公式:Xfloat=(Xq−zero_point)×scale
逐个还原:
-
-128 (−128−11)×0.008627=−139×0.008627≈−1.199
-
-47 (−47−11)×0.008627=−58×0.008627≈−0.500
-
11 (11−11)×0.008627=0 👉 0 完美还原
-
46 (46−11)×0.008627=35×0.008627≈0.302
-
127 (127−11)×0.008627=116×0.008627≈1.001
反量化结果: [-1.199, -0.500, 0.0, 0.302, 1.001]
最终对比
原始浮点:[-1.2, -0.5, 0.0, 0.3, 1.0]反量化结果:[-1.199, -0.5, 0.0, 0.302, 1.001]
可以看到:
- 几乎一样
- 有一点点微小误差(这就是量化误差)
- 0 永远精确还原
用一句话总结整个流程
- 看浮点最大最小
- 算出 scale 把区间缩放到 INT8
- 算出 zero_point 保证 0 不偏移
- 浮点 ÷ scale + zero_point → 整数
- 推理完再 (整数 − zero_point) × scale → 还原浮点
这就是模型量化的完整数学过程。