关于gpu:复现笔记-jetsondlatutorial复现笔记

11次阅读

共计 9859 个字符,预计需要花费 25 分钟才能阅读完成。

前言

本文形容以下知识点:
1. 基于 cifar-10 的模型训练,导出,转换为 tensorrt 模型,并应用 Nsight 剖析;
2.tensorRT Python API,并进行数据校准(Calibration)和模型评估(eval)性能;
预计浏览工夫 15 分钟左右,复现工夫 1 小时左右
明天来整顿之前复现 jetson 上的 DLA Core 应用入门资料:jetson_dla_tutorial。
DLA 全称 Deep Learning Accelerator 简略来说是 Nvidia 在嵌入式设施上实现的用于减速神经网络层的硬件外围,相似 RKNN 开发板上的 NPU。算是继 tensor core 当前目前最新的减速 core(当然 Nvidia 卡上减速神经网络的货色很多)。因为 DLA core 是物理外围,目前反对 DLA core 的设施就不像 tensor core 那么多了,能够参看下表:

设施 DLAv1.0 DLAv2.0
jetson xavier nx series 反对 x
jetson orin series x 反对
NVIDIA DRIVE Hyperion x 反对

Nvidia 凭借做硬件的技术劣势,逐渐在降维打击国内外的中小算例嵌入式开发板厂商。Jetson Orin 系列基本上就是老黄一统江湖的野心体现。Jetson 基本上是单位瓦数里能达到的最高算力(当然价格根本也是最高)。在用于 SLAM,主动驾驶方面都有后劲。我隐约感觉这方面仿佛有点能做的货色,故买了 Jetson Agx Orin 和 Jetson Orin nano 开始学习相干的材料。jetson_dla_tutorial 这篇教程绝对简略,包含以下几方面:

  • torch 模型转换 tensorRT 模型
  • Nsight System 做性能剖析
  • DLA core 根底应用

作为 jetson 的在深度学习方面利用的入门资料很不错,进而编写了这篇复现的文章,心愿有谬误的中央,读者不吝惜赐教。

筹备环境和数据集

先说说硬件环境,我应用的是一块 64GB 的 jetson Agx Orin toolkit,算是官网举荐设施。官网教程举荐应用 jetson Orin 系列(jetson Agx Orin 和 Jetson Orin nano),我预计 NVIDIA DRIVE Hyperion 也是能够的。
git clone 我的项目:

git clone https://github.com/NVIDIA-AI-IOT/jetson_dla_tutorial.git
# home/jou/Documents/jetson_dla_tutorial
mdkir data/cifar

下载 cifar-10 的数据集压缩包,放到 data/cifar 目录下(也能够本人用迅雷之类的下载,放到同一目录即可)

curl -O https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz
cp cifar-10-binary.tar.gz /home/jou/Documents/jetson_dla_tutorial/data/cifar

这里将应用 docker 治理环境,docker 镜像能够从 NGC 查问 和拉取(Docker 镜像须要和你应用 Jetson 的 Jetpack 对齐):

sudo docker pull nvcr.io/nvidia/l4t-ml:r35.2.1-py3

启动 docker 时,将我的项目挂载进 docker

sudo docker run -it --rm --runtime nvidia --network host -v /home/jou/Documents/jetson_dla_tutorial:/home/ nvcr.io/nvidia/l4t-ml:r35.2.1-py3

这样根底的工作就筹备好了。接下来,让咱们看看 jetson_dla_tutorial

训练 model_GN 模型

docker 中蕴含所有的 pip 包,所以咱们不须要重下 pip 包。
model_gn 的定义在 models.py 中,构造大抵如下:

        super().__init__()
        self.cnn = nn.Sequential(nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1),
            nn.GroupNorm(8, 64),
            nn.ReLU(),
            nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),
            nn.GroupNorm(8, 128),
            nn.ReLU(),
            nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1),
            nn.GroupNorm(8, 256),
            nn.ReLU(),
            nn.Conv2d(256, 512, kernel_size=3, stride=2, padding=1),
            nn.GroupNorm(8, 512),
            nn.ReLU())
        self.pool = nn.AdaptiveAvgPool2d((1, 1))
        self.linear = nn.Linear(512, 10)

训练该模型则应用 train.py 文件,指令如下:

python3 train.py model_gn --checkpoint_path=data/model_gn.pth

执行 train.py 当前,程序会自动检测是否在 data 目录下蕴含 cifar-10 的数据集,所以之前要把数据集保留在这个目录下。50epoch 训练工夫在 30 分钟左右,本我的项目次要走为了流程,所以能够在 train.py 中把训练 epoch 改成 10,以节省时间。
最初咱们须要定义导出成 onnx 的数据结构,如下:

data = torch.zeros(1, 3, 32, 32).cuda()

torch.onnx.export(model_gn, data, 'model_gn.onnx',
    input_names=['input'],
    output_names=['output'],
    dynamic_axes={'input': {0: 'batch_size'},
        'output': {0: 'batch_size'}
    }
)

应用 pth 文件导出到 onnx 模型:

python3 export.py model_gn data/model_gn.onnx --checkpoint_path=data/model_gn.pth

理论业务中因为模型结构复杂,可能还须要做 onnx-simplfy。这里模型比较简单,就跳过这步了。

model_GN 模型转 trt 模型

将 onnx 模型转换成 trt 模型实质上是通过 trt 来读取 onnx 模型后重构(build)成 trt 模型的过程。次要办法有两种:trtexec 转换工具和 tensorrt Python 接口,上面我将先展现 trtexec 接口的应用:

alias trtexec=/usr/src/tensorrt/bin/trtexec
trtexec --onnx=model_gn.onnx --shapes=input:32x3x32x32 --saveEngine=model_gn.engine --exportProfile=model_gn.json --int8 --useDLACore=0 --allowGPUFallback --useSpinWait --separateProfileRun > model_gn.log

主要参数解释如下:

  • –onnx:读取 onnx 模型
  • –shapes:设定输出模型的 tensor 大小(这里名称要与 netron.app 关上的 onnx 名称对应)
  • –saveEngine:保留 trt 模型地位
  • –exportProfile:应用随机参数做 profle 保留 profile 后果到
  • –int8:量化成 int8 模型
  • –useDLACore = 0:应用 DLA core
  • –allGPUFallback:容许将 DLA 不反对的 layer 转到 tensort 中解决
  • –useSpinWait:让 CPU 被动去做 GPU context 切换
  • –separateProfileRun 启用基准测试中的性能剖析

在上述的 CLI 中,trtexec 为导出的 model_gn 应用随机数填充做了数据基准测试,并且把测试后果保留在 model_gn.json 中,咱们能够剖析 model_gn.json,其中有两局部比拟重要:
1. 对于该模型 GPU layer 比照 DLA layer 分配情况:

2. 对于该模型应用随机数推理性能剖析:

以上两个状况阐明:

  1. 以后模型产生 DLA Layer 到 GPU layer 的 context swicth 比拟多;
  2. model_gn 模型的性能为 357.164qps

然而以上数据的剖析都很粗略,咱们须要更加具体的信息来下一步的优化,比方每层的执行工夫,总共产生多少次 context switch 等,于是,咱们须要引入 Nsight system 来剖析整个过程。
s

Msight System 剖析 model_GN 模型

Nvidia 上的剖析工具有很多,从 Nvpovf 到 MemoryCheck。自 2013 年开始,Nvidia 开始举荐在 Nsight 下的三个次要剖析工具:

  • Nsight Systems: 实用于零碎级剖析(Nvidia Nvprof 也蕴含在其中)
  • Nsight Compute:实用于 Kernel 剖析
  • Nsight Graphic:实用于图像性能剖析

上面咱们通过 CLI 启动 Nsight Systems 中的 nvprofile 工具对 trtexec 的随机数据测试性能过程做剖析

sudo nsys profile --trace=cuda,nvtx,cublas,cudla,cusparse,cudnn,nvmedia --output=model_gn.nvvp /usr/src/tensorrt/bin/trtexec --loadEngine=model_gn.engine --iterations=10 --idleTime=500 --duration=0 --useSpinWait

主要参数解释如下:

  • –trace==cuda,nvtx,cublas,cudla,cusparse,cudnn,nvmedia:定义在调用过程中捕捉哪些事件,cudla 用来捕捉 dla 应用,nvtx 用来捕捉 tensorrt layer 的切换;
  • –output=model_gn.nvvp:输入数据后果;
  • –iterations=10:迭代次数
  • –idleTime=500:在迭代之间插入 500ms 的梗塞,以便咱们更好分辨
  • –useSpinWait:在 context switch 之间退出显式的 CPU 同步

如果你应用 sudo 模式,将在当前目录下生成剖析报告,如果你没应用 sudo 模式,剖析报告将在 /tmp/ 文件夹下生成:

Generating '/tmp/nsys-report-259c.qdstrm'
[1/1] [========================100%] model_gn.nvvp.nsys-rep
Generated:
    /home/jou/Documents/jetson_dla_tutorial/data/model_gn.nvvp.nsys-rep

能够将这个剖析报告拷贝到任何机器上的 Nsight Systems 关上,你能看到如下图:

这个报告粗略有两个局部,右边的蓝笔局部蕴含的是 trtexec 对 trt 模型(engine)的解析过程,右侧局部是应用随机数据填充,对模型进行的 10 次迭代测试(这也体现冷启动的第一次总是会比其余几次慢一点)。咱们选一次迭代的数据放大如下图:

其中灰色的局部应用 cuDLA API,黄色应用 tensorRT API,咱们能够发现,每次解决 GroupNrom 的时候,就会产生 cuDLA context 和 tensorRT context switch。这个模型中产生屡次替换,数据就须要从新传输,这种是比拟影响性能的点;
接下来咱们尝试优化一下。

model_GN 的改良模型 model_BN 模型

咱们把 model_gn 中的所有 GroupNorm 换成 BathNorm,就能失去 model_bn 模型:

class ModelBN(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
        self.cnn = nn.Sequential(nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.Conv2d(256, 512, kernel_size=3, stride=2, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU())

因为这个模型输出和输入构造没有扭转,咱们仍旧应用 model_gn 的办法导出和训练:
训练:

python3 train.py model_gn --checkpoint_path=data/model_gn.pth

导出 onnx 模型:

python3 export.py model_bn data/model_bn.onnx --checkpoint_path=data/model_bn.pth

将 model 导出成 trt 模型:

trtexec --onnx=model_bn.onnx --shapes=input:32x3x32x32 --saveEngine=model_bn.engine --exportProfile=model_bn.json --int8 --useDLACore=0 --allowGPUFallback --useSpinWait --separateProfileRun > model_bn.log

看看 model_bn.log:

[08/31/2023-07:07:49] [I] [TRT] ---------- Layers Running on DLA ----------
[08/31/2023-07:07:49] [I] [TRT] [DlaLayer] {ForeignNode[/cnn/cnn.0/Conv.../cnn/cnn.11/Relu]}
[08/31/2023-07:07:49] [I] [TRT] [DlaLayer] {ForeignNode[/linear/Gemm]}
[08/31/2023-07:07:49] [I] [TRT] ---------- Layers Running on GPU ----------
[08/31/2023-07:07:49] [I] [TRT] [GpuLayer] POOLING: /pool/GlobalAveragePool
[08/31/2023-07:07:49] [I] [TRT] [GpuLayer] SHUFFLE: reshape_after_/linear/Gemm
...
[08/31/2023-07:07:55] [I] 
[08/31/2023-07:07:55] [I] === Performance summary ===
[08/31/2023-07:07:55] [I] Throughput: 1663.19 qps
[08/31/2023-07:07:55] [I] Latency: min = 0.617432 ms, max = 0.829102 ms, mean = 0.634685 ms, median = 0.629639 ms, percentile(90%) = 0.657227 ms, percentile(95%) = 0.675781 ms, percentile(99%) = 0.689697 ms

当初这个样子比 model_gn.log 看起来好多了,咱们再去看看单次迭代中 context 状况,分析模型:

nsys profile --trace=cuda,nvtx,cublas,cudla,cusparse,cudnn,nvmedia --output=model_bn.nvvp /usr/src/tensorrt/bin/trtexec --loadEngine=model_bn.engine --iterations=10 --idleTime=500 --duration=0 --useSpinWait

生成图像:

咱们能够看到,调整模型构造,使得 cuDLA 和 tensorRT 之间的数据交换缩小了不少。缩小了 context switch 的事件,进而缩小总耗时,进步了计算性能。

应用 TensorRT Python API 导出数据

接下来咱们应用 TensorAPI 导出 trt 模型,并且应用原始数据集来校准咱们的 trt 模型。能够参考上面代码片段。
1. 应用 tensorrt 读取 onnx 模型

logger = trt.Logger(trt.Logger.INFO)
builder = trt.Builder(logger)
builder.max_batch_size = args.batch_size
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
parser = trt.OnnxParser(network, logger)

with open(args.onnx, 'rb') as f:
    parser.parse(f.read())

2. 启用 profile 和定义 profile config

profile = builder.create_optimization_profile()
profile.set_shape(
    'input',
    (args.batch_size, 3, 32, 32),
    (args.batch_size, 3, 32, 32),
    (args.batch_size, 3, 32, 32)
)

3. 为 trt 模型启用 DLA,设定 build config

if args.int8:
    config.set_flag(trt.BuilderFlag.INT8)
    config.int8_calibrator = DatasetCalibrator(data, train_dataset)

if args.dla_core is not None:
    config.default_device_type = trt.DeviceType.DLA
    config.DLA_core = args.dla_core

if args.gpu_fallback:
    config.set_flag(trt.BuilderFlag.GPU_FALLBACK)
    
config.add_optimization_profile(profile)
config.set_calibration_profile(profile)

engine = builder.build_serialized_network(network, config)

数据集校准(Calibration)模型

因为咱们曾经有残缺的 cifar-10 数据集,为了谋求 model_bn.engine 的精度,咱们还能够在用数据集对导出的模型进行校准,具体能够参看如下
先设计 calibrator.py 用于生成给 DLA int8 模型的数据集:

import torch
import tensorrt as trt

__all__ = ['DatasetCalibrator']

class DatasetCalibrator(trt.IInt8Calibrator):
    
    def __init__(self, 
            input, dataset, 
            algorithm=trt.CalibrationAlgoType.ENTROPY_CALIBRATION_2):
        super(DatasetCalibrator, self).__init__()
        self.dataset = dataset
        self.algorithm = algorithm
        self.buffer = torch.zeros_like(input).contiguous()
        self.count = 0
        
    def get_batch(self, *args, **kwargs):
        if self.count < len(self.dataset):
            for buffer_idx in range(self.get_batch_size()):
                dataset_idx = self.count % len(self.dataset) # roll around if not multiple of dataset
                image, _ = self.dataset[dataset_idx]
                image = image.to(self.buffer.device)
                self.buffer[buffer_idx].copy_(image)

                self.count += 1
            return [int(self.buffer.data_ptr())]
        else:
            return []
        
    def get_algorithm(self):
        return self.algorithm
    
    def get_batch_size(self):
        return int(self.buffer.shape[0])
    
    def read_calibration_cache(self, *args, **kwargs):
        return None
    
    def write_calibration_cache(self, cache, *args, **kwargs):
        pass

应用该数据集来校准 DLA int8 模型:

transform = transforms.Compose([transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
])

train_dataset = torchvision.datasets.CIFAR10(root=os.path.join(args.data_dir, 'cifar10'), 
    train=True,
    download=True, 
    transform=transform
)

batch_size = 32

data = torch.zeros(batch_size, 3, 32, 32).cuda()

config.int8_calibrator = DatasetCalibrator(data, train_dataset)

最初咱们应用 build.py 来输入 engine 模型:

python3 build.py data/model_bn.onnx --output=data/model_bn.engine --int8 --dla_core=0 --gpu_fallback --batch_size=32

评估(eval)模型的准确性

须要晓得这个模型在新的数据上的成果,咱们须要评估这个模型,评估过程分为以下三步:
1. 通过 torch 创立数据集;
2. 通过 tensorrt 的 python API 读取模型,创立 tensorrt 的 context;
3. 配置 tensorrt 的 runtime 的输出和输入;
具体操作细节能够参考 eval.py,这里因为篇幅关系就不详述了。

总结

这篇文章复现根本没有难度,库根本都在 docker 里,算是入门级别的比拟好的文章。

补充点

1.GPU 上执行程序,都会创立 context 来实现对资源的治理和调配,以及异样解决等状况。event 和 stream 更加细分的归属于 context 的概念。而 handle 代表一个更加具体的指代(相似于内存空间和指针);
2.docker 基于快照理念,docker image 在退出后会复原到快照,除非对 docker images 做 commit,否则所有批改都会失落(批改相似于 git add);
3.DLA core 自身的应用有两种模式:

  • 混合模型:与 trt 混用,config 中容许 GPU_CallBack;
  • 独立模型:模型全副搭载进 DLA core,config 不容许 GPU_CallBack;

本文次要介绍混合模型,至于独立模型的形式,能够参考接下来的对于 cuDLA-sample 的文章。

正文完
 0