1.1:我们为什么需要量化(Why) 为什么要做量化(量化的意义是什么),然后讲量化的基本原理是什么,以及量化的分类(PTQ和QAT,weight-only 和 weight-act),原理讲解+基本原理代码。 1.1.0 前言:为什么做量化(或者说模型压缩) 举个: 小明想跑一个30G的模型,但是他的显存只有10G,怎么办?一时间小明陷入了沉思。突然他想到了去餐厅吃小龙虾饭打包,小明剩了20个龙虾,但打包盒只能装10个,怎么办呢,小明灵机一动,剥头去尾,这样就可以把20个龙虾打包了。类似的方法能不能在这里实现呢,比如把参数存储的类型从float32变成int8,这样就可以把30G的模型压缩到10G,这样就可以在显存只有10G的设备上跑30G的模型了。
为什么要做量化(量化的意义是什么),然后讲量化的基本原理是什么,以及量化的分类(PTQ和QAT,weight-only 和 weight-act),原理讲解+基本原理代码。
举个:
小明想跑一个30G的模型,但是他的显存只有10G,怎么办?一时间小明陷入了沉思。突然他想到了去餐厅吃小龙虾饭打包,小明剩了20个龙虾,但打包盒只能装10个,怎么办呢,小明灵机一动,剥头去尾,这样就可以把20个龙虾打包了。类似的方法能不能在这里实现呢,比如把参数存储的类型从float32变成int8,这样就可以把30G的模型压缩到10G,这样就可以在显存只有10G的设备上跑30G的模型了。
而这就是模型量化的思想了,通过改变参数存储的类型(从较大比特的存储方式转化为较小比特的存储方式),实现模型大小的压缩,从而实现模型在显存较小的设备上跑大模型。
所有大模型量化(或者说模型压缩)是一种重要的优化技术,其目的主要在于减少深度学习模型的内存占用、提高推理速度,同时尽量保持模型的精度。以下是进行大模型量化的主要原因:
大模型量化是一种优化技术,旨在减少深度学习模型的内存占用和提高推理速度,同时尽量保持模型的精度。这一技术对于当前大规模、高复杂度的深度学习模型来说尤为重要,其意义主要体现在以下几个方面:
首先,什么是量化?
大模型量化是一种模型压缩和优化技术,旨在减少深度学习模型的内存占用和提高推理速度,同时尽量保持模型的精度。量化后的模型权重占用更少的内存,从而可以在有限的硬件资源上部署更大的模型。而模型的压缩方法有很多,比如剪枝、量化、知识蒸馏,低秩分解等,这些方法都可以在一定程度上降低模型的参数量,从而减小模型的体积,提高模型的推理速度,降低模型的能耗。但是,这些方法在压缩模型的同时,也会带来一些问题,比如模型的精度下降、模型的推理速度不稳定等。首先我们从感性上来介绍几种不同方法的差异性:

如上图所示,大家可以很好的了解不同压缩方法的差异性了,首先,
1)量化,是通过降低模型中参数的精度,从而减小模型的体积,从图像来说就是把原先的高分辨率的彩图,变成了低分辨率,但整体图片的大小是没变的。这其实意味着模型的参数量是没变的,变化的只是每个参数的粒度,就像一个文件夹里面有100个txt文档,每个文档大小为10M,量化就是保持原始的文件数量还是100个,但把每个文件大小降低为1M,这样总的文件夹大小就降低了。
2)剪枝,则是在保持模型精度不变的情况下,把模型中的部分参数删除,从而减小模型的体积,从图像上来说就是删掉了某些不重要的局部信息。这种情况下模型的参数量就变了,比如从100个txt文件,删掉了一些文件,变成了80个,但每个的大小都还是10M。
3)知识蒸馏,则是通过训练一个小的模型,使其学习一个大模型的特征,从而减小模型的体积。从图像上来说就是做了一个小的类似的东西。这种意味着参考之前的文件夹,再重新建了一个文件夹,然后用10个新的txt文件,每个文档大小为10M,实现了和之前一样的功能,虽然他们完全不一样
4)低秩分解,则是通过将模型的参数分解为低秩矩阵,从而减小模型的体积。(这个图像不好说,可以参考看SVD),这种意味着我把文件夹里面的100个txt文件,分解为了3个其他文件,然后每个文件和原始的不同(并且每个只有10K到20M不等),但文件组合起来能基本恢复100个txt文件(这里可能有点抽象,详细内容大家可以去看矩阵论的SVD分解,会有更为清晰的认识)。
(1)量化粒度
量化粒度决定了量化的细粒度。量化粒度决定了量化的细粒度。常见的量化粒度包括逐层量化(per-tensor)、逐通道量化(per-channel或vector-wise quantization)和逐组量化(per-group或Group-wise)。逐通道量化通常能获得更好的精度,因为它对每个通道分别进行量化,可以更好地适应不同通道的权重分布。然而,逐通道量化可能需要更多的计算资源。逐层量化和逐张量量化更简单且计算效率更高,但可能会引入较大的精度损失。
(2)量化算法
量化算法决定了如何将浮点数映射到低精度表示。常见的量化算法包括线性量化(如对称量化和非对称量化)和非线性量化(如对数量化)。
量化技术是一种有效的模型压缩手段,它能够将深度学习模型中的浮点数转换为整数或其他离散形式,从而显著降低模型的存储和计算负担。模型量化的核心思想是利用数学变换和编码技术,将浮点数的存储和运算转换为整数的存储和运算。具体来说,量化过程包括两个主要步骤:前向量化和反向量化。
前向量化:将浮点数转换为整数的过程。在这个过程中,将浮点数的范围映射到一个整数范围内,并将浮点数转换为对应的整数。这个映射过程通常是通过一个缩放因子和一个偏移量来实现的。缩放因子用于调整整数的范围,使其能够覆盖浮点数的范围;偏移量则用于将浮点数的零点映射到整数的零点。
前向量化的数学表达式可以表示为:
q = clip(round(r/s + z), q_min, q_max)
其中,r表示量化前的浮点数,q表示量化后的整数,s是数据量化的间隔,z是表示数据偏移的偏置,q_min和q_max是量化后的最小值和最大值。round()和clip()分别表示取整和截断操作。
反向量化:将整数转换回浮点数的过程。这个过程是前向量化的逆操作,用于在推理过程中将整数还原为浮点数,以便进行后续的运算。
反向量化的数学表达式可以表示为:
r' = s * (q - z)
其中,r'表示还原后的浮点数,q表示量化后的整数,s和z分别是量化时的缩放因子和偏移量。
大模型量化可以根据不同的标准进行分类,常见的分类方式包括训练过程中的量化方式(PTQ和QAT)、量化对象(weight-only和weight-act)等。
我们依据是否要对量化后的参数进行调整,我们可以将量化方法分为量化感知训练(QAT)和训练后量化(PTQ)两种。 这两种方法的操作区别如下图所示(图左为QAT,图右为PTQ):

量化感知训练 QAT 是将训练过的模型量化后又再进行重训练。由于定点数值无法用于反向梯度计算,实际操作过程是在某些op前插入伪量化节点(fake quantization nodes), 用于在训练时获取流经该op的数据的截断值,便于在部署量化模型时对节点进行量化时使用。我们需要在训练中通过不断优化精度来获取最佳的量化参数。由于它需要对模型进行训练, 对操作人员技术要求较高。
训练后量化 PTQ 是使用一批校准数据对训练好的模型进行校准, 将训练过的FP32网络直接转换为定点计算的网络,过程中无需对原始模型进行任何训练。只对几个超参数调整就可完成量化过程, 且过程简单快速, 无需训练, 因此此方法已被广泛应用于大量的端侧和云侧部署场景,我们优先推荐您尝试PTQ方法来查看是否满足您的部署精度和性能要求。
(1)训练后量化(Post-Training Quantization, PTQ)
PTQ是在模型训练完成后对其参数进行量化,只需要少量校准数据,适用于追求高易用性和缺乏训练资源的场景。其主要目标是减少模型的存储和计算复杂性,而无需对模型架构进行修改或进行重新训练。PTQ的主要优势在于其简单性和高效性,但可能会在量化过程中引入一定程度的精度损失。
量化感知训练(QAT)是一种在模型训练过程中就考虑量化影响的技术。它的核心思想是将量化目标无缝地集成到模型的训练过程中,使模型在训练阶段就能够适应低精度表示,从而减少量化后可能带来的精度损失。
定义与原理:
实施步骤:
优势:
PTQ的具体流程通常包括以下几个步骤:
(2)量化感知训练(Quantization Aware Training, QAT)
QAT是在模型训练过程中加入伪量化算子,通过训练时统计输入输出的数据范围来提升量化后模型的精度。这种方法适用于对模型精度要求较高的场景。
训练后量化(PTQ)是在模型训练完成后对其进行量化的技术。它的主要目标是减少模型的存储和计算复杂性,而无需对模型架构进行修改或进行重新训练。由于无需复杂的fine-tuning或训练过程,因此开销较小。训练后量化可以分为权重量化和全量化两种。
定义与原理:
实施步骤:
优势与局限性:
QAT的具体流程通常包括以下几个步骤:
QAT通过模拟量化过程,使模型在训练过程中适应低精度表示,从而增强了其处理由量化引起的精度损失的能力。
(1)仅权重量化(Weight-Only Quantization)
仅权重量化是指仅对模型的权重进行量化,而保持激活值为浮点数。这种方法相对简单,但可能无法充分利用量化带来的性能提升。因为在实际应用中,激活值往往占内存使用的大头,量化激活值可以进一步减少内存占用和提高推理速度。
(2)权重和激活量化(Weight-and-Activation Quantization)
权重和激活量化是指同时对模型的权重和激活值进行量化。这种方法能够更全面地利用量化技术带来的优势,但也可能引入更大的精度损失。因此,在量化过程中需要仔细调整量化参数和量化策略,以确保在保持模型精度的同时最大化性能提升。
可能有部分小伙伴对量化的概念中所提到的线性映射和舍入操作不太清楚,也量化有所误解,认为量化就是将浮点数转换为整数,其实不然,量化不仅仅是简单的浮点数到整数的转换,还包括了线性映射和舍入操作,下面我们通过一个简单的例子来理解量化。


以下是一个简单的Python代码示例,用于演示如何进行大模型量化。该示例使用了PyTorch框架和FakeQuantize模块来模拟量化过程。
import torch import torch.nn as nn import torch.nn.functional as F import torchvision.models as models # 定义一个简单的卷积神经网络模型 class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() self.conv1 = nn.Conv2d(3, 16, 3, padding=1) self.conv2 = nn.Conv2d(16, 32, 3, padding=1) self.fc1 = nn.Linear(32 * 8 * 8, 128) # 假设输入图像大小为32x32 self.fc2 = nn.Linear(128, 10) def forward(self, x): x = F.relu(self.conv1(x)) x = F.max_pool2d(x, 2) x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 2) x = x.view(x.size(0), -1) x = F.relu(self.fc1(x)) x = self.fc2(x) return x # 加载预训练的模型 model = SimpleCNN() # 假设已经对模型进行了训练,这里省略训练过程 # 定义量化参数 scale = 0.02 zero_point = 128 dtype = torch.qint8 # 对模型进行量化 class QuantizedSimpleCNN(nn.Module): def __init__(self, original_model): super(QuantizedSimpleCNN, self).__init__() self.conv1 = nn.quantized.Conv2d( original_model.conv1.in_channels, original_model.conv1.out_channels, original_model.conv1.kernel_size, original_model.conv1.stride, original_model.conv1.padding, original_model.conv1.dilation, original_model.conv1.groups, bias=original_model.conv1.bias is not None, dtype=dtype ) self.conv2 = nn.quantized.Conv2d( original_model.conv2.in_channels, original_model.conv2.out_channels, original_model.conv2.kernel_size, original_model.conv2.stride, original_model.conv2.padding, original_model.conv2.dilation, original_model.conv2.groups, bias=original_model.conv2.bias is not None, dtype=dtype ) self.fc1 = nn.quantized.Linear( original_model.fc1.in_features, original_model.fc1.out_features, dtype=dtype ) self.fc2 = nn.quantized.Linear( original_model.fc2.in_features, original_model.fc2.out_features, dtype=dtype ) # 注意:在实际应用中,通常需要使用torch.quantization.prepare和torch.quantization.convert函数 # 来准备和转换模型,这里为了简化直接手动创建了量化层。 def forward(self, x): x = torch.quantization.quantize(x, scale=scale, zero_point=zero_point, dtype=dtype) x = F.relu(self.conv1(x)) x = F.max_pool2d(x, 2) x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 2) x = x.view(x.size(0), -1) x = F.relu(self.fc1(x)) x = self.fc2(x) # 注意:这里需要反量化回浮点数以便后续处理或评估,但在实际应用中, # 量化模型的输出通常直接用于后续的低精度计算或存储。 x = torch.quantization.dequantize(x) return x # 注意:上面的QuantizedSimpleCNN类是一个简化的示例,实际中量化模型的创建和转换过程 # 要复杂得多,通常需要使用PyTorch的量化工具包(如torch.quantization)来完成。 # 下面是一个更贴近实际使用的量化流程示例: # 准备模型进行量化 model.qconfig = torch.quantization.get_default_qconfig('fbgemm') # 选择后端和量化配置 model.prepare_qconfig() # 准备量化配置 # 校准模型(这一步在PTQ中是必需的,用于收集激活值的统计信息) # 通常需要用一个代表性的数据集来运行模型,以便收集量化所需的统计信息。 # 这里省略具体代码,因为实际校准过程可能涉及数据加载、模型推理等步骤。 # 转换模型为量化模型 model_quantized = torch.quantization.convert(model, inplace=False) # 现在model_quantized是一个量化后的模型,可以像普通模型一样进行推理。 # 请注意,在实际应用中,量化模型的推理过程通常涉及低精度计算或存储, # 因此可能需要在支持低精度运算的硬件上运行才能获得最佳性能。 # 示例:使用量化模型进行推理 # input_tensor = ... # 加载或创建输入张量 # output_tensor = model_quantized(input_tensor) # 进行推理
注意:上面的代码示例中,QuantizedSimpleCNN 类和量化流程的描述是为了展示量化的基本原理和步骤,并不是一个可以直接运行的完整示例。在实际应用中,量化模型的创建和转换通常需要使用 PyTorch 提供的量化工具包(如 torch.quantization)来完成,并且需要仔细调整量化参数和策略以确保量化后的模型具有可接受的精度和性能。
此外,上面的代码示例中使用了 torch.quantization.quantize 和 torch.quantization.dequantize 函数来模拟量化和反量化过程,但在实际应用中,这些操作通常由量化框架在内部自动处理。在量化模型的推理过程中,通常不需要显式地进行反量化操作,因为量化模型的输出通常直接用于后续的低精度计算或存储。
最后,需要注意的是,量化模型的精度和性能取决于多种因素,包括量化策略、量化粒度、量化参数、校准数据的质量等。因此,在进行量化时,需要仔细调整这些因素,并通过实验来找到最佳的量化配置。