第4章:量化基础——从浮点到整数的“压缩艺术”

大家好,我是你们的老朋友。今天我们来聊聊量化。

量化这个词,听起来挺高大上的。说白了,就是把模型里的浮点数(FP16、FP32)变成整数(INT8、INT4)。为什么要这么干?因为整数运算快、省显存。你想想看,一个大模型动辄几十GB,不压缩一下,消费级显卡根本跑不动。

我个人习惯把量化比作“照片压缩”。原始照片是RAW格式,细节丰富但体积大。量化就是转成JPEG,体积小了,但画质会损失一点点。我们的目标就是:在能接受的画质下,把体积压到最小。

4.1 什么是量化?

量化的数学定义很简单:

Q = round( (x - zero_point) / scale )

其中:

  • x 是原始浮点值
  • scale 是缩放因子
  • zero_point 是零点偏移
  • Q 是量化后的整数值

反量化就是逆过程:

x_approx = Q * scale + zero_point

嗯,这里要注意:量化是有损的。round操作会丢失精度。我在项目中遇到过,有些模型对精度极其敏感,量化后直接崩了。所以量化不是万能的,需要选对方法。

核心思想:用更少的比特数表示数值范围,牺牲精度换取速度和存储。

4.2 对称量化 vs 非对称量化

这是量化里最基础的选择题。我刚开始做量化时,也纠结过到底用哪个。

4.2.1 对称量化

对称量化,zero_point固定为0。公式简化成:

Q = round( x / scale )

它的特点是:量化后的整数范围是对称的。比如INT8,范围是[-128, 127]。这意味着,如果你的浮点数值分布不对称(比如全是正数),那有一半的整数范围就浪费了。

优点:计算简单,硬件友好。很多GPU的Tensor Core对对称量化有优化。

缺点:如果数据分布不对称,量化精度会下降。

我的经验:对于权重(weights),我一般用对称量化。因为训练好的权重分布通常接近对称。但对于激活值(activations),尤其是经过ReLU后的值,全是非负的,这时候对称量化就不太合适了。

4.2.2 非对称量化

非对称量化,zero_point可以不为0。公式就是最原始的那个:

Q = round( (x - zero_point) / scale )

它的好处是:可以充分利用整数范围。比如你的数据范围是[0.0, 10.0],非对称量化可以把整个INT8范围[-128, 127]都映射过去,精度更高。

优点:对数据分布适应性强,精度损失更小。

缺点:计算稍微复杂一点,需要额外处理zero_point。

对比项 对称量化 非对称量化
zero_point 固定为0 可调整
计算复杂度
硬件支持 优秀(Tensor Core优化) 一般
适用场景 权重、对称分布数据 激活值、非对称分布数据

避坑指南:我曾经在一个项目里,对激活值用了对称量化,结果模型精度掉了5个点。后来换成非对称量化,精度只掉了0.5个点。所以,别偷懒,该用非对称就用非对称。

4.3 量化粒度:per-tensor, per-channel, per-group

量化粒度,说白了就是“量化参数(scale和zero_point)共享的范围”。粒度越细,精度越高,但计算开销也越大。

4.3.1 Per-tensor 量化

整个张量(tensor)共享一组scale和zero_point。这是最简单的量化方式。

# 伪代码示例
scale = max(abs(tensor)) / 127
quantized_tensor = round(tensor / scale)

优点:实现简单,计算量小。

缺点:如果张量内部数值分布差异大,精度损失严重。

举个例子:一个卷积层的权重,有些通道数值大,有些通道数值小。用per-tensor量化,小数值通道的细节会被“淹没”在量化噪声里。

4.3.2 Per-channel 量化

每个通道(channel)有自己的scale和zero_point。这是目前最常用的量化粒度。

# 伪代码示例
for c in range(num_channels):
    scale[c] = max(abs(tensor[c])) / 127
    quantized_tensor[c] = round(tensor[c] / scale[c])

优点:精度比per-tensor好很多,尤其适合卷积层。

缺点:计算量增加,但通常可以接受。

我的建议:对于权重,我习惯用per-channel量化。对于激活值,如果模型不大,也可以用per-channel。但大模型里,激活值用per-tensor更常见,因为计算量太大。

4.3.3 Per-group 量化

这是最近比较火的一种粒度。把通道分成若干组,每组共享量化参数。它是per-tensor和per-channel的折中方案。

# 伪代码示例
group_size = 128  # 每组128个元素
for g in range(0, tensor.numel(), group_size):
    group = tensor.flatten()[g:g+group_size]
    scale = max(abs(group)) / 127
    quantized_group = round(group / scale)

优点:精度接近per-channel,但计算量更小。

缺点:实现稍微复杂,需要处理分组逻辑。

为什么per-group会火?因为大模型里,权重矩阵动辄几千行几千列。per-channel要算几千个scale,per-tensor只算1个。per-group算几十个,精度和效率的平衡点找得比较好。

粒度 精度 计算开销 适用场景
Per-tensor 小模型、激活值
Per-channel 权重、中等模型
Per-group 中高 中低 大模型、LLM

4.4 量化知识体系总览

为了让大家更直观地理解,我画了一张图:

量化知识体系总览 量化 (Quantization) 对称量化 vs 非对称量化 对称量化 zero_point=0 适合权重 非对称量化 zero_point可调 适合激活值 量化粒度 Per-tensor 整个张量共享参数 Per-channel 每个通道独立参数 Per-group 每组共享参数 精度:Per-channel > Per-group > Per-tensor 速度:Per-tensor > Per-group > Per-channel

4.5 实战中的选择建议

说了这么多理论,到底怎么选?我给大家一个实战清单:

  1. 小模型(<1B参数):权重用per-channel对称量化,激活值用per-tensor非对称量化。
  2. 中等模型(1B-7B):权重用per-channel对称量化,激活值用per-channel非对称量化(如果显存够)。
  3. 大模型(>7B):权重用per-group对称量化(group_size=128),激活值用per-tensor非对称量化。

一个小技巧:如果你不确定选什么粒度,先从per-channel开始。精度够就留着,不够就换per-group或per-tensor。我一般这么试,省时间。

好了,这一章的内容就到这里。量化是模型部署的基石,理解清楚对称/非对称、per-tensor/per-channel/per-group的区别,后面学TensorRT-LLM的量化工具时会轻松很多。

专注资料整理