前言
本文旨在为大家提供jetson嵌入式系列模型部署两个简单的技术路线,直白的说就是给大家安利两个仓库分别是tensorrtx和tensorRT_Pro。本文采用常见的yolov5(v6.0版本)目标检测算法实现在jetson nano上的模型部署工作(PS:手头只有nano,太穷了,😂)。假设各位看官的jetson nano环境配置已经完成,能够使用yolov5成功训练自己的数据集。我们重点关注jetson nano上的部署工作。有错误欢迎各位批评指正!!!
本次训练的模型使用yolov5s-6.0,类别数为1,为口罩识别😷。先看效果图,第一张图为tensorrtx在jetson nano上的推理效果图,可参考Jetson嵌入式系列模型部署-2查看详细流程;第二张图为tensorRT_Pro在jetson nano上的推理效果图,可参考Jetson嵌入式系列模型部署-3查看详细流程。
1. What、Why and How
问题: 什么是深度学习模型部署?为什么需要部署?如何去部署呢?
1.1 What
什么是深度学习模型部署?
简单来说就是将你训练好的深度学习模型应用在不同场景下的不同设备上(即特定环境下运行),这些设备可能是服务器、移动端、嵌入式…
1.2 Why
为什么要学习部署呢?
直接将模型放在不同的设备上跑不就完事了吗😕?其实不然,这样做存在两个问题:模型框架兼容性差以及模型运行速度慢。大家知道目前训练模型都基于深度学习框架如pytorch、tensorflow、paddle 等,这些框架的兼容性差(直白点说就是环境配置麻烦,一想起深度学习环境配置就头痛🙃),而且这些框架基于python语言其运行速度无法和C++这类语言相比。
假设我们需要在jetson nano上去部署属于自己的yolov5模型,难道要求我们在jetson nano上配置pytorch等深度学习环境吗?,那未免也太折磨人了,单纯基于pytorch 等框架去进行模型的推理存在以下几个问题:
- 环境配置繁琐,且不说在arm架构的嵌入式上配置深度学习环境了,光在PC端都要折腾一阵
- 携带的框架太过笨重,训练出的模型太冗余需要优化才能满足实际需求
- 语言问题,框架大多基于python语言,运行速度慢
- 移植问题,框架环境依赖性强,耦合性高,无法更加方便的移植
- 参考自模型部署简介
基于以上问题,我们想能不能将框架隔离呢?即仅通过pytorch、tensorflow、paddle 等框架训练模型,后续在不同场景下的部署实现仅需要训练好的模型即可,而不需要依赖框架推理。
1.3 How
如何去部署?
即解决方案。怎么利用训练好的模型不依赖框架推理呢?——通过模型推理部署框架,依旧是框架不过这次换成了模型推理框架😎。目前主流的模型推理部署框架有以下几种:
- NVIDIA的TensorRT。首当其冲的肯定是tensorRT,NVIDIA通过其自家的GPU,CUDA、CUDNN等软件环境形成了一个强大的生态圈。该推理框架主要是针对NVIDIA的显卡和其推出的jetson系列嵌入式设备。
- Intel的OpenVINO。openvino是Intel开发的基于inter CPU计算设备的推理引擎。
- Tencent的NCNN。ncnn是腾讯基于移动端的推理引擎。
- Microsoft的ONNXRuntime。onnx是microsoft开发的一个中间格式,而ort(onnxruntime)是其为onnx开发的推理引擎
- Rockchip的RKNN。rknn是瑞芯微为其NPU设计的nn推理引擎。
- 参考自业界主流模型推理部署框架,RKNN使用
具体使用那种推理框架呢?—看需求,部署的方式取决于需求。如果需要在jetson系列嵌入式平台上推理,那么选择tensorRT再合适不过了;如果需要在手机移动端推理,那么可以腾讯的ncnn推理框架;能做到见招拆招即可。参考自训练好的深度学习模型式怎么部署的?
2. tensorRT
分享jetson系列嵌入式设备的模型部署,那肯定需要聊聊tensorRT。
2.1 什么是tensorRT?
tensorRT 是一个SDK(Software Development Kit)即软件开发工具包,用于优化经过训练的深度学习模型以实现高性能推理
2.2 tensorRT特性
TensorRT为什么能加速推理过程,它是如何优化的?主要体现在以下几个方面:
- 算子融合
Conv+Bias+ReLU -> CBR - 量化
- INT8或FP16以及TF32
- 存储优势、计算优势、通信优势
- 内核自动调整
- 根据不同显卡架构、SM数量、内核频率等选择不同的优化策略以及计算方式,寻找最适合当前架构的计算方式
- Kernel可以更加不同大小的batch和问题的复杂度去选择最合适的算法,TensorRT预先写了很多GPU实现,有一个自动选择的过程
- 动态张量线程WorkSpace
- 多流执行
- 参考自tensorRT如何进行推理加速?
通过tensorRT能够在Nvidia系列GPU上发挥出最好的性能。值得注意的是,tensorRT的模型,需要在目标GPU上以实际运行的方式选择最优算法和配置,也因此tensorRT生成的模型是与其设备强绑定的,与其编译时的trt版本、cuda版本、GPU型号相关联。同时tensorRT支持FP32、FP16、INT8等多种精度,如何查看自身GPU是否支持FP16/INT8精度呢?主要分以下两步
比如说jetson nano算力是5.3,只支持FP32不支持FP16、INT8。
如果想了解关于tensorRT更多细节请查看tensorRT官方文档
2.3 tensorRT工作流程
tensorRT是如何构建模型呢?主要通过两种方式
3. 驾驭tensorRT的几种方案
Copy自详解TensorRT的C++/Python高性能部署,建议看原视频的详细讲解
3.1 repo1 https://github.com/wang-xinyu/tensorrtx
repo1为每个模型写硬代码,流程如下所示
- 使用作者自定义的
gen_wts.py 存储权重 - 使用C++硬代码调用TRT API构建模型结构,加载
gen_wts.py 产生的权重文件 - 优点
- 可以控制每个layer的细节和权重,直接面对TRT API
- 在认为ONNX方案适配性差的前提下,这种方案不存在算子问题,如果存在不支持的算子,可以自行增加插件。灵活性最高
- 这种方案与官方的samples相似度高,有参照
- 作者提供了大量场景模型的硬代码,方便直接使用,受到Yolov5官方引用
- 缺点
- 过于灵活,需要控制的细节太多,对技能要求较高
- 模型构建的方式采用的硬代码,灵活度差。新模型需要自己一个layer一个layer的写C++代码构建,不具有通用性
- 作者提供的推理代码是demo级,到使用阶段时,需要修改太多。可以看作官方的扩展
- 部署时无法查看网络结构进行分析和排查
3.2 repo2 https://github.com/NVIDIA-AI-IOT/torch2trt
repo2为每个算子写Converter,反射Moule.forward捕获输入输出和图结构,流程如下所示
- 作者为pytorch的每一个操作做了
Converter ,为每个操作的forward 反射到自定义函数下 - 通过反射
torch的forward 操作捕获模块的权重,调用Python API 接口实现模型构建 - 优点
- 直接集成Python、Pytorch,可以实现pytorch模型到tensorRT模型的无缝无脑简单转换
- 缺点
- 提供的是Python的方案,并没有C++的方案
- 新的算子需要自己实现Converter,需要维护新的算子库
- 直接用Pytorch转到tensorRT存储的模型是tensorRT模型,如果跨设备则必须在设备上安装pytorch,灵活度不利于部署
- 部署时无法查看网络结构进行分析和排查
3.3 repo3 https://github.com/shouxieai/tensorRT_Pro
repo3基于ONNX路线,提供C++、Python接口,深度定制ONNXParser,低耦合封装,实现常用模型Yolov7、YoloX、Yolov5、Yolov3、Unet、RetinaFace、Arcface、SCRFD、DeepSORT等等。算子由官方维护,模型直接导出,流程如下所示
- 对,就是he,知道别个的优缺点,就知道该怎么设计了
- 优点
- 集成工业级推理方案,支持tensorRT从模型导出到应用到项目中的全部工作
- 案例有Yolov5、YoloX、AlphaPose、RetinaFace、SCRFD、Arcface、DeepSORT,每个应用均为高性能工业级拿来即可用,低耦合
- 具有简单的模型导出方法和onnx问题的解决方案
- 具有简单的模型推理接口,封装tensorRT细节。支持插件
- 支持python接口导出模型和推理接口
- 依赖onnx,pytorch方面有官方支持,tensorRT方面也有官方支持。咱们做的是桥梁。虽然onnx存在各种兼容性问题,搞清楚了,还是可以轻松驾驭它
- 缺点
4. tensorRT_Pro
单独将这个repo拿出来讲是因为里面有些内容值得深挖,该repo基于ONNX路线完成tensorRT模型构建,需要编译protobuf,下面简单聊聊为什么需要编译protobuf、什么是protobuf以及onnx是什么等相关内容
4.1 Protobuf
关于protobuf的相关介绍Copy自赵老师的百度Apollo智能驾驶课程,建议看原视频,关于protobuf的编译请参考here
4.1.1 Protobuf简介
概念
Protobuf 全称Protocol buffers ,是Google 研发的一种跨语言、跨平台的序列化结构的数据格式,是一个灵活的、高效的用于序列化数据的协议
特点
在序列化数据时常用的数据格式还有XML 、JSON 等,相比较而言,Protobuf 更小、效率更高且使用更为便捷,Protobuf 内置编译器protoc ,可以将protobuf文件 编译成C++ 、Python 、Java 、C# 、Go 等多种语言对应的代码,然后可以直接被对应语言使用,轻松实现对数据流的读或写操作而不需要再做特殊解析。
Protobuf 的优点如下:
- 高效——序列化后字节占用空间少,序列化的时间效率高
- 便捷——可以将结构化数据封装为类,使用方便
- 跨语言——支持多种编程语言
- 高兼容性——当数据交互的双方使用同一数据协议,如果一方修改了数据结构,不影响另一方的使用
Protobuf 也有缺点:
4.1.2 基本使用流程
现有需求如下
创建一个protobuf文件,在该文件中声明学生的姓名、身高、年龄…等信息,然后分别使用C++和Python实现学生数据的读写操作。
实现流程如下
-
1.编写proto文件 -
2.编译生成对应的C++或Python文件 -
3.在C++或Python中调用
1.编写proto文件,如下所示
syntax = "proto2"
package person;
message Student{
required string name = 1;
optional unit64 age = 2;
optional double height = 3;
repeated string books = 4;
}
2.编译,指令如下
$ protoc student.proto --cpp_out=./
执行完成后,在当前目录下student.proto 文件会生成student.pb.h 和student.pb.cc ,将.cc 后缀修改为.cpp 可供C++调用
3.C++调用,调用demo如下
#include <student.pb.h>
using namespace std;
using namespace person;
int main(int argc, char const *argv[])
{
person::Student stu;
stu.set_name("zhangsan");
stu.set_age(18);
stu.set_height(1.75);
stu.add_books("c++");
stu.add_books("python");
std::string name = stu.name();
uint64_t age = stu.age();
double height = stu.height();
std::cout << name << " == " << age << " == " << height << std::endl;
for (int i = 0; i < stu.books_size(); i++)
{
std::cout << stu.books(i) << "-";
}
std::cout << std::endl;
return 0;
}
CMakeLists.txt如下
cmake_minimum_required(VERSION 3.0)
project(test)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pthread -std=c++11")
set(CMAKE_BUILD_TYPE Debug)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/workspace)
set(PROTOBUF_DIR "/home/zhlab/protobuf")
include_directories(
${PROTOBUF_DIR}/include
${PROJECT_SOURCE_DIR}/src
)
link_directories(
${PROTOBUF_DIR}/lib
)
add_executable(main ${PROJECT_SOURCE_DIR}/src/test.cpp ${PROJECT_SOURCE_DIR}/src/student.pb.cpp)
# add protobuf
target_link_libraries(main protobuf)
target_link_libraries(main pthread)
编译test.cpp 文件,在workspace/ 文件夹下运行可执行文件,图解如下所示
给出protobuf的demo演示源码下载链接Baidu Drive[password:yolo]
4.2 ONNX
4.2.1 概念
-
onnx的本质是一种protobuf格式文件 -
protobuf通过编译onnx-ml.proto 文件得到onnx-ml.pb.h和onnx-ml.pb.cc 用于C++调用或onnx_ml_pb2.py 用于python调用,如下图所示。如果本地python环境下安装了onnx第三方库,则在该库下可以找到onnx_ml_pb2.py 文件 -
通过编译得到的onnx-ml.pb.cc 和代码就可以操作onnx模型文件,实现对应的增删改 -
onnx-ml.proto 用于描述onnx文件时如何组成的,具有什么结构,它是onnx经常参照的东西,如下是onnx-ml.proto部分内容,参考自https://github.com/shouxieai/tensorRT_Pro/blob/main/onnx/onnx-ml.proto
4.2.2 组成
onnx文件组成如下图所示
- model:表示整个onnx模型,包括图结构和解析器版本、opset版本、导出程序类型
- opset版本即operator版本号即pytorch得op(操作算子)版本
- model.graph:表示图结构,通常是Netron可视化工具中看到的结构
- model.graph.node:表示图结构中所有节点如conv、bn、relu等
- model.graph.initializer:权重数据大都存储在这里
- model.graph.input:模型的输入
- model.graph.output:模型的输出
5. Jetson nano
关于jetson nano刷机就不再赘述了,需要各位看官自行配置好相关环境😄,外网访问较慢,这里提供Jetson nano的几个JetPack镜像下载链接Baidu Drive[password:nano]【更新完毕!!!】(PS:提供4.6和4.6.1两个版本,注意4GB和2GB的区别,不要刷错了),关于Jetson Nano 2GB和4GB的区别可参考链接Jetson NANO是什么?如何选?。(吐槽下这玩意上传忒慢了,超级会员不顶用呀,终于上传完了,折磨!!!),博主使用的jetpack版本为JetPack4.6.1,其详细信息如下所示
6. 结语
本篇博客简单介绍了模型部署的相关工作以及驾驭tensorRT的几种方案。后续通过tesorrtx和tensorRT_Pro两个repo带大家实现在jetson nano上的yolov5模型部署。可参考Jetson嵌入式系列模型部署-2和Jetson嵌入式系列模型部署-3
7. 下载链接
8. 参考
感谢各位看到最后,创作不易,读后有收获的看官请帮忙点个👍??
|