文章目录
- 说明
- [一 大模型量化与蒸馏技术](#一 大模型量化与蒸馏技术)
-
- [1.1 大模型量化技术](#1.1 大模型量化技术)
- [1.2 适用场景](#1.2 适用场景)
- [二 量化位宽](#二 量化位宽)
-
- [2.1 常见量化位宽](#2.1 常见量化位宽)
- [2.2 区别和用途](#2.2 区别和用途)
- [三 线性量化原理](#三 线性量化原理)
-
- [3.1 线性量化公式](#3.1 线性量化公式)
- [3.2 线性量化原理](#3.2 线性量化原理)
- [3.3 不同量化方式距离](#3.3 不同量化方式距离)
-
- [3.3.1 对称量化(权重量化)](#3.3.1 对称量化(权重量化))
- [3.3.2 非对称量化(激活值量化)](#3.3.2 非对称量化(激活值量化))
- [3.3.3 带负值的非对称量化](#3.3.3 带负值的非对称量化)
- [3.4 量化误差和缓解办法](#3.4 量化误差和缓解办法)
- [3.5 量化因子与零点](#3.5 量化因子与零点)
- [四 量化粒度](#四 量化粒度)
-
- [4.1 三种量化粒度](#4.1 三种量化粒度)
- [4.2 不同量化粒度的差异](#4.2 不同量化粒度的差异)
- [4.3 量化粒度与量化参数的关系](#4.3 量化粒度与量化参数的关系)
- [4.4 应用场景建议](#4.4 应用场景建议)
- [五 量化作用位置](#五 量化作用位置)
-
- [5.1 激活值量化与权重量化](#5.1 激活值量化与权重量化)
- [5.2 不同类型层的量化特点](#5.2 不同类型层的量化特点)
- [5.3 量化层参考](#5.3 量化层参考)
- [六 模型量化方法](#六 模型量化方法)
-
- [6.1 混合精度量化](#6.1 混合精度量化)
- [6.2 量化感知训练](#6.2 量化感知训练)
-
- [6.2.1 量化感知训练的操作流程](#6.2.1 量化感知训练的操作流程)
- [6.2.2 量化感知训练的本质](#6.2.2 量化感知训练的本质)
- [七 量化位置及适用场景](#七 量化位置及适用场景)
-
- [7.1 主要量化位置对比](#7.1 主要量化位置对比)
- [7.2 详细比较与适用场景](#7.2 详细比较与适用场景)
- [7.3 量化方法选择和部署流程](#7.3 量化方法选择和部署流程)
- [7.4 支持静态量化的框架](#7.4 支持静态量化的框架)
说明
- 本文内容学自网络资料,仅供学习和交流使用,最终著作权归原作者所有。
一 大模型量化与蒸馏技术
1.1 大模型量化技术
- 大模型量化技术是将大模型中的高精度浮点数(通常为FP32或FP16)转换为低精度数值表示(如INT8、INT4等)的过程,旨在减少模型的计算和存储需求,同时尽可能保持模型性能。
- 静态量化是一种强大的模型压缩技术,尤其适合大模型在资源受限环境下的部署。它通过降低数值精度减小模型体积并提升推理速度,与其他模型压缩技术相比,实现简单且不改变模型结构,但可能带来一定的精度损失。在实际应用中,常将量化与其他压缩技术结合使用,以达到最佳性能与效率平衡。
- 举例:原来模型中的权重值如0.7352可能被转换成整数7,配合一个缩放因子0.1,在使用时再还原为0.7。
量化的主要优势包括:
- 模型体积减小:INT8比FP32节省75%存储空间,INT4可节省87.5%
- 推理速度提升:低精度运算通常更快,许多硬件有专门的低精度加速
- 内存占用降低:减少运行时内存需求,使大模型能在受限设备上运行
- 能耗降低:低精度计算通常能耗更低,对移动设备和边缘设备尤为重要
- 吞吐量提升:可以在同样硬件上处理更多并发请求
1.2 适用场景
静态量化特别适用于以下场景:
- 资源受限环境:边缘设备、移动设备、消费级GPU,手机、平板等内存有限的设备等。
- 高吞吐量需求:需要处理大量并发请求的服务
- 延迟敏感应用:需要快速响应的实时系统
- 批量推理场景:预测任务批量处理
- 部署环境固定:应用场景和数据分布相对稳定
- 量化技术优缺点
| 优点 | 缺点 |
|---|---|
| 实现相对简单,大多数框架直接支持 | 精度损失,尤其在高压缩比(如INT4)下 |
| 不改变模型结构,易于集成到现有系统 | 对某些任务(如长文本生成)影响较大 |
| 显著减小模型体积(4-8倍) | 可能需要特定优化才能保持性能 |
| 硬件加速支持广泛(特别是INT8) | 不同于蒸馏,不减少模型结构复杂度 |
| 部署友好,无需专门训练 |
- 与其他模型压缩技术的对比
| 技术 | 原理 | 优势 | 劣势 | 与量化的互补性 |
|---|---|---|---|---|
| 量化 | 降低数值精度 | 直接实现、性能提升明显、适用大多数模型 | 精度损失、可能需要微调 | - |
| 知识蒸馏 | 小模型学习大模型行为 | 模型体积和结构简化、适应性强 | 需要重新训练、蒸馏过程复杂 | 可先蒸馏后量化,双重压缩 |
| 剪枝 | 移除不重要连接/神经元 | 减少计算路径、保留关键结构 | 需专门硬件/软件支持才能发挥优势、训练复杂 | 常与量化结合使用 |
| 参数共享 | 多个权重使用同一参数 | 直接减少独立参数数量 | 可能显著降低模型表达能力 | 能与量化协同工作 |
二 量化位宽
- 位宽就是用多少个二进制位来存储一个数字。位数越多,能表示的数字范围越大,精度越高,但占用的空间也越大。
2.1 常见量化位宽
- FP32(全精度,32位浮点数):原始模型通常使用这种精度,一个数字占用4个字节,计算又慢又费电,精度最高,几乎不会有误差。
- FP16(半精度,16位浮点数):占用空间是FP32的一半,速度大约是FP32的1.5-2倍,大多数情况下,模型效果几乎和FP32一样,现代GPU都支持这种精度。
- INT8(8位整数):只占FP32的四分之一空间,速度约是FP32的3-4倍,只能表示-128到127的整数,需要用缩放因子来转换。
- INT4(4位整数):只占FP32的八分之一空间,速度更快,但精度损失明显,只能表示0-15的范围,应用较为广泛。
- INT2/INT1(2位/1位整数):INT2只能表示0-3四个数,INT1只能表示0或1两个状态,空间最省,但精度损失最大,应用非常有限。
2.2 区别和用途
- FP32(100%空间,原始精度,最准确) > FP1(50%空间,轻微损失,几乎可忽略)> INT8(25%空间,有损失但大多数任务可接受) > INT4(12.5%空间,明显损失,需要额外技术保持效果) > INT2 > INT1(严重损失,只适合特定任务)
- 使用建议 :
- 普通应用:INT8是最佳选择。
- 资源极其有限:考虑INT4。
- 对精度要求高:使用FP16。
- 大型模型部署:INT8或混合精度。
三 线性量化原理
3.1 线性量化公式
- 量化公式(浮点数→整数)
json
q = round((x / scale) + zero_point)
- 反量化公式(整数→浮点数)
json
x' = (q - zero_point) * scale
其中:
-
x:原始浮点数
-
q:量化后的整数
-
scale:缩放因子
-
zero_point:零点偏移值
-
x':反量化后的浮点数(近似值)
-
缩放因子(scale)计算
json
scale = (x_max - x_min) / (q_max - q_min)
- 零点偏移值(zero_point)计算
json
zero_point = round(q_min - x_min / scale)
- 或者对于对称量化
json
zero_point = 0
3.2 线性量化原理
- 线性量化的核心思想是用线性映射关系将连续的浮点数空间映射到离散的整数空间:
- 确定映射范围 :找出浮点数据的最小值
x_min和最大值x_max,确定目标整数类型的范围(如INT8为-128到127)。 - 建立线性映射 :用直线方程
y = ax + b建立映射,其中a是缩放因子(scale),b是零点偏移。 - 执行量化:对每个浮点数应用公式,转为整数,使用四舍五入处理小数部分。
- 执行计算:用整数进行计算(如矩阵乘法),计算完成后反量化回浮点数。## 量化误差的产生和影响
3.3 不同量化方式距离
3.3.1 对称量化(权重量化)
- 假设我们有一组权重数据:[-3, -1.5, 0, 2, 2.5],要量化为INT8(-128到127)。
第1步:找出数据范围:最小值 = -3、最大值 = 2.5、最大绝对值 = 3。
第2步:计算scale:scale = 最大绝对值/127 = 3/127 = 0.0236。
第3步:确定zero_point:对称量化的zero_point = 0。
第4步:进行量化
- -3 量化为: round(-3/0.0236 + 0) = -127
- -1.5 量化为: round(-1.5/0.0236 + 0) = -64
- 0 量化为: round(0/0.0236 + 0) = 0
- 2 量化为: round(2/0.0236 + 0) = 85
- 2.5 量化为: round(2.5/0.0236 + 0) = 106
第5步:验证反量化
- -127 反量化为: (-127 - 0) * 0.0236 = -3.0
- -64 反量化为: (-64 - 0) * 0.0236 = -1.51
- 0 反量化为: (0 - 0) * 0.0236 = 0
- 85 反量化为: (85 - 0) * 0.0236 = 2.01
- 106 反量化为: (106 - 0) * 0.0236 = 2.50
3.3.2 非对称量化(激活值量化)
假设我们有激活值:[0, 1.2, 3.5, 4.8, 5.2],要量化为UINT8(0到255)。
第1步:找出数据范围:最小值 = 0,最大值 = 5.2
第2步:计算scale:scale = (最大值 - 最小值)/255 = 5.2/255 = 0.0204
第3步:确定zero_point:zero_point = round(-最小值/scale) = round(-0/0.0204) = 0
第4步:进行量化
- 0 量化为: round(0/0.0204 + 0) = 0
- 1.2 量化为: round(1.2/0.0204 + 0) = 59
- 3.5 量化为: round(3.5/0.0204 + 0) = 172
- 4.8 量化为: round(4.8/0.0204 + 0) = 235
- 5.2 量化为: round(5.2/0.0204 + 0) = 255
第5步:验证反量化
- 0 反量化为: (0 - 0) * 0.0204 = 0
- 59 反量化为: (59 - 0) * 0.0204 = 1.20
- 172 反量化为: (172 - 0) * 0.0204 = 3.51
- 235 反量化为: (235 - 0) * 0.0204 = 4.79
- 255 反量化为: (255 - 0) * 0.0204 = 5.20
3.3.3 带负值的非对称量化
- 假设我们有数据:[-2.1, -0.5, 0, 3.2, 4.7],要量化为INT8(-128到127)。
第1步:找出数据范围:最小值 = -2.1,最大值 = 4.7。
第2步:计算scale:scale = (最大值 - 最小值)/255 = 6.8/255 = 0.0267
第3步:确定zero_point:zero_point = round(-最小值/scale - 128) = round(2.1/0.0267 - 128) = round(78.7 - 128) = -49
第4步:进行量化
- -2.1 量化为: round(-2.1/0.0267 + (-49)) = round(-78.7 - 49) = -128
- -0.5 量化为: round(-0.5/0.0267 + (-49)) = round(-18.7 - 49) = -68
- 0 量化为: round(0/0.0267 + (-49)) = -49
- 3.2 量化为: round(3.2/0.0267 + (-49)) = round(119.9 - 49) = 71
- 4.7 量化为: round(4.7/0.0267 + (-49)) = round(176.0 - 49) = 127
第5步:验证反量化
- -128 反量化为: (-128 - (-49)) * 0.0267 = -2.11
- -68 反量化为: (-68 - (-49)) * 0.0267 = -0.51
- -49 反量化为: (-49 - (-49)) * 0.0267 = 0
- 71 反量化为: (71 - (-49)) * 0.0267 = 3.21
- 127 反量化为: (127 - (-49)) * 0.0267 = 4.70
3.4 量化误差和缓解办法
线性量化会产生以下几种误差:
- 舍入误差:浮点数转整数时的四舍五入。
x/scale可能有小数部分,round后丢失精度,如1.53/0.5 = 3.06,四舍五入为3,反量化后变为1.5,误差0.03。 - 表示范围误差:整数位宽有限,无法表示范围外的值。超出范围的值被截断到边界值,如INT8只能表示-128到127,更大或更小的值会被钳位。
- 分布不均匀误差:线性量化等距分配量化级别。对数值分布不均的数据,稀疏区域精度过高,密集区域精度不足。如如果大部分值集中在[-0.1,0.1],但极少数值达到[-10,10],会导致[-0.1,0.1]区间分配很少的量化级别
- 传播误差:多层网络中误差累积。前面层的量化误差会影响后续计算,误差可能放大,如微小的权重误差经过多层计算可能导致最终结果显著偏离。
- 误差会导致模型精度下降,对不同任务影响不同,对分类影响较小,对生成任务和回归任务影响较大。
减轻量化误差的方法
- 校准优化:使用代表性数据校准量化参数,采用百分位数而非极值确定范围。
- 感知训练:量化感知训练(QAT)在训练中模拟量化误差,使模型参数适应量化。
- 混合精度:敏感层保持高精度,不敏感层使用低精度。
- 特殊处理:LayerNorm等层特殊处理,使用更复杂的量化方案(如非线性量化)。
3.5 量化因子与零点
-
神经网络中"0"是一个特殊值,经常出现在ReLU激活后的负值区域,同时注意力掩码(0表示忽略)。
-
没有零点情况:假设数据范围[-2.1, 4.7]直接映射到[-128, 127]:
- scale = 6.8/255 = 0.0267
- 0值量化为: round(0/0.0267) = 0
- 反量化为: 0 * 0.0267 = 0(看似正确)
- 但约-0.013也会量化为0,无法区分
-
有零点情况:zero_point = -49
- 0值量化为: round(0/0.0267 + (-49)) = -49
- 反量化为: (-49 - (-49)) * 0.0267 = 0(精确)
- -2.1值量化为-128,反量化为约-2.11
- 没有信息丢失
- 零点帮助充分利用整数范围 :对于偏向一侧的数据分布(如都是正值的输出),零点可以帮助我们利用整个INT8范围。如数据范围为[0, 5.2]
- 没有zero_point,只能映射到[0, 127],浪费了[-128, -1]的表示空间
- 有zero_point,可以映射到[-128, 127],表示精度提高一倍
- 零点使不对称数据可以有效量化:对于范围[-2, 8]这样不对称的数据,对称量化会浪费表示空间,因为负数范围用不完。非对称量化利用zero_point,可以充分利用整个整数范围
四 量化粒度
- 量化粒度指的是确定量化参数(scale和zero_point)时所考虑的数据范围大小。主要有三种粒度:
- 按张量量化(Per-Tensor Quantization)
- 按通道量化(Per-Channel Quantization)
- 按组量化(Per-Group Quantization)
4.1 三种量化粒度
- 按张量量化
- 张量(Tensor)是深度学习中的基本数据结构,可以理解为多维数组。根据维度不同,张量有不同的形式:
- 0维张量 :标量(scalar),如单个数字
5- 1维张量 :向量(vector),如一组数字
[1, 2, 3, 4]- 2维张量 :矩阵(matrix),如表格数据
[[1,2], [3,4]]- 3维张量 :如彩色图像(高度×宽度×通道)
- 4维及以上:更高维数据结构
- 在神经网络中,各种数据都以张量形式存在:输入数据、权重参数、激活值、梯度。
- 矩阵维度表示方法:当提到权重矩阵 W[64, 32] 时,这表示一个二维矩阵:64表示输出特征数量 ,32表示输入特征数量,权重矩阵连接具有32个特征的输入层和具有64个特征的输出层。
- 权重矩阵用于将输入映射到输出: 输出 = 权重矩阵 × 输入 输出 = 权重矩阵 × 输入 输出=权重矩阵×输入
- 基本原理:为整个张量(如一个权重矩阵)使用相同的scale和zero_point,整个张量中的所有元素共享这两个量化参数。
- 假设有权重矩阵W形状为[64, 32](64个输出通道,32个输入通道)
json
W的值范围 = [-1.8, 2.1]
scale = 3.9/255 ≈ 0.0153
zero_point = 0(对称量化)
- 矩阵中的每个元素都用这一组参数量化。
- 按通道量化
-
基本原理:为每个输出通道使用独立的scale和zero_point,同一通道内的元素共享参数,不同通道有不同参数。
-
权重矩阵W[64, 32],总共有64组不同的量化参数,每个输出通道一组。
bash
第1通道范围 = [-0.3, 0.5],scale_1 = 0.8/255 ≈ 0.0031,zero_point_1 = 0
第2通道范围 = [-1.8, 0.2],scale_2 = 2.0/255 ≈ 0.0078,zero_point_2 = 0
...
第64通道范围 = [-0.1, 2.1],scale_64 = 2.2/255 ≈ 0.0086,zero_point_64 = 0
- 按组量化
- 基本原理:将通道分成若干组,每组共享量化参数,按张量和按通道之间的折中方案。
- 将权重矩阵W[64, 32]分成8组,每组8个通道,总共有8组不同的量化参数。
bash
组1(通道1-8):范围 = [-1.2, 1.5],scale_g1 = 2.7/255 ≈ 0.0106,zero_point_g1 = 0
组2(通道9-16):范围 = [-0.8, 1.1],scale_g2 = 1.9/255 ≈ 0.0075,zero_point_g2 = 0
...
组8(通道57-64):范围 = [-1.5, 2.1],scale_g8 = 3.6/255 ≈ 0.0141,zero_point_g8 = 0
4.2 不同量化粒度的差异
| 量化粒度 | 精度差异 | 参数数量差异 (以 W[64, 32] 为例) | 硬件友好性差异 |
|---|---|---|---|
| 按张量量化 | 精度最低。只能考虑整个张量的全局范围,如果数据分布不均匀,容易造成大量量化误差。 | 1组参数 (1个scale, 1个zero_point) | - 硬件实现最简单 - 几乎所有硬件都支持 - 计算效率最高 |
| 按组量化 | 精度介于两者之间(平衡精度和复杂度) | 8组参数 (8个scale, 8个zero_point) | - 硬件复杂性适中 - 支持度介于两者之间 |
| 按通道量化 | 精度最高。考虑每个通道的局部分布,特别适合不同通道数值范围差异大的情况。 | 64组参数 (64个scale, 64个zero_point) | - 实现更复杂 - 部分硬件加速器不支持 - 可能会增加内存访问开销 |
4.3 量化粒度与量化参数的关系
- 量化公式保持不变:
json
q = round(x/scale + zero_point)
不同的是:
- 按张量量化:整个张量共用一组scale和zero_point。
- 按组量化:每组通道共享一组scale和zero_point。
- 按通道量化:每个通道有自己的scale和zero_point。
- 假设有一个权重矩阵,其中部分通道数值较小([-0.1, 0.1]范围),部分通道数值较大([-2.0, 2.0]范围):
按张量量化:
- 整体范围[-2.0, 2.0]
- scale = 4.0/255 ≈ 0.0157
- 小数值通道中,如0.05会量化为round(0.05/0.0157) = 3
- 反量化后为3 * 0.0157 = 0.0471(误差很大)
按通道量化:
- 小数值通道范围[-0.1, 0.1]
- 小数值通道scale = 0.2/255 ≈ 0.0008
- 同样的0.05会量化为round(0.05/0.0008) = 63
- 反量化后为63 * 0.0008 = 0.0504(误差小得多)
4.4 应用场景建议
-
按张量量化适合:内存和计算资源极度受限的环境,数据分布比较均匀的张量,计算速度优先于精度的场景。
-
按通道量化适合:卷积神经网络的权重,通道间数值分布差异大的情况,可接受额外计算开销但需要高精度的场景。
-
按组量化适合:在按张量和按通道之间寻求平衡的场景,硬件或内存有一定限制但又需要较好精度的情况。
在大语言模型中:
- 权重通常使用按通道量化(精度更高)
- 激活值通常使用按张量量化(更简单,动态范围)
- 特别敏感的层(如最后的投影层)可能使用更高精度或更细粒度的量化
五 量化作用位置
- 激活值 是神经网络层的输出,也是下一层的输入。它们是模型推理过程中的中间结果。例如:对于全连接层:
z = R e L U ( W ⋅ x + b ) x 是输入, W 是权重, b 是偏置, y 是激活值 z = ReLU(W·x + b) \\ x是输入,W是权重,b是偏置,y是激活值 z=ReLU(W⋅x+b)x是输入,W是权重,b是偏置,y是激活值
5.1 激活值量化与权重量化
| 分类 | 权重 | 激活值 |
|---|---|---|
| 数据分布特点不同 | - 通常呈对称分布(中心在0附近) - 范围相对稳定 - 在训练完成后是固定的 | - 通常分布不对称(如ReLU后全为正值) - 范围可能变化很大 - 在推理过程中随输入变化 |
| 量化方式不同 | - 通常使用对称量化(zero_point=0) - 适合使用按通道量化 - 量化参数在模型部署前确定 | - 通常使用非对称量化(zero_point≠0) - 通常使用按张量量化 - 可能需要动态或静态范围 |
| 精度敏感度不同 | - 模型对权重精度通常更敏感 - 低位量化(如INT4)可能造成明显精度损失 | - 对某些层(如中间层)精度要求较低 - 对输入输出层精度要求高 |
5.2 不同类型层的量化特点
| 层类型 | 特点 | 量化适用性 | 建议 |
|---|---|---|---|
| 线性层/全连接层 | - 计算密集(权重×输入),参数量大,对称分布的权重 | - 非常适合量化,权重通常可用INT8甚至INT4,激活值通常用INT8 | - 使用按通道量化权重,使用非对称量化激活值 |
| 卷积层 | - 与线性层类似,但有空间局部性,计算密集,权重共享 | - 非常适合量化,量化效果通常优于线性层,计算加速明显 | - 使用按通道量化权重,使用按张量量化激活值 |
| LayerNorm层 | - 涉及均值、方差计算,包含除法运算,对数值范围敏感 | - 量化困难,精度损失大,是量化瓶颈层 | - 保持高精度(FP16),如必须量化,使用特殊优化方法,考虑INT16而非INT8 |
| Softmax层 | - 包含指数计算,数值范围大,归一化操作 | - 量化困难,浮点运算更合适,精度要求高 | - 保持FP16或FP32精度,可考虑用近似算法替代原始实现 |
| 注意力机制 | - 包含矩阵乘法和Softmax,长序列时计算量大,QKV投影层参数多 | - QKV投影适合量化,注意力分数计算敏感 | - 投影矩阵可用INT8,注意力计算考虑FP16 |
| 残差连接 | - 加法操作,连接不同层,防止梯度消失 | - 需要相同尺度,加法前需反量化或使用同样量化参数 | - 确保加法两边使用一致量化方法,可能需要额外重量化 |
| 输入输出层 | - 直接影响模型表现,处理原始数据或生成最终输出,精度要求高 | - 量化可能影响明显,输出层尤其敏感 | - 输入层可量化但需谨慎,输出层考虑高精度 |
5.3 量化层参考
- GPT/LLaMA模型中的层级结构
json
输入文本
↓
词嵌入层 (Embedding) ← 适合量化
↓
循环N次 Transformer块:
├── LayerNorm ← 不适合量化
├── 自注意力层
│ ├── QKV投影(线性层) ← 非常适合量化
│ ├── 注意力计算 ← 部分适合
│ └── 输出投影(线性层) ← 非常适合量化
├── LayerNorm ← 不适合量化
└── 前馈网络
├── 线性层1 ← 非常适合量化
├── GELU激活 ← 不太适合量化
└── 线性层2 ← 非常适合量化
↓
LayerNorm ← 不适合量化
↓
输出层(线性层) ← 较敏感,谨慎量化
↓
最终输出
六 模型量化方法
| 方法 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 整体量化法 | 将整个模型统一量化至同一精度(如INT8) | 实现简单,部署方便 | 容易导致敏感层精度损失 |
| 混合精度量化 | 不同层使用不同的量化精度 | 平衡性能和精度 | 实现略复杂,需要识别敏感层 |
| 量化感知训练(QAT) | 在训练过程中模拟量化效果,使模型适应量化误差 | 量化精度最高,损失最小 | 需要完整训练资源,耗时较长 |
| 量化后微调 | 先量化,再用少量数据微调恢复精度 | 比QAT更快,比直接量化更准确 | 仍需要训练资源和数据 |
| 专用量化算法 | 针对大模型的特殊量化算法,如GPTQ、AWQ、SmoothQuant等 | 专为大模型优化,性能好 | 实现复杂,通常需要特定框架 |
6.1 混合精度量化
- 如果选择混合精度量化(最常用且平衡的方法),以下是实施步骤:
- 分析模型结构,识别层类型
python
# 伪代码示例
for name, module in model.named_modules():
if isinstance(module, nn.Linear):
print(f"线性层: {name}")
elif "LayerNorm" in module.__class__.__name__:
print(f"LayerNorm层: {name}")
# 其他层类型...
- 确定各层量化精度:根据层的重要性和敏感度设置不同精度。
| 层类型 | 参考精度 | 原因 |
|---|---|---|
| 大多数线性层 | INT8 | 计算密集,适合量化 |
| 嵌入层 | INT8 | 查表操作,适合量化 |
| QKV投影层 | INT8 | 矩阵乘法,适合量化 |
| LayerNorm | FP16 | 数值敏感,不适合低精度 |
| 最终输出层 | FP16/INT8 | 直接影响结果质量 |
| 注意力计算 | FP16 | 含Softmax,精度敏感 |
- 实现混合精度量化。
python
# 定义量化配置
qconfig_dict = {
# 默认配置
"": torch.quantization.default_dynamic_qconfig,
# 对特定模块使用不同配置
"module_name": None, # 不量化的模块
}
# 准备模型
model_fp32 = load_model()
model_prepared = torch.quantization.prepare_dynamic(
model_fp32,
qconfig_dict,
)
# 校准(可选)
calibrate_model(model_prepared, calibration_data)
# 完成量化
model_quantized = torch.quantization.convert(model_prepared)
- 特殊层处理:对于LayerNorm等特殊层,可以用特殊方法。
python
# 示例:手动处理LayerNorm
for name, module in model.named_modules():
if "LayerNorm" in module.__class__.__name__:
# 将该模块转回FP16
with torch.no_grad():
module.weight = torch.nn.Parameter(module.weight.to(torch.float16))
module.bias = torch.nn.Parameter(module.bias.to(torch.float16))
6.2 量化感知训练
- 普通量化的问题:训练完成后得到精确模型,将精确数字转换为整数(量化),模型精度下降。这会导致一定的精度损失,对模型性能造成一定的影响。模型没有机会"适应"这种精度损失。
- 量化感知训练的核心思想是:在训练过程中就让模型体验并适应量化带来的精度损失。
6.2.1 量化感知训练的操作流程
- 伪量化操作:在训练过程中,插入量化和反量化操作,使模型在训练时就能"感受"到量化带来的误差
json
精确数字 → 整数(量化) → 再转回精确数字(反量化) → 继续计算
原始权重: 0.77
量化步骤: 0.77 → 10(整数) → 0.78(反量化后)
训练时使用: 0.78(而不是原始的0.77)
- 让梯度穿过量化操作:量化操作本身不可微分(无法求导),所以使用一个技巧让梯度能够传递。"直通估计器"(STE)
json
前向传播: y = 量化(x) → 反量化
反向传播: 假装量化操作不存在,梯度直接通过
- 训练循环的变化
普通训练:
python
# 前向传播
output = model(input)
loss = loss_function(output, target)
# 反向传播
loss.backward()
optimizer.step() # 更新权重
量化感知训练:
python
# 前向传播(插入伪量化)
for layer in model:
weights = layer.weight
# 伪量化
weights_q = quantize(weights) # 量化
weights_dq = dequantize(weights_q) # 反量化
output = compute_with(weights_dq, input) # 使用伪量化的权重
loss = loss_function(output, target)
# 反向传播(梯度穿过量化操作)
loss.backward()
optimizer.step() # 更新权重
假设有一个神经网络层,权重为 [0.41, -1.65, 0.28, 2.07]。量化感知训练中:
- 确定量化参数:范围[-1.65, 2.07],scale≈0.0147
- 量化:
- 0.41→28→0.412(量化后反量化)
- -1.65→-112→-1.646
- 0.28→19→0.279
- 2.07→141→2.073
- 使用量化后的值
[0.412, -1.646, 0.279, 2.073]进行前向计算 - 反向传播时,梯度照常传递
- 模型逐渐调整权重,使其在量化后仍能表现良好
- 模型"感受"到量化误差 :因为前向传播时使用的是量化后再反量化的值 (含有量化误差),而不是原始精确值:原始值 :0.7734;伪量化后使用的值 :0.77;量化误差 :0.0034(误差被模型"感受"到)。这相当于"提前体验"了量化带来的精度损失。感受到误差有意义
- 权重调整:模型可以调整其权重,使关键值落在"量化友好"的位置
- 误差补偿:模型可以学会在其他参数中补偿量化误差
- 鲁棒性增强:提高模型对量化噪声的容忍度
6.2.2 量化感知训练的本质
- 基本思想:训练时就模拟量化误差,让模型适应
- 核心机制:插入"伪量化"操作,让模型看到量化后的值,梯度仍能正常传递,模型权重逐渐调整为"量化友好"的值。
- 使用的量化算法: 仍然是线性量化,公式不变,只是训练过程变了,不是量化算法变了。
- 与普通量化区别 :
- 普通量化:训练→量化→精度下降
- 量化感知训练:训练时模拟量化→量化→精度保持
- 量化感知训练就像是"带着量化眼镜训练",让模型提前适应量化视角,因此在实际量化后能表现得更好。
七 量化位置及适用场景
- 量化可以在模型生命周期的不同阶段进行,每个位置有其特点和适用场景。下面详细分析各种量化位置的区别及适用场景。
7.1 主要量化位置对比
- 训练后量化(PTQ):完成训练后,部署前
json
预训练 → 微调 → [量化] → 部署
- 量化感知训练(QAT): 训练/微调过程中
json
预训练 → [量化感知微调] → 转换 → 部署
- 量化后微调(QFT): 先量化,再微调
json
预训练 → 微调 → 量化 → [微调] → 部署
- 训练阶段量化(TTQ/QT): 完整训练过程中
json
[低精度训练] → 导出量化模型 → 部署
7.2 详细比较与适用场景
| 方法 | 实施方式 | 优势 | 劣势 | 适用场景 | 实际应用例子 |
|---|---|---|---|---|---|
| 1. 训练后量化(PTQ) | - 在完全训练好的模型上直接应用量化,使用少量校准数据确定量化参数,不需要重新训练或微调 | - 实施简单快速, 计算资源需求低,无需训练数据或仅需少量校准数据,可以快速尝试不同量化配置 | - 精度损失可能较大,对某些模型结构效果不佳,控制精度损失的能力有限 | - 资源有限环境,时间紧迫场景,探索阶段,模型结构适合,通用模型部署 | - 将通用大模型(如BERT/GPT)快速量化部署到线上服务 - 将视觉模型量化到移动设备 - 快速评估模型在低精度下的表现 |
| 2. 量化感知训练(QAT) | - 在训练或微调过程中模拟量化操作,使用伪量化节点和直通估计器,模型学习补偿量化误差 | - 精度损失最小,模型能适应量化带来的精度影响,可以追求极限压缩比(如INT4/INT2) | - 需要完整训练/微调流程,计算资源需求高,训练时间长,实现复杂度高 | - 性能极其敏感,极限压缩需求,资源充足,复杂模型,特定领域优化 | - 将语音识别模型量化到4位精度而保持准确率 - 医疗诊断模型的高精度量化 - 自动驾驶感知模型的关键组件量化 |
| 3. 量化后微调(QFT) | - 先对模型进行量化,再使用少量数据微调已量化的模型,针对量化后的精度损失进行恢复 | - 比QAT资源需求低,比PTQ精度更高,可以针对特定任务优化,训练时间相对较短 | - 仍需要训练资源,需要适当的微调数据,实现稍复杂 | - 平衡场景,特定任务适应,有限数据场景,增量改善,部署后优化 | - 将量化后的大语言模型针对客服场景微调 - 量化视觉模型后,针对新环境条件微调 - 解决量化后模型在特定输入上的性能下降 |
| 4. 训练阶段量化(TTQ/QT) | - 从头开始就使用低精度训练,权重和激活值都使用低精度表示,设计特殊训练策略适应低精度 | - 训练和推理一致,训练过程也能节省资源,理论上量化误差最小,适合从头训练的场景 | - 实现技术要求高,训练不稳定风险,需要专门优化的训练流程,框架支持可能不完善 | - 从头训练,极端资源限制,专用硬件,研究场景,一致性要求 | - 在量化友好的专用硬件上训练模型 - 边缘AI系统的完整低精度训练流程 - 为资源受限环境设计的模型架构 |
7.3 量化方法选择和部署流程
结束 否 是 非常高 中等 相对宽松 不确定要求 是 否 是 否 结束 开始选择量化方法 是否有足够计算资源进行训练? 使用 PTQ训练后量化 精度要求有多高? 使用QAT量化感知训 是否有足够的任务特定数据? 使用 PTQ 使用 PTQ先测试效果 使用 QFT量化后微调 使用优化版 PTQ如 GPTQ/AWQ 是否从头开始训练模型? 考虑 TTQ/QT训练阶段量化 使用前面选择的方法
- 部署流程
量化模型准备 模型格式转换 推理引擎选择 部署环境适配 运行时验证 资源配置优化 服务封装 性能监控优化
7.4 支持静态量化的框架
| 框架 | 静态量化支持 | 位宽支持 | 使用难度 | 生态系统 |
|---|---|---|---|---|
| PyTorch | ✓ | INT8/INT4/FP16 | 中等 | 丰富 |
| TensorFlow/TFLite | ✓ | INT8/FP16 | 简单 | 完整 |
| ONNX Runtime | ✓ | INT8/INT4 | 简单 | 广泛 |
| TensorRT | ✓ | INT8/FP16 | 复杂 | NVIDIA专用 |
| MXNet | ✓ | INT8 | 中等 | 有限 |
| OpenVINO | ✓ | INT8/INT4 | 简单 | 英特尔优化 |
| Core ML | ✓ | INT8/INT4 | 简单 | 苹果生态 |
| TVM | ✓ | 任意位宽 | 复杂 | 灵活 |
- PyTorch: 研究新量化方法、灵活量化策略、服务器部署
- TensorFlow/TFLite: 移动应用、边缘设备、产品级部署
- ONNX Runtime: 跨平台部署、企业应用、框架无关解决方案
- TensorRT: NVIDIA GPU高性能推理、批处理、吞吐量优先
- OpenVINO: Intel硬件部署、计算机视觉应用、CPU优化
- Core ML: Apple设备部署、iOS应用、电池效率优先
- TVM: 极致性能优化、特殊硬件支持、研究探索