3、量化基础:定点数与浮点数,量化误差与精度损失
聊量化之前,咱们得先把最底层的数学工具搞清楚。说白了,量化就是把连续的数值映射到离散的集合上。你想想看,计算机里本来就只有0和1,浮点数也好,定点数也罢,本质上都是二进制的一种编码方式。但为什么量化的时候我们偏偏要聊定点数?因为GPU和NPU的硬件加速器,它们最擅长的就是定点运算。
3.1 浮点数:精度高,但代价也高
浮点数大家都很熟了。IEEE 754标准定义了单精度(float32)和双精度(float64)。它的结构是:符号位 + 指数位 + 尾数位。举个例子,float32的1位符号、8位指数、23位尾数。
浮点数的好处是动态范围大。它能表示从1.4e-45到3.4e38这么宽的范围。但代价呢?计算单元面积大、功耗高。我在项目中遇到过,一个float32乘法器占用的硅面积,大概是int8乘法器的10倍以上。你想想看,大模型动辄几十亿参数,全用float32推理,硬件成本根本扛不住。
核心问题:浮点数的精度分布是不均匀的。数值越靠近0,精度越高;数值越大,相邻两个可表示数的间隔越大。这就是为什么大数加小数,小数可能直接被“吃掉”。
3.2 定点数:硬件的最爱
定点数就简单多了。它没有指数位,只有符号位和数值位。比如int8,就是1位符号 + 7位数值,范围是[-128, 127]。定点数的精度是均匀的,相邻两个数的间隔永远是1。
但定点数的问题也很明显:动态范围太小。int8只能表示256个不同的值。如果原始浮点数的范围是[-10.0, 10.0],那量化到int8后,每个步长就是20.0/255 ≈ 0.078。这个精度够不够?得看具体场景。
我习惯把定点数理解成“带刻度的尺子”。尺子的总长度是固定的(动态范围),刻度数量也是固定的(量化位数)。刻度越密,精度越高,但能测量的总范围就越小。这就是量化的根本矛盾。
3.3 量化误差:从哪来,到哪去?
量化误差说白了就是“舍入误差”。你把一个浮点数映射到最近的定点数上,这个映射过程一定会丢精度。误差来源主要有三个:
- 截断误差:直接砍掉低位。比如1.9量化到int8,如果取整就是1,误差0.9。
- 舍入误差:四舍五入。1.9变成2,误差0.1。比截断好一点,但也不是零误差。
- 溢出误差:数值超出定点数范围。比如128.5量化到int8,直接变成127,误差1.5。这是最严重的误差。
注意:溢出误差是灾难性的。我曾经在量化一个BERT模型时,没注意一个中间层的激活值范围,结果量化后推理结果完全不对。排查了半天才发现是某个层的最大值超过了127,所有大于127的值都被截断成了127。这就是典型的溢出问题。
3.4 量化误差的数学表达
咱们用数学语言描述一下。假设原始浮点数为x,量化后的定点数为q,量化步长为Δ,那么:
q = round(x / Δ)
x_hat = q * Δ
量化误差 = |x - x_hat|
这个误差的上限是Δ/2。为什么?因为四舍五入的最大偏差就是半个步长。所以步长越小,误差上限越小。但步长小意味着动态范围小,又容易溢出。这就是个trade-off。
我建议你在做量化时,先统计一下模型参数的分布。看看最大值、最小值、标准差。然后根据这些统计量来选择合适的量化参数。比如,如果参数集中在[-1.0, 1.0]之间,那量化到int8时,步长就是2.0/255 ≈ 0.0078,精度损失很小。但如果参数范围是[-100, 100],同样的int8量化,步长就变成了200/255 ≈ 0.78,精度损失就大了。
3.5 量化误差的传播
单个量化操作的误差可能不大,但神经网络里动辄几十层、上百层。每一层都做量化,误差会累积。我见过一个案例,一个50层的ResNet,每层量化误差0.1%,50层下来,累积误差可能达到5%以上。这会导致最终的分类准确率下降好几个百分点。
怎么应对?有两个思路:
- 逐层校准:每一层都单独统计输入输出分布,单独选择量化参数。这样每层的误差都能控制在最小。
- 量化感知训练:在训练过程中就模拟量化误差,让模型自己学会适应。这是目前最主流的方法。
小技巧:如果你在做后训练量化(PTQ),建议先用少量校准数据跑一遍推理,收集每一层的激活值分布。然后根据分布选择量化参数。我习惯用KL散度来寻找最优的量化阈值,效果比直接取min/max要好。
3.6 知识体系图
下面这张图总结了本章的核心逻辑:从浮点数到定点数的映射过程,以及误差的来源和传播路径。
3.7 实战建议
最后给几个实操建议:
- 先做分布分析:量化前一定要跑一遍校准数据,收集每层的权重和激活值分布。我习惯用Python的numpy.histogram来统计,一目了然。
- 选择合适的量化粒度:逐层量化比全局量化效果好,逐通道量化比逐层量化效果好。但粒度越细,计算量越大。需要权衡。
- 关注异常值:如果分布中有明显的离群点,建议先做裁剪(clipping),把异常值限制在合理范围内。否则量化步长会被这些离群点拉大,导致整体精度下降。
- 验证误差:量化后一定要跑一遍完整的验证集,对比量化前后的准确率。如果下降超过1%,说明量化参数需要调整。
核心总结:量化就是一场“精度”和“效率”的博弈。定点数用有限的位数去近似浮点数,必然有误差。但只要我们理解误差的来源和传播规律,就能通过合理的量化策略把误差控制在可接受范围内。GGUF源码里大量使用了对称量化和非对称量化,本质上都是在解决这个博弈问题。