赞
踩
如果要将深度学习的AI模型部署到受限设备(FPGA)上,往往需要更小的存储需求和最低的计算复杂度。当然,还得保持一定的性能(下降在能够接受的范围)。受限设备资源的环境,一般是指的手机,嵌入式系统,而对于我目前的场景,主要是针对FPGA,但因为本文并不涉及具体硬件的实现,所以,完全不涉及FPGA领域知识。不管部署到哪种受限设备,要达到这种要求,最常见的方法就是 量化和剪枝了。
对于FPGA,不好意思,我还得多说几句,因为有些情况它还是和其它硬件不太一样。它更擅长于定点数的运算(而不是浮点运算),原因是什么?因为FPGA由大量的基本逻辑单元组成。而这些逻辑单元(LUT 和触发器)非常适合于布尔逻辑和整数(定点数)运算。因为定点数的运算不涉及复杂的位移和归一化操作,逻辑实现直接,资源利用率高。而对于FPGA中配置的DSP,它通常也会优化用于执点定点数运算。如果一定要执行浮点运算,那么浮点数的归一化,舍入,溢出检测等复杂操作,会显著增加FPGA的资源消耗和功耗。低功耗是FPGA中非常关键的一环。另外,对于FPGA的应用场景,经常是需要高效的实时计算。如果运算过于复杂,必须无法满足实时性的要求。而且需要更多的资源。
那什么是量化,什么是剪枝呢?
定义:模型量化是通过减少模型中参数的表示精度来实现模型压缩的过程。通常,神经网络模型中的参数是使用浮点数表示的,而量化则将这些参数表示为更少比特的定点数或整数,从而减小了内存占用和计算成本。
目标:减小模型的存储空间和加速推理过程。通过使用较少位数的表示来存储权重和激活值,模型的存储需求减少,且在硬件上执行推理时,可以更快地进行计算。
定义:剪枝是一种技术,通过减少神经网络中的连接或参数来减小模型的大小。在剪枝过程中,通过将权重较小或对模型贡献较小的连接移除或设为零,从而减少模型的复杂度。
目标:减小模型的尺寸和计算负载。剪枝不仅可以减少模型的存储需求,还可以在推理时减少乘法操作,因为移除了部分连接或参数,从而提高推理速度。
虽然两者都致力于减小模型的大小和计算复杂度,但方法和实现方式略有不同。模型量化侧重于减小参数表示的精度,而剪枝则专注于减少模型的连接或参数数量。通常,这两种技术可以结合使用,以更大程度地减小神经网络模型的尺寸和提高推理效率。
我们再具体说一下量化的效果。
1:更少的存储开销和带宽需求。即使用更少的bit存储数据,有效减少应用对存储资源的依赖;
2:更快的计算速度。即对大多数处理器而言,整型运算(定点运算)的速度一般(但不总是)要比浮点运算更快一些;
举例:int8 量化可减少 75% 的模型大小(相比于F32),int8 量化模型大小一般为 32 位浮点模型大小的 1/4 加快推理速度,访问一次 32 位浮点型可以访问四次 int8 整型,整型运算比浮点型运算更快;
这是应用量化形式的最简单方法,其中权重提前量化,但激活在推理过程中动态量化。
动态量化 (Dynamic Quantization) 是一种在推理过程中将浮点数模型转换为低精度整数模型的量化技术。其主要目的是减少模型的内存占用和加速推理过程,同时尽量保持模型的精度。
float32
) 量化为低精度整数 (如 int8
)。动态量化的做法:
1. 模型权重的量化
int8
,这是静态量化的一部分。每个权重的量化操作通过 scale
和 zero-point
完成:
int8_weight = (float32_weight / scale) + zero_point
scale
和 zero_point
是预先计算好的,用来确保权重在转换成 int8
后尽可能地保留其信息。2. 激活函数的动态量化:
scale
和 zero_point
。int8
,并在后续运算中使用这个量化后的值。float32_activation = (int8_activation - zero_point) * scale
对于RNN和全连接层的运算(都是统一的矩阵乘法),可以很方便的使用动态量化,但是,对于CNN运算,
卷积运算的复杂性:卷积神经网络中的主要运算是卷积运算,而卷积运算的本质是对输入的局部区域和卷积核进行乘法和累加。这种操作的特点是涉及到复杂的内存访问模式和数据处理方式。
内存和计算模式的差异:与全连接层的矩阵乘法不同,卷积运算需要在输入张量的不同局部区域上滑动,并与卷积核做点积。动态量化在这种滑动窗口操作中,难以像在全连接层中那样简单直接地进行量化,因为卷积核和输入的动态范围可能随位置变化,不易统一处理。因为我们不太可能针对每次卷积都采用不同的scale和zero_point(这不适应于并行运算,运算量过大),如果采用更大的粒度,比如:通道,批次数据……,就会产生上面说的问题。
所以,对于CNN,不适合使用动态量化,我们一般采用另一种量化的方法,PTQ。
是一种常用于深度学习模型的量化方法,它通过在模型训练完成后进行量化,从而减少模型的存储需求和计算复杂度,同时尽量保持模型的推理性能。以下是对 PTQ 量化的详细说明,包括其原理、做法以及适用的场景。
PTQ 量化 的核心思想是在模型训练完成之后,将模型的权重和激活函数从高精度的浮点数(通常是 32 位浮点数,FP32)转换为低精度的整数格式(如 8 位整数,INT8)。这能够显著减少模型的存储大小和计算复杂度。
主要量化步骤:
PTQ 量化 一般包括以下步骤:
1. 模型训练
2. 采集校准数据
3. 量化权重和激活值
4. 测试和评估
我们刚才提到了,对于CNN激活值的量化,可以使用PTQ,我们来看看实际的做法:
第一步:训练原始 CNN 模型
首先,训练一个高精度的 CNN 模型,通常使用浮点数(FP32)进行训练。
第二步:收集校准数据
使用一部分代表性的数据(校准数据集)来计算模型各层激活值的统计信息,包括最小值、最大值等。
第三步:确定量化参数
根据校准数据,计算每一层激活函数的量化参数,如缩放因子(scale factor)和零点(zero-point)。
第四步:量化模型权重和激活函数
将模型的权重和激活函数量化为整数格式(如 INT8),并将计算出的量化参数应用到推理过程中。
最后:评估和调整
通过评估量化后的模型精度,进行必要的调整,以优化量化效果。
- import torch
- import torch.quantization
- import torch.nn as nn
- import torch.optim as optim
- from torch.utils.data import DataLoader
-
- # 示例CNN模型
- class SimpleCNN(nn.Module):
- def __init__(self):
- super(SimpleCNN, self).__init__()
- self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
- self.relu1 = nn.ReLU()
- self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
- self.relu2 = nn.ReLU()
- self.fc1 = nn.Linear(64 * 28 * 28, 128)
- self.fc2 = nn.Linear(128, 10)
-
- def forward(self, x):
- x = self.conv1(x)
- x = self.relu1(x)
- x = self.conv2(x)
- x = self.relu2(x)
- x = x.view(x.size(0), -1)
- x = self.fc1(x)
- x = self.fc2(x)
- return x
-
- # 1. 训练原始模型
- model = SimpleCNN()
- # 训练代码略,假设模型已经训练完成
-
- # 2. 准备校准数据集
- calibration_data_loader = DataLoader(...) # 载入校准数据集
-
- # 3. 准备模型进行量化
- model.eval()
- model.qconfig = torch.quantization.get_default_qconfig('fbgemm')
-
- # 4. 融合模型中的卷积层和ReLU层
- model_fused = torch.quantization.fuse_modules(model, [['conv1', 'relu1'], ['conv2', 'relu2']])
-
- # 5. 将模型转换为量化模型
- model_prepared = torch.quantization.prepare(model_fused)
-
- # 6. 使用校准数据集进行量化校准
- with torch.no_grad():
- for images, _ in calibration_data_loader:
- model_prepared(images)
-
- # 7. 转换为量化后的模型
- model_quantized = torch.quantization.convert(model_prepared)
-
- # 8. 评估量化模型的精度
- # 加载测试集进行模型评估

PTQ 的优势
克服动态量化的不足:动态量化仅在推理时计算量化参数,这可能导致较大的量化误差,尤其是在激活值的分布范围变化较大的情况下。PTQ 通过提前使用校准数据来确定激活函数的量化参数,使得推理阶段的量化误差较小,模型精度更高。
更精确的量化:通过校准数据的分析,PTQ 可以为每一层选择最适合的量化参数(如缩放因子和零点),从而在低精度下尽可能保留模型的原始性能。
在传统的训练过程中,模型的权重和激活值通常以高精度的浮点数(如 32 位浮点数, FP32)存储和运算。然而,在推理阶段,特别是在资源受限的设备(如嵌入式设备、移动设备)上,使用高精度浮点数进行计算的开销较大。因此,通过量化将模型的权重和激活值从高精度(如 FP32)减少到低精度(如 8 位整数, INT8),可以显著降低模型的存储需求和计算复杂度。
量化感知训练通过在训练阶段引入模拟量化的操作,使得模型在推理阶段以低精度运算时的性能尽可能接近高精度运算的效果。其核心思想是在训练时对模型进行“量化感知”,使得模型在训练过程中学习如何应对量化误差,从而在低精度运算下仍然能保持较好的性能。
QAT 在训练过程中通过以下步骤来实现:
模拟量化操作:
反向传播和梯度计算:
训练迭代:
量化模型生成:
QAT 主要适用于以下场景:
推理速度要求高的场景:
对精度要求高的应用:
从理论上讲,QAT的效果会好于PTQ,但是,代价较高,一定要看对于性能的要求是否需要,来确定是否要使用QAT。
具体的实现代码, 我们以Pytorch为例,给出代码。
网络上有相应的教程: Pytorch QAT 教程
- Python
- # create a model instance
- model_fp32 = M()
-
- # model must be set to train mode for QAT logic to work
- model_fp32.train()
-
- # attach a global qconfig, which contains information about what kind
- # of observers to attach. Use 'fbgemm' for server inference and
- # 'qnnpack' for mobile inference. Other quantization configurations such
- # as selecting symmetric or assymetric quantization and MinMax or L2Norm
- # calibration techniques can be specified here.
- model_fp32.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')
-
- # fuse the activations to preceding layers, where applicable
- # this needs to be done manually depending on the model architecture
- model_fp32_fused = torch.quantization.fuse_modules(model_fp32,
- [['conv', 'bn', 'relu']])
-
- # Prepare the model for QAT. This inserts observers and fake_quants in
- # the model that will observe weight and activation tensors during calibration.
- model_fp32_prepared = torch.quantization.prepare_qat(model_fp32_fused)
-
- # run the training loop (not shown)
- training_loop(model_fp32_prepared)

注意,在动态量化和PTQ时,会提到有可能做反量化。反量化的做法很简单,但是为什么要做反量化呢?
一般是在对精度要求较的场合,或者最终需要返回的值。我们可以有针对性的对某一些步骤进行反量化,提升性能。
剪枝分为结构化剪枝和非结构化剪枝。
深度学习模型压缩的一种方法,它通过删除模型中不重要的个别权重来减少模型的规模和复杂度,同时尽量保持模型的准确性和性能。
针对各个权重参数进行剪枝,形成不规则的稀疏结构。
原理:在深度神经网络中,通常存在大量的权重参数,但并非所有权重都对模型的最终输出有显著影响。非结构化剪枝的核心思想是通过识别和移除对模型性能贡献较小的权重,从而减少模型的参数量。将部分权重置0,
具体过程:
代码示例:
- Python
- parameters_to_prune = (
- (model.conv1, 'weight'),
- (model.conv2, 'weight'),
- (model.fc1, 'weight'),
- (model.fc2, 'weight'),
- (model.fc3, 'weight'),
- )
-
- prune.global_unstructured(
- parameters_to_prune,
- pruning method=prune.L1Unstructured,
- amount=0.2,
- )
剪枝操作会导致模型精度下降,因此通常需要对剪枝后的模型进行微调(Fine-tuning)。微调过程中,模型重新训练以适应被剪枝的结构,并尽量恢复因剪枝而丢失的精度。
是一种深度学习模型压缩技术,通过移除模型中的特定结构(如整个卷积核、神经元、过滤器、通道等),来减少模型的计算复杂度和存储需求。与非结构化剪枝不同,结构化剪枝移除的是模型中的大块结构,而不是单个权重。
在深度神经网络中,某些卷积核、过滤器、神经元或通道的贡献较小,删除这些结构不会显著影响模型的性能。结构化剪枝通过评估这些结构的贡献度,将低贡献度的结构整体删除,从而简化模型。
评估标准:结构化剪枝通常基于结构的重要性进行评估。重要性可以通过各种标准来衡量,例如基于权重的绝对值大小、特征图的激活值、梯度信息、或基于模型训练过程中的统计数据。
剪枝对象:结构化剪枝的对象通常包括:
第一步:初始化模型
第二步:评估结构重要性
第三步:剪枝
第四步:微调模型
第五步:迭代剪枝
一种通道剪枝方法:基于BN层中的缩放因子来对不重要的通道进行裁剪
具体过程:
训练模型->压缩模型->微调模型,这个过程可以多次进行
有一种很简单的做法,对于有重复多次调用的多层模块,进行简单的减少层数。这样往往是可以在不太大影响性能的情况下达成剪枝的效果。当然,这要结合不断的重训评估,来找到最合适的值,但原则上是不减少原有模型的处理,只是减少重复处理的次数。实测有效噢。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。