深度学习的量化和剪枝

一:背景

如果要将深度学习的AI模型部署到受限设备(FPGA)上,往往需要更小的存储需求最低的计算复杂度。当然,还得保持一定的性能(下降在能够接受的范围)。受限设备资源的环境,一般是指的手机,嵌入式系统,而对于我目前的场景,主要是针对FPGA,但因为本文并不涉及具体硬件的实现,所以,完全不涉及FPGA领域知识。不管部署到哪种受限设备,要达到这种要求,最常见的方法就是 量化和剪枝了。

对于FPGA,不好意思,我还得多说几句,因为有些情况它还是和其它硬件不太一样。它更擅长于定点数的运算 (而不是浮点运算),原因是什么?因为FPGA由大量的基本逻辑单元组成。而这些逻辑单元(LUT 和触发器)非常适合于布尔逻辑和整数(定点数)运算。因为定点数的运算不涉及复杂的位移和归一化操作,逻辑实现直接,资源利用率高。而对于FPGA中配置的DSP,它通常也会优化用于执点定点数运算。如果一定要执行浮点运算,那么浮点数的归一化,舍入,溢出检测等复杂操作,会显著增加FPGA的资源消耗和功耗。低功耗 是FPGA中非常关键的一环。另外,对于FPGA的应用场景,经常是需要高效的实时计算。如果运算过于复杂,必须无法满足实时性 的要求。而且需要更多的资源

二:概念

那什么是量化,什么是剪枝呢?

2.1:模量化(Quantization)

定义:模型量化是通过减少模型中参数的表示精度来实现模型压缩的过程。通常,神经网络模型中的参数是使用浮点数表示的,而量化则将这些参数表示为更少比特的定点数或整数,从而减小了内存占用和计算成本。

目标:减小模型的存储空间和加速推理过程。通过使用较少位数的表示来存储权重和激活值,模型的存储需求减少,且在硬件上执行推理时,可以更快地进行计算。

2.2:剪枝(Pruning)

定义:剪枝是一种技术,通过减少神经网络中的连接或参数来减小模型的大小。在剪枝过程中,通过将权重较小或对模型贡献较小的连接移除或设为零,从而减少模型的复杂度。

目标:减小模型的尺寸和计算负载。剪枝不仅可以减少模型的存储需求,还可以在推理时减少乘法操作,因为移除了部分连接或参数,从而提高推理速度。

2.3:对比

虽然两者都致力于减小模型的大小和计算复杂度,但方法和实现方式略有不同。模型量化侧重于减小参数表示的精度,而剪枝则专注于减少模型的连接或参数数量。通常,这两种技术可以结合使用,以更大程度地减小神经网络模型的尺寸和提高推理效率。

三:量化

3.1:量化的作用

我们再具体说一下量化的效果。

1:更少的存储开销和带宽需求。即使用更少的bit存储数据,有效减少应用对存储资源的依赖;

2:更快的计算速度。即对大多数处理器而言,整型运算(定点运算)的速度一般(但不总是)要比浮点运算更快一些;

举例:int8 量化可减少 75% 的模型大小(相比于F32),int8 量化模型大小一般为 32 位浮点模型大小的 1/4 加快推理速度,访问一次 32 位浮点型可以访问四次 int8 整型,整型运算比浮点型运算更快;

3.2:量化的方法

3.2.1:动态量化(Dynamic Quantization)

这是应用量化形式的最简单方法,其中权重提前量化,但激活在推理过程中动态量化。

动态量化 (Dynamic Quantization) 是一种在推理过程中将浮点数模型转换为低精度整数模型的量化技术。其主要目的是减少模型的内存占用和加速推理过程,同时尽量保持模型的精度。

  • 在动态量化中,模型的权重通常在推理之前就被量化,而激活函数的量化则是在推理过程中动态完成的。
  • 权重通常从浮点数 (如 float32) 量化为低精度整数 (如 int8)。
  • 激活值根据输入数据在推理过程中动态计算缩放因子,再将其量化为整数形式。
  • 通过动态量化,计算时使用低精度整数进行运算,然后在必要时将结果重新缩放回浮点格式。

动态量化的做法:

1. 模型权重的量化

  • 静态量化权重 :模型权重在部署之前会被量化为 int8,这是静态量化的一部分。每个权重的量化操作通过 scalezero-point 完成:
    • int8_weight = (float32_weight / scale) + zero_point
  • 这里的 scalezero_point 是预先计算好的,用来确保权重在转换成 int8 后尽可能地保留其信息。

2. 激活函数的动态量化:

  • 动态量化激活 :输入数据经过激活函数后,其输出在推理过程中动态量化。即:
    • 首先,根据输入数据计算 scalezero_point
    • 然后将激活值量化为 int8,并在后续运算中使用这个量化后的值。
  • 激活值在使用之前会被反量化为浮点数,进行最终的输出计算:
    • float32_activation = (int8_activation - zero_point) * scale

对于RNN和全连接层的运算(都是统一的矩阵乘法),可以很方便的使用动态量化,但是,对于CNN运算,

卷积运算的复杂性:卷积神经网络中的主要运算是卷积运算,而卷积运算的本质是对输入的局部区域和卷积核进行乘法和累加。这种操作的特点是涉及到复杂的内存访问模式和数据处理方式。

内存和计算模式的差异:与全连接层的矩阵乘法不同,卷积运算需要在输入张量的不同局部区域上滑动,并与卷积核做点积。动态量化在这种滑动窗口操作中,难以像在全连接层中那样简单直接地进行量化,因为卷积核和输入的动态范围可能随位置变化,不易统一处理。因为我们不太可能针对每次卷积都采用不同的scale和zero_point(这不适应于并行运算,运算量过大),如果采用更大的粒度,比如:通道,批次数据......,就会产生上面说的问题。

所以,对于CNN,不适合使用动态量化,我们一般采用另一种量化的方法,PTQ。

3.2.2:PTQ (PostTrain Quantity)

是一种常用于深度学习模型的量化方法,它通过在模型训练完成后进行量化,从而减少模型的存储需求和计算复杂度,同时尽量保持模型的推理性能。以下是对 PTQ 量化的详细说明,包括其原理、做法以及适用的场景。

PTQ 量化 的核心思想是在模型训练完成之后,将模型的权重和激活函数从高精度的浮点数(通常是 32 位浮点数,FP32)转换为低精度的整数格式(如 8 位整数,INT8)。这能够显著减少模型的存储大小和计算复杂度。

主要量化步骤

  • 权重量化:将模型的权重从浮点数格式转换为整数格式。
  • 激活函数量化:将模型推理过程中产生的激活值从浮点数转换为整数格式。
  • 缩放因子:为了在低精度格式下保持数值的动态范围,使用缩放因子(scale factor)来调整量化后的整数值,使其能够近似原始的浮点值。

PTQ 量化 一般包括以下步骤:

1. 模型训练

  • 原始模型训练:首先使用标准的浮点数精度(FP32)训练模型。此时模型在高精度环境下运行,并且不进行任何量化处理。

2. 采集校准数据

  • 收集校准数据:在模型训练完成后,使用一部分未见过的训练数据或验证数据来采集模型的激活分布。这些数据用于帮助模型计算权重和激活值的量化参数(如缩放因子)。

3. 量化权重和激活值

  • 量化权重:使用校准数据的统计信息,将模型的权重从浮点数(FP32)转换为整数(通常是 INT8)。这个过程可能涉及到缩放因子的计算和权重的离散化。
  • 量化激活值:类似于权重量化,将激活值也转换为整数。激活值的量化通常是动态的,即在每次推理时使用当前输入的数据动态计算量化参数。

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 可以为每一层选择最适合的量化参数(如缩放因子和零点),从而在低精度下尽可能保留模型的原始性能。

3.2.3:QAT(Quantization-Aware Training,QAT)

在传统的训练过程中,模型的权重和激活值通常以高精度的浮点数(如 32 位浮点数, FP32)存储和运算。然而,在推理阶段,特别是在资源受限的设备(如嵌入式设备、移动设备)上,使用高精度浮点数进行计算的开销较大。因此,通过量化将模型的权重和激活值从高精度(如 FP32)减少到低精度(如 8 位整数, INT8),可以显著降低模型的存储需求和计算复杂度。

量化感知训练通过在训练阶段引入模拟量化的操作,使得模型在推理阶段以低精度运算时的性能尽可能接近高精度运算的效果。其核心思想是在训练时对模型进行"量化感知",使得模型在训练过程中学习如何应对量化误差,从而在低精度运算下仍然能保持较好的性能。

QAT 在训练过程中通过以下步骤来实现:

  1. 模拟量化操作

    • 在前向传播时,模拟将浮点数(FP32)的权重和激活值量化为低精度整数(如 INT8)。这一过程通常包括对权重和激活值进行缩放、截断和映射到低精度整数范围。
    • 例如,假设一个神经网络的权重范围是 [-2.0, 2.0],则可以将其映射到 [0, 255] 的 8 位整数范围内。前向传播时,模型的权重和激活值会被模拟为这些整数值。
  2. 反向传播和梯度计算

    • 在反向传播时,尽管前向传播中使用了量化值,梯度的计算仍然在浮点精度下进行,以避免梯度信息的丢失。
    • 反向传播过程中,模型更新的是未量化的浮点数权重,但更新后的权重将在下一次前向传播时继续通过量化过程。
  3. 训练迭代

    • 训练过程迭代进行,通过模拟量化的训练过程,模型逐渐适应量化误差,并学会如何在低精度下保持性能
  4. 量化模型生成

    • 训练完成后,最终生成的模型权重被正式量化为低精度格式,以便在推理阶段直接使用。

QAT 主要适用于以下场景:

  1. 推理速度要求高的场景

    • 在需要快速响应的应用中(如实时视频处理、自动驾驶等),通过量化模型来加速推理过程是非常重要的。QAT 可以帮助减少计算延迟,同时保持模型的精度。
  2. 对精度要求高的应用

    • 对于一些精度敏感的应用,传统的后量化方法(Post-Training Quantization, PTQ)可能导致显著的精度下降。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)
3.2.4:何时需要反量化?

注意,在动态量化和PTQ时,会提到有可能做反量化。反量化的做法很简单,但是为什么要做反量化呢?

一般是在对精度要求较的场合,或者最终需要返回的值。我们可以有针对性的对某一些步骤进行反量化,提升性能。

四:剪枝

剪枝分为结构化剪枝和非结构化剪枝。

4.1:非结构化剪枝:

深度学习模型压缩的一种方法,它通过删除模型中不重要的个别权重来减少模型的规模和复杂度,同时尽量保持模型的准确性和性能。

针对各个权重参数进行剪枝,形成不规则的稀疏结构。

原理:在深度神经网络中,通常存在大量的权重参数,但并非所有权重都对模型的最终输出有显著影响。非结构化剪枝的核心思想是通过识别和移除对模型性能贡献较小的权重,从而减少模型的参数量。将部分权重置0,

具体过程:

  • 计算每个权重的绝对值。
  • 基于阈值的剪枝:设定一个阈值,删除绝对值小于该阈值的权重。
  • 基于比例的剪枝:设定一个剪枝比例,例如剪除最小的 20% 权重。
  • 渐进式剪枝:逐步增加剪枝比例或降低阈值,以便模型逐步适应剪枝过程。

代码示例:

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)。微调过程中,模型重新训练以适应被剪枝的结构,并尽量恢复因剪枝而丢失的精度。

4.2:结构化剪枝

是一种深度学习模型压缩技术,通过移除模型中的特定结构(如整个卷积核、神经元、过滤器、通道等),来减少模型的计算复杂度和存储需求。与非结构化剪枝不同,结构化剪枝移除的是模型中的大块结构,而不是单个权重。

4.2.1:原理

在深度神经网络中,某些卷积核、过滤器、神经元或通道的贡献较小,删除这些结构不会显著影响模型的性能。结构化剪枝通过评估这些结构的贡献度,将低贡献度的结构整体删除,从而简化模型。

  • 评估标准:结构化剪枝通常基于结构的重要性进行评估。重要性可以通过各种标准来衡量,例如基于权重的绝对值大小、特征图的激活值、梯度信息、或基于模型训练过程中的统计数据。

  • 剪枝对象:结构化剪枝的对象通常包括:

    • 卷积核:在卷积层中移除某些卷积核。
    • 过滤器:在卷积神经网络 (CNN) 中移除某些过滤器(即卷积核的输出通道)。
    • 神经元:在全连接层中移除某些神经元。
    • 通道:在卷积层中移除某些通道(即卷积层的输入或输出通道)。
4.2.2: 实施步骤

第一步:初始化模型

  • 首先,训练一个全尺寸的深度神经网络模型至收敛,确保其在完整结构下达到期望的性能。

第二步:评估结构重要性

  • 对模型中的各个结构(如过滤器、通道、神经元等)进行重要性评估。常用的方法包括:
    • 权重标准:评估每个结构的权重绝对值的平均值或平方和,较小值的结构被认为不重要。
    • 激活标准:评估每个结构的激活值(如特征图的均值或方差),较小激活值的结构被认为不重要。
    • 梯度标准:利用训练过程中计算的梯度来评估结构的重要性,较小梯度的结构被认为不重要。

第三步:剪枝

  • 根据评估结果,对模型中的不重要结构进行剪枝。例如,移除不重要的卷积核、过滤器或通道。

第四步:微调模型

  • 剪枝后,模型的性能通常会下降,因此需要对剪枝后的模型进行微调(Fine-tuning)。通过微调,模型可以适应被移除结构后的新形态,并尽可能恢复性能。

第五步:迭代剪枝

  • 可以多次重复剪枝和微调的过程,每次剪除一定比例的结构,直到模型达到预期的压缩效果或性能要求。
4.2.3:通道剪枝方法

一种通道剪枝方法:基于BN层中的缩放因子来对不重要的通道进行裁剪

  1. 在训练期间,通过对网络BN层的gamma系数施加L1正则约束,使得模型朝着结构性稀疏的方向调整参数
  2. 完成稀疏训练或者正则化之后,便可以按照既定的压缩比裁剪模型

具体过程:

训练模型->压缩模型->微调模型,这个过程可以多次进行

4.2.4:重复模块减化

有一种很简单的做法,对于有重复多次调用的多层模块,进行简单的减少层数。这样往往是可以在不太大影响性能的情况下达成剪枝的效果。当然,这要结合不断的重训评估,来找到最合适的值,但原则上是不减少原有模型的处理,只是减少重复处理的次数。实测有效噢。

相关推荐
Chef_Chen17 分钟前
从0开始学习机器学习--Day22--优化总结以及误差作业(上)
人工智能·学习·机器学习
Mr.简锋22 分钟前
opencv常用api
人工智能·opencv·计算机视觉
华清元宇宙实验中心27 分钟前
【每天学点AI】前向传播、损失函数、反向传播
深度学习·机器学习·ai人工智能
手握风云-28 分钟前
零基础Java第十六期:抽象类接口(二)
数据结构·算法
DevinLGT1 小时前
6Pin Type-C Pin脚定义:【图文讲解】
人工智能·单片机·嵌入式硬件
宋一诺331 小时前
机器学习—高级优化方法
人工智能·机器学习
龙的爹23331 小时前
论文 | The Capacity for Moral Self-Correction in LargeLanguage Models
人工智能·深度学习·机器学习·语言模型·自然语言处理·prompt
Mr.简锋1 小时前
opencv视频读写
人工智能·opencv·音视频
Baihai_IDP1 小时前
「混合专家模型」可视化指南:A Visual Guide to MoE
人工智能·llm·aigc
笨小古1 小时前
路径规划——RRT-Connect算法
算法·路径规划·导航