目录
1. 简介
在《Vitis AI 基本认知(模型校准+量化)》一文中,笔者简单分享了 inspector 的简单用法,本文将深入探究其用法。
inspector 的函数,用来诊断不同器件架构下的神经网络 (NN) 模型。检查器可以基于硬件约束来预测目标器件分配。生成的检查报告可用于指导用户对 NN 模型进行修改或最优化,从而显著降低部署难度并缩短部署时间。
建议在量化浮点模型前对其进行检查。
NNDCT:Neural Network Development and Compiler Tools。
2. 代码详解
2.1 导入所需的库
Vitis AI 2.5, Pytorch 版本信息:
Python 3.7.12
PyTorch 1.10.1
torchvision 0.11.2
import torch
from torchvision.models import resnet18
from pytorch_nndct.apis import Inspector
from IPython.display import Image
功能解释:
- import torch:PyTorch 库,用于深度学习的开源框架,核心功能是张量计算(类似于NumPy)以及深度神经网络的自动求导机制。
- torchvision.models:建立在 PyTorch 之上的一个库,专门用来处理图像数据。提供了三大主要功能。
- 一是加载和预处理图像数据的工具。
- 二是常用的图像数据集,如ImageNet、CIFAR10、MNIST等。
- 三是预训练好的模型,如VGG、ResNet等。
- Inspector 是 Vitis AI 工具的一部分,用于检查和诊断浮点模型。
2.2 创建 Inspector
target = "DPUCZDX8G_ISA1_B4096"
inspector = Inspector(target)
#---
#输出
#---
[VAIQ_NOTE]: =>Inspector is initialized successfully with target:
name: DPUCZDX8G_ISA1_B4096
type: DPUCZDX8G
isa_version: 1
该方法来自于类:
# <Vitis-AI-2.5/src/Vitis-AI-Quantizer/vai_q_pytorch/pytorch_binding/pytorch_nndct/apis.py>
---
class Inspector(object):
def __init__(self, name_or_fingerprint: str):
两种创建方法:
inspector = Inspector("0X101000016010407") # by target fingerprint
# or
inspector = Inspector("DPUCZDX8G_ISA1_B4096") # by target name
目标指纹(target fingerprint)是 Vitis AI 框架中用于表征不同 DPU 目标的唯一标识符。它由1个字节表示 DPU 类型、1个字节表示 ISA 版本、6个字节表示具体配置组成(feature code)。
2.3 下载模型
model = resnet18(pretrained=True)
model.eval()
当使用 pretrained=True 参数时,模型会自动从网络上下载预训练的权重并将其加载到模型中。 下载完毕,显示如下内容:
Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /home/vitis-ai-user/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth
100% [-------------------------------]44.7M/44.7M [00:06<00:00, 7.62MB/s]
如果使用 model = resnet18(),则不会从网上下载预训练的权重。这种情况下,模型会初始化为随机权重,需要自己进行训练。
训练模式model.train() vs 评估模式model.eval(),这两种模式的主要区别在于某些层(如 Dropout 和 Batch Normalization)的行为不同。
1. Dropout 层:在训练过程中,Dropout 会随机丢弃一部分神经元以防止过拟合。但在评估模式下,Dropout 会被关闭,所有神经元都会被使用。
2. Batch Normalization 层:在评估模式下,Batch Normalization 层会使用在训练过程中计算得到的全局均值和方差,而不是当前 mini-batch 的均值和方差。
3. 梯度计算和权重更新:在评估模式下,模型只进行前向传播,不会计算梯度,也不会更新权重,需要配合 torch.no_grad() 一起使用。
2.4 检查模型
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
dummy_input = torch.randn(1, 3, 224, 224)
inspector.inspect(model, (dummy_input,), device=device, output_dir="inspect", image_format="png")
- device:指定模型在检查过程中的计算设备。
检查完毕,输出:
[VAIQ_NOTE]: =>Start to inspect model...
[VAIQ_NOTE]: =>Quant Module is in 'cpu'.
[VAIQ_NOTE]: =>Parsing ResNet...
[VAIQ_NOTE]: Start to trace model...
[VAIQ_NOTE]: Finish tracing.
[VAIQ_NOTE]: Processing ops...
██████████████████████████████████████████████████| 71/71 [00:00<00:00, 750.15it/s, OpInfo: name = return_0, type = Return]
[VAIQ_NOTE]: =>Doing weights equalization...
[VAIQ_NOTE]: =>Quantizable module is generated.(inspect/ResNet.py)
[VAIQ_NOTE]: All the operators are assigned to the DPU(see more details in 'inspect/inspect_DPUCZDX8G_ISA1_B4096.txt')
[VAIQ_NOTE]: Dot image is generated.(inspect/inspect_DPUCZDX8G_ISA1_B4096.png)
[VAIQ_NOTE]: =>Finish inspecting.
重点关注:All the operators are assigned to the DPU(see more details in 'inspect/inspect_DPUCZDX8G_ISA1_B4096.txt'),所有操作都分配给了DPU。
2.5 分析输出文件txt
-rw-r--r-- 1 xx xx 17607 inspect_DPUCZDX8G_ISA1_B4096.gv
-rw-r--r-- 1 xx xx 1289898 inspect_DPUCZDX8G_ISA1_B4096.png
-rw-r--r-- 1 xx xx 299103 inspect_DPUCZDX8G_ISA1_B4096.txt
-rw-r--r-- 1 xx xx 11791 ResNet.py
我们重点分析 inspect_DPUCZDX8G_ISA1_B4096.txt 文件中的内容。
2.5.1 Self Introduction
在该文件的开始,便有自我介绍,告知本文件所包含的信息内容,翻译后如下:
'inspect.txt' 文件用于显示每个操作在神经网络模型中的所有细节。
字段描述:
- target info: 目标设备信息。
- inspection summary: 检查的总结报告。
- graph name: 表示神经网络模型的图名称。
- node name: 图中节点的名称。
- input nodes: 节点的父节点。
- output nodes: 节点的子节点。
- op type: 操作的类型。
- output shape: 节点输出张量的形状(数据布局遵循XIR的要求)。
- op attributes: 操作的属性(描述与XIR一致)。
- assigned device: 执行操作的设备类型。
- hardware constraints: 如果操作被分配到CPU,此字段将提供一些关于为什么DPU不支持该操作的提示。
- node messages: 此字段将提供有关节点的额外信息(例如,如果量化器需要插入一个permute操作以将数据布局从'NCHW'转换为'NHWC',或从'NCHW'转换为'NHWC'进行部署,这条消息将添加到node_messages中)。
- source range: 指向一个源,该源是一个堆栈跟踪,有助于找到源代码中该操作的确切位置。
2.5.2 Hints
接下来文件展示提示信息,并且举了两个例子。
提示:
由于 Pytorch ('NCHW') 和 XIR ('NHWC') 之间的数据布局差异,如果量化器插入了一些 permute(来自节点消息),这些 permute 可能会阻止整个模型被部署到目标设备。某些情况下,可以通过在原始浮点模型中插入一个 permute 来取消自动插入的 permute,有时则不能。
以下两个示例用于说明这个问题:
示例 1:
Pytorch: conv:[1, 64, 1, 1] -> reshape(shape=(1, -1):[1, 64]
=>
Xmodel: conv:[1, 1, 1, 64] -> permute(order=(0, 3, 1, 2)):[1, 64, 1, 1] -> reshape(shape=(1, -1):[1, 64]
在原始浮点模型中插入一个 permute:
Pytorch: conv:[1, 64, 1, 1] -> permute(order=(0, 2, 3, 1)):[1, 1, 1, 64] -> reshape(shape=(1, -1):[1, 64]
=>
Xmodel: conv:[1, 1, 1, 64] -> reshape(shape=(1, -1):[1, 64]
量化器插入的 permute 可以通过在浮点模型中插入一个 permute 来取消。
修改模型后,输出形状和数据内存布局与之前相同。
示例 2:
Pytorch: conv:[1, 3, 4, 4] -> reshape(shape=(1, -1):[1, 48]
=>
Xmodel: conv:[1, 4, 4, 3] -> permute(order=(0, 3, 1, 2)):[1, 3, 4, 4] -> reshape(shape=(1, -1):[1, 48]
在原始浮点模型中插入一个 permute:
Pytorch: conv:[1, 3, 4, 4] -> permute(order=(0, 2, 3, 1)):[1, 4, 4, 3] -> reshape(shape=(1, -1):[1, 48]
=>
Xmodel: conv:[1, 4, 4, 3] -> reshape(shape=(1, -1):[1, 48]
量化器插入的 permute 不能通过在浮点模型中插入一个 permute 来取消。
修改模型后,输出数据内存布局发生了变化。
2.5.3 Target Info
================================================================================================================================================================
target info:
================================================================================================================================================================
name: "DPUCZDX8G_ISA1_B4096"
type: "DPUCZDX8G" - DPU的类型
isa_version: 1 - 指令集架构版本
feature_code: 369165319 - 特性代码
---------------------------------------------------------------------
- 描述了不同类型的内存组及其配置。
- VB0 和 VB1: 虚拟内存组,每组有8个内存块,每块宽度为16,深度为2048。
- CONVW: 参数内存组,有16个内存块,每块宽度为16,深度为2048。
- DWCONVW 和 BIAS: 参数内存组,各有1个内存块,每块宽度为16,深度为2048。
bank_group {
name: "VB0"
bank_num: 8
bank_width: 16
bank_depth: 2048
type: "Virtual"
}
bank_group {
name: "VB1"
base_id: 8
bank_num: 8
bank_width: 16
bank_depth: 2048
type: "Virtual"
}
bank_group {
name: "CONVW"
base_id: 16
bank_num: 16
bank_width: 16
bank_depth: 2048
type: "Param"
}
bank_group {
name: "DWCONVW"
base_id: 32
bank_num: 1
bank_width: 16
bank_depth: 2048
type: "Param"
}
bank_group {
name: "BIAS"
base_id: 33
bank_num: 1
bank_width: 16
bank_depth: 2048
type: "Param"
}
---------------------------------------------------------------------
load_engine { - 加载引擎的配置
channel_parallel: 16
output_bank: "VB0"
output_bank: "VB1"
}
save_engine { - 保存引擎的配置
channel_parallel: 16
input_bank: "VB0"
input_bank: "VB1"
}
conv_engine { - 卷积引擎的配置
input_channel_parallel: 16
output_channel_parallel: 16
pixel_parallel: 8
input_bank: "VB0"
input_bank: "VB1"
weight_bank: "CONVW" - 权重内存组
bias_bank: "BIAS" - 偏置内存组
channel_augmentation { - 通道增强
channel_num: 32
}
nonlinear { - 非线性激活函数类型
nonlinear_type: relu
nonlinear_type: leaky_relu
nonlinear_type: relu_six
}
output_bank: "VB0"
output_bank: "VB1"
conv_limit { - 卷积限制
kernel_size: "1-16"
stride: "1-8"
stride_out_h: "1-4"
}
}
eltwise_engine { - 逐元素操作引擎
channel_parallel: 16
pixel_parallel: 4
input_bank: "VB0"
input_bank: "VB1"
output_bank: "VB0"
output_bank: "VB1"
nonlinear {
nonlinear_type: relu
}
elew_type: add
elew_type: mult
}
alu_engine { - 算术逻辑单元(ALU)引擎
channel_parallel: 16
pixel_parallel: 4
input_bank: "VB0"
input_bank: "VB1"
output_bank: "VB0"
output_bank: "VB1"
weight_bank: "DWCONVW"
bias_bank: "BIAS"
alu_type: dwconv
alu_type: prelu
alu_type: avg_pool
alu_type: max_pool
alu_type: leaky_relu
alu_type: max_reduce
alu_type: dwconv_no_bias
alu_type: hsigmoid
alu_type: w16b0
nonlinear {
nonlinear_type: relu
nonlinear_type: relu_six
}
alu_limit { - ALU操作限制
kernel_size: "1-256"
stride: "1-256"
stride_out_h: "1-4"
}
pad_limit { - 填充限制
pad_left: "0-15"
pad_right: "0-255"
pad_top: "0-15"
pad_bottom: "0-255"
}
}
2.5.4 Inspection Summary
如果一切顺利:
All the operators are assigned to the DPU.
2.5.5 Node Details
================================================================================================================================================================
node name: ResNet::ResNet/Sequential[layer1]/BasicBlock[0]/Conv2d[conv1]/input.9
input nodes: ['ResNet::ResNet/MaxPool2d[maxpool]/input.7']
output nodes: ['ResNet::ResNet/Sequential[layer1]/BasicBlock[0]/ReLU[relu]/input.13']
op type: conv2d
outputs shape: [(1, 56, 56, 64)]
op attributes:
kernel: [3, 3]
stride: [1, 1]
dilation: [1, 1]
pad_mode: 0
pad: [1, 1, 1, 1]
group: 1
bias_term: True
in_dim: 64
out_dim: 64
assigned device: dpu
source range:
/opt/vitis_ai/conda/envs/vitis-ai-pytorch/lib/python3.7/site-packages/torch/nn/modules/conv.py(443): _conv_forward
/opt/vitis_ai/conda/envs/vitis-ai-pytorch/lib/python3.7/site-packages/torch/nn/modules/conv.py(446): forward
...
3. 其他函数
3.1 查看 torchvision 中模型
在 torchvision.models 包中包含了许多图像方面的深度学习任务模型,包括:
- 图像分类:如 ResNet、VGG、AlexNet 等模型。
- 语义分割:如 FCN、DeepLab 等模型。
- 目标检测:如 Faster R-CNN、RetinaNet 等模型。
- 实例分割:如 Mask R-CNN 等模型。
- 人物关键点检测:如 Keypoint R-CNN 等模型。
- 视频分类:如 R3D、MC3 等模型。
- 光流估计:如 RAFT 等模型。
查看所有 torchvision.models 中的模型的方法:
import torchvision.models as models
dir(models)
---
AlexNet
DenseNet
EfficientNet
GoogLeNet
Inception3
MNASNet
MobileNetV2
MobileNetV3
RegNet
ResNet
ShuffleNetV2
SqueezeNet
VGG
每个模型名称后面可能还有具体的版本号或变种,如 resnet50 或 vgg16_bn,它们指的是同一模型的不同配置或使用批量归一化的版本。
3.2 保存模型
3.2.1 保存模型参数
torch.save(model.state_dict(), "my_model.pth")
- 这条语句只保存模型的参数(weights 和 biases),即模型的状态字典(state_dict)。
- state_dict 是一个 Python 字典,包含了模型中所有可学习参数的名称及其对应的张量值。
- 这种方法适用于只保存模型的参数,而不需要保存模型的结构时。
3.2.2 保存完整模型
torch.save(model, "my_model.pth")
- 这条语句保存整个模型对象,包括模型的结构和参数。
- 这种方法适用于需要保存完整的模型,包括其架构和状态,以便在加载时不需要重新定义模型结构。
检查方法:
print(isinstance(model, torch.nn.Module))
如果 model 继承自 torch.nn.Module,应该输出 True。
3.2.3 加载模型
model = torch.load(PATH)
从指定路径 PATH 加载一个用 torch.save 保存的对象。这个对象可以是一个完整的模型、模型的状态字典(state_dict)、张量或其他数据结构。
torch.load 使用 Python 的反序列化机制(pickle)来加载数据,并且可以指定加载到的设备(例如 CPU 或 GPU)。
4. 总结
本文分享了如何使用 Vitis AI 的 Inspector 工具来检查和优化神经网络模型,以适应特定的硬件设备。通过引入 Inspector 的使用方法,从初始化、模型下载、模型检查到输出文件的解析,每一步都详细说明了操作的意义和背后的理由。特别强调了模型在不同硬件配置下的检查重要性,以及如何通过调整模型结构来适应特定的硬件限制。
文章还探讨了 PyTorch 框架中模型的保存和加载技巧,这对于模型的部署和迁移至关重要。通过这些操作,开发者可以更有效地管理和部署他们的神经网络模型。