量化基础:浮点数与定点数、量化误差、均匀量化与非均匀量化
量化,说白了就是把连续的值掰成离散的台阶。我第一次在嵌入式设备上跑量化模型时,以为这只是个简单的精度取舍,结果被功耗和性能的现实狠狠教训了一顿。嗯,从那天起,这块内容我就没敢再跳过。
为什么要量化?
深度学习模型训练时,我们通常用单精度浮点(FP32)。每个参数占 32 位。一个 GPT-3 级别的模型,光存参数就得几百 GB 显存,这还没算中间激活值。
- 模型体积:FP32 的参数存成 INT8,体积直接缩到四分之一。
- 推理速度:整数运算比浮点运算快得多,尤其在 GPU 张量核心和移动端 NPU 上。
- 能耗控制:整数乘加器比浮点乘加器省电。数据中心里,每一瓦电都算成本。
量化就是用一点点精度损失,换取推理效率的大幅提升。
浮点数与定点数
这两个概念,是我在调校第一个量化模型时花时间最多的部分。
浮点数,比如 FP32,由三部分组成:符号位、指数、尾数。它能表示从 1.4e-45 到 3.4e38 这么大的范围,但每个区间内能表示的数值密度不同。靠近零的地方,表示得很密;离零越远,表示得越稀疏。
定点数,比如 INT8,就简单多了。它只有一个符号位和一个整数位。数值范围固定(比如 -128 到 127),步长是固定的 1。它不会浪费位数在指数上,所以同等位宽下,定点数的存储效率和计算效率都更高。
我建议这样理解:浮点数是“可伸缩的尺子”——精度随距离变化;定点数是“固定刻度的尺子”——精度在全范围一致。
| 特性 | FP32 | INT8 |
|---|---|---|
| 位宽 | 32 | 8 |
| 数值分布 | 非均匀,靠近零更密 | 均匀 |
| 资源消耗 | 高(乘法器面积大) | 低(乘法器面积小) |
| 推理延迟 | 高 | 低 |
| 在 K-quant 中 | 主要用于校准参考 | 最常见的量化目标 |
量化误差
量化一定会引入误差。误差的来源主要有两个:
- 截断误差:浮点数超出定点数表示范围的部分会被截断。比如你想用 INT8 表示一个 300 的数,它就超出了 -128 到 127 的范围,结果直接饱和成 127。这在数学上叫 clipping。
- 舍入误差:浮点数落在两个整数台阶之间的部分,被近似到最近的整数值。同样的数值范围,台阶越少,舍入误差越大。
量化误差怎么管理?很简单,缩短台阶间距——要么拓宽表示范围,要么增加台阶数量。但位宽是固定的,所以关键就在于怎么把有限的台阶放在最有价值的位置上。
均匀量化与非均匀量化
这两种方式,在 K-quant 框架里我都会结合着用。它们没有绝对的好坏,关键看数据。
均匀量化:台阶在整个范围上等间距排列。INT8 就是典型的均匀量化。实现起来最快,GPU 张量核心对 INT8 也最友好。但问题是,如果数据分布很集中,中间区域分辨率就不够;如果数据分布很稀疏,两端区域又浪费台阶。
非均匀量化:台阶不等距。在数据密集区用小台阶,在数据稀疏区用大台阶。举个常见做法:对数量化。用对数把大值压缩,小值拉伸。这样既能处理很宽的动态范围,又能保证小值区域的精度。在 K-quant 中,我经常对权重用均匀量化,对激活值用非均匀量化,因为激活值往往有长尾。
| 维度 | 均匀量化 | 非均匀量化 |
|---|---|---|
| 实现难度 | 低(硬件支持好) | 高(查表或分段计算) |
| 分布适应性 | 弱 | 强 |
| 典型场景 | 权重、卷积核 | 激活值、Embedding |
| 精度保留 | 数据均匀时最好 | 数据长尾或尖峰时更好 |
| K-quant 倾向 | 默认对称量化 | 动态位宽分配 |
说到非均匀量化,还有一个常见思路叫“二进制编码”或“权重共享”。你想想看,如果权重的分布呈现多个峰值,每个峰值附近用一组相同的量化值去表示,那就相当于对不同的峰值区间做了不同的均匀量化。这样做,本质上也是非均匀的。在 K-quant 框架里,这种灵活性非常重要。
下面的图把这个量化过程的核心逻辑做了整理:
从这张图你能看到,量化的核心不是简单截断,而是一整套映射、编码、解码的流程。K-quant 框架里的很多参数调优,都围绕着这幅图的每一步展开:
- 要不要做异常值裁剪?裁多大比例?
- 映射是线性还是对数?零点偏移如何设置?
- 编码时选均匀还是非均匀?非均匀的量化区间怎么划分?