前言
本文形容以下知识点:
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_tutorialmdkir data/cifar
下载cifar-10的数据集压缩包,放到data/cifar目录下(也能够本人用迅雷之类的下载,放到同一目录即可)
curl -O https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gzcp 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/trtexectrtexec --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.对于该模型应用随机数推理性能剖析:
以上两个状况阐明:
- 以后模型产生DLA Layer 到GPU layer 的context swicth比拟多;
- 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-repGenerated: /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_sizenetwork = 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_coreif 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 torchimport 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 = 32data = 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的文章。