欢迎来到 MMDeploy 的中文文档!¶
点击页面左下角切换中英文。
操作概述¶
MMDeploy 提供了一系列工具,帮助您更轻松的将 OpenMMLab 下的算法部署到各种设备与平台上。
您可以使用我们设计的流程一“部”到位,也可以定制您自己的转换流程。
流程简介¶
MMDeploy 定义的模型部署流程,如下图所示:
模型转换(Model Converter)¶
模型转换的主要功能是把输入的模型格式,转换为目标设备的推理引擎所要求的模型格式。
目前,MMDeploy 可以把 PyTorch 模型转换为 ONNX、TorchScript 等和设备无关的 IR 模型。也可以将 ONNX 模型转换为推理后端模型。两者相结合,可实现端到端的模型转换,也就是从训练端到生产端的一键式部署。
MMDeploy 模型(MMDeploy Model)¶
也称 SDK Model。它是模型转换结果的集合。不仅包括后端模型,还包括模型的元信息。这些信息将用于推理 SDK 中。
推理 SDK(Inference SDK)¶
封装了模型的前处理、网络推理和后处理过程。对外提供多语言的模型推理接口。
准备工作¶
对于端到端的模型转换和推理,MMDeploy 依赖 Python 3.6+ 以及 PyTorch 1.8+。
第一步:从官网下载并安装 Miniconda
第二步:创建并激活 conda 环境
conda create --name mmdeploy python=3.8 -y
conda activate mmdeploy
第三步: 参考官方文档并安装 PyTorch
在 GPU 环境下:
conda install pytorch=={pytorch_version} torchvision=={torchvision_version} cudatoolkit={cudatoolkit_version} -c pytorch -c conda-forge
在 CPU 环境下:
conda install pytorch=={pytorch_version} torchvision=={torchvision_version} cpuonly -c pytorch
注解
在 GPU 环境下,请务必保证 {cudatoolkit_version} 和主机的 CUDA Toolkit 版本一致,避免在使用 TensorRT 时,可能引起的版本冲突问题。
安装 MMDeploy¶
pip install -U openmim
mim install "mmcv>=2.0.0rc2"
第二步: 安装 MMDeploy 和 推理引擎
我们推荐用户使用预编译包安装和体验 MMDeploy 功能。目前提供模型转换(trt/ort)以及 SDK 推理的 pypi 预编译包,SDK 的 c/cpp 库可从这里 选择最新版本下载并安装。
目前,MMDeploy 的预编译包支持的平台和设备矩阵如下:
OS-Arch | Device | ONNX Runtime | TensorRT |
---|---|---|---|
Linux-x86_64 | CPU | Y | N/A |
CUDA | Y | Y | |
Windows-x86_64 | CPU | Y | N/A |
CUDA | Y | Y |
注:对于不在上述表格中的软硬件平台,请参考源码安装文档,正确安装和配置 MMDeploy。
以最新的预编译包为例,你可以参考以下命令安装:
Linux-x86_64
# 1. 安装 MMDeploy 模型转换工具(含trt/ort自定义算子)
pip install mmdeploy==1.3.1
# 2. 安装 MMDeploy SDK推理工具
# 根据是否需要GPU推理可任选其一进行下载安装
# 2.1 支持 onnxruntime 推理
pip install mmdeploy-runtime==1.3.1
# 2.2 支持 onnxruntime-gpu tensorrt 推理
pip install mmdeploy-runtime-gpu==1.3.1
# 3. 安装推理引擎
# 3.1 安装推理引擎 TensorRT
# !!! 若要进行 TensorRT 模型的转换以及推理,从 NVIDIA 官网下载 TensorRT-8.2.3.0 CUDA 11.x 安装包并解压到当前目录。
pip install TensorRT-8.2.3.0/python/tensorrt-8.2.3.0-cp38-none-linux_x86_64.whl
pip install pycuda
export TENSORRT_DIR=$(pwd)/TensorRT-8.2.3.0
export LD_LIBRARY_PATH=${TENSORRT_DIR}/lib:$LD_LIBRARY_PATH
# !!! 另外还需要从 NVIDIA 官网下载 cuDNN 8.2.1 CUDA 11.x 安装包并解压到当前目录
export CUDNN_DIR=$(pwd)/cuda
export LD_LIBRARY_PATH=$CUDNN_DIR/lib64:$LD_LIBRARY_PATH
# 3.2 安装推理引擎 ONNX Runtime
# 根据是否需要GPU推理可任选其一进行下载安装
# 3.2.1 onnxruntime
wget https://github.com/microsoft/onnxruntime/releases/download/v1.8.1/onnxruntime-linux-x64-1.8.1.tgz
tar -zxvf onnxruntime-linux-x64-1.8.1.tgz
export ONNXRUNTIME_DIR=$(pwd)/onnxruntime-linux-x64-1.8.1
export LD_LIBRARY_PATH=$ONNXRUNTIME_DIR/lib:$LD_LIBRARY_PATH
# 3.2.2 onnxruntime-gpu
pip install onnxruntime-gpu==1.8.1
wget https://github.com/microsoft/onnxruntime/releases/download/v1.8.1/onnxruntime-linux-x64-gpu-1.8.1.tgz
tar -zxvf onnxruntime-linux-x64-gpu-1.8.1.tgz
export ONNXRUNTIME_DIR=$(pwd)/onnxruntime-linux-x64-gpu-1.8.1
export LD_LIBRARY_PATH=$ONNXRUNTIME_DIR/lib:$LD_LIBRARY_PATH
Windows-x86_64
请阅读 这里,了解 MMDeploy 预编译包在 Windows 平台下的使用方法。
模型转换¶
在准备工作就绪后,我们可以使用 MMDeploy 中的工具 tools/deploy.py
,将 OpenMMLab 的 PyTorch 模型转换成推理后端支持的格式。
对于tools/deploy.py
的使用细节,请参考 如何转换模型。
以 MMDetection 中的 Faster R-CNN
为例,我们可以使用如下命令,将 PyTorch 模型转换为 TenorRT 模型,从而部署到 NVIDIA GPU 上.
# 克隆 mmdeploy 仓库。转换时,需要使用 mmdeploy 仓库中的配置文件,建立转换流水线, `--recursive` 不是必须的
git clone -b main --recursive https://github.com/open-mmlab/mmdeploy.git
# 安装 mmdetection。转换时,需要使用 mmdetection 仓库中的模型配置文件,构建 PyTorch nn module
git clone -b 3.x https://github.com/open-mmlab/mmdetection.git
cd mmdetection
mim install -v -e .
cd ..
# 下载 Faster R-CNN 模型权重
wget -P checkpoints https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth
# 执行转换命令,实现端到端的转换
python mmdeploy/tools/deploy.py \
mmdeploy/configs/mmdet/detection/detection_tensorrt_dynamic-320x320-1344x1344.py \
mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py \
checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth \
mmdetection/demo/demo.jpg \
--work-dir mmdeploy_model/faster-rcnn \
--device cuda \
--dump-info
转换结果被保存在 --work-dir
指向的文件夹中。该文件夹中不仅包含推理后端模型,还包括推理元信息。这些内容的整体被定义为 SDK Model。推理 SDK 将用它进行模型推理。
小技巧
把上述转换命令中的detection_tensorrt_dynamic-320x320-1344x1344.py 换成 detection_onnxruntime_dynamic.py,并修改 –device 为 cpu, 即可以转出 onnx 模型,并用 ONNXRuntime 进行推理
模型推理¶
在转换完成后,你既可以使用 Model Converter 进行推理,也可以使用 Inference SDK。
使用 Model Converter 的推理 API¶
Model Converter 屏蔽了推理后端接口的差异,对其推理 API 进行了统一封装,接口名称为 inference_model
。
以上文中 Faster R-CNN 的 TensorRT 模型为例,你可以使用如下方式进行模型推理工作:
from mmdeploy.apis import inference_model
result = inference_model(
model_cfg='mmdetection/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py',
deploy_cfg='mmdeploy/configs/mmdet/detection/detection_tensorrt_dynamic-320x320-1344x1344.py',
backend_files=['mmdeploy_model/faster-rcnn/end2end.engine'],
img='mmdetection/demo/demo.jpg',
device='cuda:0')
注解
接口中的 model_path 指的是推理引擎文件的路径,比如例子当中end2end.engine文件的路径。路径必须放在 list 中,因为有的推理引擎模型结构和权重是分开存储的。
使用推理 SDK¶
你可以直接运行预编译包中的 demo 程序,输入 SDK Model 和图像,进行推理,并查看推理结果。
wget https://github.com/open-mmlab/mmdeploy/releases/download/v1.3.1/mmdeploy-1.3.1-linux-x86_64-cuda11.8.tar.gz
tar xf mmdeploy-1.3.1-linux-x86_64-cuda11.8
cd mmdeploy-1.3.1-linux-x86_64-cuda11.8
# 运行 python demo
python example/python/object_detection.py cuda ../mmdeploy_model/faster-rcnn ../mmdetection/demo/demo.jpg
# 运行 C/C++ demo
# 根据文件夹内的 README.md 进行编译
./bin/object_detection cuda ../mmdeploy_model/faster-rcnn ../mmdetection/demo/demo.jpg
注解
以上述命令中,输入模型是 SDK Model 的路径(也就是 Model Converter 中 –work-dir 参数),而不是推理引擎文件的路径。 因为 SDK 不仅要获取推理引擎文件,还需要推理元信息(deploy.json, pipeline.json)。它们合在一起,构成 SDK Model,存储在 –work-dir 下
除了 demo 程序,预编译包还提供了 SDK 多语言接口。你可以根据自己的项目需求,选择合适的语言接口, 把 MMDeploy SDK 集成到自己的项目中,进行二次开发。
Python API¶
对于检测功能,你也可以参考如下代码,集成 MMDeploy SDK Python API 到自己的项目中:
from mmdeploy_runtime import Detector
import cv2
# 读取图片
img = cv2.imread('mmdetection/demo/demo.jpg')
# 创建检测器
detector = Detector(model_path='mmdeploy_models/faster-rcnn', device_name='cuda', device_id=0)
# 执行推理
bboxes, labels, _ = detector(img)
# 使用阈值过滤推理结果,并绘制到原图中
indices = [i for i in range(len(bboxes))]
for index, bbox, label_id in zip(indices, bboxes, labels):
[left, top, right, bottom], score = bbox[0:4].astype(int), bbox[4]
if score < 0.3:
continue
cv2.rectangle(img, (left, top), (right, bottom), (0, 255, 0))
cv2.imwrite('output_detection.png', img)
更多示例,请查阅这里。
C++ API¶
使用 C++ API 进行模型推理的流程符合下面的模式:
以下是具体过程:
#include <cstdlib>
#include <opencv2/opencv.hpp>
#include "mmdeploy/detector.hpp"
int main() {
const char* device_name = "cuda";
int device_id = 0;
// mmdeploy SDK model,以上文中转出的 faster r-cnn 模型为例
std::string model_path = "mmdeploy_model/faster-rcnn";
std::string image_path = "mmdetection/demo/demo.jpg";
// 1. 读取模型
mmdeploy::Model model(model_path);
// 2. 创建预测器
mmdeploy::Detector detector(model, mmdeploy::Device{device_name, device_id});
// 3. 读取图像
cv::Mat img = cv::imread(image_path);
// 4. 应用预测器推理
auto dets = detector.Apply(img);
// 5. 处理推理结果: 此处我们选择可视化推理结果
for (int i = 0; i < dets.size(); ++i) {
const auto& box = dets[i].bbox;
fprintf(stdout, "box %d, left=%.2f, top=%.2f, right=%.2f, bottom=%.2f, label=%d, score=%.4f\n",
i, box.left, box.top, box.right, box.bottom, dets[i].label_id, dets[i].score);
if (dets[i].score < 0.3) {
continue;
}
cv::rectangle(img, cv::Point{(int)box.left, (int)box.top},
cv::Point{(int)box.right, (int)box.bottom}, cv::Scalar{0, 255, 0});
}
cv::imwrite("output_detection.png", img);
return 0;
}
在您的项目CMakeLists中,增加:
find_package(MMDeploy REQUIRED)
target_link_libraries(${name} PRIVATE mmdeploy ${OpenCV_LIBS})
编译时,使用 -DMMDeploy_DIR,传入MMDeloyConfig.cmake所在的路径。它在预编译包中的sdk/lib/cmake/MMDeloy下。 更多示例,请查阅此处。
对于 C API、C# API、Java API 的使用方法,请分别阅读代码C demos, C# demos 和 Java demos。 我们将在后续版本中详细讲述它们的用法。
模型精度评估¶
为了测试部署模型的精度,推理效率,我们提供了 tools/test.py
来帮助完成相关工作。以上文中的部署模型为例:
python mmdeploy/tools/test.py \
mmdeploy/configs/detection/detection_tensorrt_dynamic-320x320-1344x1344.py \
mmdetection/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py \
--model mmdeploy_model/faster-rcnn/end2end.engine \
--metrics ${METRICS} \
--device cuda:0
注解
关于 –model 选项,当使用 Model Converter 进行推理时,它代表转换后的推理后端模型的文件路径。而当使用 SDK 测试模型精度时,该选项表示 MMDeploy Model 的路径.
请阅读 如何进行模型评估 了解关于 tools/test.py
的使用细节。
源码手动安装¶
如果网络良好,我们建议使用 docker 或 一键式脚本 方式。
下载¶
git clone -b main git@github.com:open-mmlab/mmdeploy.git --recursive
FAQ¶
如果由于网络等原因导致拉取仓库子模块失败,可以尝试通过如下指令手动再次安装子模块:
git clone git@github.com:NVIDIA/cub.git third_party/cub cd third_party/cub git checkout c3cceac115 # 返回至 third_party 目录, 克隆 pybind11 cd .. git clone git@github.com:pybind/pybind11.git pybind11 cd pybind11 git checkout 70a58c5 cd .. git clone git@github.com:gabime/spdlog.git spdlog cd spdlog git checkout 9e8e52c048
如果以
SSH
方式git clone
代码失败,您可以尝试使用HTTPS
协议下载代码:git clone -b main https://github.com/open-mmlab/mmdeploy.git MMDeploy cd MMDeploy git submodule update --init --recursive
编译¶
根据您的目标平台,点击如下对应的链接,按照说明编译 MMDeploy
使用 Docker 镜像¶
本文简述如何使用Docker安装mmdeploy
获取镜像¶
为了方便用户,mmdeploy在Docker Hub上提供了多个版本的镜像,例如对于mmdeploy==1.2.0
,
其镜像标签为openmmlab/mmdeploy:ubuntu20.04-cuda11.8-mmdeploy1.2.0
,而最新的镜像标签为openmmlab/mmdeploy:ubuntu20.04-cuda11.8-mmdeploy
。
镜像相关规格信息如下表所示:
Item | Version |
---|---|
OS | Ubuntu20.04 |
CUDA | 11.8 |
CUDNN | 8.9 |
Python | 3.8.10 |
Torch | 2.0.0 |
TorchVision | 0.15.0 |
TorchScript | 2.0.0 |
TensorRT | 8.6.1.6 |
ONNXRuntime | 1.15.1 |
OpenVINO | 2022.3.0 |
ncnn | 20230816 |
openppl | 0.8.1 |
用户可选择一个镜像并运行docker pull
拉取镜像到本地:
export TAG=openmmlab/mmdeploy:ubuntu20.04-cuda11.8-mmdeploy
docker pull $TAG
构建镜像(可选)¶
如果已提供的镜像无法满足要求,用户可修改docker/Release/Dockerfile
并在本地构建镜像。其中,构建参数MMDEPLOY_VERSION
可以是mmdeploy项目的一个标签或者分支。
export MMDEPLOY_VERSION=main
export TAG=mmdeploy-${MMDEPLOY_VERSION}
docker build docker/Release/ -t ${TAG} --build-arg MMDEPLOY_VERSION=${MMDEPLOY_VERSION}
运行 docker 容器¶
当拉取或构建 docker 镜像后,用户可使用 docker run
启动 docker 服务:
export TAG=openmmlab/mmdeploy:ubuntu20.04-cuda11.8-mmdeploy
docker run --gpus=all -it --rm $TAG
常见问答¶
CUDA error: the provided PTX was compiled with an unsupported toolchain:
如 这里所说,更新 GPU 的驱动到您的GPU能使用的最新版本。
docker: Error response from daemon: could not select device driver “” with capabilities: [gpu].
# Add the package repositories distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit sudo systemctl restart docker
一键式脚本安装¶
通过用户调研,我们得知多数使用者在了解 mmdeploy 前,已经熟知 python 和 torch 用法。因此我们提供脚本简化 mmdeploy 安装。
假设您已经准备好
python3 -m pip(必须,conda 或 pyenv 皆可)
nvcc(取决于推理后端)
torch(非必须,可延后安装)
运行这个脚本来安装 mmdeploy + ncnn backend,nproc
可以不指定。
$ cd /path/to/mmdeploy
$ python3 tools/scripts/build_ubuntu_x64_ncnn.py
..
期间可能需要 sudo 密码,脚本会尽最大努力完成 mmdeploy SDK 和 demo:
检测系统版本、make 使用的 job 个数、是否 root 用户,也会自动修复 pip 问题
寻找必须的基础工具,如 g++-7、cmake、wget 等
编译必须的依赖,如 pyncnn、 protobuf
脚本也会尽量避免影响 host 环境:
源码编译的依赖,都放在与 mmdeploy 同级的
mmdeploy-dep
目录中不会主动修改 PATH、LD_LIBRARY_PATH、PYTHONPATH 等变量
会打印需要修改的环境变量,需要注意最终的输出信息
脚本最终会执行 python3 tools/check_env.py
,安装成功应显示对应 backend 的版本号和 ops_is_available: True
,例如:
$ python3 tools/check_env.py
..
2022-09-13 14:49:13,767 - mmdeploy - INFO - **********Backend information**********
2022-09-13 14:49:14,116 - mmdeploy - INFO - onnxruntime: 1.8.0 ops_is_avaliable : True
2022-09-13 14:49:14,131 - mmdeploy - INFO - tensorrt: 8.4.1.5 ops_is_avaliable : True
2022-09-13 14:49:14,139 - mmdeploy - INFO - ncnn: 1.0.20220901 ops_is_avaliable : True
2022-09-13 14:49:14,150 - mmdeploy - INFO - pplnn_is_avaliable: True
..
这是已验证的安装脚本。如果想让 mmdeploy 同时支持多种 backend,每个脚本执行一次即可:
script | OS version |
---|---|
build_ubuntu_x64_ncnn.py | 18.04/20.04 |
build_ubuntu_x64_ort.py | 18.04/20.04 |
build_ubuntu_x64_pplnn.py | 18.04/20.04 |
build_ubuntu_x64_torchscript.py | 18.04/20.04 |
build_ubuntu_x64_tvm.py | 18.04/20.04 |
build_jetson_orin_python38.sh | JetPack5.0 L4T 34.1 |
cmake 编译选项说明¶
选项 | 取值范围 | 缺省值 | 说明 |
---|---|---|---|
MMDEPLOY_SHARED_LIBS | {ON, OFF} | ON | 动态库的编译开关。设置OFF时,编译静态库 |
MMDEPLOY_BUILD_SDK | {ON, OFF} | OFF | MMDeploy SDK 编译开关 |
MMDEPLOY_BUILD_SDK_MONOLITHIC | {ON, OFF} | OFF | 编译生成单个 lib 文件 |
MMDEPLOY_BUILD_TEST | {ON, OFF} | OFF | MMDeploy SDK 的单元测试程序编译开关 |
MMDEPLOY_BUILD_SDK_PYTHON_API | {ON, OFF} | OFF | SDK python package的编译开关 |
MMDEPLOY_BUILD_SDK_CSHARP_API | {ON, OFF} | OFF | SDK C# package的编译开关 |
MMDEPLOY_BUILD_SDK_JAVA_API | {ON, OFF} | OFF | SDK Java package的编译开关 |
MMDEPLOY_BUILD_EXAMPLES | {ON, OFF} | OFF | 是否编译 demo |
MMDEPLOY_SPDLOG_EXTERNAL | {ON, OFF} | OFF | 是否使用系统自带的 spdlog 安装包 |
MMDEPLOY_ZIP_MODEL | {ON, OFF} | OFF | 是否使用 zip 格式的 sdk 目录 |
MMDEPLOY_COVERAGE | {ON, OFF} | OFF | 额外增加编译选项,以生成代码覆盖率报表 |
MMDEPLOY_TARGET_DEVICES | {"cpu", "cuda"} | cpu | 设置目标设备。当有多个设备时,设备名称之间使用分号隔开。 比如,-DMMDEPLOY_TARGET_DEVICES="cpu;cuda" |
MMDEPLOY_TARGET_BACKENDS | {"trt", "ort", "pplnn", "ncnn", "openvino", "torchscript", "snpe", "coreml", "tvm"} | N/A | 默认情况下,SDK不设置任何后端, 因为它与应用场景高度相关。 当选择多个后端时, 中间使用分号隔开。比如,
构建时,几乎每个后端,都需设置一些路径变量,用来查找依赖包。1. trt: 表示 TensorRT。需要设置 TENSORRT_DIR 和 CUDNN_DIR 。
2. ort: 表示 ONNXRuntime。需要设置 ONNXRUNTIME_DIR 。
3. pplnn: 表示 PPL.NN。需要设置 pplnn_DIR 。4. ncnn:表示 ncnn。需要设置 ncnn_DIR 。 5. openvino: 表示 OpenVINO。需要设置 InferenceEngine_DIR 。6. torchscript: 表示 TorchScript。需要设置 Torch_DIR 。7. snpe: 表示 qcom snpe。需要环境变量设置 SNPE_ROOT。 8. coreml: 表示 Core ML。目前在进行模型转换时需要设置 Torch_DIR 。 9. tvm: 表示 TVM。需要设置 TVM_DIR 。 |
MMDEPLOY_CODEBASES | {"mmpretrain", "mmdet", "mmseg", "mmagic", "mmocr", "all"} | all | 用来设置SDK后处理组件,加载 OpenMMLab 算法仓库的后处理功能。如果选择多个 codebase,中间使用分号隔开。比如,-DMMDEPLOY_CODEBASES="mmcls;mmdet" 。也可以通过 -DMMDEPLOY_CODEBASES=all 方式,加载所有 codebase。 |
如何转换模型¶
这篇教程介绍了如何使用 MMDeploy 的工具将一个 OpenMMlab 模型转换成某个后端的模型文件。
注意:
现在已支持的后端包括 ONNXRuntime ,TensorRT ,ncnn ,PPLNN ,OpenVINO。
现在已支持的代码库包括 MMPretrain ,MMDetection ,MMSegmentation ,MMOCR ,MMagic。
如何将模型从pytorch形式转换成其他后端形式¶
准备工作¶
安装您的目标后端。 您可以参考 ONNXRuntime-install ,TensorRT-install ,ncnn-install ,PPLNN-install, OpenVINO-install。
安装您的目标代码库。 您可以参考 MMPretrain-install,MMDetection-install,MMSegmentation-install,MMOCR-install,MMagic-install。
使用方法¶
python ./tools/deploy.py \
${DEPLOY_CFG_PATH} \
${MODEL_CFG_PATH} \
${MODEL_CHECKPOINT_PATH} \
${INPUT_IMG} \
--test-img ${TEST_IMG} \
--work-dir ${WORK_DIR} \
--calib-dataset-cfg ${CALIB_DATA_CFG} \
--device ${DEVICE} \
--log-level INFO \
--show \
--dump-info
参数描述¶
deploy_cfg
: mmdeploy 针对此模型的部署配置,包含推理框架类型、是否量化、输入 shape 是否动态等。配置文件之间可能有引用关系,configs/mmpretrain/classification_ncnn_static.py
是一个示例。model_cfg
: mm 算法库的模型配置,例如mmpretrain/configs/vision_transformer/vit-base-p32_ft-64xb64_in1k-384.py
,与 mmdeploy 的路径无关。checkpoint
: torch 模型路径。可以 http/https 开头,详见mmcv.FileClient
的实现。img
: 模型转换时,用做测试的图像或点云文件路径。--test-img
: 用于测试模型的图像文件路径。默认设置成None
。--work-dir
: 工作目录,用来保存日志和模型文件。--calib-dataset-cfg
: 此参数只有int8模式下生效,用于校准数据集配置文件。若在int8模式下未传入参数,则会自动使用模型配置文件中的’val’数据集进行校准。--device
: 用于模型转换的设备。 默认是cpu
,对于 trt 可使用cuda:0
这种形式。--log-level
: 设置日记的等级,选项包括'CRITICAL', 'FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'
。 默认是INFO
。--show
: 是否显示检测的结果。--dump-info
: 是否输出 SDK 信息。
如何查找pytorch模型对应的部署配置文件¶
在
configs/
文件夹中找到模型对应的代码库文件夹。 例如,转换一个yolov3模型您可以查找到configs/mmdet
文件夹。根据模型的任务类型在
configs/codebase_folder/
下查找对应的文件夹。 例如yolov3模型,您可以查找到configs/mmdet/detection
文件夹。在
configs/codebase_folder/task_folder/
下找到模型的部署配置文件。 例如部署yolov3您可以使用configs/mmdet/detection/detection_onnxruntime_dynamic.py
。
示例¶
python ./tools/deploy.py \
configs/mmdet/detection/detection_tensorrt_dynamic-320x320-1344x1344.py \
$PATH_TO_MMDET/configs/yolo/yolov3_d53_8xb8-ms-608-273e_coco.py \
$PATH_TO_MMDET/checkpoints/yolo/yolov3_d53_mstrain-608_273e_coco_20210518_115020-a2c3acb8.pth \
$PATH_TO_MMDET/demo/demo.jpg \
--work-dir work_dir \
--show \
--device cuda:0
如何评测模型¶
您可以尝试去评测转换出来的模型 ,参考 profile 模型。
如何写模型转换配置¶
这篇教程介绍了如何编写模型转换和部署的配置文件。部署配置文件由ONNX配置
, 代码库配置
, 推理框架配置
组成。
1. 如何编写ONNX配置¶
ONNX 配置描述了如何将PyTorch模型转换为ONNX模型。
ONNX配置参数说明¶
type
: 配置类型。 默认为onnx
。export_params
: 如果指定,将导出模型所有参数。如果您只想导出未训练模型将此项设置为 False。keep_initializers_as_inputs
: 如果为 True,则所有初始化器(通常对应为参数)也将作为输入导出,添加到计算图中。 如果为 False,则初始化器不会作为输入导出,不添加到计算图中,仅将非参数输入添加到计算图中。opset_version
: ONNX的算子集版本,默认为11。save_file
: 输出ONNX模型文件。input_names
: 模型计算图中输入节点的名称。output_names
: 模型计算图中输出节点的名称。input_shape
: 模型输入张量的高度和宽度。
示例¶
onnx_config = dict(
type='onnx',
export_params=True,
keep_initializers_as_inputs=False,
opset_version=11,
save_file='end2end.onnx',
input_names=['input'],
output_names=['output'],
input_shape=None)
2. 如何编写代码库配置¶
代码库主要指OpenMMLab 系列模型代码库,代码库配置由OpenMMLab 系列模型代码库的简称和OpenMMLab 系列模型任务类型组成。
3. 如何编写推理框架配置¶
推理框架配置主要用于指定模型运行在哪个推理框架,并提供模型在推理框架运行时所需的信息,具体参考 ONNX Runtime, TensorRT, ncnn, PPLNN。
type
: 模型推理框架, 包括onnxruntime
,ncnn
,pplnn
,tensorrt
,openvino
。
示例¶
backend_config = dict(
type='tensorrt',
common_config=dict(
fp16_mode=False, max_workspace_size=1 << 30),
model_inputs=[
dict(
input_shapes=dict(
input=dict(
min_shape=[1, 3, 512, 1024],
opt_shape=[1, 3, 1024, 2048],
max_shape=[1, 3, 2048, 2048])))
])
4. 部署配置完整示例¶
这里我们提供了一个以TensorRT为推理框架的基于mmpretrain图像分类任务的完整部署配置示例。
codebase_config = dict(type='mmpretrain', task='Classification')
backend_config = dict(
type='tensorrt',
common_config=dict(
fp16_mode=False,
max_workspace_size=1 << 30),
model_inputs=[
dict(
input_shapes=dict(
input=dict(
min_shape=[1, 3, 224, 224],
opt_shape=[4, 3, 224, 224],
max_shape=[64, 3, 224, 224])))])
onnx_config = dict(
type='onnx',
dynamic_axes={
'input': {
0: 'batch',
2: 'height',
3: 'width'
},
'output': {
0: 'batch'
}
},
export_params=True,
keep_initializers_as_inputs=False,
opset_version=11,
save_file='end2end.onnx',
input_names=['input'],
output_names=['output'],
input_shape=[224, 224])
5. 部署配置文件命名规则¶
我们遵循以下样式来命名配置文件。建议贡献者遵循相同的风格。
(task name)_(backend name)_(dynamic or static).py
task name
: 模型任务类型。backend name
: 推理框架名称。注意:如果您使用了量化,您需要指出量化类型。例如tensorrt-int8
。dynamic or static
: 动态或者静态尺寸导出。 注意:如果推理框架需要明确的形状信息,您需要添加输入大小的描述,格式为高度 x 宽度
。 例如dynamic-512x1024-2048x2048
, 这意味着最小输入形状是512x1024
,最大输入形状是2048x2048
。
示例¶
detection_tensorrt-int8_dynamic-320x320-1344x1344.py
6. 如何编写模型配置文件¶
请根据模型具体任务的代码库,编写模型配置文件。 模型配置文件用于初始化模型,详情请参考MMPretrain,MMDetection, MMSegmentation, MMOCR,MMagic。
如何 Profile 模型¶
模型转换结束后,MMDeploy 提供了 tools/test.py
做为单测工具。
用法¶
python tools/test.py \
${DEPLOY_CFG} \
${MODEL_CFG} \
--model ${BACKEND_MODEL_FILES} \
[--speed-test] \
[--warmup ${WARM_UP}] \
[--log-interval ${LOG_INTERVERL}] \
[--log2file ${LOG_RESULT_TO_FILE}]
参数详解¶
参数 | 说明 |
---|---|
deploy_cfg | 部署配置文件 |
model_cfg | codebase 中的模型配置文件 |
log2file | 保存日志和运行文件的路径 |
speed-test | 是否做速度测试 |
warm-up | 执行前是否 warm-up |
log-interval | 日志打印间隔 |
使用样例¶
执行模型推理
python tools/test.py \
configs/mmpretrain/classification_onnxruntime_static.py \
{MMPRETRAIN_DIR}/configs/resnet/resnet50_b32x8_imagenet.py \
--model model.onnx \
--out out.pkl \
--device cuda:0
profile 速度测试
python tools/test.py \
configs/mmpretrain/classification_onnxruntime_static.py \
{MMPRETRAIN_DIR}/configs/resnet/resnet50_b32x8_imagenet.py \
--model model.onnx \
--speed-test \
--device cpu
如何量化模型¶
为什么要量化¶
相对于 fp32 模型,定点模型有诸多优点:
体积更小,8-bit 模型可降低 75% 文件大小
由于模型变小,Cache 命中率提升,速度更快
芯片往往有对应的定点加速指令,这些指令更快、能耗更低(常见 CPU 上 int8 大约只需要 10% 能量)
安装包体积、发热都是移动端评价 APP 的关键指标;而在服务端,“加速”意味着可以维持相同 QPS、增大模型换取精度提升。
mmdeploy 离线量化方案¶
以 ncnn backend 为例,完整的工作流如下:

mmdeploy 基于静态图(onnx)生成推理框架所需的量化表,再用后端工具把浮点模型转为定点。
目前 mmdeploy 支持 ncnn PTQ。
模型怎么转定点¶
mmdeploy 安装完成后,加载 ppq 并安装
git clone https://github.com/openppl-public/ppq.git
cd ppq
pip install -r requirements.txt
python3 setup.py install
回到 mmdeploy, 使用 tools/deploy.py --quant
选项开启量化。
cd /path/to/mmdeploy
export MODEL_CONFIG=/path/to/mmpretrain/configs/resnet/resnet18_8xb16_cifar10.py
export MODEL_PATH=https://download.openmmlab.com/mmclassification/v0/resnet/resnet18_b16x8_cifar10_20210528-bd6371c8.pth
# 找一些 imagenet 样例图
git clone https://github.com/nihui/imagenet-sample-images --depth=1
# 量化模型
python3 tools/deploy.py configs/mmpretrain/classification_ncnn-int8_static.py ${MODEL_CONFIG} ${MODEL_PATH} /path/to/self-test.png --work-dir work_dir --device cpu --quant --quant-image-dir /path/to/imagenet-sample-images
...
参数说明
参数 | 含义 |
---|---|
--quant | 是否开启量化,默认为 False |
--quant-image-dir | 校准数据集,默认使用 MODEL_CONFIG 中的验证集 |
更多工具介绍¶
除 deploy.py
以外, tools 目录下有很多实用工具
torch2onnx¶
把 OpenMMLab 模型转 onnx 格式。
用法¶
python tools/torch2onnx.py \
${DEPLOY_CFG} \
${MODEL_CFG} \
${CHECKPOINT} \
${INPUT_IMG} \
--work-dir ${WORK_DIR} \
--device cpu \
--log-level INFO
参数说明¶
deploy_cfg
: The path of the deploy config file in MMDeploy codebase.model_cfg
: The path of model config file in OpenMMLab codebase.checkpoint
: The path of the model checkpoint file.img
: The path of the image file used to convert the model.--work-dir
: Directory to save output ONNX models Default is./work-dir
.--device
: The device used for conversion. If not specified, it will be set tocpu
.--log-level
: To set log level which in'CRITICAL', 'FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'
. If not specified, it will be set toINFO
.
extract¶
有 Mark
节点的 onnx 模型会被分成多个子图,这个工具用来提取 onnx 模型中的子图。
用法¶
python tools/extract.py \
${INPUT_MODEL} \
${OUTPUT_MODEL} \
--start ${PARITION_START} \
--end ${PARITION_END} \
--log-level INFO
参数说明¶
input_model
: The path of input ONNX model. The output ONNX model will be extracted from this model.output_model
: The path of output ONNX model.--start
: The start point of extracted model with format<function_name>:<input/output>
. Thefunction_name
comes from the decorator@mark
.--end
: The end point of extracted model with format<function_name>:<input/output>
. Thefunction_name
comes from the decorator@mark
.--log-level
: To set log level which in'CRITICAL', 'FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'
. If not specified, it will be set toINFO
.
注意事项¶
要支持模型分块,必须在 onnx 模型中添加 mark 节点,用@mark
修饰。
下面这个例子里 mark 了 multiclass_nms
,在 NMS 前设置 end=multiclass_nms:input
提取子图。
@mark('multiclass_nms', inputs=['boxes', 'scores'], outputs=['dets', 'labels'])
def multiclass_nms(*args, **kwargs):
"""Wrapper function for `_multiclass_nms`."""
onnx2pplnn¶
这个工具可以把 onnx 模型转成 pplnn 格式。
用法¶
python tools/onnx2pplnn.py \
${ONNX_PATH} \
${OUTPUT_PATH} \
--device cuda:0 \
--opt-shapes [224,224] \
--log-level INFO
参数说明¶
onnx_path
: The path of theONNX
model to convert.output_path
: The convertedPPLNN
algorithm path in json format.device
: The device of the model during conversion.opt-shapes
: Optimal shapes for PPLNN optimization. The shape of each tensor should be wrap with “[]” or “()” and the shapes of tensors should be separated by “,”.--log-level
: To set log level which in'CRITICAL', 'FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'
. If not specified, it will be set toINFO
.
onnx2tensorrt¶
这个工具把 onnx 转成 trt .engine 格式。
用法¶
python tools/onnx2tensorrt.py \
${DEPLOY_CFG} \
${ONNX_PATH} \
${OUTPUT} \
--device-id 0 \
--log-level INFO \
--calib-file /path/to/file
参数说明¶
deploy_cfg
: The path of the deploy config file in MMDeploy codebase.onnx_path
: The ONNX model path to convert.output
: The path of output TensorRT engine.--device-id
: The device index, default to0
.--calib-file
: The calibration data used to calibrate engine to int8.--log-level
: To set log level which in'CRITICAL', 'FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'
. If not specified, it will be set toINFO
.
onnx2ncnn¶
onnx 转 ncnn
用法¶
python tools/onnx2ncnn.py \
${ONNX_PATH} \
${NCNN_PARAM} \
${NCNN_BIN} \
--log-level INFO
参数说明¶
onnx_path
: The path of theONNX
model to convert from.output_param
: The convertedncnn
param path.output_bin
: The convertedncnn
bin path.--log-level
: To set log level which in'CRITICAL', 'FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'
. If not specified, it will be set toINFO
.
profiler¶
这个工具用来测试 torch 和 trt 等后端的速度,注意测试不包含前后处理。
用法¶
python tools/profiler.py \
${DEPLOY_CFG} \
${MODEL_CFG} \
${IMAGE_DIR} \
--model ${MODEL} \
--device ${DEVICE} \
--shape ${SHAPE} \
--num-iter ${NUM_ITER} \
--warmup ${WARMUP} \
--cfg-options ${CFG_OPTIONS} \
--batch-size ${BATCH_SIZE} \
--img-ext ${IMG_EXT}
参数说明¶
deploy_cfg
: The path of the deploy config file in MMDeploy codebase.model_cfg
: The path of model config file in OpenMMLab codebase.image_dir
: The directory to image files that used to test the model.--model
: The path of the model to be tested.--shape
: Input shape of the model byHxW
, e.g.,800x1344
. If not specified, it would useinput_shape
from deploy config.--num-iter
: Number of iteration to run inference. Default is100
.--warmup
: Number of iteration to warm-up the machine. Default is10
.--device
: The device type. If not specified, it will be set tocuda:0
.--cfg-options
: Optional key-value pairs to be overrode for model config.--batch-size
: the batch size for test inference. Default is1
. Note that not all models supportbatch_size>1
.--img-ext
: the file extensions for input images fromimage_dir
. Defaults to['.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif']
.
使用举例¶
python tools/profiler.py \
configs/mmpretrain/classification_tensorrt_dynamic-224x224-224x224.py \
../mmpretrain/configs/resnet/resnet18_8xb32_in1k.py \
../mmpretrain/demo/ \
--model work-dirs/mmpretrain/resnet/trt/end2end.engine \
--device cuda \
--shape 224x224 \
--num-iter 100 \
--warmup 10 \
--batch-size 1
输出:
----- Settings:
+------------+---------+
| batch size | 1 |
| shape | 224x224 |
| iterations | 100 |
| warmup | 10 |
+------------+---------+
----- Results:
+--------+------------+---------+
| Stats | Latency/ms | FPS |
+--------+------------+---------+
| Mean | 1.535 | 651.656 |
| Median | 1.665 | 600.569 |
| Min | 1.308 | 764.341 |
| Max | 1.689 | 591.983 |
+--------+------------+---------+
generate_md_table¶
生成mmdeploy支持的后端表。
用法¶
python tools/generate_md_table.py \
${YML_FILE} \
${OUTPUT} \
--backends ${BACKENDS}
参数说明¶
yml_file:
输入 yml 配置路径output:
输出markdown文件路径--backends:
要输出的后端,默认为 onnxruntime tensorrt torchscript pplnn openvino ncnn
使用举例¶
从 mmocr.yml 生成mmdeploy支持的后端表
python tools/generate_md_table.py tests/regression/mmocr.yml tests/regression/mmocr.md --backends onnxruntime tensorrt torchscript pplnn openvino ncnn
输出:
model | task | onnxruntime | tensorrt | torchscript | pplnn | openvino | ncnn |
---|---|---|---|---|---|---|---|
DBNet | TextDetection | Y | Y | Y | Y | Y | Y |
DBNetpp | TextDetection | Y | Y | N | N | Y | Y |
PANet | TextDetection | Y | Y | Y | Y | Y | Y |
PSENet | TextDetection | Y | Y | Y | Y | Y | Y |
TextSnake | TextDetection | Y | Y | Y | N | N | N |
MaskRCNN | TextDetection | Y | Y | Y | N | N | N |
CRNN | TextRecognition | Y | Y | Y | Y | N | Y |
SAR | TextRecognition | Y | N | Y | N | N | N |
SATRN | TextRecognition | Y | Y | Y | N | N | N |
ABINet | TextRecognition | Y | Y | Y | N | N | N |
SDK 使用说明¶
安装 & 使用方法¶
快速开始¶
大多数 ML 模型除了推理外,需要对输入数据进行一些预处理,并对输出进行一些后处理步骤,以获得结构化输出。 MMDeploy sdk 提供了常见的预处理和后处理步骤。 当您使用 MMDeploy 进行模型转换后,您可以直接使用mmdeploy sdk 进行推理。
模型转换¶
可参考 convert model 获得更多信息.
转模型时通过增加 --dump-info
参数得到如下的目录结构(tensorrt)。 如果转换为其他后端,结构会略有不同。其中两个图片为不同后端推理结果
├── deploy.json
├── detail.json
├── pipeline.json
├── end2end.onnx
├── end2end.engine
├── output_pytorch.jpg
└── output_tensorrt.jpg
和SDK相关的文件有:
deploy.json // 模型信息.
pipeline.json // pipeline信息,包括前处理、模型以及后处理.
end2end.engine // 模型文件
SDK 可以直接读取模型目录,也可以读取相关文件打包成 zip 压缩包。 要读取 zip 文件,sdk 在编译时要设置 -DMMDEPLOY_ZIP_MODEL=ON
SDK 推理¶
一般来讲,模型推理包含以下三个部分。
创建 pipeline
读取数据
模型推理
以下使用 classifier
作为例子来展示三个步骤.
创建 pipeline¶
std::string model_path = "/data/resnet"; // or "/data/resnet.zip" if build with `-DMMDEPLOY_ZIP_MODEL=ON`
mmdeploy_model_t model;
mmdeploy_model_create_by_path(model_path, &model);
mmdeploy_classifier_t classifier{};
mmdeploy_classifier_create(model, "cpu", 0, &classifier);
std::string model_path = "/data/resnet.zip"
std::ifstream ifs(model_path, std::ios::binary); // /path/to/zipmodel
ifs.seekg(0, std::ios::end);
auto size = ifs.tellg();
ifs.seekg(0, std::ios::beg);
std::string str(size, '\0'); // binary data, should decrypt if it's encrypted
ifs.read(str.data(), size);
mmdeploy_model_t model;
mmdeploy_model_create(str.data(), size, &model);
mmdeploy_classifier_t classifier{};
mmdeploy_classifier_create(model, "cpu", 0, &classifier);
读取数据¶
cv::Mat img = cv::imread(image_path);
模型推理¶
mmdeploy_classification_t* res{};
int* res_count{};
mmdeploy_classifier_apply(classifier, &mat, 1, &res, &res_count);
Pipeline 速度分析 (profiler)¶
sdk 提供 pipeline 各模块耗时统计功能,默认关闭,若要使用该功能,需要两个步骤:
生成性能数据
分析性能数据
生成性能数据¶
以 C 接口,分类 pipeline 为例。在创建 pipeline 时需要使用带有 context 信息的接口,并在 context 中加入 profiler 信息。 详细代码如下。 正常运行 demo 会在当前目录生成 profiler 数据 profiler_data.txt
。
#include <fstream>
#include <opencv2/imgcodecs/imgcodecs.hpp>
#include <string>
#include "mmdeploy/classifier.h"
int main(int argc, char* argv[]) {
if (argc != 4) {
fprintf(stderr, "usage:\n image_classification device_name dump_model_directory image_path\n");
return 1;
}
auto device_name = argv[1];
auto model_path = argv[2];
auto image_path = argv[3];
cv::Mat img = cv::imread(image_path);
if (!img.data) {
fprintf(stderr, "failed to load image: %s\n", image_path);
return 1;
}
mmdeploy_model_t model{};
mmdeploy_model_create_by_path(model_path, &model);
// create profiler and add it to context
// profiler data will save to profiler_data.txt
mmdeploy_profiler_t profiler{};
mmdeploy_profiler_create("profiler_data.txt", &profiler);
mmdeploy_context_t context{};
mmdeploy_context_create_by_device(device_name, 0, &context);
mmdeploy_context_add(context, MMDEPLOY_TYPE_PROFILER, nullptr, profiler);
mmdeploy_classifier_t classifier{};
int status{};
status = mmdeploy_classifier_create_v2(model, context, &classifier);
if (status != MMDEPLOY_SUCCESS) {
fprintf(stderr, "failed to create classifier, code: %d\n", (int)status);
return 1;
}
mmdeploy_mat_t mat{
img.data, img.rows, img.cols, 3, MMDEPLOY_PIXEL_FORMAT_BGR, MMDEPLOY_DATA_TYPE_UINT8};
// inference loop
for (int i = 0; i < 100; i++) {
mmdeploy_classification_t* res{};
int* res_count{};
status = mmdeploy_classifier_apply(classifier, &mat, 1, &res, &res_count);
mmdeploy_classifier_release_result(res, res_count, 1);
}
mmdeploy_classifier_destroy(classifier);
mmdeploy_model_destroy(model);
mmdeploy_profiler_destroy(profiler);
mmdeploy_context_destroy(context);
return 0;
}
分析性能数据¶
使用脚本可对性能数据进行解析。
python tools/sdk_analyze.py profiler_data.txt
解析结果如下,其中 name 表示节点的名称,n_call表示调用的次数,t_mean 表示平均耗时,t_50% t_90% 表示耗时的百分位数。
+---------------------------+--------+-------+--------+--------+-------+-------+
| name | occupy | usage | n_call | t_mean | t_50% | t_90% |
+===========================+========+=======+========+========+=======+=======+
| ./Pipeline | - | - | 100 | 4.831 | 1.913 | 1.946 |
+---------------------------+--------+-------+--------+--------+-------+-------+
| Preprocess/Compose | - | - | 100 | 0.125 | 0.118 | 0.144 |
+---------------------------+--------+-------+--------+--------+-------+-------+
| LoadImageFromFile | 0.017 | 0.017 | 100 | 0.081 | 0.077 | 0.098 |
+---------------------------+--------+-------+--------+--------+-------+-------+
| Resize | 0.003 | 0.003 | 100 | 0.012 | 0.012 | 0.013 |
+---------------------------+--------+-------+--------+--------+-------+-------+
| CenterCrop | 0.002 | 0.002 | 100 | 0.008 | 0.008 | 0.008 |
+---------------------------+--------+-------+--------+--------+-------+-------+
| Normalize | 0.002 | 0.002 | 100 | 0.009 | 0.009 | 0.009 |
+---------------------------+--------+-------+--------+--------+-------+-------+
| ImageToTensor | 0.002 | 0.002 | 100 | 0.008 | 0.007 | 0.007 |
+---------------------------+--------+-------+--------+--------+-------+-------+
| Collect | 0.001 | 0.001 | 100 | 0.005 | 0.005 | 0.005 |
+---------------------------+--------+-------+--------+--------+-------+-------+
| resnet | 0.968 | 0.968 | 100 | 4.678 | 1.767 | 1.774 |
+---------------------------+--------+-------+--------+--------+-------+-------+
| postprocess | 0.003 | 0.003 | 100 | 0.015 | 0.015 | 0.017 |
+---------------------------+--------+-------+--------+--------+-------+-------+
API Reference¶
C API Reference¶
common.h¶
-
enum mmdeploy_pixel_format_t¶
Values:
-
enumerator MMDEPLOY_PIXEL_FORMAT_BGR¶
-
enumerator MMDEPLOY_PIXEL_FORMAT_RGB¶
-
enumerator MMDEPLOY_PIXEL_FORMAT_GRAYSCALE¶
-
enumerator MMDEPLOY_PIXEL_FORMAT_NV12¶
-
enumerator MMDEPLOY_PIXEL_FORMAT_NV21¶
-
enumerator MMDEPLOY_PIXEL_FORMAT_BGRA¶
-
enumerator MMDEPLOY_PIXEL_FORMAT_COUNT¶
-
enumerator MMDEPLOY_PIXEL_FORMAT_BGR¶
-
enum mmdeploy_data_type_t¶
Values:
-
enumerator MMDEPLOY_DATA_TYPE_FLOAT¶
-
enumerator MMDEPLOY_DATA_TYPE_HALF¶
-
enumerator MMDEPLOY_DATA_TYPE_UINT8¶
-
enumerator MMDEPLOY_DATA_TYPE_INT32¶
-
enumerator MMDEPLOY_DATA_TYPE_COUNT¶
-
enumerator MMDEPLOY_DATA_TYPE_FLOAT¶
-
enum mmdeploy_status_t¶
Values:
-
enumerator MMDEPLOY_SUCCESS¶
-
enumerator MMDEPLOY_E_INVALID_ARG¶
-
enumerator MMDEPLOY_E_NOT_SUPPORTED¶
-
enumerator MMDEPLOY_E_OUT_OF_RANGE¶
-
enumerator MMDEPLOY_E_OUT_OF_MEMORY¶
-
enumerator MMDEPLOY_E_FILE_NOT_EXIST¶
-
enumerator MMDEPLOY_E_FAIL¶
-
enumerator MMDEPLOY_STATUS_COUNT¶
-
enumerator MMDEPLOY_SUCCESS¶
-
typedef struct mmdeploy_device *mmdeploy_device_t¶
-
typedef struct mmdeploy_profiler *mmdeploy_profiler_t¶
-
struct mmdeploy_mat_t¶
Public Members
-
uint8_t *data¶
-
int height¶
-
int width¶
-
int channel¶
-
mmdeploy_pixel_format_t format¶
-
mmdeploy_device_t device¶
-
uint8_t *data¶
-
struct mmdeploy_rect_t¶
-
struct mmdeploy_point_t¶
-
typedef struct mmdeploy_value *mmdeploy_value_t¶
-
typedef struct mmdeploy_context *mmdeploy_context_t¶
-
mmdeploy_value_t mmdeploy_value_copy(mmdeploy_value_t value)¶
Copy value
- 参数
value –
- 返回
-
void mmdeploy_value_destroy(mmdeploy_value_t value)¶
Destroy value
- 参数
value –
-
int mmdeploy_device_create(const char *device_name, int device_id, mmdeploy_device_t *device)¶
Create device handle
- 参数
device_name –
device_id –
device –
- 返回
-
void mmdeploy_device_destroy(mmdeploy_device_t device)¶
Destroy device handle
- 参数
device –
-
int mmdeploy_profiler_create(const char *path, mmdeploy_profiler_t *profiler)¶
Create profiler
- 参数
path – path to save the profile data
profiler – handle for profiler, should be added to context and deleted by mmdeploy_profiler_destroy
- 返回
status of create
-
void mmdeploy_profiler_destroy(mmdeploy_profiler_t profiler)¶
Destroy profiler handle
- 参数
profiler – handle for profiler, profile data will be written to disk after this call
-
int mmdeploy_context_create(mmdeploy_context_t *context)¶
Create context
- 参数
context –
- 返回
-
int mmdeploy_context_create_by_device(const char *device_name, int device_id, mmdeploy_context_t *context)¶
Create context
- 参数
device_name –
device_id –
context –
- 返回
-
void mmdeploy_context_destroy(mmdeploy_context_t context)¶
Destroy context
- 参数
context –
-
int mmdeploy_context_add(mmdeploy_context_t context, mmdeploy_context_type_t type, const char *name, const void *object)¶
Add context object
- 参数
context –
type –
name –
object –
- 返回
-
int mmdeploy_common_create_input(const mmdeploy_mat_t *mats, int mat_count, mmdeploy_value_t *value)¶
Create input value from array of mats
- 参数
mats –
mat_count –
value –
- 返回
executor.h¶
-
typedef mmdeploy_value_t (*mmdeploy_then_fn_t)(mmdeploy_value_t, void*)¶
-
typedef mmdeploy_value_t (*mmdeploy_then_fn_v2_t)(mmdeploy_value_t*, void*)¶
-
typedef int (*mmdeploy_then_fn_v3_t)(mmdeploy_value_t *input, mmdeploy_value_t *output, void*)¶
-
typedef struct mmdeploy_sender *mmdeploy_sender_t¶
-
typedef struct mmdeploy_scheduler *mmdeploy_scheduler_t¶
-
typedef mmdeploy_sender_t (*mmdeploy_let_value_fn_t)(mmdeploy_value_t, void*)¶
-
mmdeploy_scheduler_t mmdeploy_executor_inline()¶
-
mmdeploy_scheduler_t mmdeploy_executor_system_pool()¶
-
mmdeploy_scheduler_t mmdeploy_executor_create_thread_pool(int num_threads)¶
Create a thread pool with the given number of worker threads
- 参数
num_threads – [in]
- 返回
the handle to the created thread pool
-
mmdeploy_scheduler_t mmdeploy_executor_create_thread()¶
-
mmdeploy_scheduler_t mmdeploy_executor_dynamic_batch(mmdeploy_scheduler_t scheduler, int max_batch_size, int timeout)¶
-
int mmdeploy_scheduler_destroy(mmdeploy_scheduler_t scheduler)¶
-
mmdeploy_sender_t mmdeploy_sender_copy(mmdeploy_sender_t input)¶
Create a copy of a copyable sender. Only senders created by mmdeploy_executor_split is copyable for now.
- 参数
input – [in] copyable sender,
- 返回
the sender created, or nullptr if the sender is not copyable
-
int mmdeploy_sender_destroy(mmdeploy_sender_t sender)¶
Destroy a sender, notice that all sender adapters will consume input senders, only unused senders should be destroyed using this function.
- 参数
input – [in]
-
mmdeploy_sender_t mmdeploy_executor_just(mmdeploy_value_t value)¶
Create a sender that sends the provided value.
- 参数
value – [in]
- 返回
created sender
-
mmdeploy_sender_t mmdeploy_executor_schedule(mmdeploy_scheduler_t scheduler)¶
- 参数
scheduler – [in]
- 返回
the sender created
-
mmdeploy_sender_t mmdeploy_executor_transfer_just(mmdeploy_scheduler_t scheduler, mmdeploy_value_t value)¶
-
mmdeploy_sender_t mmdeploy_executor_transfer(mmdeploy_sender_t input, mmdeploy_scheduler_t scheduler)¶
Transfer the execution to the execution agent of the provided scheduler
- 参数
input – [in]
scheduler – [in]
- 返回
the sender created
-
mmdeploy_sender_t mmdeploy_executor_on(mmdeploy_scheduler_t scheduler, mmdeploy_sender_t input)¶
-
mmdeploy_sender_t mmdeploy_executor_then(mmdeploy_sender_t input, mmdeploy_then_fn_t fn, void *context)¶
-
mmdeploy_sender_t mmdeploy_executor_let_value(mmdeploy_sender_t input, mmdeploy_let_value_fn_t fn, void *context)¶
-
mmdeploy_sender_t mmdeploy_executor_split(mmdeploy_sender_t input)¶
Convert the input sender into a sender that is copyable via mmdeploy_sender_copy. Notice that this function doesn’t make the sender multi-shot, it just return a sender that is copyable.
- 参数
input – [in]
- 返回
the sender that is copyable
-
mmdeploy_sender_t mmdeploy_executor_when_all(mmdeploy_sender_t inputs[], int32_t n)¶
-
mmdeploy_sender_t mmdeploy_executor_ensure_started(mmdeploy_sender_t input)¶
-
int mmdeploy_executor_start_detached(mmdeploy_sender_t input)¶
-
mmdeploy_value_t mmdeploy_executor_sync_wait(mmdeploy_sender_t input)¶
-
int mmdeploy_executor_sync_wait_v2(mmdeploy_sender_t input, mmdeploy_value_t *output)¶
-
void mmdeploy_executor_execute(mmdeploy_scheduler_t scheduler, void (*fn)(void*), void *context)¶
model.h¶
-
typedef struct mmdeploy_model *mmdeploy_model_t¶
-
int mmdeploy_model_create_by_path(const char *path, mmdeploy_model_t *model)¶
Create SDK Model instance from given model path.
- 参数
path – [in] model path
model – [out] sdk model instance that must be destroyed by mmdeploy_model_destroy
- 返回
status code of the operation
-
int mmdeploy_model_create(const void *buffer, int size, mmdeploy_model_t *model)¶
Create SDK Model instance from memory.
- 参数
buffer – [in] a linear buffer contains the model information
size – [in] size of
buffer
in bytesmodel – [out] sdk model instance that must be destroyed by mmdeploy_model_destroy
- 返回
status code of the operation
-
void mmdeploy_model_destroy(mmdeploy_model_t model)¶
Destroy model instance.
- 参数
model – [in] sdk model instance created by mmdeploy_model_create_by_path or mmdeploy_model_create
pipeline.h¶
-
typedef struct mmdeploy_pipeline *mmdeploy_pipeline_t¶
-
int mmdeploy_pipeline_create_v3(mmdeploy_value_t config, mmdeploy_context_t context, mmdeploy_pipeline_t *pipeline)¶
Create pipeline
- 参数
config –
context –
pipeline –
- 返回
-
int mmdeploy_pipeline_create_from_model(mmdeploy_model_t model, mmdeploy_context_t context, mmdeploy_pipeline_t *pipeline)¶
Create pipeline from internal pipeline config of the model
- 参数
model –
context –
pipeline –
- 返回
-
int mmdeploy_pipeline_apply(mmdeploy_pipeline_t pipeline, mmdeploy_value_t input, mmdeploy_value_t *output)¶
Apply pipeline.
- 参数
pipeline – [in] handle of the pipeline
input – [in] input value
output – [out] output value
- 返回
status of the operation
-
int mmdeploy_pipeline_apply_async(mmdeploy_pipeline_t pipeline, mmdeploy_sender_t input, mmdeploy_sender_t *output)¶
Apply pipeline asynchronously
- 参数
pipeline – handle of the pipeline
input – input sender that will be consumed by the operation
output – output sender
- 返回
status of the operation
-
void mmdeploy_pipeline_destroy(mmdeploy_pipeline_t pipeline)¶
destroy pipeline
- 参数
pipeline – [in]
classifier.h¶
-
struct mmdeploy_classification_t¶
-
typedef struct mmdeploy_classifier *mmdeploy_classifier_t¶
-
int mmdeploy_classifier_create(mmdeploy_model_t model, const char *device_name, int device_id, mmdeploy_classifier_t *classifier)¶
Create classifier’s handle.
- 参数
model – [in] an instance of mmclassification sdk model created by mmdeploy_model_create_by_path or mmdeploy_model_create in model.h
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
classifier – [out] instance of a classifier, which must be destroyed by mmdeploy_classifier_destroy
- 返回
status of creating classifier’s handle
-
int mmdeploy_classifier_create_by_path(const char *model_path, const char *device_name, int device_id, mmdeploy_classifier_t *classifier)¶
Create classifier’s handle.
- 参数
model_path – [in] path of mmclassification sdk model exported by mmdeploy model converter
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
classifier – [out] instance of a classifier, which must be destroyed by mmdeploy_classifier_destroy
- 返回
status of creating classifier’s handle
-
int mmdeploy_classifier_apply(mmdeploy_classifier_t classifier, const mmdeploy_mat_t *mats, int mat_count, mmdeploy_classification_t **results, int **result_count)¶
Use classifier created by mmdeploy_classifier_create_by_path to get label information of each image in a batch.
- 参数
classifier – [in] classifier’s handle created by mmdeploy_classifier_create_by_path
mats – [in] a batch of images
mat_count – [in] number of images in the batch
results – [out] a linear buffer to save classification results of each image, which must be freed by mmdeploy_classifier_release_result
result_count – [out] a linear buffer with length being
mat_count
to save the number of classification results of each image. It must be released by mmdeploy_classifier_release_result
- 返回
status of inference
-
void mmdeploy_classifier_release_result(mmdeploy_classification_t *results, const int *result_count, int count)¶
Release the inference result buffer created mmdeploy_classifier_apply.
- 参数
results – [in] classification results buffer
result_count – [in]
results
size buffercount – [in] length of
result_count
-
void mmdeploy_classifier_destroy(mmdeploy_classifier_t classifier)¶
Destroy classifier’s handle.
- 参数
classifier – [in] classifier’s handle created by mmdeploy_classifier_create_by_path
-
int mmdeploy_classifier_create_v2(mmdeploy_model_t model, mmdeploy_context_t context, mmdeploy_classifier_t *classifier)¶
Same as mmdeploy_classifier_create, but allows to control execution context of tasks via context.
-
int mmdeploy_classifier_create_input(const mmdeploy_mat_t *mats, int mat_count, mmdeploy_value_t *value)¶
Pack classifier inputs into mmdeploy_value_t.
- 参数
mats – [in] a batch of images
mat_count – [in] number of images in the batch
value – [out] the packed value
- 返回
status of the operation
-
int mmdeploy_classifier_apply_v2(mmdeploy_classifier_t classifier, mmdeploy_value_t input, mmdeploy_value_t *output)¶
Same as mmdeploy_classifier_apply, but input and output are packed in mmdeploy_value_t.
-
int mmdeploy_classifier_apply_async(mmdeploy_classifier_t classifier, mmdeploy_sender_t input, mmdeploy_sender_t *output)¶
Apply classifier asynchronously.
- 参数
classifier – [in] handle of the classifier
input – [in] input sender that will be consumed by the operation
output – [out] output sender
- 返回
status of the operation
-
int mmdeploy_classifier_get_result(mmdeploy_value_t output, mmdeploy_classification_t **results, int **result_count)¶
- 参数
output – [in] output obtained by applying a classifier
results – [out] a linear buffer containing classification results of each image, released by mmdeploy_classifier_release_result
result_count – [out] a linear buffer containing the number of results for each input image, released by mmdeploy_classifier_release_result
- 返回
status of the operation
detector.h¶
-
struct mmdeploy_instance_mask_t¶
-
struct mmdeploy_detection_t¶
-
typedef struct mmdeploy_detector *mmdeploy_detector_t¶
-
int mmdeploy_detector_create(mmdeploy_model_t model, const char *device_name, int device_id, mmdeploy_detector_t *detector)¶
Create detector’s handle.
- 参数
model – [in] an instance of mmdetection sdk model created by mmdeploy_model_create_by_path or mmdeploy_model_create in model.h
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
detector – [out] instance of a detector
- 返回
status of creating detector’s handle
-
int mmdeploy_detector_create_by_path(const char *model_path, const char *device_name, int device_id, mmdeploy_detector_t *detector)¶
Create detector’s handle.
- 参数
model_path – [in] path of mmdetection sdk model exported by mmdeploy model converter
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
detector – [out] instance of a detector
- 返回
status of creating detector’s handle
-
int mmdeploy_detector_apply(mmdeploy_detector_t detector, const mmdeploy_mat_t *mats, int mat_count, mmdeploy_detection_t **results, int **result_count)¶
Apply detector to batch images and get their inference results.
- 参数
detector – [in] detector’s handle created by mmdeploy_detector_create_by_path
mats – [in] a batch of images
mat_count – [in] number of images in the batch
results – [out] a linear buffer to save detection results of each image. It must be released by mmdeploy_detector_release_result
result_count – [out] a linear buffer with length being
mat_count
to save the number of detection results of each image. And it must be released by mmdeploy_detector_release_result
- 返回
status of inference
-
void mmdeploy_detector_release_result(mmdeploy_detection_t *results, const int *result_count, int count)¶
Release the inference result buffer created by mmdeploy_detector_apply.
- 参数
results – [in] detection results buffer
result_count – [in]
results
size buffercount – [in] length of
result_count
-
void mmdeploy_detector_destroy(mmdeploy_detector_t detector)¶
Destroy detector’s handle.
- 参数
detector – [in] detector’s handle created by mmdeploy_detector_create_by_path
-
int mmdeploy_detector_create_v2(mmdeploy_model_t model, mmdeploy_context_t context, mmdeploy_detector_t *detector)¶
Same as mmdeploy_detector_create, but allows to control execution context of tasks via context.
-
int mmdeploy_detector_create_input(const mmdeploy_mat_t *mats, int mat_count, mmdeploy_value_t *input)¶
Pack detector inputs into mmdeploy_value_t.
- 参数
mats – [in] a batch of images
mat_count – [in] number of images in the batch
- 返回
the created value
-
int mmdeploy_detector_apply_v2(mmdeploy_detector_t detector, mmdeploy_value_t input, mmdeploy_value_t *output)¶
Same as mmdeploy_detector_apply, but input and output are packed in mmdeploy_value_t.
-
int mmdeploy_detector_apply_async(mmdeploy_detector_t detector, mmdeploy_sender_t input, mmdeploy_sender_t *output)¶
Apply detector asynchronously.
- 参数
detector – [in] handle to the detector
input – [in] input sender
- 返回
output sender
-
int mmdeploy_detector_get_result(mmdeploy_value_t output, mmdeploy_detection_t **results, int **result_count)¶
Unpack detector output from a mmdeploy_value_t.
- 参数
output – [in] output obtained by applying a detector
results – [out] a linear buffer to save detection results of each image. It must be released by mmdeploy_detector_release_result
result_count – [out] a linear buffer with length number of input images to save the number of detection results of each image. Must be released by mmdeploy_detector_release_result
- 返回
status of the operation
pose_detector.h¶
-
struct mmdeploy_pose_detection_t¶
Public Members
-
mmdeploy_point_t *point¶
keypoint
-
float *score¶
keypoint score
-
int length¶
number of keypoint
-
mmdeploy_point_t *point¶
-
typedef struct mmdeploy_pose_detector *mmdeploy_pose_detector_t¶
-
int mmdeploy_pose_detector_create(mmdeploy_model_t model, const char *device_name, int device_id, mmdeploy_pose_detector_t *detector)¶
Create a pose detector instance.
- 参数
model – [in] an instance of mmpose model created by mmdeploy_model_create_by_path or mmdeploy_model_create in model.h
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
detector – [out] handle of the created pose detector, which must be destroyed by mmdeploy_pose_detector_destroy
- 返回
status code of the operation
-
int mmdeploy_pose_detector_create_by_path(const char *model_path, const char *device_name, int device_id, mmdeploy_pose_detector_t *detector)¶
Create a pose detector instance.
- 参数
model_path – [in] path to pose detection model
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
detector – [out] handle of the created pose detector, which must be destroyed by mmdeploy_pose_detector_destroy
- 返回
status code of the operation
-
int mmdeploy_pose_detector_apply(mmdeploy_pose_detector_t detector, const mmdeploy_mat_t *mats, int mat_count, mmdeploy_pose_detection_t **results)¶
Apply pose detector to a batch of images with full image roi.
- 参数
detector – [in] pose detector’s handle created by mmdeploy_pose_detector_create_by_path
images – [in] a batch of images
count – [in] number of images in the batch
results – [out] a linear buffer contains the pose result, must be release by mmdeploy_pose_detector_release_result
- 返回
status code of the operation
-
int mmdeploy_pose_detector_apply_bbox(mmdeploy_pose_detector_t detector, const mmdeploy_mat_t *mats, int mat_count, const mmdeploy_rect_t *bboxes, const int *bbox_count, mmdeploy_pose_detection_t **results)¶
Apply pose detector to a batch of images supplied with bboxes(roi)
- 参数
detector – [in] pose detector’s handle created by mmdeploy_pose_detector_create_by_path
images – [in] a batch of images
image_count – [in] number of images in the batch
bboxes – [in] bounding boxes(roi) detected by mmdet
bbox_count – [in] number of bboxes of each
images
, must be same length asimages
results – [out] a linear buffer contains the pose result, which has the same length as
bboxes
, must be release by mmdeploy_pose_detector_release_result
- 返回
status code of the operation
-
void mmdeploy_pose_detector_release_result(mmdeploy_pose_detection_t *results, int count)¶
Release result buffer returned by mmdeploy_pose_detector_apply or mmdeploy_pose_detector_apply_bbox.
- 参数
results – [in] result buffer by pose detector
count – [in] length of
result
-
void mmdeploy_pose_detector_destroy(mmdeploy_pose_detector_t detector)¶
destroy pose_detector
- 参数
detector – [in] handle of pose_detector created by mmdeploy_pose_detector_create_by_path or mmdeploy_pose_detector_create
-
int mmdeploy_pose_detector_create_v2(mmdeploy_model_t model, mmdeploy_context_t context, mmdeploy_pose_detector_t *detector)¶
-
int mmdeploy_pose_detector_create_input(const mmdeploy_mat_t *mats, int mat_count, const mmdeploy_rect_t *bboxes, const int *bbox_count, mmdeploy_value_t *value)¶
-
int mmdeploy_pose_detector_apply_v2(mmdeploy_pose_detector_t detector, mmdeploy_value_t input, mmdeploy_value_t *output)¶
-
int mmdeploy_pose_detector_apply_async(mmdeploy_pose_detector_t detector, mmdeploy_sender_t input, mmdeploy_sender_t *output)¶
-
int mmdeploy_pose_detector_get_result(mmdeploy_value_t output, mmdeploy_pose_detection_t **results)¶
pose_tracker.h¶
-
typedef struct mmdeploy_pose_tracker *mmdeploy_pose_tracker_t¶
-
typedef struct mmdeploy_pose_tracker_state *mmdeploy_pose_tracker_state_t¶
-
struct mmdeploy_pose_tracker_param_t¶
Public Members
-
int32_t det_interval¶
-
int32_t det_label¶
-
float det_thr¶
-
float det_min_bbox_size¶
-
float det_nms_thr¶
-
int32_t pose_max_num_bboxes¶
-
float pose_kpt_thr¶
-
int32_t pose_min_keypoints¶
-
float pose_bbox_scale¶
-
float pose_min_bbox_size¶
-
float pose_nms_thr¶
-
float *keypoint_sigmas¶
-
int32_t keypoint_sigmas_size¶
-
float track_iou_thr¶
-
int32_t track_max_missing¶
-
int32_t track_history_size¶
-
float std_weight_position¶
-
float std_weight_velocity¶
-
float smooth_params[3]¶
-
int32_t det_interval¶
-
struct mmdeploy_pose_tracker_target_t¶
Public Members
-
mmdeploy_point_t *keypoints¶
-
int32_t keypoint_count¶
-
float *scores¶
-
mmdeploy_rect_t bbox¶
-
uint32_t target_id¶
-
mmdeploy_point_t *keypoints¶
-
int mmdeploy_pose_tracker_default_params(mmdeploy_pose_tracker_param_t *params)¶
Fill params with default parameters.
- 参数
params – [inout]
- 返回
status of the operation
-
int mmdeploy_pose_tracker_create(mmdeploy_model_t det_model, mmdeploy_model_t pose_model, mmdeploy_context_t context, mmdeploy_pose_tracker_t *pipeline)¶
Create pose tracker pipeline.
- 参数
det_model – [in] detection model object, created by mmdeploy_model_create
pose_model – [in] pose model object
context – [in] context object describing execution environment (device, profiler, etc…), created by mmdeploy_context_create
pipeline – [out] handle of the created pipeline
- 返回
status of the operation
-
void mmdeploy_pose_tracker_destroy(mmdeploy_pose_tracker_t pipeline)¶
Destroy pose tracker pipeline.
- 参数
pipeline – [in]
-
int mmdeploy_pose_tracker_create_state(mmdeploy_pose_tracker_t pipeline, const mmdeploy_pose_tracker_param_t *params, mmdeploy_pose_tracker_state_t *state)¶
Create a tracker state handle corresponds to a video stream.
- 参数
pipeline – [in] handle of a pose tracker pipeline
params – [in] params for creating the tracker state
state – [out] handle of the created tracker state
- 返回
status of the operation
-
void mmdeploy_pose_tracker_destroy_state(mmdeploy_pose_tracker_state_t state)¶
Destroy tracker state.
- 参数
state – [in] handle of the tracker state
-
int mmdeploy_pose_tracker_apply(mmdeploy_pose_tracker_t pipeline, mmdeploy_pose_tracker_state_t *states, const mmdeploy_mat_t *frames, const int32_t *use_detect, int32_t count, mmdeploy_pose_tracker_target_t **results, int32_t **result_count)¶
Apply pose tracker pipeline, notice that this function supports batch operation by feeding arrays of size
count
tostates
,frames
anduse_detect
.- 参数
pipeline – [in] handle of a pose tracker pipeline
states – [in] tracker states handles, array of size
count
frames – [in] input frames of size
count
use_detect – [in] control the use of detector, array of size
count
-1: use params.det_interval, 0: don’t use detector, 1: force use detectorcount – [in] batch size
results – [out] a linear buffer contains the tracked targets of input frames. Should be released by mmdeploy_pose_tracker_release_result
result_count – [out] a linear buffer of size
count
contains the number of tracked targets of the frames. Should be released by mmdeploy_pose_tracker_release_result
- 返回
status of the operation
-
void mmdeploy_pose_tracker_release_result(mmdeploy_pose_tracker_target_t *results, const int32_t *result_count, int count)¶
Release result objects.
- 参数
results – [in]
result_count – [in]
count – [in]
rotated_detector.h¶
-
struct mmdeploy_rotated_detection_t¶
-
typedef struct mmdeploy_rotated_detector *mmdeploy_rotated_detector_t¶
-
int mmdeploy_rotated_detector_create(mmdeploy_model_t model, const char *device_name, int device_id, mmdeploy_rotated_detector_t *detector)¶
Create rotated detector’s handle.
- 参数
model – [in] an instance of mmrotate sdk model created by mmdeploy_model_create_by_path or mmdeploy_model_create in model.h
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
detector – [out] instance of a rotated detector
- 返回
status of creating rotated detector’s handle
-
int mmdeploy_rotated_detector_create_by_path(const char *model_path, const char *device_name, int device_id, mmdeploy_rotated_detector_t *detector)¶
Create rotated detector’s handle.
- 参数
model_path – [in] path of mmrotate sdk model exported by mmdeploy model converter
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
detector – [out] instance of a rotated detector
- 返回
status of creating rotated detector’s handle
-
int mmdeploy_rotated_detector_apply(mmdeploy_rotated_detector_t detector, const mmdeploy_mat_t *mats, int mat_count, mmdeploy_rotated_detection_t **results, int **result_count)¶
Apply rotated detector to batch images and get their inference results.
- 参数
detector – [in] rotated detector’s handle created by mmdeploy_rotated_detector_create_by_path
mats – [in] a batch of images
mat_count – [in] number of images in the batch
results – [out] a linear buffer to save detection results of each image. It must be released by mmdeploy_rotated_detector_release_result
result_count – [out] a linear buffer with length being
mat_count
to save the number of detection results of each image. And it must be released by mmdeploy_rotated_detector_release_result
- 返回
status of inference
-
void mmdeploy_rotated_detector_release_result(mmdeploy_rotated_detection_t *results, const int *result_count)¶
Release the inference result buffer created by mmdeploy_rotated_detector_apply.
- 参数
results – [in] rotated detection results buffer
result_count – [in]
results
size buffer
-
void mmdeploy_rotated_detector_destroy(mmdeploy_rotated_detector_t detector)¶
Destroy rotated detector’s handle.
- 参数
detector – [in] rotated detector’s handle created by mmdeploy_rotated_detector_create_by_path or by mmdeploy_rotated_detector_create
-
int mmdeploy_rotated_detector_create_v2(mmdeploy_model_t model, mmdeploy_context_t context, mmdeploy_rotated_detector_t *detector)¶
Same as mmdeploy_detector_create, but allows to control execution context of tasks via context.
-
int mmdeploy_rotated_detector_create_input(const mmdeploy_mat_t *mats, int mat_count, mmdeploy_value_t *input)¶
Pack rotated detector inputs into mmdeploy_value_t.
- 参数
mats – [in] a batch of images
mat_count – [in] number of images in the batch
- 返回
the created value
-
int mmdeploy_rotated_detector_apply_v2(mmdeploy_rotated_detector_t detector, mmdeploy_value_t input, mmdeploy_value_t *output)¶
Same as mmdeploy_rotated_detector_apply, but input and output are packed in mmdeploy_value_t.
-
int mmdeploy_rotated_detector_apply_async(mmdeploy_rotated_detector_t detector, mmdeploy_sender_t input, mmdeploy_sender_t *output)¶
Apply rotated detector asynchronously.
- 参数
detector – [in] handle to the detector
input – [in] input sender
- 返回
output sender
-
int mmdeploy_rotated_detector_get_result(mmdeploy_value_t output, mmdeploy_rotated_detection_t **results, int **result_count)¶
Unpack rotated detector output from a mmdeploy_value_t.
- 参数
output – [in] output obtained by applying a detector
results – [out] a linear buffer to save detection results of each image. It must be released by mmdeploy_detector_release_result
result_count – [out] a linear buffer with length number of input images to save the number of detection results of each image. Must be released by mmdeploy_detector_release_result
- 返回
status of the operation
segmentor.h¶
-
struct mmdeploy_segmentation_t¶
Public Members
-
int height¶
height of
mask
that equals to the input image’s height
-
int width¶
width of
mask
that equals to the input image’s width
-
int classes¶
the number of labels in
mask
-
int *mask¶
segmentation mask of the input image, in which mask[i * width + j] indicates the label id of pixel at (i, j), this field might be null
-
float *score¶
segmentation score map of the input image in CHW format, in which score[height * width * k + i * width + j] indicates the score of class k at pixel (i, j), this field might be null
-
int height¶
-
typedef struct mmdeploy_segmentor *mmdeploy_segmentor_t¶
-
int mmdeploy_segmentor_create(mmdeploy_model_t model, const char *device_name, int device_id, mmdeploy_segmentor_t *segmentor)¶
Create segmentor’s handle.
- 参数
model – [in] an instance of mmsegmentation sdk model created by mmdeploy_model_create_by_path or mmdeploy_model_create in model.h
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
segmentor – [out] instance of a segmentor, which must be destroyed by mmdeploy_segmentor_destroy
- 返回
status of creating segmentor’s handle
-
int mmdeploy_segmentor_create_by_path(const char *model_path, const char *device_name, int device_id, mmdeploy_segmentor_t *segmentor)¶
Create segmentor’s handle.
- 参数
model_path – [in] path of mmsegmentation sdk model exported by mmdeploy model converter
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
segmentor – [out] instance of a segmentor, which must be destroyed by mmdeploy_segmentor_destroy
- 返回
status of creating segmentor’s handle
-
int mmdeploy_segmentor_apply(mmdeploy_segmentor_t segmentor, const mmdeploy_mat_t *mats, int mat_count, mmdeploy_segmentation_t **results)¶
Apply segmentor to batch images and get their inference results.
- 参数
segmentor – [in] segmentor’s handle created by mmdeploy_segmentor_create_by_path or mmdeploy_segmentor_create
mats – [in] a batch of images
mat_count – [in] number of images in the batch
results – [out] a linear buffer of length
mat_count
to save segmentation result of each image. It must be released by mmdeploy_segmentor_release_result
- 返回
status of inference
-
void mmdeploy_segmentor_release_result(mmdeploy_segmentation_t *results, int count)¶
Release result buffer returned by mmdeploy_segmentor_apply.
- 参数
results – [in] result buffer
count – [in] length of
results
-
void mmdeploy_segmentor_destroy(mmdeploy_segmentor_t segmentor)¶
Destroy segmentor’s handle.
- 参数
segmentor – [in] segmentor’s handle created by mmdeploy_segmentor_create_by_path
-
int mmdeploy_segmentor_create_v2(mmdeploy_model_t model, mmdeploy_context_t context, mmdeploy_segmentor_t *segmentor)¶
-
int mmdeploy_segmentor_create_input(const mmdeploy_mat_t *mats, int mat_count, mmdeploy_value_t *value)¶
-
int mmdeploy_segmentor_apply_v2(mmdeploy_segmentor_t segmentor, mmdeploy_value_t input, mmdeploy_value_t *output)¶
-
int mmdeploy_segmentor_apply_async(mmdeploy_segmentor_t segmentor, mmdeploy_sender_t input, mmdeploy_sender_t *output)¶
-
int mmdeploy_segmentor_get_result(mmdeploy_value_t output, mmdeploy_segmentation_t **results)¶
text_detector.h¶
-
struct mmdeploy_text_detection_t¶
Public Members
-
mmdeploy_point_t bbox[4]¶
a text bounding box of which the vertex are in clock-wise
-
float score¶
-
mmdeploy_point_t bbox[4]¶
-
typedef struct mmdeploy_text_detector *mmdeploy_text_detector_t¶
-
int mmdeploy_text_detector_create(mmdeploy_model_t model, const char *device_name, int device_id, mmdeploy_text_detector_t *detector)¶
Create text-detector’s handle.
- 参数
model – [in] an instance of mmocr text detection model created by mmdeploy_model_create_by_path or mmdeploy_model_create in model.h
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
detector – [out] instance of a text-detector, which must be destroyed by mmdeploy_text_detector_destroy
- 返回
status of creating text-detector’s handle
-
int mmdeploy_text_detector_create_by_path(const char *model_path, const char *device_name, int device_id, mmdeploy_text_detector_t *detector)¶
Create text-detector’s handle.
- 参数
model_path – [in] path to text detection model
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device
detector – [out] instance of a text-detector, which must be destroyed by mmdeploy_text_detector_destroy
- 返回
status of creating text-detector’s handle
-
int mmdeploy_text_detector_apply(mmdeploy_text_detector_t detector, const mmdeploy_mat_t *mats, int mat_count, mmdeploy_text_detection_t **results, int **result_count)¶
Apply text-detector to batch images and get their inference results.
- 参数
detector – [in] text-detector’s handle created by mmdeploy_text_detector_create_by_path
mats – [in] a batch of images
mat_count – [in] number of images in the batch
results – [out] a linear buffer to save text detection results of each image. It must be released by calling mmdeploy_text_detector_release_result
result_count – [out] a linear buffer of length
mat_count
to save the number of detection results of each image. It must be released by mmdeploy_detector_release_result
- 返回
status of inference
-
void mmdeploy_text_detector_release_result(mmdeploy_text_detection_t *results, const int *result_count, int count)¶
Release the inference result buffer returned by mmdeploy_text_detector_apply.
- 参数
results – [in] text detection result buffer
result_count – [in]
results
size buffercount – [in] the length of buffer
result_count
-
void mmdeploy_text_detector_destroy(mmdeploy_text_detector_t detector)¶
Destroy text-detector’s handle.
- 参数
detector – [in] text-detector’s handle created by mmdeploy_text_detector_create_by_path or mmdeploy_text_detector_create
-
int mmdeploy_text_detector_create_v2(mmdeploy_model_t model, mmdeploy_context_t context, mmdeploy_text_detector_t *detector)¶
Same as mmdeploy_text_detector_create, but allows to control execution context of tasks via context.
-
int mmdeploy_text_detector_create_input(const mmdeploy_mat_t *mats, int mat_count, mmdeploy_value_t *input)¶
Pack text-detector inputs into mmdeploy_value_t.
- 参数
mats – [in] a batch of images
mat_count – [in] number of images in the batch
- 返回
the created value
-
int mmdeploy_text_detector_apply_v2(mmdeploy_text_detector_t detector, mmdeploy_value_t input, mmdeploy_value_t *output)¶
Same as mmdeploy_text_detector_apply, but input and output are packed in mmdeploy_value_t.
-
int mmdeploy_text_detector_apply_async(mmdeploy_text_detector_t detector, mmdeploy_sender_t input, mmdeploy_sender_t *output)¶
Apply text-detector asynchronously.
- 参数
detector – [in] handle to the detector
input – [in] input sender that will be consumed by the operation
- 返回
output sender
-
int mmdeploy_text_detector_get_result(mmdeploy_value_t output, mmdeploy_text_detection_t **results, int **result_count)¶
Unpack detector output from a mmdeploy_value_t.
- 参数
output – [in] output sender returned by applying a detector
results – [out] a linear buffer to save detection results of each image. It must be released by mmdeploy_text_detector_release_result
result_count – [out] a linear buffer with length number of input images to save the number of detection results of each image. Must be released by mmdeploy_text_detector_release_result
- 返回
status of the operation
-
typedef int (*mmdeploy_text_detector_continue_t)(mmdeploy_text_detection_t *results, int *result_count, void *context, mmdeploy_sender_t *output)¶
-
int mmdeploy_text_detector_apply_async_v3(mmdeploy_text_detector_t detector, const mmdeploy_mat_t *imgs, int img_count, mmdeploy_sender_t *output)¶
-
int mmdeploy_text_detector_continue_async(mmdeploy_sender_t input, mmdeploy_text_detector_continue_t cont, void *context, mmdeploy_sender_t *output)¶
text_recognizer.h¶
-
struct mmdeploy_text_recognition_t¶
-
typedef struct mmdeploy_text_recognizer *mmdeploy_text_recognizer_t¶
-
int mmdeploy_text_recognizer_create(mmdeploy_model_t model, const char *device_name, int device_id, mmdeploy_text_recognizer_t *recognizer)¶
Create a text recognizer instance.
- 参数
model – [in] an instance of mmocr text recognition model created by mmdeploy_model_create_by_path or mmdeploy_model_create in model.h
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
recognizer – [out] handle of the created text recognizer, which must be destroyed by mmdeploy_text_recognizer_destroy
- 返回
status code of the operation
-
int mmdeploy_text_recognizer_create_by_path(const char *model_path, const char *device_name, int device_id, mmdeploy_text_recognizer_t *recognizer)¶
Create a text recognizer instance.
- 参数
model_path – [in] path to text recognition model
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
recognizer – [out] handle of the created text recognizer, which must be destroyed by mmdeploy_text_recognizer_destroy
- 返回
status code of the operation
-
int mmdeploy_text_recognizer_apply(mmdeploy_text_recognizer_t recognizer, const mmdeploy_mat_t *images, int count, mmdeploy_text_recognition_t **results)¶
Apply text recognizer to a batch of text images.
- 参数
recognizer – [in] text recognizer’s handle created by mmdeploy_text_recognizer_create_by_path
images – [in] a batch of text images
count – [in] number of images in the batch
results – [out] a linear buffer contains the recognized text, must be release by mmdeploy_text_recognizer_release_result
- 返回
status code of the operation
-
int mmdeploy_text_recognizer_apply_bbox(mmdeploy_text_recognizer_t recognizer, const mmdeploy_mat_t *images, int image_count, const mmdeploy_text_detection_t *bboxes, const int *bbox_count, mmdeploy_text_recognition_t **results)¶
Apply text recognizer to a batch of images supplied with text bboxes.
- 参数
recognizer – [in] text recognizer’s handle created by mmdeploy_text_recognizer_create_by_path
images – [in] a batch of text images
image_count – [in] number of images in the batch
bboxes – [in] bounding boxes detected by text detector
bbox_count – [in] number of bboxes of each
images
, must be same length asimages
results – [out] a linear buffer contains the recognized text, which has the same length as
bboxes
, must be release by mmdeploy_text_recognizer_release_result
- 返回
status code of the operation
-
void mmdeploy_text_recognizer_release_result(mmdeploy_text_recognition_t *results, int count)¶
Release result buffer returned by mmdeploy_text_recognizer_apply or mmdeploy_text_recognizer_apply_bbox.
- 参数
results – [in] result buffer by text recognizer
count – [in] length of
result
-
void mmdeploy_text_recognizer_destroy(mmdeploy_text_recognizer_t recognizer)¶
destroy text recognizer
- 参数
recognizer – [in] handle of text recognizer created by mmdeploy_text_recognizer_create_by_path or mmdeploy_text_recognizer_create
-
int mmdeploy_text_recognizer_create_v2(mmdeploy_model_t model, mmdeploy_context_t context, mmdeploy_text_recognizer_t *recognizer)¶
Same as mmdeploy_text_recognizer_create, but allows to control execution context of tasks via context.
-
int mmdeploy_text_recognizer_create_input(const mmdeploy_mat_t *images, int image_count, const mmdeploy_text_detection_t *bboxes, const int *bbox_count, mmdeploy_value_t *output)¶
Pack text-recognizer inputs into mmdeploy_value_t.
- 参数
images – [in] a batch of images
image_count – [in] number of images in the batch
bboxes – [in] bounding boxes detected by text detector
bbox_count – [in] number of bboxes of each
images
, must be same length asimages
- 返回
value created
-
int mmdeploy_text_recognizer_apply_v2(mmdeploy_text_recognizer_t recognizer, mmdeploy_value_t input, mmdeploy_value_t *output)¶
-
int mmdeploy_text_recognizer_apply_async(mmdeploy_text_recognizer_t recognizer, mmdeploy_sender_t input, mmdeploy_sender_t *output)¶
Same as mmdeploy_text_recognizer_apply_bbox, but input and output are packed in mmdeploy_value_t.
-
int mmdeploy_text_recognizer_apply_async_v3(mmdeploy_text_recognizer_t recognizer, const mmdeploy_mat_t *imgs, int img_count, const mmdeploy_text_detection_t *bboxes, const int *bbox_count, mmdeploy_sender_t *output)¶
-
int mmdeploy_text_recognizer_continue_async(mmdeploy_sender_t input, mmdeploy_text_recognizer_continue_t cont, void *context, mmdeploy_sender_t *output)¶
-
int mmdeploy_text_recognizer_get_result(mmdeploy_value_t output, mmdeploy_text_recognition_t **results)¶
Unpack text-recognizer output from a mmdeploy_value_t.
- 参数
output – [in]
results – [out]
- 返回
status of the operation
video_recognizer.h¶
-
struct mmdeploy_video_recognition_t¶
-
struct mmdeploy_video_sample_info_t¶
-
typedef struct mmdeploy_video_recognizer *mmdeploy_video_recognizer_t¶
-
int mmdeploy_video_recognizer_create(mmdeploy_model_t model, const char *device_name, int device_id, mmdeploy_video_recognizer_t *recognizer)¶
Create video recognizer’s handle.
- 参数
model – [in] an instance of mmaction sdk model created by mmdeploy_model_create_by_path or mmdeploy_model_create in model.h
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
recognizer – [out] handle of the created video recognizer, which must be destroyed by mmdeploy_video_recognizer_destroy
- 返回
status of creating video recognizer’s handle
-
int mmdeploy_video_recognizer_create_by_path(const char *model_path, const char *device_name, int device_id, mmdeploy_video_recognizer_t *recognizer)¶
Create a video recognizer instance.
- 参数
model_path – [in] path to video recognition model
device_name – [in] name of device, such as “cpu”, “cuda”, etc.
device_id – [in] id of device.
recognizer – [out] handle of the created video recognizer, which must be destroyed by mmdeploy_video_recognizer_destroy
- 返回
status code of the operation
-
int mmdeploy_video_recognizer_apply(mmdeploy_video_recognizer_t recognizer, const mmdeploy_mat_t *images, const mmdeploy_video_sample_info_t *video_info, int video_count, mmdeploy_video_recognition_t **results, int **result_count)¶
Apply video recognizer to a batch of videos.
- 参数
recognizer – [in] video recognizer’s handle created by mmdeploy_video_recognizer_create_by_path
images – [in] a batch of videos
video_info – [in] video information of each video
video_count – [in] number of videos
results – [out] a linear buffer contains the recognized video, must be release by mmdeploy_video_recognizer_release_result
result_count – [out] a linear buffer with length being
video_count
to save the number of recognition results of each video. It must be released by mmdeploy_video_recognizer_release_result
- 返回
status code of the operation
-
void mmdeploy_video_recognizer_release_result(mmdeploy_video_recognition_t *results, int *result_count, int video_count)¶
Release result buffer returned by mmdeploy_video_recognizer_apply.
- 参数
results – [in] result buffer by video recognizer
result_count – [in]
results
size buffervideo_count – [in] length of
result_count
-
void mmdeploy_video_recognizer_destroy(mmdeploy_video_recognizer_t recognizer)¶
destroy video recognizer
- 参数
recognizer – [in] handle of video recognizer created by mmdeploy_video_recognizer_create_by_path or mmdeploy_video_recognizer_create
-
int mmdeploy_video_recognizer_create_v2(mmdeploy_model_t model, mmdeploy_context_t context, mmdeploy_video_recognizer_t *recognizer)¶
Same as mmdeploy_video_recognizer_create, but allows to control execution context of tasks via context.
-
int mmdeploy_video_recognizer_create_input(const mmdeploy_mat_t *images, const mmdeploy_video_sample_info_t *video_info, int video_count, mmdeploy_value_t *value)¶
Pack video recognizer inputs into mmdeploy_value_t.
- 参数
images – [in] a batch of videos
video_info – [in] video information of each video
video_count – [in] number of videos in the batch
value – [out] created value
- 返回
status code of the operation
-
int mmdeploy_video_recognizer_apply_v2(mmdeploy_video_recognizer_t recognizer, mmdeploy_value_t input, mmdeploy_value_t *output)¶
Apply video recognizer to a batch of videos.
- 参数
input – [in] packed input
output – [out] inference output
- 返回
status code of the operation
-
int mmdeploy_video_recognizer_get_result(mmdeploy_value_t output, mmdeploy_video_recognition_t **results, int **result_count)¶
Apply video recognizer to a batch of videos.
- 参数
output – [in] inference output
results – [out] structured output
result_count – [out] number of each videos
- 返回
status code of the operation
模型支持列表¶
自测完成的 model-backend 组合:
Model config | Codebase | TorchScript | OnnxRuntime | TensorRT | ncnn | PPLNN | OpenVINO | Ascend | RKNN |
---|---|---|---|---|---|---|---|---|---|
RetinaNet | MMDetection | Y | Y | Y | Y | Y | Y | Y | Y |
Faster R-CNN | MMDetection | Y | Y | Y | Y | Y | Y | Y | N |
YOLOv3 | MMDetection | Y | Y | Y | Y | N | Y | Y | Y |
YOLOX | MMDetection | Y | Y | Y | Y | N | Y | N | Y |
FCOS | MMDetection | Y | Y | Y | Y | N | Y | N | N |
FSAF | MMDetection | Y | Y | Y | Y | Y | Y | N | Y |
Mask R-CNN | MMDetection | Y | Y | Y | N | N | Y | N | N |
SSD* | MMDetection | Y | Y | Y | Y | N | Y | N | Y |
FoveaBox | MMDetection | Y | Y | N | N | N | Y | N | N |
ATSS | MMDetection | N | Y | Y | N | N | Y | N | N |
GFL | MMDetection | N | Y | Y | N | ? | Y | N | N |
Cascade R-CNN | MMDetection | N | Y | Y | N | Y | Y | N | N |
Cascade Mask R-CNN | MMDetection | N | Y | Y | N | N | Y | N | N |
Swin Transformer* | MMDetection | N | Y | Y | N | N | Y | N | N |
VFNet | MMDetection | N | N | N | N | N | Y | N | N |
RepPoints | MMDetection | N | N | Y | N | ? | Y | N | N |
DETR | MMDetection | N | Y | Y | N | ? | N | N | N |
CenterNet | MMDetection | N | Y | Y | N | ? | Y | N | N |
SOLO | MMDetection | N | Y | N | N | N | Y | N | N |
SOLOv2 | MMDetection | N | Y | N | N | N | Y | N | N |
ResNet | MMPretrain | Y | Y | Y | Y | Y | Y | Y | Y |
ResNeXt | MMPretrain | Y | Y | Y | Y | Y | Y | Y | Y |
SE-ResNet | MMPretrain | Y | Y | Y | Y | Y | Y | Y | Y |
MobileNetV2 | MMPretrain | Y | Y | Y | Y | Y | Y | Y | Y |
MobileNetV3 | MMPretrain | Y | Y | Y | Y | N | Y | N | N |
ShuffleNetV1 | MMPretrain | Y | Y | Y | Y | Y | Y | Y | Y |
ShuffleNetV2 | MMPretrain | Y | Y | Y | Y | Y | Y | Y | Y |
VisionTransformer | MMPretrain | Y | Y | Y | Y | ? | Y | Y | N |
SwinTransformer | MMPretrain | Y | Y | Y | N | ? | N | ? | N |
MobileOne | MMPretrain | N | Y | Y | N | N | N | N | N |
FCN | MMSegmentation | Y | Y | Y | Y | Y | Y | Y | Y |
PSPNet*static | MMSegmentation | Y | Y | Y | Y | Y | Y | Y | Y |
DeepLabV3 | MMSegmentation | Y | Y | Y | Y | Y | Y | Y | N |
DeepLabV3+ | MMSegmentation | Y | Y | Y | Y | Y | Y | Y | N |
Fast-SCNN*static | MMSegmentation | Y | Y | Y | N | Y | Y | N | Y |
UNet | MMSegmentation | Y | Y | Y | Y | Y | Y | Y | Y |
ANN* | MMSegmentation | Y | Y | Y | N | N | N | N | N |
APCNet | MMSegmentation | Y | Y | Y | Y | N | N | N | Y |
BiSeNetV1 | MMSegmentation | Y | Y | Y | Y | N | Y | N | Y |
BiSeNetV2 | MMSegmentation | Y | Y | Y | Y | N | Y | N | N |
CGNet | MMSegmentation | Y | Y | Y | Y | N | Y | N | Y |
DMNet | MMSegmentation | ? | Y | N | N | N | N | N | N |
DNLNet | MMSegmentation | ? | Y | Y | Y | N | Y | N | N |
EMANet | MMSegmentation | Y | Y | Y | N | N | Y | N | N |
EncNet | MMSegmentation | Y | Y | Y | N | N | Y | N | N |
ERFNet | MMSegmentation | Y | Y | Y | Y | N | Y | N | Y |
FastFCN | MMSegmentation | Y | Y | Y | Y | N | Y | N | N |
GCNet | MMSegmentation | Y | Y | Y | N | N | N | N | N |
ICNet* | MMSegmentation | Y | Y | Y | N | N | Y | N | N |
ISANet*static | MMSegmentation | N | Y | Y | N | N | Y | N | Y |
NonLocal Net | MMSegmentation | ? | Y | Y | Y | N | Y | N | N |
OCRNet | MMSegmentation | ? | Y | Y | Y | N | Y | N | Y |
PointRend | MMSegmentation | Y | Y | Y | N | N | Y | N | N |
Semantic FPN | MMSegmentation | Y | Y | Y | Y | N | Y | N | Y |
STDC | MMSegmentation | Y | Y | Y | Y | N | Y | N | Y |
UPerNet* | MMSegmentation | ? | Y | Y | N | N | N | N | Y |
DANet | MMSegmentation | ? | Y | Y | N | N | N | N | N |
Segmenter *static | MMSegmentation | Y | Y | Y | Y | N | Y | N | N |
SRCNN | MMagic | Y | Y | Y | Y | Y | Y | N | N |
ESRGAN | MMagic | Y | Y | Y | Y | Y | Y | N | N |
SRGAN | MMagic | Y | Y | Y | Y | Y | Y | N | N |
SRResNet | MMagic | Y | Y | Y | Y | Y | Y | N | N |
Real-ESRGAN | MMagic | Y | Y | Y | Y | Y | Y | N | N |
EDSR | MMagic | Y | Y | Y | Y | N | Y | N | N |
RDN | MMagic | Y | Y | Y | Y | Y | Y | N | N |
DBNet | MMOCR | Y | Y | Y | Y | Y | Y | Y | N |
DBNetpp | MMOCR | Y | Y | Y | ? | ? | Y | ? | N |
PANet | MMOCR | Y | Y | Y | Y | ? | Y | Y | N |
PSENet | MMOCR | Y | Y | Y | Y | ? | Y | Y | N |
TextSnake | MMOCR | Y | Y | Y | Y | ? | ? | ? | N |
MaskRCNN | MMOCR | Y | Y | Y | ? | ? | ? | ? | N |
CRNN | MMOCR | Y | Y | Y | Y | Y | N | N | N |
SAR | MMOCR | N | Y | N | N | N | N | N | N |
SATRN | MMOCR | Y | Y | Y | N | N | N | N | N |
ABINet | MMOCR | Y | Y | Y | N | N | N | N | N |
HRNet | MMPose | N | Y | Y | Y | N | Y | N | N |
MSPN | MMPose | N | Y | Y | Y | N | Y | N | N |
LiteHRNet | MMPose | N | Y | Y | N | N | Y | N | N |
Hourglass | MMPose | N | Y | Y | Y | N | Y | N | N |
SimCC | MMPose | N | Y | Y | Y | N | N | N | N |
PointPillars | MMDetection3d | ? | Y | Y | N | N | Y | N | N |
CenterPoint (pillar) | MMDetection3d | ? | Y | Y | N | N | Y | N | N |
RotatedRetinaNet | RotatedDetection | N | Y | Y | N | N | N | N | N |
Oriented RCNN | RotatedDetection | N | Y | Y | N | N | N | N | N |
Gliding Vertex | RotatedDetection | N | N | Y | N | N | N | N | N |
Note¶
Tag:
static: This model only support static export. Please use
static
deploy config, just like $MMDEPLOY_DIR/configs/mmseg/segmentation_tensorrt_static-1024x2048.py.
SSD: When you convert SSD model, you need to use min shape deploy config just like 300x300-512x512 rather than 320x320-1344x1344, for example $MMDEPLOY_DIR/configs/mmdet/detection/detection_tensorrt_dynamic-300x300-512x512.py.
YOLOX: YOLOX with ncnn only supports static shape.
Swin Transformer: For TensorRT, only version 8.4+ is supported.
SAR: Chinese text recognition model is not supported as the protobuf size of ONNX is limited.
精度速度测试结果¶
软硬件环境¶
Ubuntu 18.04
ncnn 20211208
Cuda 11.3
TensorRT 7.2.3.4
Docker 20.10.8
NVIDIA tesla T4 tensor core GPU for TensorRT
速度测试¶
mmpretrain | TensorRT(ms) | PPLNN(ms) | ncnn(ms) | Ascend(ms) | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|
model | spatial | T4 | JetsonNano2GB | Jetson TX2 | T4 | SnapDragon888 | Adreno660 | Ascend310 | |||
fp32 | fp16 | int8 | fp32 | fp16 | fp32 | fp16 | fp32 | fp32 | fp32 | ||
ResNet | 224x224 | 2.97 | 1.26 | 1.21 | 59.32 | 30.54 | 24.13 | 1.30 | 33.91 | 25.93 | 2.49 |
ResNeXt | 224x224 | 4.31 | 1.42 | 1.37 | 88.10 | 49.18 | 37.45 | 1.36 | 133.44 | 69.38 | - |
SE-ResNet | 224x224 | 3.41 | 1.66 | 1.51 | 74.59 | 48.78 | 29.62 | 1.91 | 107.84 | 80.85 | - |
ShuffleNetV2 | 224x224 | 1.37 | 1.19 | 1.13 | 15.26 | 10.23 | 7.37 | 4.69 | 9.55 | 10.66 | - |
mmdet part1 | TensorRT(ms) | PPLNN(ms) | ||||
---|---|---|---|---|---|---|
model | spatial | T4 | Jetson TX2 | T4 | ||
fp32 | fp16 | int8 | fp32 | fp16 | ||
YOLOv3 | 320x320 | 14.76 | 24.92 | 24.92 | - | 18.07 |
SSD-Lite | 320x320 | 8.84 | 9.21 | 8.04 | 1.28 | 19.72 |
RetinaNet | 800x1344 | 97.09 | 25.79 | 16.88 | 780.48 | 38.34 |
FCOS | 800x1344 | 84.06 | 23.15 | 17.68 | - | - |
FSAF | 800x1344 | 82.96 | 21.02 | 13.50 | - | 30.41 |
Faster R-CNN | 800x1344 | 88.08 | 26.52 | 19.14 | 733.81 | 65.40 |
Mask R-CNN | 800x1344 | 104.83 | 58.27 | - | - | 86.80 |
mmdet part2 | ncnn | ||
---|---|---|---|
model | spatial | SnapDragon888 | Adreno660 |
fp32 | fp32 | ||
MobileNetv2-YOLOv3 | 320x320 | 48.57 | 66.55 |
SSD-Lite | 320x320 | 44.91 | 66.19 |
YOLOX | 416x416 | 111.60 | 134.50 |
mmagic | TensorRT(ms) | PPLNN(ms) | ||||
---|---|---|---|---|---|---|
model | spatial | T4 | Jetson TX2 | T4 | ||
fp32 | fp16 | int8 | fp32 | fp16 | ||
ESRGAN | 32x32 | 12.64 | 12.42 | 12.45 | - | 7.67 |
SRCNN | 32x32 | 0.70 | 0.35 | 0.26 | 58.86 | 0.56 |
mmocr | TensorRT(ms) | PPLNN(ms) | ncnn(ms) | ||||
---|---|---|---|---|---|---|---|
model | spatial | T4 | T4 | SnapDragon888 | Adreno660 | ||
fp32 | fp16 | int8 | fp16 | fp32 | fp32 | ||
DBNet | 640x640 | 10.70 | 5.62 | 5.00 | 34.84 | - | - |
CRNN | 32x32 | 1.93 | 1.40 | 1.36 | - | 10.57 | 20.00 |
mmseg | TensorRT(ms) | PPLNN(ms) | ||||
---|---|---|---|---|---|---|
model | spatial | T4 | Jetson TX2 | T4 | ||
fp32 | fp16 | int8 | fp32 | fp16 | ||
FCN | 512x1024 | 128.42 | 23.97 | 18.13 | 1682.54 | 27.00 |
PSPNet | 1x3x512x1024 | 119.77 | 24.10 | 16.33 | 1586.19 | 27.26 |
DeepLabV3 | 512x1024 | 226.75 | 31.80 | 19.85 | - | 36.01 |
DeepLabV3+ | 512x1024 | 151.25 | 47.03 | 50.38 | 2534.96 | 34.80 |
精度测试¶
mmpretrain | PyTorch | TorchScript | ONNX Runtime | TensorRT | PPLNN | Ascend | |||
---|---|---|---|---|---|---|---|---|---|
model | metric | fp32 | fp32 | fp32 | fp32 | fp16 | int8 | fp16 | fp32 |
ResNet-18 | top-1 | 69.90 | 69.90 | 69.88 | 69.88 | 69.86 | 69.86 | 69.86 | 69.91 |
top-5 | 89.43 | 89.43 | 89.34 | 89.34 | 89.33 | 89.38 | 89.34 | 89.43 | |
ResNeXt-50 | top-1 | 77.90 | 77.90 | 77.90 | 77.90 | - | 77.78 | 77.89 | - |
top-5 | 93.66 | 93.66 | 93.66 | 93.66 | - | 93.64 | 93.65 | - | |
SE-ResNet-50 | top-1 | 77.74 | 77.74 | 77.74 | 77.74 | 77.75 | 77.63 | 77.73 | - |
top-5 | 93.84 | 93.84 | 93.84 | 93.84 | 93.83 | 93.72 | 93.84 | - | |
ShuffleNetV1 1.0x | top-1 | 68.13 | 68.13 | 68.13 | 68.13 | 68.13 | 67.71 | 68.11 | - |
top-5 | 87.81 | 87.81 | 87.81 | 87.81 | 87.81 | 87.58 | 87.80 | - | |
ShuffleNetV2 1.0x | top-1 | 69.55 | 69.55 | 69.55 | 69.55 | 69.54 | 69.10 | 69.54 | - |
top-5 | 88.92 | 88.92 | 88.92 | 88.92 | 88.91 | 88.58 | 88.92 | - | |
MobileNet V2 | top-1 | 71.86 | 71.86 | 71.86 | 71.86 | 71.87 | 70.91 | 71.84 | 71.87 |
top-5 | 90.42 | 90.42 | 90.42 | 90.42 | 90.40 | 89.85 | 90.41 | 90.42 | |
Vision Transformer | top-1 | 85.43 | 85.43 | - | 85.43 | 85.42 | - | - | 85.43 |
top-5 | 97.77 | 97.77 | - | 97.77 | 97.76 | - | - | 97.77 | |
Swin Transformer | top-1 | 81.18 | 81.18 | 81.18 | 81.18 | 81.18 | - | - | - |
top-5 | 95.61 | 95.61 | 95.61 | 95.61 | 95.61 | - | - | - | |
EfficientFormer | top-1 | 80.46 | 80.45 | 80.46 | 80.46 | - | - | - | - |
top-5 | 94.99 | 94.98 | 94.99 | 94.99 | - | - | - | - |
mmdet | Pytorch | TorchScript | ONNXRuntime | TensorRT | PPLNN | Ascend | OpenVINO | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
model | task | dataset | metric | fp32 | fp32 | fp32 | fp32 | fp16 | int8 | fp16 | fp32 | fp32 |
YOLOV3 | Object Detection | COCO2017 | box AP | 33.7 | 33.7 | - | 33.5 | 33.5 | 33.5 | - | - | - |
SSD | Object Detection | COCO2017 | box AP | 25.5 | 25.5 | - | 25.5 | 25.5 | - | - | - | - |
RetinaNet | Object Detection | COCO2017 | box AP | 36.5 | 36.4 | - | 36.4 | 36.4 | 36.3 | 36.5 | 36.4 | - |
FCOS | Object Detection | COCO2017 | box AP | 36.6 | - | - | 36.6 | 36.5 | - | - | - | - |
FSAF | Object Detection | COCO2017 | box AP | 37.4 | 37.4 | - | 37.4 | 37.4 | 37.2 | 37.4 | - | - |
CenterNet | Object Detection | COCO2017 | box AP | 25.9 | 26.0 | 26.0 | 26.0 | 25.8 | - | - | - | - |
YOLOX | Object Detection | COCO2017 | box AP | 40.5 | 40.3 | - | 40.3 | 40.3 | 29.3 | - | - | - |
Faster R-CNN | Object Detection | COCO2017 | box AP | 37.4 | 37.3 | - | 37.3 | 37.3 | 37.1 | 37.3 | 37.2 | - |
ATSS | Object Detection | COCO2017 | box AP | 39.4 | - | - | 39.4 | 39.4 | - | - | - | - |
Cascade R-CNN | Object Detection | COCO2017 | box AP | 40.4 | - | - | 40.4 | 40.4 | - | 40.4 | - | - |
GFL | Object Detection | COCO2017 | box AP | 40.2 | - | 40.2 | 40.2 | 40.0 | - | - | - | - |
RepPoints | Object Detection | COCO2017 | box AP | 37.0 | - | - | 36.9 | - | - | - | - | - |
DETR | Object Detection | COCO2017 | box AP | 40.1 | 40.1 | - | 40.1 | 40.1 | - | - | - | - |
Mask R-CNN | Instance Segmentation | COCO2017 | box AP | 38.2 | 38.1 | - | 38.1 | 38.1 | - | 38.0 | - | - |
mask AP | 34.7 | 34.7 | - | 33.7 | 33.7 | - | - | - | - | |||
Swin-Transformer | Instance Segmentation | COCO2017 | box AP | 42.7 | - | 42.7 | 42.5 | 37.7 | - | - | - | - |
mask AP | 39.3 | - | 39.3 | 39.3 | 35.4 | - | - | - | - | |||
SOLO | Instance Segmentation | COCO2017 | mask AP | 33.1 | - | 32.7 | - | - | - | - | - | 32.7 |
SOLOv2 | Instance Segmentation | COCO2017 | mask AP | 34.8 | - | 34.5 | - | - | - | - | - | 34.5 |
mmagic | Pytorch | TorchScript | ONNX Runtime | TensorRT | PPLNN | |||||
---|---|---|---|---|---|---|---|---|---|---|
model | task | dataset | metric | fp32 | fp32 | fp32 | fp32 | fp16 | int8 | fp16 |
SRCNN | Super Resolution | Set5 | PSNR | 28.4316 | 28.4120 | 28.4323 | 28.4323 | 28.4286 | 28.1995 | 28.4311 |
SSIM | 0.8099 | 0.8106 | 0.8097 | 0.8097 | 0.8096 | 0.7934 | 0.8096 | |||
ESRGAN | Super Resolution | Set5 | PSNR | 28.2700 | 28.2619 | 28.2592 | 28.2592 | - | - | 28.2624 |
SSIM | 0.7778 | 0.7784 | 0.7764 | 0.7774 | - | - | 0.7765 | |||
ESRGAN-PSNR | Super Resolution | Set5 | PSNR | 30.6428 | 30.6306 | 30.6444 | 30.6430 | - | - | 27.0426 |
SSIM | 0.8559 | 0.8565 | 0.8558 | 0.8558 | - | - | 0.8557 | |||
SRGAN | Super Resolution | Set5 | PSNR | 27.9499 | 27.9252 | 27.9408 | 27.9408 | - | - | 27.9388 |
SSIM | 0.7846 | 0.7851 | 0.7839 | 0.7839 | - | - | 0.7839 | |||
SRResNet | Super Resolution | Set5 | PSNR | 30.2252 | 30.2069 | 30.2300 | 30.2300 | - | - | 30.2294 |
SSIM | 0.8491 | 0.8497 | 0.8488 | 0.8488 | - | - | 0.8488 | |||
Real-ESRNet | Super Resolution | Set5 | PSNR | 28.0297 | - | 27.7016 | 27.7016 | - | - | 27.7049 |
SSIM | 0.8236 | - | 0.8122 | 0.8122 | - | - | 0.8123 | |||
EDSR | Super Resolution | Set5 | PSNR | 30.2223 | 30.2192 | 30.2214 | 30.2214 | 30.2211 | 30.1383 | - |
SSIM | 0.8500 | 0.8507 | 0.8497 | 0.8497 | 0.8497 | 0.8469 | - |
mmocr | Pytorch | TorchScript | ONNXRuntime | TensorRT | PPLNN | OpenVINO | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
model | task | dataset | metric | fp32 | fp32 | fp32 | fp32 | fp16 | int8 | fp16 | fp32 |
DBNet* | TextDetection | ICDAR2015 | recall | 0.7310 | 0.7308 | 0.7304 | 0.7198 | 0.7179 | 0.7111 | 0.7304 | 0.7309 |
precision | 0.8714 | 0.8718 | 0.8714 | 0.8677 | 0.8674 | 0.8688 | 0.8718 | 0.8714 | |||
hmean | 0.7950 | 0.7949 | 0.7950 | 0.7868 | 0.7856 | 0.7821 | 0.7949 | 0.7950 | |||
DBNetpp | TextDetection | ICDAR2015 | recall | 0.8209 | 0.8209 | 0.8209 | 0.8199 | 0.8204 | 0.8204 | - | 0.8209 |
precision | 0.9079 | 0.9079 | 0.9079 | 0.9117 | 0.9117 | 0.9142 | - | 0.9079 | |||
hmean | 0.8622 | 0.8622 | 0.8622 | 0.8634 | 0.8637 | 0.8648 | - | 0.8622 | |||
PSENet | TextDetection | ICDAR2015 | recall | 0.7526 | 0.7526 | 0.7526 | 0.7526 | 0.7520 | 0.7496 | - | 0.7526 |
precision | 0.8669 | 0.8669 | 0.8669 | 0.8669 | 0.8668 | 0.8550 | - | 0.8669 | |||
hmean | 0.8057 | 0.8057 | 0.8057 | 0.8057 | 0.8054 | 0.7989 | - | 0.8057 | |||
PANet | TextDetection | ICDAR2015 | recall | 0.7401 | 0.7401 | 0.7401 | 0.7357 | 0.7366 | - | - | 0.7401 |
precision | 0.8601 | 0.8601 | 0.8601 | 0.8570 | 0.8586 | - | - | 0.8601 | |||
hmean | 0.7955 | 0.7955 | 0.7955 | 0.7917 | 0.7930 | - | - | 0.7955 | |||
TextSnake | TextDetection | CTW1500 | recall | 0.8052 | 0.8052 | 0.8052 | 0.8055 | - | - | - | - |
precision | 0.8535 | 0.8535 | 0.8535 | 0.8538 | - | - | - | - | |||
hmean | 0.8286 | 0.8286 | 0.8286 | 0.8290 | - | - | - | - | |||
MaskRCNN | TextDetection | ICDAR2015 | recall | 0.7766 | 0.7766 | 0.7766 | 0.7766 | 0.7761 | 0.7670 | - | - |
precision | 0.8644 | 0.8644 | 0.8644 | 0.8644 | 0.8630 | 0.8705 | - | - | |||
hmean | 0.8182 | 0.8182 | 0.8182 | 0.8182 | 0.8172 | 0.8155 | - | - | |||
CRNN | TextRecognition | IIIT5K | acc | 0.8067 | 0.8067 | 0.8067 | 0.8067 | 0.8063 | 0.8067 | 0.8067 | - |
SAR | TextRecognition | IIIT5K | acc | 0.9517 | - | 0.9287 | - | - | - | - | - |
SATRN | TextRecognition | IIIT5K | acc | 0.9470 | 0.9487 | 0.9487 | 0.9487 | 0.9483 | 0.9483 | - | - |
ABINet | TextRecognition | IIIT5K | acc | 0.9603 | 0.9563 | 0.9563 | 0.9573 | 0.9507 | 0.9510 | - | - |
mmseg | Pytorch | TorchScript | ONNXRuntime | TensorRT | PPLNN | Ascend | ||||
---|---|---|---|---|---|---|---|---|---|---|
model | dataset | metric | fp32 | fp32 | fp32 | fp32 | fp16 | int8 | fp16 | fp32 |
FCN | Cityscapes | mIoU | 72.25 | 72.36 | - | 72.36 | 72.35 | 74.19 | 72.35 | 72.35 |
PSPNet | Cityscapes | mIoU | 78.55 | 78.66 | - | 78.26 | 78.24 | 77.97 | 78.09 | 78.67 |
deeplabv3 | Cityscapes | mIoU | 79.09 | 79.12 | - | 79.12 | 79.12 | 78.96 | 79.12 | 79.06 |
deeplabv3+ | Cityscapes | mIoU | 79.61 | 79.60 | - | 79.60 | 79.60 | 79.43 | 79.60 | 79.51 |
Fast-SCNN | Cityscapes | mIoU | 70.96 | 70.96 | - | 70.93 | 70.92 | 66.00 | 70.92 | - |
UNet | Cityscapes | mIoU | 69.10 | - | - | 69.10 | 69.10 | 68.95 | - | - |
ANN | Cityscapes | mIoU | 77.40 | - | - | 77.32 | 77.32 | - | - | - |
APCNet | Cityscapes | mIoU | 77.40 | - | - | 77.32 | 77.32 | - | - | - |
BiSeNetV1 | Cityscapes | mIoU | 74.44 | - | - | 74.44 | 74.43 | - | - | - |
BiSeNetV2 | Cityscapes | mIoU | 73.21 | - | - | 73.21 | 73.21 | - | - | - |
CGNet | Cityscapes | mIoU | 68.25 | - | - | 68.27 | 68.27 | - | - | - |
EMANet | Cityscapes | mIoU | 77.59 | - | - | 77.59 | 77.6 | - | - | - |
EncNet | Cityscapes | mIoU | 75.67 | - | - | 75.66 | 75.66 | - | - | - |
ERFNet | Cityscapes | mIoU | 71.08 | - | - | 71.08 | 71.07 | - | - | - |
FastFCN | Cityscapes | mIoU | 79.12 | - | - | 79.12 | 79.12 | - | - | - |
GCNet | Cityscapes | mIoU | 77.69 | - | - | 77.69 | 77.69 | - | - | - |
ICNet | Cityscapes | mIoU | 76.29 | - | - | 76.36 | 76.36 | - | - | - |
ISANet | Cityscapes | mIoU | 78.49 | - | - | 78.49 | 78.49 | - | - | - |
OCRNet | Cityscapes | mIoU | 74.30 | - | - | 73.66 | 73.67 | - | - | - |
PointRend | Cityscapes | mIoU | 76.47 | 76.47 | - | 76.41 | 76.42 | - | - | - |
Semantic FPN | Cityscapes | mIoU | 74.52 | - | - | 74.52 | 74.52 | - | - | - |
STDC | Cityscapes | mIoU | 75.10 | - | - | 75.10 | 75.10 | - | - | - |
STDC | Cityscapes | mIoU | 77.17 | - | - | 77.17 | 77.17 | - | - | - |
UPerNet | Cityscapes | mIoU | 77.10 | - | - | 77.19 | 77.18 | - | - | - |
Segmenter | ADE20K | mIoU | 44.32 | 44.29 | 44.29 | 44.29 | 43.34 | 43.35 | - | - |
mmpose | Pytorch | ONNXRuntime | TensorRT | PPLNN | OpenVINO | ||||
---|---|---|---|---|---|---|---|---|---|
model | task | dataset | metric | fp32 | fp32 | fp32 | fp16 | fp16 | fp32 |
HRNet | Pose Detection | COCO | AP | 0.748 | 0.748 | 0.748 | 0.748 | - | 0.748 |
AR | 0.802 | 0.802 | 0.802 | 0.802 | - | 0.802 | |||
LiteHRNet | Pose Detection | COCO | AP | 0.663 | 0.663 | 0.663 | - | - | 0.663 |
AR | 0.728 | 0.728 | 0.728 | - | - | 0.728 | |||
MSPN | Pose Detection | COCO | AP | 0.762 | 0.762 | 0.762 | 0.762 | - | 0.762 |
AR | 0.825 | 0.825 | 0.825 | 0.825 | - | 0.825 | |||
Hourglass | Pose Detection | COCO | AP | 0.717 | 0.717 | 0.717 | 0.717 | - | 0.717 |
AR | 0.774 | 0.774 | 0.774 | 0.774 | - | 0.774 | |||
SimCC | Pose Detection | COCO | AP | 0.607 | - | 0.608 | - | - | - |
AR | 0.668 | - | 0.672 | - | - | - |
mmrotate | Pytorch | ONNXRuntime | TensorRT | PPLNN | OpenVINO | ||||
---|---|---|---|---|---|---|---|---|---|
model | task | dataset | metrics | fp32 | fp32 | fp32 | fp16 | fp16 | fp32 |
RotatedRetinaNet | Rotated Detection | DOTA-v1.0 | mAP | 0.698 | 0.698 | 0.698 | 0.697 | - | - |
Oriented RCNN | Rotated Detection | DOTA-v1.0 | mAP | 0.756 | 0.756 | 0.758 | 0.730 | - | - |
GlidingVertex | Rotated Detection | DOTA-v1.0 | mAP | 0.732 | - | 0.733 | 0.731 | - | - |
RoI Transformer | Rotated Detection | DOTA-v1.0 | mAP | 0.761 | - | 0.758 | - | - | - |
mmaction2 | Pytorch | ONNXRuntime | TensorRT | PPLNN | OpenVINO | ||||
---|---|---|---|---|---|---|---|---|---|
model | task | dataset | metrics | fp32 | fp32 | fp32 | fp16 | fp16 | fp32 |
TSN | Recognition | Kinetics-400 | top-1 | 69.71 | - | 69.71 | - | - | - |
top-5 | 88.75 | - | 88.75 | - | - | - | |||
SlowFast | Recognition | Kinetics-400 | top-1 | 74.45 | - | 75.62 | - | - | - |
top-5 | 91.55 | - | 92.10 | - | - | - |
由于某些数据集在代码库中包含各种分辨率的图像,例如 MMDet,速度基准是通过 MMDeploy 中的静态配置获得的,而性能基准是通过动态配置获得的
TensorRT 的一些 int8 性能基准测试需要有 tensor core 的 Nvidia 卡,否则性能会大幅下降
DBNet 在模型
neck
使用了nearest
插值,TensorRT-7 用了与 Pytorch 完全不同的策略。为了使与 TensorRT-7 兼容,我们重写了neck
以使用bilinear
插值,这提高了检测性能。为了获得与 Pytorch 匹配的性能,推荐使用 TensorRT-8+,其插值方法与 Pytorch 相同。对于 mmpose 模型,在模型配置文件中
flip_test
需设置为False
部分模型在 fp16 模式下可能存在较大的精度损失,请根据具体情况对模型进行调整。
边、端设备测试结果¶
这里给出我们边、端设备的测试结论,用户可以直接通过 model profiling 获得自己环境的结果。
软硬件环境¶
host OS ubuntu 18.04
backend SNPE-1.59
device Mi11 (qcom 888)
mmpretrain 模型¶
model | dataset | spatial | fp32 top-1 (%) | snpe gpu hybrid fp32 top-1 (%) | latency (ms) |
---|---|---|---|---|---|
ShuffleNetV2 | ImageNet-1k | 224x224 | 69.55 | 69.83* | 20±7 |
MobilenetV2 | ImageNet-1k | 224x224 | 71.86 | 72.14* | 15±6 |
tips:
ImageNet-1k 数据集较大,仅使用一部分测试(8000/50000)
边、端设备发热会降频,因此耗时实际上会波动。这里给出运行一段时间后、稳定的数值。这个结果更贴近实际需求
mmocr 检测¶
model | dataset | spatial | fp32 hmean | snpe gpu hybrid hmean | latency(ms) |
---|---|---|---|---|---|
PANet | ICDAR2015 | 1312x736 | 0.795 | 0.785 @thr=0.9 | 3100±100 |
mmpose 模型¶
model | dataset | spatial | snpe hybrid AR@IoU=0.50 | snpe hybrid AP@IoU=0.50 | latency(ms) |
---|---|---|---|---|---|
pose_hrnet_w32 | Animalpose | 256x256 | 0.997 | 0.989 | 630±50 |
tips:
测试 pose_hrnet 用的是 AnimalPose 的 test dataset,而非 val dataset
mmseg¶
model | dataset | spatial | mIoU | latency(ms) |
---|---|---|---|---|
fcn | Cityscapes | 512x1024 | 71.11 | 4915±500 |
tips:
fcn 用 512x1024 尺寸运行正常。Cityscapes 数据集 1024x2048 分辨率会导致设备重启
其他模型¶
mmdet 需要手动把模型拆成两部分。因为
snpe 源码中
onnx_to_ir.py
仅能解析输入,ir_to_dlc.py
还不支持 topkUDO (用户自定义算子)无法和
snpe-onnx-to-dlc
配合使用
mmagic 模型
srcnn 需要 cubic resize,snpe 不支持
esrgan 可正常转换,但加载模型会导致设备重启
mmrotate 依赖 e2cnn ,需要手动安装 其 Python3.6 兼容分支
TVM 测试¶
支持模型列表¶
Model | Codebase | Model config |
---|---|---|
RetinaNet | MMDetection | config |
Faster R-CNN | MMDetection | config |
YOLOv3 | MMDetection | config |
YOLOX | MMDetection | config |
Mask R-CNN | MMDetection | config |
SSD | MMDetection | config |
ResNet | MMPretrain | config |
ResNeXt | MMPretrain | config |
SE-ResNet | MMPretrain | config |
MobileNetV2 | MMPretrain | config |
ShuffleNetV1 | MMPretrain | config |
ShuffleNetV2 | MMPretrain | config |
VisionTransformer | MMPretrain | config |
FCN | MMSegmentation | config |
PSPNet | MMSegmentation | config |
DeepLabV3 | MMSegmentation | config |
DeepLabV3+ | MMSegmentation | config |
UNet | MMSegmentation | config |
表中仅列出已测试模型,未列出的模型可能同样支持,可以自行尝试转换。
Test¶
Ubuntu 20.04
tvm 0.9.0
mmpretrain | metric | PyTorch | TVM |
---|---|---|---|
ResNet-18 | top-1 | 69.90 | 69.90 |
ResNeXt-50 | top-1 | 77.90 | 77.90 |
ShuffleNet V2 | top-1 | 69.55 | 69.55 |
MobileNet V2 | top-1 | 71.86 | 71.86 |
mmdet(*) | metric | PyTorch | TVM |
---|---|---|---|
SSD | box AP | 25.5 | 25.5 |
*: 由于暂时不支持动态转换,因此仅提供 SSD 的精度测试结果。
mmseg | metric | PyTorch | TVM |
---|---|---|---|
FCN | mIoU | 72.25 | 72.36 |
PSPNet | mIoU | 78.55 | 77.90 |
量化测试结果¶
目前 mmdeploy 支持 ncnn 量化
ncnn 量化¶
分类任务¶
model | dataset | fp32 top-1 (%) | int8 top-1 (%) |
---|---|---|---|
ResNet-18 | Cifar10 | 94.82 | 94.83 |
ResNeXt-32x4d-50 | ImageNet-1k | 77.90 | 78.20* |
MobileNet V2 | ImageNet-1k | 71.86 | 71.43* |
HRNet-W18* | ImageNet-1k | 76.75 | 76.25* |
备注:
因为 imagenet-1k 数据量很大、ncnn 未正式发布 Vulkan int8 版本,考虑到 CPU 运行时间,仅用部分测试集(4000/50000)
量化后精度会有差异,分类模型涨点 1% 以内是正常情况
MMPretrain 模型部署¶
MMPretrain 是基于 Python 的的图像分类工具,属于 OpenMMLab。
安装¶
安装 mmdeploy¶
mmdeploy 有以下几种安装方式:
方式一: 安装预编译包
请参考安装概述
方式二: 一键式脚本安装
如果部署平台是 Ubuntu 18.04 及以上版本, 请参考脚本安装说明,完成安装过程。
比如,以下命令可以安装 mmdeploy 以及配套的推理引擎——ONNX Runtime
.
git clone --recursive -b main https://github.com/open-mmlab/mmdeploy.git
cd mmdeploy
python3 tools/scripts/build_ubuntu_x64_ort.py $(nproc)
export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH
export LD_LIBRARY_PATH=$(pwd)/../mmdeploy-dep/onnxruntime-linux-x64-1.8.1/lib/:$LD_LIBRARY_PATH
方式三: 源码安装
在方式一、二都满足不了的情况下,请参考源码安装说明 安装 mmdeploy 以及所需推理引擎。
模型转换¶
你可以使用 tools/deploy.py 把 mmpretrain 模型一键式转换为推理后端模型。 该工具的详细使用说明请参考这里.
以下,我们将演示如何把 resnet18
转换为 onnx 模型。
cd mmdeploy
# download resnet18 model from mmpretrain model zoo
mim download mmpretrain --config resnet18_8xb32_in1k --dest .
# convert mmpretrain model to onnxruntime model with dynamic shape
python tools/deploy.py \
configs/mmpretrain/classification_onnxruntime_dynamic.py \
resnet18_8xb32_in1k.py \
resnet18_8xb32_in1k_20210831-fbbb1da6.pth \
tests/data/tiger.jpeg \
--work-dir mmdeploy_models/mmpretrain/ort \
--device cpu \
--show \
--dump-info
转换的关键之一是使用正确的配置文件。项目中已内置了各后端部署配置文件。 文件的命名模式是:
classification_{backend}-{precision}_{static | dynamic}_{shape}.py
其中:
{backend}: 推理后端名称。比如,onnxruntime、tensorrt、pplnn、ncnn、openvino、coreml 等等
{precision}: 推理精度。比如,fp16、int8。不填表示 fp32
{static | dynamic}: 动态、静态 shape
{shape}: 模型输入的 shape 或者 shape 范围
在上例中,你也可以把 resnet18
转为其他后端模型。比如使用classification_tensorrt-fp16_dynamic-224x224-224x224.py
,把模型转为 tensorrt-fp16 模型。
小技巧
当转 tensorrt 模型时, –device 需要被设置为 “cuda”
模型规范¶
在使用转换后的模型进行推理之前,有必要了解转换结果的结构。 它存放在 --work-dir
指定的路路径下。
上例中的mmdeploy_models/mmpretrain/ort
,结构如下:
mmdeploy_models/mmpretrain/ort
├── deploy.json
├── detail.json
├── end2end.onnx
└── pipeline.json
重要的是:
end2end.onnx: 推理引擎文件。可用 ONNX Runtime 推理
*.json: mmdeploy SDK 推理所需的 meta 信息
整个文件夹被定义为mmdeploy SDK model。换言之,mmdeploy SDK model既包括推理引擎,也包括推理 meta 信息。
模型推理¶
后端模型推理¶
以上述模型转换后的 end2end.onnx
为例,你可以使用如下代码进行推理:
from mmdeploy.apis.utils import build_task_processor
from mmdeploy.utils import get_input_shape, load_config
import torch
deploy_cfg = 'configs/mmpretrain/classification_onnxruntime_dynamic.py'
model_cfg = './resnet18_8xb32_in1k.py'
device = 'cpu'
backend_model = ['./mmdeploy_models/mmpretrain/ort/end2end.onnx']
image = 'tests/data/tiger.jpeg'
# read deploy_cfg and model_cfg
deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg)
# build task and backend model
task_processor = build_task_processor(model_cfg, deploy_cfg, device)
model = task_processor.build_backend_model(backend_model)
# process input image
input_shape = get_input_shape(deploy_cfg)
model_inputs, _ = task_processor.create_input(image, input_shape)
# do model inference
with torch.no_grad():
result = model.test_step(model_inputs)
# visualize results
task_processor.visualize(
image=image,
model=model,
result=result[0],
window_name='visualize',
output_file='output_classification.png')
SDK 模型推理¶
你也可以参考如下代码,对 SDK model 进行推理:
from mmdeploy_runtime import Classifier
import cv2
img = cv2.imread('tests/data/tiger.jpeg')
# create a classifier
classifier = Classifier(model_path='./mmdeploy_models/mmpretrain/ort', device_name='cpu', device_id=0)
# perform inference
result = classifier(img)
# show inference result
for label_id, score in result:
print(label_id, score)
除了python API,mmdeploy SDK 还提供了诸如 C、C++、C#、Java等多语言接口。 你可以参考样例学习其他语言接口的使用方法。
模型支持列表¶
Model | TorchScript | ONNX Runtime | TensorRT | ncnn | PPLNN | OpenVINO |
---|---|---|---|---|---|---|
ResNet | Y | Y | Y | Y | Y | Y |
ResNeXt | Y | Y | Y | Y | Y | Y |
SE-ResNet | Y | Y | Y | Y | Y | Y |
MobileNetV2 | Y | Y | Y | Y | Y | Y |
MobileNetV3 | Y | Y | Y | Y | ? | Y |
ShuffleNetV1 | Y | Y | Y | Y | Y | Y |
ShuffleNetV2 | Y | Y | Y | Y | Y | Y |
VisionTransformer | Y | Y | Y | Y | ? | Y |
SwinTransformer | Y | Y | Y | N | ? | Y |
MobileOne | Y | Y | Y | Y | ? | Y |
EfficientNet | Y | Y | Y | N | ? | Y |
Conformer | Y | Y | Y | N | ? | Y |
EfficientFormer | Y | Y | Y | N | ? | Y |
MMDetection 模型部署¶
MMDetection ,又称 mmdet
, 是一个基于 PyTorch 的目标检测开源工具箱。它是 OpenMMLab 项目的一部分。
安装¶
安装 mmdeploy¶
mmdeploy 有以下几种安装方式:
方式一: 安装预编译包
请参考安装概述
方式二: 一键式脚本安装
如果部署平台是 Ubuntu 18.04 及以上版本, 请参考脚本安装说明,完成安装过程。
比如,以下命令可以安装 mmdeploy 以及配套的推理引擎——ONNX Runtime
.
git clone --recursive -b main https://github.com/open-mmlab/mmdeploy.git
cd mmdeploy
python3 tools/scripts/build_ubuntu_x64_ort.py $(nproc)
export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH
export LD_LIBRARY_PATH=$(pwd)/../mmdeploy-dep/onnxruntime-linux-x64-1.8.1/lib/:$LD_LIBRARY_PATH
方式三: 源码安装
在方式一、二都满足不了的情况下,请参考源码安装说明 安装 mmdeploy 以及所需推理引擎。
模型转换¶
你可以使用 tools/deploy.py 把 mmdet 模型一键式转换为推理后端模型。 该工具的详细使用说明请参考这里.
以下,我们将演示如何把 Faster R-CNN
转换为 onnx 模型。
cd mmdeploy
# download faster r-cnn model from mmdet model zoo
mim download mmdet --config faster-rcnn_r50_fpn_1x_coco --dest .
# convert mmdet model to onnxruntime model with dynamic shape
python tools/deploy.py \
configs/mmdet/detection/detection_onnxruntime_dynamic.py \
faster-rcnn_r50_fpn_1x_coco.py \
faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth \
demo/resources/det.jpg \
--work-dir mmdeploy_models/mmdet/ort \
--device cpu \
--show \
--dump-info
转换的关键之一是使用正确的配置文件。项目中已内置了各后端部署配置文件。 文件的命名模式是:
{task}/{task}_{backend}-{precision}_{static | dynamic}_{shape}.py
其中:
{task}: mmdet 中的任务
mmdet 任务有2种:物体检测(detection)、实例分割(instance-seg)。例如,
RetinaNet
、Faster R-CNN
、DETR
等属于前者。Mask R-CNN
、SOLO
等属于后者。更多模型-任务
的划分,请参考章节模型支持列表。请务必使用
detection/detection_*.py
转换检测模型,使用instance-seg/instance-seg_*.py
转换实例分割模型。{backend}: 推理后端名称。比如,onnxruntime、tensorrt、pplnn、ncnn、openvino、coreml 等等
{precision}: 推理精度。比如,fp16、int8。不填表示 fp32
{static | dynamic}: 动态、静态 shape
{shape}: 模型输入的 shape 或者 shape 范围
在上例中,你也可以把 Faster R-CNN
转为其他后端模型。比如使用detection_tensorrt-fp16_dynamic-320x320-1344x1344.py
,把模型转为 tensorrt-fp16 模型。
小技巧
当转 tensorrt 模型时, –device 需要被设置为 “cuda”
模型规范¶
在使用转换后的模型进行推理之前,有必要了解转换结果的结构。 它存放在 --work-dir
指定的路路径下。
上例中的mmdeploy_models/mmdet/ort
,结构如下:
mmdeploy_models/mmdet/ort
├── deploy.json
├── detail.json
├── end2end.onnx
└── pipeline.json
重要的是:
end2end.onnx: 推理引擎文件。可用 ONNX Runtime 推理
*.json: mmdeploy SDK 推理所需的 meta 信息
整个文件夹被定义为mmdeploy SDK model。换言之,mmdeploy SDK model既包括推理引擎,也包括推理 meta 信息。
模型推理¶
后端模型推理¶
以上述模型转换后的 end2end.onnx
为例,你可以使用如下代码进行推理:
from mmdeploy.apis.utils import build_task_processor
from mmdeploy.utils import get_input_shape, load_config
import torch
deploy_cfg = 'configs/mmdet/detection/detection_onnxruntime_dynamic.py'
model_cfg = './faster-rcnn_r50_fpn_1x_coco.py'
device = 'cpu'
backend_model = ['./mmdeploy_models/mmdet/ort/end2end.onnx']
image = './demo/resources/det.jpg'
# read deploy_cfg and model_cfg
deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg)
# build task and backend model
task_processor = build_task_processor(model_cfg, deploy_cfg, device)
model = task_processor.build_backend_model(backend_model)
# process input image
input_shape = get_input_shape(deploy_cfg)
model_inputs, _ = task_processor.create_input(image, input_shape)
# do model inference
with torch.no_grad():
result = model.test_step(model_inputs)
# visualize results
task_processor.visualize(
image=image,
model=model,
result=result[0],
window_name='visualize',
output_file='output_detection.png')
SDK 模型推理¶
你也可以参考如下代码,对 SDK model 进行推理:
from mmdeploy_runtime import Detector
import cv2
img = cv2.imread('./demo/resources/det.jpg')
# create a detector
detector = Detector(model_path='./mmdeploy_models/mmdet/ort', device_name='cpu', device_id=0)
# perform inference
bboxes, labels, masks = detector(img)
# visualize inference result
indices = [i for i in range(len(bboxes))]
for index, bbox, label_id in zip(indices, bboxes, labels):
[left, top, right, bottom], score = bbox[0:4].astype(int), bbox[4]
if score < 0.3:
continue
cv2.rectangle(img, (left, top), (right, bottom), (0, 255, 0))
cv2.imwrite('output_detection.png', img)
除了python API,mmdeploy SDK 还提供了诸如 C、C++、C#、Java等多语言接口。 你可以参考样例学习其他语言接口的使用方法。
模型支持列表¶
Model | Task | OnnxRuntime | TensorRT | ncnn | PPLNN | OpenVINO |
---|---|---|---|---|---|---|
ATSS | Object Detection | Y | Y | N | N | Y |
FCOS | Object Detection | Y | Y | Y | N | Y |
FoveaBox | Object Detection | Y | N | N | N | Y |
FSAF | Object Detection | Y | Y | Y | Y | Y |
RetinaNet | Object Detection | Y | Y | Y | Y | Y |
SSD | Object Detection | Y | Y | Y | N | Y |
VFNet | Object Detection | N | N | N | N | Y |
YOLOv3 | Object Detection | Y | Y | Y | N | Y |
YOLOX | Object Detection | Y | Y | Y | N | Y |
Cascade R-CNN | Object Detection | Y | Y | N | Y | Y |
Faster R-CNN | Object Detection | Y | Y | Y | Y | Y |
Faster R-CNN + DCN | Object Detection | Y | Y | Y | Y | Y |
GFL | Object Detection | Y | Y | N | ? | Y |
RepPoints | Object Detection | N | Y | N | ? | Y |
DETR* | Object Detection | Y | Y | N | ? | Y |
Deformable DETR* | Object Detection | Y | Y | N | ? | Y |
Conditional DETR* | Object Detection | Y | Y | N | ? | Y |
DAB-DETR* | Object Detection | Y | Y | N | ? | Y |
DINO* | Object Detection | Y | Y | N | ? | Y |
CenterNet | Object Detection | Y | Y | N | ? | Y |
RTMDet | Object Detection | Y | Y | N | ? | Y |
Cascade Mask R-CNN | Instance Segmentation | Y | Y | N | N | Y |
HTC | Instance Segmentation | Y | Y | N | ? | Y |
Mask R-CNN | Instance Segmentation | Y | Y | N | N | Y |
Swin Transformer | Instance Segmentation | Y | Y | N | N | Y |
SOLO | Instance Segmentation | Y | N | N | N | Y |
SOLOv2 | Instance Segmentation | Y | N | N | N | Y |
CondInst | Instance Segmentation | Y | Y | N | N | N |
Panoptic FPN | Panoptic Segmentation | Y | Y | N | N | N |
MaskFormer | Panoptic Segmentation | Y | Y | N | N | N |
Mask2Former* | Panoptic Segmentation | Y | Y | N | N | N |
注意事项¶
强烈建议使用
TensorRT>=8.4
来转换基于transformer
的模型.Mask2Former 请使用
TensorRT>=8.6.1
以保证动态尺寸正常推理.DETR系列模型 不支持多批次推理.
MMDetection3d 模型部署¶
MMDetection3d,又称 mmdet3d
, 是一个基于 PyTorch 的目标检测开源工具箱, 下一代面向3D检测的平台。它是 OpenMMLab 项目的一部分。
安装 mmdet3d¶
我们可以通过 mim 来安装 mmdet3d. 更多安装方式可参考该文档
python3 -m pip install -U openmim
python3 -m mim install "mmdet3d>=1.1.0"
模型转换¶
使用 tools/deploy.py
把 mmdet3d 转到相应后端,以 centerpoint onnxruntime 为例:
# 切换到 mmdeploy 根目录
# 通过mim下载centerpoint模型
mim download mmdet3d --config centerpoint_pillar02_second_secfpn_head-circlenms_8xb4-cyclic-20e_nus-3d --dest .
export MODEL_CONFIG=centerpoint_pillar02_second_secfpn_head-circlenms_8xb4-cyclic-20e_nus-3d.py
export MODEL_PATH=centerpoint_02pillar_second_secfpn_circlenms_4x8_cyclic_20e_nus_20220811_031844-191a3822.pth
export TEST_DATA=tests/data/n008-2018-08-01-15-16-36-0400__LIDAR_TOP__1533151612397179.pcd.bin
python3 tools/deploy.py configs/mmdet3d/voxel-detection/voxel-detection_onnxruntime_dynamic.py $MODEL_CONFIG $MODEL_PATH $TEST_DATA --work-dir centerpoint
work-dir
应生成对应 onnx
ls -lah centerpoint
..
-rw-rw-r-- 1 rg rg 87M 11月 4 19:48 end2end.onnx
模型支持列表¶
model | task | dataset | onnxruntime | openvino | tensorrt* |
---|---|---|---|---|---|
centerpoint | voxel detection | nuScenes | ✔️ | ✔️ | ✔️ |
pointpillars | voxel detection | nuScenes | ✔️ | ✔️ | ✔️ |
pointpillars | voxel detection | KITTI | ✔️ | ✔️ | ✔️ |
smoke | monocular detection | KITTI | ✔️ | x | ✔️ |
考虑到 ScatterND、动态 shape 等已知问题,请确保 trt >= 8.6
MMagic 模型部署¶
MMagic,又称 mmagic
,是基于 PyTorch 的开源图像和视频编辑工具箱。它是 OpenMMLab 项目成员之一。
安装¶
安装 mmdeploy¶
mmdeploy 有以下几种安装方式:
方式一: 安装预编译包
请参考安装概述
方式二: 一键式脚本安装
如果部署平台是 Ubuntu 18.04 及以上版本, 请参考脚本安装说明,完成安装过程。
比如,以下命令可以安装 mmdeploy 以及配套的推理引擎——ONNX Runtime
.
git clone --recursive -b main https://github.com/open-mmlab/mmdeploy.git
cd mmdeploy
python3 tools/scripts/build_ubuntu_x64_ort.py $(nproc)
export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH
export LD_LIBRARY_PATH=$(pwd)/../mmdeploy-dep/onnxruntime-linux-x64-1.8.1/lib/:$LD_LIBRARY_PATH
方式三: 源码安装
在方式一、二都满足不了的情况下,请参考源码安装说明 安装 mmdeploy 以及所需推理引擎。
模型转换¶
你可以使用 tools/deploy.py 把 mmagic 模型一键式转换为推理后端模型。 该工具的详细使用说明请参考这里.
转换的关键之一是使用正确的配置文件。项目中已内置了各后端部署配置文件。 文件的命名模式是:
{task}/{task}_{backend}-{precision}_{static | dynamic}_{shape}.py
其中:
{task}: mmagic 中的任务
mmagic 中任务有多种。目前,mmdeploy 支持其中的超分(super resolution)任务。关于
模型-任务
的划分,请参考章节模型支持列表。请务必使用对应的部署文件转换相关的模型。
{backend}: 推理后端名称。比如,onnxruntime、tensorrt、pplnn、ncnn、openvino、coreml 等等
{precision}: 推理精度。比如,fp16、int8。不填表示 fp32
{static | dynamic}: 动态、静态 shape
{shape}: 模型输入的 shape 或者 shape 范围
超分任务模型转换¶
以下,我们将演示如何把 ESRGAN
转换为 onnx 模型。
cd mmdeploy
# download esrgan model from mmagic model zoo
mim download mmagic --config esrgan_psnr-x4c64b23g32_1xb16-1000k_div2k --dest .
# convert esrgan model to onnxruntime model with dynamic shape
python tools/deploy.py \
configs/mmagic/super-resolution/super-resolution_onnxruntime_dynamic.py \
esrgan_psnr-x4c64b23g32_1xb16-1000k_div2k.py \
esrgan_psnr_x4c64b23g32_1x16_1000k_div2k_20200420-bf5c993c.pth \
demo/resources/face.png \
--work-dir mmdeploy_models/mmagic/ort \
--device cpu \
--show \
--dump-info
你也可以把 ESRGAN
转为其他后端模型。比如使用super-resolution/super-resolution_tensorrt-_dynamic-32x32-512x512.py
,把模型转为 tensorrt 模型。
小技巧
当转 tensorrt 模型时, –device 需要被设置为 “cuda”
模型规范¶
在使用转换后的模型进行推理之前,有必要了解转换结果的结构。 它存放在 --work-dir
指定的路路径下。
上例中的mmdeploy_models/mmagic/ort
,结构如下:
mmdeploy_models/mmagic/ort
├── deploy.json
├── detail.json
├── end2end.onnx
└── pipeline.json
重要的是:
end2end.onnx: 推理引擎文件。可用 ONNX Runtime 推理
*.json: mmdeploy SDK 推理所需的 meta 信息
整个文件夹被定义为mmdeploy SDK model。换言之,mmdeploy SDK model既包括推理引擎,也包括推理 meta 信息。
模型推理¶
后端模型推理¶
以上述模型转换后的 end2end.onnx
为例,你可以使用如下代码进行推理:
from mmdeploy.apis.utils import build_task_processor
from mmdeploy.utils import get_input_shape, load_config
import torch
deploy_cfg = 'configs/mmagic/super-resolution/super-resolution_onnxruntime_dynamic.py'
model_cfg = 'esrgan_psnr-x4c64b23g32_1xb16-1000k_div2k.py'
device = 'cpu'
backend_model = ['./mmdeploy_models/mmagic/ort/end2end.onnx']
image = './demo/resources/face.png'
# read deploy_cfg and model_cfg
deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg)
# build task and backend model
task_processor = build_task_processor(model_cfg, deploy_cfg, device)
model = task_processor.build_backend_model(backend_model)
# process input image
input_shape = get_input_shape(deploy_cfg)
model_inputs, _ = task_processor.create_input(image, input_shape)
# do model inference
with torch.no_grad():
result = model.test_step(model_inputs)
# visualize results
task_processor.visualize(
image=image,
model=model,
result=result[0],
window_name='visualize',
output_file='output_restorer.bmp')
SDK 模型推理¶
你也可以参考如下代码,对 SDK model 进行推理:
from mmdeploy_runtime import Restorer
import cv2
img = cv2.imread('./demo/resources/face.png')
# create a classifier
restorer = Restorer(model_path='./mmdeploy_models/mmagic/ort', device_name='cpu', device_id=0)
# perform inference
result = restorer(img)
# visualize inference result
# convert to BGR
result = result[..., ::-1]
cv2.imwrite('output_restorer.bmp', result)
除了python API,mmdeploy SDK 还提供了诸如 C、C++、C#、Java等多语言接口。 你可以参考样例学习其他语言接口的使用方法。
模型支持列表¶
Model | Task | ONNX Runtime | TensorRT | ncnn | PPLNN | OpenVINO |
---|---|---|---|---|---|---|
SRCNN | super-resolution | Y | Y | Y | Y | Y |
ESRGAN | super-resolution | Y | Y | Y | Y | Y |
ESRGAN-PSNR | super-resolution | Y | Y | Y | Y | Y |
SRGAN | super-resolution | Y | Y | Y | Y | Y |
SRResNet | super-resolution | Y | Y | Y | Y | Y |
Real-ESRGAN | super-resolution | Y | Y | Y | Y | Y |
EDSR | super-resolution | Y | Y | Y | N | Y |
RDN | super-resolution | Y | Y | Y | Y | Y |
MMOCR 模型部署¶
MMOCR,又称 mmocr
,是基于 PyTorch 和 mmdetection 的开源工具箱,专注于文本检测,文本识别以及相应的下游任务,如关键信息提取。 它是 OpenMMLab 项目的一部分。
安装¶
安装 mmdeploy¶
mmdeploy 有以下几种安装方式:
方式一: 安装预编译包
请参考安装概述
方式二: 一键式脚本安装
如果部署平台是 Ubuntu 18.04 及以上版本, 请参考脚本安装说明,完成安装过程。
比如,以下命令可以安装 mmdeploy 以及配套的推理引擎——ONNX Runtime
.
git clone --recursive -b main https://github.com/open-mmlab/mmdeploy.git
cd mmdeploy
python3 tools/scripts/build_ubuntu_x64_ort.py $(nproc)
export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH
export LD_LIBRARY_PATH=$(pwd)/../mmdeploy-dep/onnxruntime-linux-x64-1.8.1/lib/:$LD_LIBRARY_PATH
方式三: 源码安装
在方式一、二都满足不了的情况下,请参考源码安装说明 安装 mmdeploy 以及所需推理引擎。
模型转换¶
你可以使用 tools/deploy.py 把 mmocr 模型一键式转换为推理后端模型。 该工具的详细使用说明请参考这里.
转换的关键之一是使用正确的配置文件。项目中已内置了各后端部署配置文件。 文件的命名模式是:
{task}/{task}_{backend}-{precision}_{static | dynamic}_{shape}.py
其中:
{task}: mmocr 中的任务
mmdeploy 支持 mmocr 中的文字检测(text detection)、文字识别(text recognition)任务中的模型。关于
模型-任务
的划分,请参考章节模型支持列表。请务必使用对应的部署文件转换相关的模型。
{backend}: 推理后端名称。比如,onnxruntime、tensorrt、pplnn、ncnn、openvino、coreml 等等
{precision}: 推理精度。比如,fp16、int8。不填表示 fp32
{static | dynamic}: 动态、静态 shape
{shape}: 模型输入的 shape 或者 shape 范围
在接下来来的两个章节,我们将分别演示文字检测任务中的dbnet
模型,和文字识别任务中的crnn
模型转换 onnx 模型的方法。
文字检测任务模型转换¶
cd mmdeploy
# download dbnet model from mmocr model zoo
mim download mmocr --config dbnet_resnet18_fpnc_1200e_icdar2015 --dest .
# convert mmocr model to onnxruntime model with dynamic shape
python tools/deploy.py \
configs/mmocr/text-detection/text-detection_onnxruntime_dynamic.py \
dbnet_resnet18_fpnc_1200e_icdar2015.py \
dbnet_resnet18_fpnc_1200e_icdar2015_20220825_221614-7c0e94f2.pth \
demo/resources/text_det.jpg \
--work-dir mmdeploy_models/mmocr/dbnet/ort \
--device cpu \
--show \
--dump-info
文字识别任务模型转换¶
cd mmdeploy
# download crnn model from mmocr model zoo
mim download mmocr --config crnn_mini-vgg_5e_mj --dest .
# convert mmocr model to onnxruntime model with dynamic shape
python tools/deploy.py \
configs/mmocr/text-recognition/text-recognition_onnxruntime_dynamic.py \
crnn_mini-vgg_5e_mj.py \
crnn_mini-vgg_5e_mj_20220826_224120-8afbedbb.pth \
demo/resources/text_recog.jpg \
--work-dir mmdeploy_models/mmocr/crnn/ort \
--device cpu \
--show \
--dump-info
你也可以把它们转为其他后端模型。比如使用text-detection/text-detection_tensorrt-_dynamic-320x320-2240x2240.py
,把 dbnet
模型转为 tensorrt 模型。
小技巧
当转 tensorrt 模型时, –device 需要被设置为 “cuda”
模型规范¶
在使用转换后的模型进行推理之前,有必要了解转换结果的结构。 它存放在 --work-dir
指定的路路径下。
文字检测任务模型转换例子中的mmdeploy_models/mmocr/dbnet/ort
,结构如下:
mmdeploy_models/mmocr/dbnet/ort
├── deploy.json
├── detail.json
├── end2end.onnx
└── pipeline.json
重要的是:
end2end.onnx: 推理引擎文件。可用 ONNX Runtime 推理
*.json: mmdeploy SDK 推理所需的 meta 信息
整个文件夹被定义为mmdeploy SDK model。换言之,mmdeploy SDK model既包括推理引擎,也包括推理 meta 信息。
模型推理¶
后端模型推理¶
from mmdeploy.apis.utils import build_task_processor
from mmdeploy.utils import get_input_shape, load_config
import torch
deploy_cfg = 'configs/mmocr/text-detection/text-detection_onnxruntime_dynamic.py'
model_cfg = 'dbnet_resnet18_fpnc_1200e_icdar2015.py'
device = 'cpu'
backend_model = ['./mmdeploy_models/mmocr/dbnet/ort/end2end.onnx']
image = './demo/resources/text_det.jpg'
# read deploy_cfg and model_cfg
deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg)
# build task and backend model
task_processor = build_task_processor(model_cfg, deploy_cfg, device)
model = task_processor.build_backend_model(backend_model)
# process input image
input_shape = get_input_shape(deploy_cfg)
model_inputs, _ = task_processor.create_input(image, input_shape)
# do model inference
with torch.no_grad():
result = model.test_step(model_inputs)
# visualize results
task_processor.visualize(
image=image,
model=model,
result=result[0],
window_name='visualize',
output_file='output_ocr.png')
提示:
在这个脚本中,把’deploy_cfg’, ‘model_cfg’, ‘backend_model’ and ‘image’ 替换为文字识别任务模型转换中对应的参数,
就可以推理 crnn
onnx 模型了。
SDK 模型推理¶
文字检测 SDK 模型推理¶
你也可以参考如下代码,对 dbnet
SDK model 进行推理:
import cv2
from mmdeploy_runtime import TextDetector
img = cv2.imread('demo/resources/text_det.jpg')
# create text detector
detector = TextDetector(
model_path='mmdeploy_models/mmocr/dbnet/ort',
device_name='cpu',
device_id=0)
# do model inference
bboxes = detector(img)
# draw detected bbox into the input image
if len(bboxes) > 0:
pts = ((bboxes[:, 0:8] + 0.5).reshape(len(bboxes), -1,
2).astype(int))
cv2.polylines(img, pts, True, (0, 255, 0), 2)
cv2.imwrite('output_ocr.png', img)
文字识别 SDK 模型推理¶
import cv2
from mmdeploy_runtime import TextRecognizer
img = cv2.imread('demo/resources/text_recog.jpg')
# create text recognizer
recognizer = TextRecognizer(
model_path='mmdeploy_models/mmocr/crnn/ort',
device_name='cpu',
device_id=0
)
# do model inference
texts = recognizer(img)
# print the result
print(texts)
除了python API,mmdeploy SDK 还提供了诸如 C、C++、C#、Java等多语言接口。 你可以参考样例学习其他语言接口的使用方法。
模型支持列表¶
Model | Task | TorchScript | OnnxRuntime | TensorRT | ncnn | PPLNN | OpenVINO |
---|---|---|---|---|---|---|---|
DBNet | text-detection | Y | Y | Y | Y | Y | Y |
DBNetpp | text-detection | N | Y | Y | ? | ? | Y |
PSENet | text-detection | Y | Y | Y | Y | N | Y |
PANet | text-detection | Y | Y | Y | Y | N | Y |
TextSnake | text-detection | Y | Y | Y | ? | ? | ? |
MaskRCNN | text-detection | Y | Y | Y | ? | ? | ? |
CRNN | text-recognition | Y | Y | Y | Y | Y | N |
SAR | text-recognition | N | Y | Y | N | N | N |
SATRN | text-recognition | Y | Y | Y | N | N | N |
ABINet | text-recognition | Y | Y | Y | ? | ? | ? |
注意事项¶
ABINet 在 TensorRT 后端要求使用 pytorch1.10+, TensorRT 8.4+。
SAR 在网络推广中使用
valid_ratio
,这会让导出的 ONNX 文件精度下降。当测试图片的valid_ratio
s 和转换图片的值差异很大,这种下降就会越多。对于 TensorRT 后端,用户需要使用正确的配置文件。比如 CRNN 只接受单通道输入。下面是一个示例表格:
Model | Config |
---|---|
MaskRCNN | text-detection_mrcnn_tensorrt_dynamic-320x320-2240x2240.py |
CRNN | text-recognition_tensorrt_dynamic-1x32x32-1x32x640.py |
SATRN | text-recognition_tensorrt_dynamic-32x32-32x640.py |
SAR | text-recognition_tensorrt_dynamic-48x64-48x640.py |
ABINet | text-recognition_tensorrt_static-32x128.py |
MMPose 模型部署¶
MMPose,又称 mmpose
,是一个基于 PyTorch 的姿态估计的开源工具箱,是 OpenMMLab 项目的成员之一。
安装¶
安装 mmdeploy¶
mmdeploy 有以下几种安装方式:
方式一: 安装预编译包
请参考安装概述
方式二: 一键式脚本安装
如果部署平台是 Ubuntu 18.04 及以上版本, 请参考脚本安装说明,完成安装过程。
比如,以下命令可以安装 mmdeploy 以及配套的推理引擎——ONNX Runtime
.
git clone --recursive -b main https://github.com/open-mmlab/mmdeploy.git
cd mmdeploy
python3 tools/scripts/build_ubuntu_x64_ort.py $(nproc)
export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH
export LD_LIBRARY_PATH=$(pwd)/../mmdeploy-dep/onnxruntime-linux-x64-1.8.1/lib/:$LD_LIBRARY_PATH
方式三: 源码安装
在方式一、二都满足不了的情况下,请参考源码安装说明 安装 mmdeploy 以及所需推理引擎。
模型转换¶
你可以使用 tools/deploy.py 把 mmpose 模型一键式转换为推理后端模型。 该工具的详细使用说明请参考这里.
以下,我们将演示如何把 hrnet
转换为 onnx 模型。
cd mmdeploy
# download hrnet model from mmpose model zoo
mim download mmpose --config td-hm_hrnet-w32_8xb64-210e_coco-256x192 --dest .
# convert mmdet model to onnxruntime model with static shape
python tools/deploy.py \
configs/mmpose/pose-detection_onnxruntime_static.py \
td-hm_hrnet-w32_8xb64-210e_coco-256x192.py \
hrnet_w32_coco_256x192-c78dce93_20200708.pth \
demo/resources/human-pose.jpg \
--work-dir mmdeploy_models/mmpose/ort \
--device cpu \
--show
转换的关键之一是使用正确的配置文件。项目中已内置了各后端部署配置文件。 文件的命名模式是:
pose-detection_{backend}-{precision}_{static | dynamic}_{shape}.py
其中:
{backend}: 推理后端名称。比如,onnxruntime、tensorrt、pplnn、ncnn、openvino、coreml 等等
{precision}: 推理精度。比如,fp16、int8。不填表示 fp32
{static | dynamic}: 动态、静态 shape
{shape}: 模型输入的 shape 或者 shape 范围
在上例中,你也可以把 hrnet
转为其他后端模型。比如使用pose-detection_tensorrt_static-256x192.py
,把模型转为 tensorrt 模型。
小技巧
当转 tensorrt 模型时, –device 需要被设置为 “cuda”
模型规范¶
在使用转换后的模型进行推理之前,有必要了解转换结果的结构。 它存放在 --work-dir
指定的路路径下。
上例中的mmdeploy_models/mmpose/ort
,结构如下:
mmdeploy_models/mmpose/ort
├── deploy.json
├── detail.json
├── end2end.onnx
└── pipeline.json
重要的是:
end2end.onnx: 推理引擎文件。可用 ONNX Runtime 推理
*.json: mmdeploy SDK 推理所需的 meta 信息
整个文件夹被定义为mmdeploy SDK model。换言之,mmdeploy SDK model既包括推理引擎,也包括推理 meta 信息。
模型推理¶
后端模型推理¶
以上述模型转换后的 end2end.onnx
为例,你可以使用如下代码进行推理:
from mmdeploy.apis.utils import build_task_processor
from mmdeploy.utils import get_input_shape, load_config
import torch
deploy_cfg = 'configs/mmpose/pose-detection_onnxruntime_static.py'
model_cfg = 'td-hm_hrnet-w32_8xb64-210e_coco-256x192.py'
device = 'cpu'
backend_model = ['./mmdeploy_models/mmpose/ort/end2end.onnx']
image = './demo/resources/human-pose.jpg'
# read deploy_cfg and model_cfg
deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg)
# build task and backend model
task_processor = build_task_processor(model_cfg, deploy_cfg, device)
model = task_processor.build_backend_model(backend_model)
# process input image
input_shape = get_input_shape(deploy_cfg)
model_inputs, _ = task_processor.create_input(image, input_shape)
# do model inference
with torch.no_grad():
result = model.test_step(model_inputs)
# visualize results
task_processor.visualize(
image=image,
model=model,
result=result[0],
window_name='visualize',
output_file='output_pose.png')
SDK 模型推理¶
TODO
模型支持列表¶
Model | Task | ONNX Runtime | TensorRT | ncnn | PPLNN | OpenVINO |
---|---|---|---|---|---|---|
HRNet | PoseDetection | Y | Y | Y | N | Y |
MSPN | PoseDetection | Y | Y | Y | N | Y |
LiteHRNet | PoseDetection | Y | Y | Y | N | Y |
Hourglass | PoseDetection | Y | Y | Y | N | Y |
SimCC | PoseDetection | Y | Y | Y | N | Y |
RTMPose | PoseDetection | Y | Y | Y | N | Y |
YoloX-Pose | PoseDetection | Y | Y | N | N | Y |
RTMO | PoseDetection | Y | Y | N | N | N |
MMRotate 模型部署¶
MMRotate 是一个基于 PyTorch 的旋转物体检测的开源工具箱,也是 OpenMMLab 项目的一部分。
安装¶
安装 mmdeploy¶
mmdeploy 有以下几种安装方式:
方式一: 安装预编译包
请参考安装概述
方式二: 一键式脚本安装
如果部署平台是 Ubuntu 18.04 及以上版本, 请参考脚本安装说明,完成安装过程。
比如,以下命令可以安装 mmdeploy 以及配套的推理引擎——ONNX Runtime
.
git clone --recursive -b main https://github.com/open-mmlab/mmdeploy.git
cd mmdeploy
python3 tools/scripts/build_ubuntu_x64_ort.py $(nproc)
export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH
export LD_LIBRARY_PATH=$(pwd)/../mmdeploy-dep/onnxruntime-linux-x64-1.8.1/lib/:$LD_LIBRARY_PATH
说明:
把
$(pwd)/build/lib
添加到PYTHONPATH
,目的是为了加载 mmdeploy SDK python 包mmdeploy_runtime
,在章节 SDK模型推理中讲述其用法。在使用 ONNX Runtime推理后端模型时,需要加载自定义算子库,需要把 ONNX Runtime 库的路径加入环境变量
LD_LIBRARY_PATH
中。 方式三: 源码安装
在方式一、二都满足不了的情况下,请参考源码安装说明 安装 mmdeploy 以及所需推理引擎。
模型转换¶
你可以使用 tools/deploy.py 把 mmrotate 模型一键式转换为推理后端模型。 该工具的详细使用说明请参考这里.
以下,我们将演示如何把 rotated-faster-rcnn
转换为 onnx 模型。
cd mmdeploy
# download rotated-faster-rcnn model from mmrotate model zoo
mim download mmrotate --config rotated-faster-rcnn-le90_r50_fpn_1x_dota --dest .
wget https://github.com/open-mmlab/mmrotate/raw/main/demo/dota_demo.jpg
# convert mmrotate model to onnxruntime model with dynamic shape
python tools/deploy.py \
configs/mmrotate/rotated-detection_onnxruntime_dynamic.py \
rotated-faster-rcnn-le90_r50_fpn_1x_dota.py \
rotated_faster_rcnn_r50_fpn_1x_dota_le90-0393aa5c.pth \
dota_demo.jpg \
--work-dir mmdeploy_models/mmrotate/ort \
--device cpu \
--show \
--dump-info
转换的关键之一是使用正确的配置文件。项目中已内置了各后端部署配置文件。 文件的命名模式是:
rotated_detection-{backend}-{precision}_{static | dynamic}_{shape}.py
其中:
{backend}: 推理后端名称。比如,onnxruntime、tensorrt、pplnn、ncnn、openvino、coreml 等等
{precision}: 推理精度。比如,fp16、int8。不填表示 fp32
{static | dynamic}: 动态、静态 shape
{shape}: 模型输入的 shape 或者 shape 范围
在上例中,你也可以把 rotated-faster-rcnn
转为其他后端模型。比如使用rotated-detection_tensorrt-fp16_dynamic-320x320-1024x1024.py
,把模型转为 tensorrt-fp16 模型。
小技巧
当转 tensorrt 模型时, –device 需要被设置为 “cuda”
模型规范¶
在使用转换后的模型进行推理之前,有必要了解转换结果的结构。 它存放在 --work-dir
指定的路路径下。
上例中的mmdeploy_models/mmrotate/ort
,结构如下:
mmdeploy_models/mmrotate/ort
├── deploy.json
├── detail.json
├── end2end.onnx
└── pipeline.json
重要的是:
end2end.onnx: 推理引擎文件。可用 ONNX Runtime 推理
*.json: mmdeploy SDK 推理所需的 meta 信息
整个文件夹被定义为mmdeploy SDK model。换言之,mmdeploy SDK model既包括推理引擎,也包括推理 meta 信息。
模型推理¶
后端模型推理¶
以上述模型转换后的 end2end.onnx
为例,你可以使用如下代码进行推理:
from mmdeploy.apis.utils import build_task_processor
from mmdeploy.utils import get_input_shape, load_config
import torch
deploy_cfg = 'configs/mmrotate/rotated-detection_onnxruntime_dynamic.py'
model_cfg = './rotated-faster-rcnn-le90_r50_fpn_1x_dota.py'
device = 'cpu'
backend_model = ['./mmdeploy_models/mmrotate/ort/end2end.onnx']
image = './dota_demo.jpg'
# read deploy_cfg and model_cfg
deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg)
# build task and backend model
task_processor = build_task_processor(model_cfg, deploy_cfg, device)
model = task_processor.build_backend_model(backend_model)
# process input image
input_shape = get_input_shape(deploy_cfg)
model_inputs, _ = task_processor.create_input(image, input_shape)
# do model inference
with torch.no_grad():
result = model.test_step(model_inputs)
# visualize results
task_processor.visualize(
image=image,
model=model,
result=result[0],
window_name='visualize',
output_file='./output.png')
SDK 模型推理¶
你也可以参考如下代码,对 SDK model 进行推理:
from mmdeploy_runtime import RotatedDetector
import cv2
import numpy as np
img = cv2.imread('./dota_demo.jpg')
# create a detector
detector = RotatedDetector(model_path='./mmdeploy_models/mmrotate/ort', device_name='cpu', device_id=0)
# perform inference
det = detector(img)
除了python API,mmdeploy SDK 还提供了诸如 C、C++、C#、Java等多语言接口。 你可以参考样例学习其他语言接口的使用方法。
模型支持列表¶
Model | OnnxRuntime | TensorRT |
---|---|---|
Rotated RetinaNet | Y | Y |
Rotated FasterRCNN | Y | Y |
Oriented R-CNN | Y | Y |
Gliding Vertex | Y | Y |
RTMDET-R | Y | Y |
MMSegmentation 模型部署¶
MMSegmentation 又称mmseg
,是一个基于 PyTorch 的开源对象分割工具箱。它是 OpenMMLab 项目的一部分。
安装¶
安装 mmdeploy¶
mmdeploy 有以下几种安装方式:
方式一: 安装预编译包
请参考安装概述
方式二: 一键式脚本安装
如果部署平台是 Ubuntu 18.04 及以上版本, 请参考脚本安装说明,完成安装过程。
比如,以下命令可以安装 mmdeploy 以及配套的推理引擎——ONNX Runtime
.
git clone --recursive -b main https://github.com/open-mmlab/mmdeploy.git
cd mmdeploy
python3 tools/scripts/build_ubuntu_x64_ort.py $(nproc)
export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH
export LD_LIBRARY_PATH=$(pwd)/../mmdeploy-dep/onnxruntime-linux-x64-1.8.1/lib/:$LD_LIBRARY_PATH
说明:
把
$(pwd)/build/lib
添加到PYTHONPATH
,目的是为了加载 mmdeploy SDK python 包mmdeploy_runtime
,在章节 SDK模型推理中讲述其用法。在使用 ONNX Runtime推理后端模型时,需要加载自定义算子库,需要把 ONNX Runtime 库的路径加入环境变量
LD_LIBRARY_PATH
中。
方式三: 源码安装
在方式一、二都满足不了的情况下,请参考源码安装说明 安装 mmdeploy 以及所需推理引擎。
模型转换¶
你可以使用 tools/deploy.py 把 mmseg 模型一键式转换为推理后端模型。 该工具的详细使用说明请参考这里.
以下,我们将演示如何把 unet
转换为 onnx 模型。
cd mmdeploy
# download unet model from mmseg model zoo
mim download mmsegmentation --config unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024 --dest .
# convert mmseg model to onnxruntime model with dynamic shape
python tools/deploy.py \
configs/mmseg/segmentation_onnxruntime_dynamic.py \
unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py \
fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes_20211210_145204-6860854e.pth \
demo/resources/cityscapes.png \
--work-dir mmdeploy_models/mmseg/ort \
--device cpu \
--show \
--dump-info
转换的关键之一是使用正确的配置文件。项目中已内置了各后端部署配置文件。 文件的命名模式是:
segmentation_{backend}-{precision}_{static | dynamic}_{shape}.py
其中:
{backend}: 推理后端名称。比如,onnxruntime、tensorrt、pplnn、ncnn、openvino、coreml 等等
{precision}: 推理精度。比如,fp16、int8。不填表示 fp32
{static | dynamic}: 动态、静态 shape
{shape}: 模型输入的 shape 或者 shape 范围
在上例中,你也可以把 unet
转为其他后端模型。比如使用segmentation_tensorrt-fp16_dynamic-512x1024-2048x2048.py
,把模型转为 tensorrt-fp16 模型。
小技巧
当转 tensorrt 模型时, –device 需要被设置为 “cuda”
模型规范¶
在使用转换后的模型进行推理之前,有必要了解转换结果的结构。 它存放在 --work-dir
指定的路路径下。
上例中的mmdeploy_models/mmseg/ort
,结构如下:
mmdeploy_models/mmseg/ort
├── deploy.json
├── detail.json
├── end2end.onnx
└── pipeline.json
重要的是:
end2end.onnx: 推理引擎文件。可用 ONNX Runtime 推理
*.json: mmdeploy SDK 推理所需的 meta 信息
整个文件夹被定义为mmdeploy SDK model。换言之,mmdeploy SDK model既包括推理引擎,也包括推理 meta 信息。
模型推理¶
后端模型推理¶
以上述模型转换后的 end2end.onnx
为例,你可以使用如下代码进行推理:
from mmdeploy.apis.utils import build_task_processor
from mmdeploy.utils import get_input_shape, load_config
import torch
deploy_cfg = 'configs/mmseg/segmentation_onnxruntime_dynamic.py'
model_cfg = './unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py'
device = 'cpu'
backend_model = ['./mmdeploy_models/mmseg/ort/end2end.onnx']
image = './demo/resources/cityscapes.png'
# read deploy_cfg and model_cfg
deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg)
# build task and backend model
task_processor = build_task_processor(model_cfg, deploy_cfg, device)
model = task_processor.build_backend_model(backend_model)
# process input image
input_shape = get_input_shape(deploy_cfg)
model_inputs, _ = task_processor.create_input(image, input_shape)
# do model inference
with torch.no_grad():
result = model.test_step(model_inputs)
# visualize results
task_processor.visualize(
image=image,
model=model,
result=result[0],
window_name='visualize',
output_file='./output_segmentation.png')
SDK 模型推理¶
你也可以参考如下代码,对 SDK model 进行推理:
from mmdeploy_runtime import Segmentor
import cv2
import numpy as np
img = cv2.imread('./demo/resources/cityscapes.png')
# create a classifier
segmentor = Segmentor(model_path='./mmdeploy_models/mmseg/ort', device_name='cpu', device_id=0)
# perform inference
seg = segmentor(img)
# visualize inference result
## random a palette with size 256x3
palette = np.random.randint(0, 256, size=(256, 3))
color_seg = np.zeros((seg.shape[0], seg.shape[1], 3), dtype=np.uint8)
for label, color in enumerate(palette):
color_seg[seg == label, :] = color
# convert to BGR
color_seg = color_seg[..., ::-1]
img = img * 0.5 + color_seg * 0.5
img = img.astype(np.uint8)
cv2.imwrite('output_segmentation.png', img)
除了python API,mmdeploy SDK 还提供了诸如 C、C++、C#、Java等多语言接口。 你可以参考样例学习其他语言接口的使用方法。
模型支持列表¶
Model | TorchScript | OnnxRuntime | TensorRT | ncnn | PPLNN | OpenVino |
---|---|---|---|---|---|---|
FCN | Y | Y | Y | Y | Y | Y |
PSPNet* | Y | Y | Y | Y | Y | Y |
DeepLabV3 | Y | Y | Y | Y | Y | Y |
DeepLabV3+ | Y | Y | Y | Y | Y | Y |
Fast-SCNN* | Y | Y | Y | N | Y | Y |
UNet | Y | Y | Y | Y | Y | Y |
ANN* | Y | Y | Y | N | N | N |
APCNet | Y | Y | Y | Y | N | N |
BiSeNetV1 | Y | Y | Y | Y | N | Y |
BiSeNetV2 | Y | Y | Y | Y | N | Y |
CGNet | Y | Y | Y | Y | N | Y |
DMNet | ? | Y | N | N | N | N |
DNLNet | ? | Y | Y | Y | N | Y |
EMANet | Y | Y | Y | N | N | Y |
EncNet | Y | Y | Y | N | N | Y |
ERFNet | Y | Y | Y | Y | N | Y |
FastFCN | Y | Y | Y | Y | N | Y |
GCNet | Y | Y | Y | N | N | N |
ICNet* | Y | Y | Y | N | N | Y |
ISANet* | N | Y | Y | N | N | Y |
NonLocal Net | ? | Y | Y | Y | N | Y |
OCRNet | Y | Y | Y | Y | N | Y |
PointRend* | Y | Y | Y | N | N | N |
Semantic FPN | Y | Y | Y | Y | N | Y |
STDC | Y | Y | Y | Y | N | Y |
UPerNet* | N | Y | Y | N | N | N |
DANet | ? | Y | Y | N | N | Y |
Segmenter* | N | Y | Y | Y | N | Y |
SegFormer* | Y | Y | Y | N | N | Y |
SETR | ? | Y | N | N | N | Y |
CCNet | ? | N | N | N | N | N |
PSANet | ? | N | N | N | N | N |
DPT | ? | N | N | N | N | N |
注意事项¶
所有 mmseg 模型仅支持 “whole” 推理模式。
PSPNet,Fast-SCNN 仅支持静态输入,因为多数推理框架的 nn.AdaptiveAvgPool2d 不支持动态输入。
对于仅支持静态形状的模型,应使用静态形状的部署配置文件,例如
configs/mmseg/segmentation_tensorrt_static-1024x2048.py
对于喜欢部署模型生成概率特征图的用户,将
codebase_config = dict(with_argmax=False)
放在部署配置中就足够了。
MMAction2 模型部署¶
MMAction2是一款基于 PyTorch 的视频理解开源工具箱,是OpenMMLab项目的成员之一。
安装¶
安装 mmdeploy¶
mmdeploy 有以下几种安装方式:
方式一: 安装预编译包
请参考安装概述
方式二: 一键式脚本安装
如果部署平台是 Ubuntu 18.04 及以上版本, 请参考脚本安装说明,完成安装过程。
比如,以下命令可以安装 mmdeploy 以及配套的推理引擎——ONNX Runtime
.
git clone --recursive -b main https://github.com/open-mmlab/mmdeploy.git
cd mmdeploy
python3 tools/scripts/build_ubuntu_x64_ort.py $(nproc)
export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH
export LD_LIBRARY_PATH=$(pwd)/../mmdeploy-dep/onnxruntime-linux-x64-1.8.1/lib/:$LD_LIBRARY_PATH
方式三: 源码安装
在方式一、二都满足不了的情况下,请参考源码安装说明 安装 mmdeploy 以及所需推理引擎。
模型转换¶
你可以使用 tools/deploy.py 把 mmaction2 模型一键式转换为推理后端模型。 该工具的详细使用说明请参考这里.
转换的关键之一是使用正确的配置文件。项目中已内置了各后端部署配置文件。 文件的命名模式是:
{task}/{task}_{backend}-{precision}_{static | dynamic}_{shape}.py
其中:
{task}: mmaction2 中的任务
{backend}: 推理后端名称。比如,onnxruntime、tensorrt、pplnn、ncnn、openvino、coreml 等等
{precision}: 推理精度。比如,fp16、int8。不填表示 fp32
{static | dynamic}: 动态、静态 shape
{shape}: 模型输入的 shape 或者 shape 范围
{2d/3d}: 表示模型的类别
以下,我们将演示如何把视频分类任务中 tsn
模型转换为 onnx 模型。
视频分类任务模型转换¶
cd mmdeploy
# download tsn model from mmaction2 model zoo
mim download mmaction2 --config tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb --dest .
# convert mmaction2 model to onnxruntime model with dynamic shape
python tools/deploy.py \
configs/mmaction/video-recognition/video-recognition_2d_onnxruntime_static.py \
tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb \
tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb_20220906-cd10898e.pth \
tests/data/arm_wrestling.mp4 \
--work-dir mmdeploy_models/mmaction/tsn/ort \
--device cpu \
--show \
--dump-info
模型规范¶
在使用转换后的模型进行推理之前,有必要了解转换结果的结构。 它存放在 --work-dir
指定的路路径下。
上例中的mmdeploy_models/mmaction/tsn/ort
,结构如下:
mmdeploy_models/mmaction/tsn/ort
├── deploy.json
├── detail.json
├── end2end.onnx
└── pipeline.json
重要的是:
end2end.onnx: 推理引擎文件。可用 ONNX Runtime 推理
*.json: mmdeploy SDK 推理所需的 meta 信息
整个文件夹被定义为mmdeploy SDK model。换言之,mmdeploy SDK model既包括推理引擎,也包括推理 meta 信息。
模型推理¶
后端模型推理¶
以上述模型转换后的 end2end.onnx
为例,你可以使用如下代码进行推理:
from mmdeploy.apis.utils import build_task_processor
from mmdeploy.utils import get_input_shape, load_config
import numpy as np
import torch
deploy_cfg = 'configs/mmaction/video-recognition/video-recognition_2d_onnxruntime_static.py'
model_cfg = 'tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb'
device = 'cpu'
backend_model = ['./mmdeploy_models/mmaction2/tsn/ort/end2end.onnx']
image = 'tests/data/arm_wrestling.mp4'
# read deploy_cfg and model_cfg
deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg)
# build task and backend model
task_processor = build_task_processor(model_cfg, deploy_cfg, device)
model = task_processor.build_backend_model(backend_model)
# process input image
input_shape = get_input_shape(deploy_cfg)
model_inputs, _ = task_processor.create_input(image, input_shape)
# do model inference
with torch.no_grad():
result = model.test_step(model_inputs)
# show top5-results
pred_scores = result[0].pred_scores.item.tolist()
top_index = np.argsort(pred_scores)[::-1]
for i in range(5):
index = top_index[i]
print(index, pred_scores[index])
SDK 模型推理¶
你也可以参考如下代码,对 SDK model 进行推理:
视频分类 SDK 模型推理¶
from mmdeploy_runtime import VideoRecognizer
import cv2
# refer to demo/python/video_recognition.py
# def SampleFrames(cap, clip_len, frame_interval, num_clips):
# ...
cap = cv2.VideoCapture('tests/data/arm_wrestling.mp4')
clips, info = SampleFrames(cap, 1, 1, 25)
# create a recognizer
recognizer = VideoRecognizer(model_path='./mmdeploy_models/mmaction/tsn/ort', device_name='cpu', device_id=0)
# perform inference
result = recognizer(clips, info)
# show inference result
for label_id, score in result:
print(label_id, score)
除了python API,mmdeploy SDK 还提供了诸如 C、C++、C#、Java等多语言接口。 你可以参考样例学习其他语言接口的使用方法。
mmaction2 的 C#,Java接口待开发
ncnn 支持情况¶
目前对 ncnn 特性使用情况如下:
feature | windows | linux | mac | android |
---|---|---|---|---|
fp32 inference | ✔️ | ✔️ | ✔️ | ✔️ |
int8 model convert | - | ✔️ | ✔️ | - |
nchw layout | ✔️ | ✔️ | ✔️ | ✔️ |
Vulkan support | - | ✔️ | ✔️ | ✔️ |
以下特性还不能由 mmdeploy 自动开启,需要手动修改 ncnn 编译参数、或在 SDK 中调整运行参数
bf16 inference
nc4hw4 layout
profiling per layer
关闭 NCNN_STRING 以减小 so 体积
设置线程数和 CPU 亲和力
onnxruntime 支持情况¶
安装¶
请注意,目前Linux平台只支持 onnxruntime>=1.8.1 。
安装ONNX Runtime python包¶
CPU 版本
pip install onnxruntime==1.8.1 # 如果你想用cpu版本
GPU 版本
pip install onnxruntime-gpu==1.8.1 # 如果你想用gpu版本
构建自定义算子¶
下载ONNXRuntime库¶
从ONNX Runtime发布版本下载onnxruntime-linux-*.tgz
库,并解压,将onnxruntime所在路径添加到ONNXRUNTIME_DIR
环境变量,最后将lib路径添加到LD_LIBRARY_PATH
环境变量中,操作如下:
CPU 版本
wget https://github.com/microsoft/onnxruntime/releases/download/v1.8.1/onnxruntime-linux-x64-1.8.1.tgz
tar -zxvf onnxruntime-linux-x64-1.8.1.tgz
cd onnxruntime-linux-x64-1.8.1
export ONNXRUNTIME_DIR=$(pwd)
export LD_LIBRARY_PATH=$ONNXRUNTIME_DIR/lib:$LD_LIBRARY_PATH
GPU 版本
X64 GPU:
wget https://github.com/microsoft/onnxruntime/releases/download/v1.8.1/onnxruntime-linux-x64-gpu-1.8.1.tgz
tar -zxvf onnxruntime-linux-x64-gpu-1.8.1.tgz
cd onnxruntime-linux-x64-gpu-1.8.1
export ONNXRUNTIME_DIR=$(pwd)
export LD_LIBRARY_PATH=$ONNXRUNTIME_DIR/lib:$LD_LIBRARY_PATH
Arm GPU:
# Arm not have 1.8.1 version package
wget https://github.com/microsoft/onnxruntime/releases/download/v1.10.0/onnxruntime-linux-aarch64-1.10.0.tgz
tar -zxvf onnxruntime-linux-aarch64-1.10.0.tgz
cd onnxruntime-linux-aarch64-1.10.0
export ONNXRUNTIME_DIR=$(pwd)
export LD_LIBRARY_PATH=$ONNXRUNTIME_DIR/lib:$LD_LIBRARY_PATH
在Linux上构建¶
CPU 版本
cd ${MMDEPLOY_DIR} # 进入MMDeploy根目录
mkdir -p build && cd build
cmake -DMMDEPLOY_TARGET_DEVICES='cpu' -DMMDEPLOY_TARGET_BACKENDS=ort -DONNXRUNTIME_DIR=${ONNXRUNTIME_DIR} ..
make -j$(nproc) && make install
GPU 版本
cd ${MMDEPLOY_DIR} # 进入MMDeploy根目录
mkdir -p build && cd build
cmake -DMMDEPLOY_TARGET_DEVICES='cuda' -DMMDEPLOY_TARGET_BACKENDS=ort -DONNXRUNTIME_DIR=${ONNXRUNTIME_DIR} ..
make -j$(nproc) && make install
如何添加新的自定义算子¶
OpenVINO Support¶
This tutorial is based on Linux systems like Ubuntu-18.04.
Installation¶
It is recommended to create a virtual environment for the project.
Install python package¶
Install OpenVINO. It is recommended to use the installer or install using pip. Installation example using pip:
pip install openvino-dev[onnx]==2022.3.0
Download OpenVINO runtime for SDK (Optional)¶
If you want to use OpenVINO in SDK, you need install OpenVINO with install_guides.
Take openvino==2022.3.0
as example:
wget https://storage.openvinotoolkit.org/repositories/openvino/packages/2022.3/linux/l_openvino_toolkit_ubuntu20_2022.3.0.9052.9752fafe8eb_x86_64.tgz
tar xzf ./l_openvino_toolkit*.tgz
cd l_openvino*
export InferenceEngine_DIR=$pwd/runtime/cmake
bash ./install_dependencies/install_openvino_dependencies.sh
Build mmdeploy SDK with OpenVINO (Optional)¶
Install MMDeploy following the instructions.
cd ${MMDEPLOY_DIR} # To MMDeploy root directory
mkdir -p build && cd build
cmake -DMMDEPLOY_TARGET_DEVICES='cpu' -DMMDEPLOY_TARGET_BACKENDS=openvino -DInferenceEngine_DIR=${InferenceEngine_DIR} ..
make -j$(nproc) && make install
To work with models from MMDetection, you may need to install it additionally.
Usage¶
You could follow the instructions of tutorial How to convert model
Example:
python tools/deploy.py \
configs/mmdet/detection/detection_openvino_static-300x300.py \
/mmdetection_dir/mmdetection/configs/ssd/ssd300_coco.py \
/tmp/snapshots/ssd300_coco_20210803_015428-d231a06e.pth \
tests/data/tiger.jpeg \
--work-dir ../deploy_result \
--device cpu \
--log-level INFO
List of supported models exportable to OpenVINO from MMDetection¶
The table below lists the models that are guaranteed to be exportable to OpenVINO from MMDetection.
Model name | Config | Dynamic Shape |
---|---|---|
ATSS | configs/atss/atss_r50_fpn_1x_coco.py |
Y |
Cascade Mask R-CNN | configs/cascade_rcnn/cascade_mask_rcnn_r50_fpn_1x_coco.py |
Y |
Cascade R-CNN | configs/cascade_rcnn/cascade_rcnn_r50_fpn_1x_coco.py |
Y |
Faster R-CNN | configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py |
Y |
FCOS | configs/fcos/fcos_x101_64x4d_fpn_gn-head_mstrain_640-800_4x2_2x_coco.py |
Y |
FoveaBox | configs/foveabox/fovea_r50_fpn_4x4_1x_coco.py |
Y |
FSAF | configs/fsaf/fsaf_r50_fpn_1x_coco.py |
Y |
Mask R-CNN | configs/mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py |
Y |
RetinaNet | configs/retinanet/retinanet_r50_fpn_1x_coco.py |
Y |
SSD | configs/ssd/ssd300_coco.py |
Y |
YOLOv3 | configs/yolo/yolov3_d53_mstrain-608_273e_coco.py |
Y |
YOLOX | configs/yolox/yolox_tiny_8x8_300e_coco.py |
Y |
Faster R-CNN + DCN | configs/dcn/faster_rcnn_r50_fpn_dconv_c3-c5_1x_coco.py |
Y |
VFNet | configs/vfnet/vfnet_r50_fpn_1x_coco.py |
Y |
Notes:
Custom operations from OpenVINO use the domain
org.openvinotoolkit
.For faster work in OpenVINO in the Faster-RCNN, Mask-RCNN, Cascade-RCNN, Cascade-Mask-RCNN models the RoiAlign operation is replaced with the ExperimentalDetectronROIFeatureExtractor operation in the ONNX graph.
Models “VFNet” and “Faster R-CNN + DCN” use the custom “DeformableConv2D” operation.
Deployment config¶
With the deployment config, you can specify additional options for the Model Optimizer.
To do this, add the necessary parameters to the backend_config.mo_options
in the fields args
(for parameters with values) and flags
(for flags).
Example:
backend_config = dict(
mo_options=dict(
args=dict({
'--mean_values': [0, 0, 0],
'--scale_values': [255, 255, 255],
'--data_type': 'FP32',
}),
flags=['--disable_fusing'],
)
)
Information about the possible parameters for the Model Optimizer can be found in the documentation.
Troubleshooting¶
ImportError: libpython3.7m.so.1.0: cannot open shared object file: No such file or directory
To resolve missing external dependency on Ubuntu*, execute the following command:
sudo apt-get install libpython3.7
PPLNN 支持情况¶
MMDeploy supports ppl.nn v0.8.1 and later. This tutorial is based on Linux systems like Ubuntu-18.04.
Installation¶
Please install pyppl following install-guide.
Install MMDeploy following the instructions.
Usage¶
Example:
python tools/deploy.py \
configs/mmdet/detection/detection_pplnn_dynamic-800x1344.py \
/mmdetection_dir/mmdetection/configs/retinanet/retinanet_r50_fpn_1x_coco.py \
/tmp/snapshots/retinanet_r50_fpn_1x_coco_20200130-c2398f9e.pth \
tests/data/tiger.jpeg \
--work-dir ../deploy_result \
--device cuda \
--log-level INFO
支持的 RKNN 特征¶
目前, MMDeploy 只在 rk3588 和 rv1126 的 linux 平台上测试过.
以下特性需要手动在 MMDeploy 自行配置,如这里.
target_platform != default
quantization settings
optimization level != 1
SNPE 支持情况¶
目前 mmdeploy 集成了 onnx2dlc 模型转换的 SDK 推理,但以下特性还不支持:
GPU_FP16 模式
DSP/AIP 量化
算子内部 profiling
UDO 算子
TensorRT 支持情况¶
安装¶
安装TensorRT¶
请按照安装指南安装TensorRT8。
注意:
此版本不支持
pip Wheel File Installation
。我们强烈建议通过tar包的方式安装TensorRT。
安装完成后,最好通过以下方式将TensorRT环境变量添加到bashrc:
cd ${TENSORRT_DIR} # 进入TensorRT根目录 echo '# set env for TensorRT' >> ~/.bashrc echo "export TENSORRT_DIR=${TENSORRT_DIR}" >> ~/.bashrc echo 'export LD_LIBRARY_PATH=$TENSORRT_DIR/lib:$TENSORRT_DIR' >> ~/.bashrc source ~/.bashrc
构建自定义算子¶
OpenMMLab中创建了一些自定义算子来支持模型,自定义算子可以如下构建:
cd ${MMDEPLOY_DIR} # 进入TensorRT根目录
mkdir -p build && cd build
cmake -DMMDEPLOY_TARGET_BACKENDS=trt ..
make -j$(nproc)
如果你没有在默认路径下安装TensorRT,请在CMake中添加-DTENSORRT_DIR
标志。
cmake -DMMDEPLOY_TARGET_BACKENDS=trt -DTENSORRT_DIR=${TENSORRT_DIR} ..
make -j$(nproc) && make install
转换模型¶
请遵循如何转换模型中的教程。注意设备必须是cuda
设备。
Int8 支持¶
由于TensorRT支持INT8模式,因此可以提供自定义数据集配置来校准模型。MMDetection的示例如下:
# calibration_dataset.py
# 数据集设置,格式与OpenMMLab中的代码库相同
dataset_type = 'CalibrationDataset'
data_root = 'calibration/dataset/root'
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
test_pipeline = [
dict(type='LoadImageFromFile'),
dict(
type='MultiScaleFlipAug',
img_scale=(1333, 800),
flip=False,
transforms=[
dict(type='Resize', keep_ratio=True),
dict(type='RandomFlip'),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='ImageToTensor', keys=['img']),
dict(type='Collect', keys=['img']),
])
]
data = dict(
samples_per_gpu=2,
workers_per_gpu=2,
val=dict(
type=dataset_type,
ann_file=data_root + 'val_annotations.json',
pipeline=test_pipeline),
test=dict(
type=dataset_type,
ann_file=data_root + 'test_annotations.json',
pipeline=test_pipeline))
evaluation = dict(interval=1, metric='bbox')
使用此校准数据集转换您的模型:
python tools/deploy.py \
...
--calib-dataset-cfg calibration_dataset.py
如果没有提供校准数据集,则使用模型配置中的数据集进行校准。
FAQs¶
错误
Cannot found TensorRT headers
或Cannot found TensorRT libs
可以尝试在cmake时使用
-DTENSORRT_DIR
标志:cmake -DBUILD_TENSORRT_OPS=ON -DTENSORRT_DIR=${TENSORRT_DIR} .. make -j$(nproc)
请确保
${TENSORRT_DIR}
中有库和头文件。错误
error: parameter check failed at: engine.cpp::setBindingDimensions::1046, condition: profileMinDims.d[i] <= dimensions.d[i]
在部署配置中有一个输入形状的限制:
backend_config = dict( # other configs model_inputs=[ dict( input_shapes=dict( input=dict( min_shape=[1, 3, 320, 320], opt_shape=[1, 3, 800, 1344], max_shape=[1, 3, 1344, 1344]))) ]) # other configs
input
张量的形状必须限制在input_shapes["input"]["min_shape"]
和input_shapes["input"]["max_shape"]
之间。错误
error: [TensorRT] INTERNAL ERROR: Assertion failed: cublasStatus == CUBLAS_STATUS_SUCCESS
TRT 7.2.1切换到使用cuBLASLt(以前是cuBLAS)。cuBLASLt是SM版本>= 7.0的默认选择。但是,您可能需要CUDA-10.2补丁1(2020年8月26日发布)来解决一些cuBLASLt问题。如果不想升级,另一个选择是使用新的TacticSource API并禁用cuBLASLt策略。
请阅读本文了解详情。
在Jetson上安装mmdeploy
我们在这里提供了一个Jetsons入门教程。
TorchScript 支持情况¶
Introduction of TorchScript¶
TorchScript a way to create serializable and optimizable models from PyTorch code. Any TorchScript program can be saved from a Python process and loaded in a process where there is no Python dependency. Check the Introduction to TorchScript for more details.
Build custom ops¶
Prerequisite¶
Download libtorch from the official website here.
Please note that only Pre-cxx11 ABI and version 1.8.1+ on Linux platform are supported by now.
For previous versions of libtorch, users can find through the issue comment. Libtorch1.8.1+cu111 as an example, extract it, expose Torch_DIR
and add the lib path to LD_LIBRARY_PATH
as below:
wget https://download.pytorch.org/libtorch/cu111/libtorch-shared-with-deps-1.8.1%2Bcu111.zip
unzip libtorch-shared-with-deps-1.8.1+cu111.zip
cd libtorch
export Torch_DIR=$(pwd)
export LD_LIBRARY_PATH=$Torch_DIR/lib:$LD_LIBRARY_PATH
Note:
If you want to save libtorch env variables to bashrc, you could run
echo '# set env for libtorch' >> ~/.bashrc echo "export Torch_DIR=${Torch_DIR}" >> ~/.bashrc echo 'export LD_LIBRARY_PATH=$Torch_DIR/lib:$LD_LIBRARY_PATH' >> ~/.bashrc source ~/.bashrc
Build on Linux¶
cd ${MMDEPLOY_DIR} # To MMDeploy root directory
mkdir -p build && cd build
cmake -DMMDEPLOY_TARGET_BACKENDS=torchscript -DTorch_DIR=${Torch_DIR} ..
make -j$(nproc) && make install
How to convert a model¶
You could follow the instructions of tutorial How to convert model
SDK backend¶
TorchScript SDK backend may be built by passing -DMMDEPLOY_TORCHSCRIPT_SDK_BACKEND=ON
to cmake
.
Notice that libtorch
is sensitive to C++ ABI versions. On platforms defaulted to C++11 ABI (e.g. Ubuntu 16+) one may
pass -DCMAKE_CXX_FLAGS="-D_GLIBCXX_USE_CXX11_ABI=0"
to cmake
to use pre-C++11 ABI for building. In this case all
dependencies with ABI sensitive interfaces (e.g. OpenCV) must be built with pre-C++11 ABI.
FAQs¶
Error:
projects/thirdparty/libtorch/share/cmake/Caffe2/Caffe2Config.cmake:96 (message):Your installed Caffe2 version uses cuDNN but I cannot find the cuDNN libraries. Please set the proper cuDNN prefixes and / or install cuDNN.
May export CUDNN_ROOT=/root/path/to/cudnn to resolve the build error.
TVM 特性支持¶
MMDeploy 已经将 TVM 集成到模型转换工具以及 SDK 当中。可用的特性包括:
AutoTVM 调优器
Ansor 调优器
Graph Executor 运行时
Virtual Machine 运行时
Core ML 支持情况¶
目前 mmdeploy 集成了 OpenMMLab 算法库中 Pytorch 模型到 Core ML模型的转换以及推理。
安装¶
转换 mmdet 中的模型,需要安装 libtorch 支持 nms 等自定义算子(仅转换,推理时不需要)。MacOS 12 的用户,请安装 Pytorch 1.8.0,MacOS 13+ 的用户,请安装 Pytorch 2.0.0+。
cd ${PYTORCH_DIR}
mkdir build && cd build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DPYTHON_EXECUTABLE=`which python` \
-DCMAKE_INSTALL_PREFIX=install \
-DDISABLE_SVE=ON
make install
使用¶
python tools/deploy.py \
configs/mmdet/detection/detection_coreml_static-800x1344.py \
/mmdetection_dir/configs/retinanet/retinanet_r18_fpn_1x_coco.py \
/checkpoint/retinanet_r18_fpn_1x_coco_20220407_171055-614fd399.pth \
/mmdetection_dir/demo/demo.jpg \
--work-dir work_dir/retinanet \
--device cpu \
--dump-info
ncnn 自定义算子¶
ncnn Ops
Expand¶
Description¶
Broadcast the input blob following the given shape and the broadcast rule of ncnn.
Parameters¶
Expand has no parameters.
Inputs¶
- inputs[0]: ncnn.Mat
- bottom_blobs[0]; An ncnn.Mat of input data.
- inputs[1]: ncnn.Mat
- bottom_blobs[1]; An 1-dim ncnn.Mat. A valid shape of ncnn.Mat.
Outputs¶
- outputs[0]: T
- top_blob; The blob of ncnn.Mat which expanded by given shape and broadcast rule of ncnn.
Type Constraints¶
ncnn.Mat: Mat(float32)
Gather¶
Description¶
Given the data and indice blob, gather entries of the axis dimension of data indexed by indices.
Parameters¶
Type | Parameter | Description |
---|---|---|
int |
axis |
Which axis to gather on. Default is 0. |
Inputs¶
- inputs[0]: ncnn.Mat
- bottom_blobs[0]; An ncnn.Mat of input data.
- inputs[1]: ncnn.Mat
- bottom_blobs[1]; An 1-dim ncnn.Mat of indices on given axis.
Outputs¶
- outputs[0]: T
- top_blob; The blob of ncnn.Mat which gathered by given data and indice blob.
Type Constraints¶
ncnn.Mat: Mat(float32)
Shape¶
Description¶
Get the shape of the ncnn blobs.
Parameters¶
Shape has no parameters.
Inputs¶
- inputs[0]: ncnn.Mat
- bottom_blob; An ncnn.Mat of input data.
Outputs¶
- outputs[0]: T
- top_blob; 1-D ncnn.Mat of shape (bottom_blob.dims,), `bottom_blob.dims` is the input blob dimensions.
Type Constraints¶
ncnn.Mat: Mat(float32)
TopK¶
Description¶
Get the indices and value(optional) of largest or smallest k data among the axis. This op will map to onnx op TopK
, ArgMax
, and ArgMin
.
Parameters¶
Type | Parameter | Description |
---|---|---|
int |
axis |
The axis of data which topk calculate on. Default is -1, indicates the last dimension. |
int |
largest |
The binary value which indicates the TopK operator selects the largest or smallest K values. Default is 1, the TopK selects the largest K values. |
int |
sorted |
The binary value of whether returning sorted topk value or not. If not, the topk returns topk values in any order. Default is 1, this operator returns sorted topk values. |
int |
keep_dims |
The binary value of whether keep the reduced dimension or not. Default is 1, each output blob has the same dimension as input blob. |
Inputs¶
- inputs[0]: ncnn.Mat
- bottom_blob[0]; An ncnn.Mat of input data.
- inputs[1] (optional): ncnn.Mat
- bottom_blob[1]; An optional ncnn.Mat. A blob of K in TopK. If this blob not exist, K is 1.
Outputs¶
- outputs[0]: T
- top_blob[0]; If outputs has only 1 blob, outputs[0] is the indice blob of topk, if outputs has 2 blobs, outputs[0] is the value blob of topk. This blob is ncnn.Mat format with the shape of bottom_blob[0] or reduced shape of bottom_blob[0].
- outputs[1]: T
- top_blob[1] (optional); If outputs has 2 blobs, outputs[1] is the value blob of topk. This blob is ncnn.Mat format with the shape of bottom_blob[0] or reduced shape of bottom_blob[0].
Type Constraints¶
ncnn.Mat: Mat(float32)
onnxruntime 自定义算子¶
ONNX Runtime Ops
grid_sampler¶
Description¶
Perform sample from input
with pixel locations from grid
.
Parameters¶
Type | Parameter | Description |
---|---|---|
int |
interpolation_mode |
Interpolation mode to calculate output values. (0: bilinear , 1: nearest ) |
int |
padding_mode |
Padding mode for outside grid values. (0: zeros , 1: border , 2: reflection ) |
int |
align_corners |
If align_corners=1 , the extrema (-1 and 1 ) are considered as referring to the center points of the input's corner pixels. If align_corners=0 , they are instead considered as referring to the corner points of the input's corner pixels, making the sampling more resolution agnostic. |
Inputs¶
- input: T
- Input feature; 4-D tensor of shape (N, C, inH, inW), where N is the batch size, C is the numbers of channels, inH and inW are the height and width of the data.
- grid: T
- Input offset; 4-D tensor of shape (N, outH, outW, 2), where outH and outW are the height and width of offset and output.
Outputs¶
- output: T
- Output feature; 4-D tensor of shape (N, C, outH, outW).
Type Constraints¶
T:tensor(float32, Linear)
MMCVModulatedDeformConv2d¶
Description¶
Perform Modulated Deformable Convolution on input feature, read Deformable ConvNets v2: More Deformable, Better Results for detail.
Parameters¶
Type | Parameter | Description |
---|---|---|
list of ints |
stride |
The stride of the convolving kernel. (sH, sW) |
list of ints |
padding |
Paddings on both sides of the input. (padH, padW) |
list of ints |
dilation |
The spacing between kernel elements. (dH, dW) |
int |
deformable_groups |
Groups of deformable offset. |
int |
groups |
Split input into groups. input_channel should be divisible by the number of groups. |
Inputs¶
- inputs[0]: T
- Input feature; 4-D tensor of shape (N, C, inH, inW), where N is the batch size, C is the number of channels, inH and inW are the height and width of the data.
- inputs[1]: T
- Input offset; 4-D tensor of shape (N, deformable_group* 2* kH* kW, outH, outW), where kH and kW are the height and width of weight, outH and outW are the height and width of offset and output.
- inputs[2]: T
- Input mask; 4-D tensor of shape (N, deformable_group* kH* kW, outH, outW), where kH and kW are the height and width of weight, outH and outW are the height and width of offset and output.
- inputs[3]: T
- Input weight; 4-D tensor of shape (output_channel, input_channel, kH, kW).
- inputs[4]: T, optional
- Input bias; 1-D tensor of shape (output_channel).
Outputs¶
- outputs[0]: T
- Output feature; 4-D tensor of shape (N, output_channel, outH, outW).
Type Constraints¶
T:tensor(float32, Linear)
NMSRotated¶
Description¶
Non Max Suppression for rotated bboxes.
Parameters¶
Type | Parameter | Description |
---|---|---|
float |
iou_threshold |
The IoU threshold for NMS. |
Inputs¶
- inputs[0]: T
- Input feature; 2-D tensor of shape (N, 5), where N is the number of rotated bboxes, .
- inputs[1]: T
- Input offset; 1-D tensor of shape (N, ), where N is the number of rotated bboxes.
Outputs¶
- outputs[0]: T
- Output feature; 1-D tensor of shape (K, ), where K is the number of keep bboxes.
Type Constraints¶
T:tensor(float32, Linear)
RoIAlignRotated¶
Description¶
Perform RoIAlignRotated on output feature, used in bbox_head of most two-stage rotated object detectors.
Parameters¶
Type | Parameter | Description |
---|---|---|
int |
output_height |
height of output roi |
int |
output_width |
width of output roi |
float |
spatial_scale |
used to scale the input boxes |
int |
sampling_ratio |
number of input samples to take for each output sample. 0 means to take samples densely for current models. |
int |
aligned |
If aligned=0 , use the legacy implementation in MMDetection. Else, align the results more perfectly. |
int |
clockwise |
If True, the angle in each proposal follows a clockwise fashion in image space, otherwise, the angle is counterclockwise. Default: False. |
Inputs¶
- input: T
- Input feature map; 4D tensor of shape (N, C, H, W), where N is the batch size, C is the numbers of channels, H and W are the height and width of the data.
- rois: T
- RoIs (Regions of Interest) to pool over; 2-D tensor of shape (num_rois, 6) given as [[batch_index, cx, cy, w, h, theta], ...]. The RoIs' coordinates are the coordinate system of input.
Outputs¶
- feat: T
- RoI pooled output, 4-D tensor of shape (num_rois, C, output_height, output_width). The r-th batch element feat[r-1] is a pooled feature map corresponding to the r-th RoI RoIs[r-1].
Type Constraints¶
T:tensor(float32)
NMSMatch¶
Description¶
Non Max Suppression with the suppression box match.
Parameters¶
Type | Parameter | Description |
---|---|---|
float |
iou_thr |
The IoU threshold for NMSMatch. |
float |
score_thr |
The score threshold for NMSMatch. |
Inputs¶
- inputs[0]: T
- Input boxes; 3-D tensor of shape (b, N, 4), where b is the batch size, N is the number of boxes and 4 means the coordinate.
- inputs[1]: T
- Input scores; 3-D tensor of shape (b, c, N), where b is the batch size, c is the class size and N is the number of boxes.
Outputs¶
- outputs[0]: T
- Output feature; 2-D tensor of shape (K, 4), K is the number of matched boxes, 4 is batch id, class id, select boxes, suppressed boxes.
Type Constraints¶
T:tensor(float32)
TRT 自定义算子¶
TRTBatchedNMS¶
Description¶
Batched NMS with a fixed number of output bounding boxes.
Parameters¶
Type | Parameter | Description |
---|---|---|
int |
background_label_id |
The label ID for the background class. If there is no background class, set it to -1 . |
int |
num_classes |
The number of classes. |
int |
topK |
The number of bounding boxes to be fed into the NMS step. |
int |
keepTopK |
The number of total bounding boxes to be kept per-image after the NMS step. Should be less than or equal to the topK value. |
float |
scoreThreshold |
The scalar threshold for score (low scoring boxes are removed). |
float |
iouThreshold |
The scalar threshold for IoU (new boxes that have high IoU overlap with previously selected boxes are removed). |
int |
isNormalized |
Set to false if the box coordinates are not normalized, meaning they are not in the range [0,1] . Defaults to true . |
int |
clipBoxes |
Forcibly restrict bounding boxes to the normalized range [0,1] . Only applicable if isNormalized is also true . Defaults to true . |
Inputs¶
- inputs[0]: T
- boxes; 4-D tensor of shape (N, num_boxes, num_classes, 4), where N is the batch size; `num_boxes` is the number of boxes; `num_classes` is the number of classes, which could be 1 if the boxes are shared between all classes.
- inputs[1]: T
- scores; 4-D tensor of shape (N, num_boxes, 1, num_classes).
Outputs¶
- outputs[0]: T
- dets; 3-D tensor of shape (N, valid_num_boxes, 5), `valid_num_boxes` is the number of boxes after NMS. For each row `dets[i,j,:] = [x0, y0, x1, y1, score]`
- outputs[1]: tensor(int32, Linear)
- labels; 2-D tensor of shape (N, valid_num_boxes).
Type Constraints¶
T:tensor(float32, Linear)
grid_sampler¶
Description¶
Perform sample from input
with pixel locations from grid
.
Parameters¶
Type | Parameter | Description |
---|---|---|
int |
interpolation_mode |
Interpolation mode to calculate output values. (0: bilinear , 1: nearest ) |
int |
padding_mode |
Padding mode for outside grid values. (0: zeros , 1: border , 2: reflection ) |
int |
align_corners |
If align_corners=1 , the extrema (-1 and 1 ) are considered as referring to the center points of the input's corner pixels. If align_corners=0 , they are instead considered as referring to the corner points of the input's corner pixels, making the sampling more resolution agnostic. |
Inputs¶
- inputs[0]: T
- Input feature; 4-D tensor of shape (N, C, inH, inW), where N is the batch size, C is the numbers of channels, inH and inW are the height and width of the data.
- inputs[1]: T
- Input offset; 4-D tensor of shape (N, outH, outW, 2), where outH and outW are the height and width of offset and output.
Outputs¶
- outputs[0]: T
- Output feature; 4-D tensor of shape (N, C, outH, outW).
Type Constraints¶
T:tensor(float32, Linear)
MMCVInstanceNormalization¶
Description¶
Carry out instance normalization as described in the paper https://arxiv.org/abs/1607.08022.
y = scale * (x - mean) / sqrt(variance + epsilon) + B, where mean and variance are computed per instance per channel.
Parameters¶
Type | Parameter | Description |
---|---|---|
float |
epsilon |
The epsilon value to use to avoid division by zero. Default is 1e-05 |
Inputs¶
- input: T
- Input data tensor from the previous operator; dimensions for image case are (N x C x H x W), where N is the batch size, C is the number of channels, and H and W are the height and the width of the data. For non image case, the dimensions are in the form of (N x C x D1 x D2 ... Dn), where N is the batch size.
- scale: T
- The input 1-dimensional scale tensor of size C.
- B: T
- The input 1-dimensional bias tensor of size C.
Outputs¶
- output: T
- The output tensor of the same shape as input.
Type Constraints¶
T:tensor(float32, Linear)
MMCVModulatedDeformConv2d¶
Description¶
Perform Modulated Deformable Convolution on input feature. Read Deformable ConvNets v2: More Deformable, Better Results for detail.
Parameters¶
Type | Parameter | Description |
---|---|---|
list of ints |
stride |
The stride of the convolving kernel. (sH, sW) |
list of ints |
padding |
Paddings on both sides of the input. (padH, padW) |
list of ints |
dilation |
The spacing between kernel elements. (dH, dW) |
int |
deformable_group |
Groups of deformable offset. |
int |
group |
Split input into groups. input_channel should be divisible by the number of groups. |
Inputs¶
- inputs[0]: T
- Input feature; 4-D tensor of shape (N, C, inH, inW), where N is the batch size, C is the number of channels, inH and inW are the height and width of the data.
- inputs[1]: T
- Input offset; 4-D tensor of shape (N, deformable_group* 2* kH* kW, outH, outW), where kH and kW are the height and width of weight, outH and outW are the height and width of offset and output.
- inputs[2]: T
- Input mask; 4-D tensor of shape (N, deformable_group* kH* kW, outH, outW), where kH and kW are the height and width of weight, outH and outW are the height and width of offset and output.
- inputs[3]: T
- Input weight; 4-D tensor of shape (output_channel, input_channel, kH, kW).
- inputs[4]: T, optional
- Input weight; 1-D tensor of shape (output_channel).
Outputs¶
- outputs[0]: T
- Output feature; 4-D tensor of shape (N, output_channel, outH, outW).
Type Constraints¶
T:tensor(float32, Linear)
MMCVMultiLevelRoiAlign¶
Description¶
Perform RoIAlign on features from multiple levels. Used in bbox_head of most two-stage detectors.
Parameters¶
Type | Parameter | Description |
---|---|---|
int |
output_height |
height of output roi. |
int |
output_width |
width of output roi. |
list of floats |
featmap_strides |
feature map stride of each level. |
int |
sampling_ratio |
number of input samples to take for each output sample. 0 means to take samples densely for current models. |
float |
roi_scale_factor |
RoIs will be scaled by this factor before RoI Align. |
int |
finest_scale |
Scale threshold of mapping to level 0. Default: 56. |
int |
aligned |
If aligned=0 , use the legacy implementation in MMDetection. Else, align the results more perfectly. |
Inputs¶
Outputs¶
- outputs[0]: T
- RoI pooled output, 4-D tensor of shape (num_rois, C, output_height, output_width). The r-th batch element output[0][r-1] is a pooled feature map corresponding to the r-th RoI inputs[1][r-1].
Type Constraints¶
T:tensor(float32, Linear)
MMCVRoIAlign¶
Description¶
Perform RoIAlign on output feature, used in bbox_head of most two-stage detectors.
Parameters¶
Type | Parameter | Description |
---|---|---|
int |
output_height |
height of output roi |
int |
output_width |
width of output roi |
float |
spatial_scale |
used to scale the input boxes |
int |
sampling_ratio |
number of input samples to take for each output sample. 0 means to take samples densely for current models. |
str |
mode |
pooling mode in each bin. avg or max |
int |
aligned |
If aligned=0 , use the legacy implementation in MMDetection. Else, align the results more perfectly. |
Inputs¶
- inputs[0]: T
- Input feature map; 4D tensor of shape (N, C, H, W), where N is the batch size, C is the numbers of channels, H and W are the height and width of the data.
- inputs[1]: T
- RoIs (Regions of Interest) to pool over; 2-D tensor of shape (num_rois, 5) given as [[batch_index, x1, y1, x2, y2], ...]. The RoIs' coordinates are the coordinate system of inputs[0].
Outputs¶
- outputs[0]: T
- RoI pooled output, 4-D tensor of shape (num_rois, C, output_height, output_width). The r-th batch element output[0][r-1] is a pooled feature map corresponding to the r-th RoI inputs[1][r-1].
Type Constraints¶
T:tensor(float32, Linear)
ScatterND¶
Description¶
ScatterND takes three inputs data
tensor of rank r >= 1, indices
tensor of rank q >= 1, and updates
tensor of rank q + r - indices.shape[-1] - 1. The output of the operation is produced by creating a copy of the input data
, and then updating its value to values specified by updates at specific index positions specified by indices
. Its output shape is the same as the shape of data
. Note that indices
should not have duplicate entries. That is, two or more updates for the same index-location is not supported.
The output
is calculated via the following equation:
output = np.copy(data)
update_indices = indices.shape[:-1]
for idx in np.ndindex(update_indices):
output[indices[idx]] = updates[idx]
Parameters¶
None
Inputs¶
- inputs[0]: T
- Tensor of rank r>=1.
- inputs[1]: tensor(int32, Linear)
- Tensor of rank q>=1.
- inputs[2]: T
- Tensor of rank q + r - indices_shape[-1] - 1.
Outputs¶
- outputs[0]: T
- Tensor of rank r >= 1.
Type Constraints¶
T:tensor(float32, Linear), tensor(int32, Linear)
TRTBatchedRotatedNMS¶
Description¶
Batched rotated NMS with a fixed number of output bounding boxes.
Parameters¶
Type | Parameter | Description |
---|---|---|
int |
background_label_id |
The label ID for the background class. If there is no background class, set it to -1 . |
int |
num_classes |
The number of classes. |
int |
topK |
The number of bounding boxes to be fed into the NMS step. |
int |
keepTopK |
The number of total bounding boxes to be kept per-image after the NMS step. Should be less than or equal to the topK value. |
float |
scoreThreshold |
The scalar threshold for score (low scoring boxes are removed). |
float |
iouThreshold |
The scalar threshold for IoU (new boxes that have high IoU overlap with previously selected boxes are removed). |
int |
isNormalized |
Set to false if the box coordinates are not normalized, meaning they are not in the range [0,1] . Defaults to true . |
int |
clipBoxes |
Forcibly restrict bounding boxes to the normalized range [0,1] . Only applicable if isNormalized is also true . Defaults to true . |
Inputs¶
- inputs[0]: T
- boxes; 4-D tensor of shape (N, num_boxes, num_classes, 5), where N is the batch size; `num_boxes` is the number of boxes; `num_classes` is the number of classes, which could be 1 if the boxes are shared between all classes.
- inputs[1]: T
- scores; 4-D tensor of shape (N, num_boxes, 1, num_classes).
Outputs¶
- outputs[0]: T
- dets; 3-D tensor of shape (N, valid_num_boxes, 6), `valid_num_boxes` is the number of boxes after NMS. For each row `dets[i,j,:] = [x0, y0, width, height, theta, score]`
- outputs[1]: tensor(int32, Linear)
- labels; 2-D tensor of shape (N, valid_num_boxes).
Type Constraints¶
T:tensor(float32, Linear)
GridPriorsTRT¶
Description¶
Generate the anchors for object detection task.
Parameters¶
Type | Parameter | Description |
---|---|---|
int |
stride_w |
The stride of the feature width. |
int |
stride_h |
The stride of the feature height. |
Inputs¶
- inputs[0]: T
- The base anchors; 2-D tensor with shape [num_base_anchor, 4].
- inputs[1]: TAny
- height provider; 1-D tensor with shape [featmap_height]. The data will never been used.
- inputs[2]: TAny
- width provider; 1-D tensor with shape [featmap_width]. The data will never been used.
Outputs¶
- outputs[0]: T
- output anchors; 2-D tensor of shape (num_base_anchor*featmap_height*featmap_widht, 4).
Type Constraints¶
T:tensor(float32, Linear)
TAny: Any
ScaledDotProductAttentionTRT¶
Description¶
Dot product attention used to support multihead attention, read Attention Is All You Need for more detail.
Parameters¶
None
Inputs¶
- inputs[0]: T
- query; 3-D tensor with shape [batch_size, sequence_length, embedding_size].
- inputs[1]: T
- key; 3-D tensor with shape [batch_size, sequence_length, embedding_size].
- inputs[2]: T
- value; 3-D tensor with shape [batch_size, sequence_length, embedding_size].
- inputs[3]: T
- mask; 2-D/3-D tensor with shape [sequence_length, sequence_length] or [batch_size, sequence_length, sequence_length]. optional.
Outputs¶
- outputs[0]: T
- 3-D tensor of shape [batch_size, sequence_length, embedding_size]. `softmax(q@k.T)@v`
- outputs[1]: T
- 3-D tensor of shape [batch_size, sequence_length, sequence_length]. `softmax(q@k.T)`
Type Constraints¶
T:tensor(float32, Linear)
GatherTopk¶
Description¶
TensorRT 8.2~8.4 would give unexpected result for multi-index gather.
data[batch_index, bbox_index, ...]
Read this for more details.
Parameters¶
None
Inputs¶
- inputs[0]: T
- Tensor to be gathered, with shape (A0, ..., An, G0, C0, ...).
- inputs[1]: tensor(int32, Linear)
- Tensor of index. with shape (A0, ..., An, G1)
Outputs¶
- outputs[0]: T
- Tensor of output. With shape (A0, ..., An, G1, C0, ...)
Type Constraints¶
T:tensor(float32, Linear), tensor(int32, Linear)
mmdeploy 各目录功能¶
本文主要介绍 mmdeploy 各目录功能,以及从模型到具体推理框架是怎么工作的。
一、大致看下目录结构¶
整个 mmdeploy 可以看成比较独立的两部分:模型转换 和 SDK。
我们介绍整个 repo 目录结构和功能,不必细究源码、有个印象即可。
外围目录功能:
$ cd /path/to/mmdeploy
$ tree -L 1
.
├── CMakeLists.txt # 编译模型转换自定义算子和 SDK 的 cmake 配置
├── configs # 模型转换要用的算法库配置
├── csrc # SDK 和自定义算子
├── demo # 各语言的 ffi 接口应用实例,如 csharp、java、python 等
├── docker # docker build
├── mmdeploy # 用于模型转换的 python 包
├── requirements # python 包安装依赖
├── service # 有些小板子不能跑 python,模型转换用的 C/S 模式。这个目录放 Server
├── tests # 单元测试
├── third_party # SDK 和 ffi 要的第三方依赖
└── tools # 工具,也是一切功能的入口。除了 deploy.py 还有 onnx2xx.py、profiler.py 和 test.py
这样大致应该清楚了
模型转换主要看 tools + mmdeploy + 小部分 csrc 目录;
而 SDK 的本体在 csrc + third_party + demo 三个目录。
二、模型转换¶
模型以 mmpretrain 的 ViT 为例,推理框架就用 ncnn 举例。其他模型、推理都是类似的。
我们看下 mmdeploy/mmdeploy 目录结构,有个印象即可:
.
├── apis # tools 工具用的 api,都是这里实现的,如 onnx2ncnn.py
│ ├── calibration.py # trt 专用收集量化数据
│ ├── core # 软件脚手架
│ ├── extract_model.py # onnx 模型只想导出一部分,切 onnx 用的
│ ├── inference.py # 抽象函数,实际会调 torch/ncnn 具体的 inference
│ ├── ncnn # 引用 backend/ncnn 的函数,只是包了一下
│ └── visualize.py # 还是抽象函数,实际会调用 torch/ncnn 具体的 inference 和 visualize
..
├── backend # 具体的 backend 包装
│ ├── base # 因为有多个 backend,所以得有个 base 类的 OO 设计
│ ├── ncnn # 这里为模型转换调用 ncnn python 接口
│ │ ├── init_plugins.py # 找 ncnn 自定义算子和 ncnn 工具的路径
│ │ ├── onnx2ncnn.py # 把 `mmdeploy_onnx2ncnn` 封装成 python 接口
│ │ ├── quant.py # 封装 `ncnn2int8` 工具为 python 接口
│ │ └── wrapper.py # 封装 pyncnn forward 接口
..
├── codebase # mm 系列算法 forward 重写
│ ├── base # 有多个算法,需要点 OO 设计
│ ├── mmpretrain # mmpretrain 相关模型重写
│ │ ├── deploy # mmpretrain 对 base 抽象 task/model/codebase 的实现
│ │ └── models # 开始真正的模型重写
│ │ ├── backbones # 骨干网络部分的重写,例如 multiheadattention
│ │ ├── heads # 例如 MultiLabelClsHead
│ │ ├── necks # 例如 GlobalAveragePooling
│..
├── core # 软件脚手架,重写机制怎么实现的
├── mmcv # mmcv 有的 opr 也需要重写
├── pytorch # 针对 ncnn 重写 torch 的 opr,例如 Gemm
..
上面的每一行是需要读的,请勿跳过。
当敲下tools/deploy.py
转换 ViT,核心是这 3 件事:
mmpretrain ViT forward 过程的重写
ncnn 不支持 gather opr,自定义一下、和 libncnn.so 一起加载
真实跑一遍,渲染结果,确保正确
1. forward 重写¶
因为 onnx 会生成稀碎的算子、ncnn 也不是完美支持 onnx,所以 mmdeploy 的方案是劫持有问题的 forward 代码、改成适合 ncnn 的 onnx 结果。
例如把 conv -> shape -> concat_const -> reshape
过程改成 conv -> reshape
,削掉多余的 shape
和 concat
算子。
所有的 mmpretrain 算法重写都在 mmdeploy/codebase/mmpretrain/models
目录。
2. 自定义算子¶
针对 ncnn 自定义的算子都在 csrc/mmdeploy/backend_ops/ncnn/
目录,编译后和 libncnn.so 一起加载。本质是在 hotfix ncnn,目前实现了
topk
tensorslice
shape
gather
expand
constantofshape
3. 转换和测试¶
ncnn 的兼容性较好,转换用的是修改后的 mmdeploy_onnx2ncnn
,推理封装了 pyncnn
+ 自定义 ops。
遇到 snpe 这种不支持 python 的框架,则使用 C/S 模式:用 gRPC 等协议封装一个 server,转发真实的推理结果。
渲染使用上游算法框架的渲染 API,mmdeploy 自身不做绘制。
三、SDK¶
模型转换完成后,可用 C++ 编译的 SDK 执行在不同平台上。
我们看下 csrc/mmdeploy 目录结构:
.
├── apis # Csharp、java、go、Rust 等 ffi 接口
├── backend_ops # 各推理框架的自定义算子
├── CMakeLists.txt
├── codebase # 各 mm 算法框架偏好的结果类型,例如检测任务多用 bbox
├── core # 脚手架,对图、算子、设备的抽象
├── device # CPU/GPU device 抽象的实现
├── execution # 对 exec 抽象的实现
├── graph # 对图抽象的实现
├── model # 实现 zip 压缩和非压缩两种工作目录
├── net # net 的具体实现,例如封装了 ncnn forward C 接口
├── preprocess # 预处理的实现
└── utils # OCV 工具类
SDK 本质是设计了一套计算图的抽象,把多个模型的
预处理
推理
后处理
调度起来,同时提供多种语言的 ffi。
如何支持新的模型¶
我们提供了多种工具来支持模型转换
函数的重写器¶
PyTorch 神经网络是用 python 编写的,可以简化算法的开发。但与此同时 Python 的流程控制和第三方库会使得网络导出为中间语言的过程变得困难。为此我们提供了一个“MonKey path”工具将不支持的功能重写为另一个可支持中间语言导出的功能。下述是一个具体的使用例子:
from mmdeploy.core import FUNCTION_REWRITER
@FUNCTION_REWRITER.register_rewriter(
func_name='torch.Tensor.repeat', backend='tensorrt')
def repeat_static(input, *size):
ctx = FUNCTION_REWRITER.get_context()
origin_func = ctx.origin_func
if input.dim() == 1 and len(size) == 1:
return origin_func(input.unsqueeze(0), *([1] + list(size))).squeeze(0)
else:
return origin_func(input, *size)
使用函数重写器是十分容易的,只需添加一个带参数的装饰器即可:
func_name
是需要被重载的函数,它可以是其他PyTorch 的函数或者是自定义的函数。模块中的方法也可以通过工具进行重载。backend
是推理引擎。当模型被导入到引擎的时候,函数会被重载。如果没有给出,重载默认的参数就是重载的参数。如果后端的重载的参数不存在,将会按照预设的默认模式进行重载。 当参数与原始的参数相同时,除了把上下文信息ctx
作为第一的参数外,上下文也提供了一些有用的信息,例如:部署的配置ctx.cfg
和原始的函数(已经被重载)ctx.origin_func
。
可参照这些样例代码。
模型重载器¶
如果您想用另一个模块替换整个模块,我们还有另一个重载器,如下所示:
@MODULE_REWRITER.register_rewrite_module(
'mmagic.models.backbones.sr_backbones.SRCNN', backend='tensorrt')
class SRCNNWrapper(nn.Module):
def __init__(self,
module,
cfg,
channels=(3, 64, 32, 3),
kernel_sizes=(9, 1, 5),
upscale_factor=4):
super(SRCNNWrapper, self).__init__()
self._module = module
module.img_upsampler = nn.Upsample(
scale_factor=module.upscale_factor,
mode='bilinear',
align_corners=False)
def forward(self, *args, **kwargs):
"""Run forward."""
return self._module(*args, **kwargs)
def init_weights(self, *args, **kwargs):
"""Initialize weights."""
return self._module.init_weights(*args, **kwargs)
就像函数重载器一样,可添加一个带参数的装饰器:
module_type
要重载的模块类。backend
是推理引擎。当模型被导入到引擎的时候,函数会被重载。如果没有给出,重载默认的参数就是重载的参数。如果后端的重载的参数不存在,将会按照预设的默认模式进行重载。
网络中模块的所有实例都将替换为这个新类的实例。原始模块和部署配置将作为前两个参数进行传递。
符号函数重写¶
PyTorch 和 ONNX 之间的映射是通过 PyTorch 中的符号函数进行定义的。自定义符号函数可以帮助我们绕过一些推理引擎不支持的 ONNX 节点。
@SYMBOLIC_REWRITER.register_symbolic('squeeze', is_pytorch=True)
def squeeze_default(g, self, dim=None):
if dim is None:
dims = []
for i, size in enumerate(self.type().sizes()):
if size == 1:
dims.append(i)
else:
dims = [sym_help._get_const(dim, 'i', 'dim')]
return g.op('Squeeze', self, axes_i=dims)
装饰器的参数
func_name
要添加符号的函数名称。如果是自定义的,请使用完整路径torch.autograd.Function
。或者如果它是 PyTorch 内置函数,则只用写一个名称即可。backend
是推理引擎。当模型被导入到引擎的时候,函数会被重载。如果没有给出,重载默认的参数就是重载的参数。如果后端的重载的参数不存在,将会按照预设的默认模式进行重载。如果函数是 PyTorch 内置函数,则为True。
arg_descriptors
符号函数参数的描述符,将被传递给torch.onnx.symbolic_helper._parse_arg
。
就像函数重载器的ctx
一样,第一个参数会提供上下文信息。上下文中了一些有用的信息,例如部署配置ctx.cfg和原始功能(已被重载)ctx.origin_func
。请注意, ctx.origin_func
只能在is_pytorch==False
时使用。
这里有很多实现可参考。
如何支持新的后端¶
MMDeploy 支持了许多后端推理引擎,但我们依然非常欢迎新后端的贡献。在本教程中,我们将介绍在 MMDeploy 中支持新后端的一般过程。
必要条件¶
在对 MMDeploy 添加新的后端引擎之前,需要先检查所要支持的新后端是否符合一些要求:
后端必须能够支持 ONNX 作为 IR。
如果后端需要“.onnx”文件以外的模型文件或权重文件,则需要添加将“.onnx”文件转换为模型文件或权重文件的转换工具,该工具可以是 Python API、脚本或可执行程序。
强烈建议新后端可提供 Python 接口来加载后端文件和推理以进行验证。
支持后端转换¶
MMDeploy 中的后端必须支持 ONNX,因此后端能直接加载“.onnx”文件,或者使用转换工具将“.onnx”转换成自己的格式。在本节中,我们将介绍支持后端转换的步骤。
在
mmdeploy/utils/constants.py
文件中添加新推理后端变量,以表示支持的后端名称。示例:
# mmdeploy/utils/constants.py class Backend(AdvancedEnum): # 以现有的TensorRT为例 TENSORRT = 'tensorrt'
在
mmdeploy/backend/
目录下添加相应的库(一个包括__init__.py
的文件夹),例如,mmdeploy/backend/tensorrt
。在__init__.py
中,必须有一个名为is_available
的函数检查用户是否安装了后端库。如果检查通过,则将加载库的剩余文件。例子:
# mmdeploy/backend/tensorrt/__init__.py def is_available(): return importlib.util.find_spec('tensorrt') is not None if is_available(): from .utils import from_onnx, load, save from .wrapper import TRTWrapper __all__ = [ 'from_onnx', 'save', 'load', 'TRTWrapper' ]
在
configs/_base_/backends
目录中创建一个配置文件(例如,configs/_base_/backends/tensorrt.py
)。如果新后端引擎只是将“.onnx”文件作为输入,那么新的配置可以很简单,对应配置只需包含一个表示后端名称的字段(但也应该与mmdeploy/utils/constants.py
中的名称相同)。例子
backend_config = dict(type='tensorrt')
但如果后端需要其他文件,则从“.onnx”文件转换为后端文件所需的参数也应包含在配置文件中。
例子
backend_config = dict( type='tensorrt', common_config=dict( fp16_mode=False, max_workspace_size=0))
在拥有一个基本的后端配置文件后,您已经可以通过继承轻松构建一个完整的部署配置。有关详细信息,请参阅我们的配置教程。下面是一个例子:
_base_ = ['../_base_/backends/tensorrt.py'] codebase_config = dict(type='mmpretrain', task='Classification') onnx_config = dict(input_shape=None)
如果新后端需要模型文件或权重文件而不是“.onnx”文件,则需要在相应的文件夹中创建一个
onnx2backend.py
文件(例如,创建mmdeploy/backend/tensorrt/onnx2tensorrt.py
)。然后在文件中添加一个转换函数onnx2backend
。该函数应将给定的“.onnx”文件转换为给定工作目录中所需的后端文件。对函数的其他参数和实现细节没有要求,您可以使用任何工具进行转换。下面有些例子:使用python脚本
def onnx2openvino(input_info: Dict[str, Union[List[int], torch.Size]], output_names: List[str], onnx_path: str, work_dir: str): input_names = ','.join(input_info.keys()) input_shapes = ','.join(str(list(elem)) for elem in input_info.values()) output = ','.join(output_names) mo_args = f'--input_model="{onnx_path}" '\ f'--output_dir="{work_dir}" ' \ f'--output="{output}" ' \ f'--input="{input_names}" ' \ f'--input_shape="{input_shapes}" ' \ f'--disable_fusing ' command = f'mo.py {mo_args}' mo_output = run(command, stdout=PIPE, stderr=PIPE, shell=True, check=True)
使用可执行文件
def onnx2ncnn(onnx_path: str, work_dir: str): onnx2ncnn_path = get_onnx2ncnn_path() save_param, save_bin = get_output_model_file(onnx_path, work_dir) call([onnx2ncnn_path, onnx_path, save_param, save_bin])\
在
mmdeploy/apis
中创建新后端库并声明对应 APIs例子
# mmdeploy/apis/ncnn/__init__.py from mmdeploy.backend.ncnn import is_available __all__ = ['is_available'] if is_available(): from mmdeploy.backend.ncnn.onnx2ncnn import (onnx2ncnn, get_output_model_file) __all__ += ['onnx2ncnn', 'get_output_model_file']
从 BaseBackendManager 派生类,实现
to_backend
类方法。例子
@classmethod def to_backend(cls, ir_files: Sequence[str], deploy_cfg: Any, work_dir: str, log_level: int = logging.INFO, device: str = 'cpu', **kwargs) -> Sequence[str]: return ir_files
将 OpenMMLab 的模型转换后(如有必要)并在后端引擎上进行推理。如果在测试时发现一些不兼容的算子,可以尝试按照重写器教程为后端重写原始模型或添加自定义算子。
为新后端引擎代码添加相关注释和单元测试:).
支持后端推理¶
尽管后端引擎通常用C/C++实现,但如果后端提供Python推理接口,则测试和调试非常方便。我们鼓励贡献者在MMDeploy的Python接口中支持新后端推理。在本节中,我们将介绍支持后端推理的步骤。
添加一个名为
wrapper.py
的文件到mmdeploy/backend/{backend}
中相应后端文件夹。例如,mmdeploy/backend/tensorrt/wrapper
。此模块应实现并注册一个封装类,该类继承mmdeploy/backend/base/base_wrapper.py
中的基类BaseWrapper
。例子
from mmdeploy.utils import Backend from ..base import BACKEND_WRAPPER, BaseWrapper @BACKEND_WRAPPER.register_module(Backend.TENSORRT.value) class TRTWrapper(BaseWrapper):
封装类可以在函数
__init__
中初始化引擎以及在forward
函数中进行推理。请注意,该__init__
函数必须接受一个参数output_names
并将其传递给基类以确定输出张量的顺序。其中forward
输入和输出变量应表示tensors的名称和值的字典。为了方便性能测试,该类应该定义一个
execute
函数,只调用后端引擎的推理接口。该forward
函数应在预处理数据后调用execute
函数。例子
from mmdeploy.utils import Backend from mmdeploy.utils.timer import TimeCounter from ..base import BACKEND_WRAPPER, BaseWrapper @BACKEND_WRAPPER.register_module(Backend.ONNXRUNTIME.value) class ORTWrapper(BaseWrapper): def __init__(self, onnx_file: str, device: str, output_names: Optional[Sequence[str]] = None): # Initialization # # ... super().__init__(output_names) def forward(self, inputs: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: # Fetch data # ... self.__ort_execute(self.io_binding) # Postprocess data # ... @TimeCounter.count_time('onnxruntime') def __ort_execute(self, io_binding: ort.IOBinding): # Only do the inference self.sess.run_with_iobinding(io_binding)
从
BaseBackendManager
派生接口类,实现build_wrapper
静态方法例子
@BACKEND_MANAGERS.register('onnxruntime') class ONNXRuntimeManager(BaseBackendManager): @classmethod def build_wrapper(cls, backend_files: Sequence[str], device: str = 'cpu', input_names: Optional[Sequence[str]] = None, output_names: Optional[Sequence[str]] = None, deploy_cfg: Optional[Any] = None, **kwargs): from .wrapper import ORTWrapper return ORTWrapper( onnx_file=backend_files[0], device=device, output_names=output_names)
为新后端引擎代码添加相关注释和单元测试 :).
将MMDeploy作为第三方库时添加新后端¶
前面的部分展示了如何在 MMDeploy 中添加新的后端,这需要更改其源代码。但是,如果我们将 MMDeploy 视为第三方,则上述方法不再有效。为此,添加一个新的后端需要我们预先安装另一个名为 aenum
的包。我们可以直接通过pip install aenum
进行安装。
成功安装 aenum
后,我们可以通过以下方式使用它来添加新的后端:
from mmdeploy.utils.constants import Backend
from aenum import extend_enum
try:
Backend.get('backend_name')
except Exception:
extend_enum(Backend, 'BACKEND', 'backend_name')
我们可以在使用 MMDeploy 的重写逻辑之前运行上面的代码,这就完成了新后端的添加。
为推理 ops 添加测试单元¶
本教程介绍如何为后端 ops 添加单元测试。在 backend_ops 目录下添加自定义 op 时,需要添加相应的测试单元。op 的单元测试在 test/test_ops/test_ops.py
中。
添加新的自定义 op 后,需要重新编译,引用 build.md 。
ops 单元测试样例¶
@pytest.mark.parametrize('backend', [TEST_TENSORRT, TEST_ONNXRT]) # 1.1 backend test class
@pytest.mark.parametrize('pool_h,pool_w,spatial_scale,sampling_ratio', # 1.2 set parameters of op
[(2, 2, 1.0, 2), (4, 4, 2.0, 4)]) # [(# Examples of op test parameters),...]
def test_roi_align(backend,
pool_h, # set parameters of op
pool_w,
spatial_scale,
sampling_ratio,
input_list=None,
save_dir=None):
backend.check_env()
if input_list is None:
input = torch.rand(1, 1, 16, 16, dtype=torch.float32) # 1.3 op input data initialization
single_roi = torch.tensor([[0, 0, 0, 4, 4]], dtype=torch.float32)
else:
input = torch.tensor(input_list[0], dtype=torch.float32)
single_roi = torch.tensor(input_list[1], dtype=torch.float32)
from mmcv.ops import roi_align
def wrapped_function(torch_input, torch_rois): # 1.4 initialize op model to be tested
return roi_align(torch_input, torch_rois, (pool_w, pool_h),
spatial_scale, sampling_ratio, 'avg', True)
wrapped_model = WrapFunction(wrapped_function).eval()
with RewriterContext(cfg={}, backend=backend.backend_name, opset=11): # 1.5 call the backend test class interface
backend.run_and_validate(
wrapped_model, [input, single_roi],
'roi_align',
input_names=['input', 'rois'],
output_names=['roi_feat'],
save_dir=save_dir)
mmdeploy 支持的模型有两种格式:
torch 模型:参考 roi_align 单元测试,必须要求 op 相关 Python 代码
onnx 模型:参考 multi_level_roi_align 单元测试,需要调用 onnx api 进行构建
调用 run_and_validate
即可运行
def run_and_validate(self,
model,
input_list,
model_name='tmp',
tolerate_small_mismatch=False,
do_constant_folding=True,
dynamic_axes=None,
output_names=None,
input_names=None,
expected_result=None,
save_dir=None):
Parameter Description¶
参数 | 说明 |
---|---|
model | 要测试的输入模型 |
input_list | 测试数据列表,映射到input_names的顺序 |
tolerate_small_mismatch | 是否允许验证结果出现精度误差 |
do_constant_folding | 是否使用常量折叠 |
output_names | 输出节点名字 |
input_names | 输入节点名字 |
expected_result | 期望的 ground truth |
save_dir | 结果保存目录 |
测试模型重写¶
模型 rewriter 完成后,还需完成对应测试用例,以验证重写是否生效。通常我们需要对比原始模型和重写后的输出。原始模型输出可以调用模型的 forward 函数直接获取,而生成重写模型输出的方法取决于重写的复杂性。
测试简单的重写¶
如果对模型的更改很小(例如,仅更改一个或两个变量且无副作用),则可为重写函数/模块构造输入,在RewriteContext
中运行推理并检查结果。
# mmpretrain.models.classfiers.base.py
class BaseClassifier(BaseModule, metaclass=ABCMeta):
def forward(self, img, return_loss=True, **kwargs):
if return_loss:
return self.forward_train(img, **kwargs)
else:
return self.forward_test(img, **kwargs)
# Custom rewritten function
@FUNCTION_REWRITER.register_rewriter(
'mmpretrain.models.classifiers.BaseClassifier.forward', backend='default')
def forward_of_base_classifier(self, img, *args, **kwargs):
"""Rewrite `forward` for default backend."""
return self.simple_test(img, {})
在示例中,我们仅更改 forward 函数。我们可以通过编写以下函数来测试这个重写:
def test_baseclassfier_forward():
input = torch.rand(1)
from mmpretrain.models.classifiers import BaseClassifier
class DummyClassifier(BaseClassifier):
def __init__(self, init_cfg=None):
super().__init__(init_cfg=init_cfg)
def extract_feat(self, imgs):
pass
def forward_train(self, imgs):
return 'train'
def simple_test(self, img, tmp, **kwargs):
return 'simple_test'
model = DummyClassifier().eval()
model_output = model(input)
with RewriterContext(cfg=dict()), torch.no_grad():
backend_output = model(input)
assert model_output == 'train'
assert backend_output == 'simple_test'
在这个测试函数中,我们构造派生类 BaseClassifier
来测试重写能否工作。通过直接调用model(input)
来获得原始输出,并通过在RewriteContext
中调用model(input)
来获取重写的输出。最后断检查输出。
测试复杂重写¶
有时我们可能会对原始模型函数进行重大更改(例如,消除分支语句以生成正确的计算图)。即使运行在Python中的重写模型的输出是正确的,我们也不能保证重写的模型可以在后端按预期工作。因此,我们需要在后端测试重写的模型。
# Custom rewritten function
@FUNCTION_REWRITER.register_rewriter(
func_name='mmseg.models.segmentors.BaseSegmentor.forward')
def base_segmentor__forward(self, img, img_metas=None, **kwargs):
ctx = FUNCTION_REWRITER.get_context()
if img_metas is None:
img_metas = {}
assert isinstance(img_metas, dict)
assert isinstance(img, torch.Tensor)
deploy_cfg = ctx.cfg
is_dynamic_flag = is_dynamic_shape(deploy_cfg)
img_shape = img.shape[2:]
if not is_dynamic_flag:
img_shape = [int(val) for val in img_shape]
img_metas['img_shape'] = img_shape
return self.simple_test(img, img_metas, **kwargs)
此重写函数的行为很复杂,我们应该按如下方式测试它:
def test_basesegmentor_forward():
from mmdeploy.utils.test import (WrapModel, get_model_outputs,
get_rewrite_outputs)
segmentor = get_model()
segmentor.cpu().eval()
# Prepare data
# ...
# Get the outputs of original model
model_inputs = {
'img': [imgs],
'img_metas': [img_metas],
'return_loss': False
}
model_outputs = get_model_outputs(segmentor, 'forward', model_inputs)
# Get the outputs of rewritten model
wrapped_model = WrapModel(segmentor, 'forward', img_metas = None, return_loss = False)
rewrite_inputs = {'img': imgs}
rewrite_outputs, is_backend_output = get_rewrite_outputs(
wrapped_model=wrapped_model,
model_inputs=rewrite_inputs,
deploy_cfg=deploy_cfg)
if is_backend_output:
# If the backend plugins have been installed, the rewrite outputs are
# generated by backend.
rewrite_outputs = torch.tensor(rewrite_outputs)
model_outputs = torch.tensor(model_outputs)
model_outputs = model_outputs.unsqueeze(0).unsqueeze(0)
assert torch.allclose(rewrite_outputs, model_outputs)
else:
# Otherwise, the outputs are generated by python.
assert rewrite_outputs is not None
我们已经提供了一些使用函数做测试,例如可以先 build 模型,用 get_model_outputs
获取原始输出;然后用WrapModel
包装重写函数,使用get_rewrite_outputs
获取结果。这个例子里会返回输出内容和是否来自后端两个结果。
因为我们也不确定用户是否正确安装后端,所以得检查结果来自 Python 还是真实后端推理结果。单元测试必须涵盖这两种结果,最后用torch.allclose
对比两种结果的差异。
API 文档中有测试用例完整用法。
如何拆分 onnx 模型¶
MMDeploy 支持将PyTorch模型导出到onnx模型并进行拆分得到多个onnx模型文件,用户可以自由的对模型图节点进行标记并根据这些标记的节点定制任意的onnx模型拆分策略。在这个教程中,我们将通过具体例子来展示如何进行onnx模型拆分。在这个例子中,我们的目标是将YOLOV3模型拆分成两个部分,保留不带后处理的onnx模型,丢弃包含Anchor生成,NMS的后处理部分。
步骤 1: 添加模型标记点¶
为了进行图拆分,我们定义了Mark
类型op,标记模型导出的边界。在实现方法上,采用mark
装饰器对函数的输入、输出Tensor
打标记。需要注意的是,我们的标记函数需要在某个重写函数中执行才能生效。
为了对YOLOV3进行拆分,首先我们需要标记模型的输入。这里为了通用性,我们标记检测器父类BaseDetector
的forward
方法中的img
Tensor
,同时为了支持其他拆分方案,也对forward
函数的输出进行了标记,分别是dets
, labels
和masks
。下面的代码是截图mmdeploy/codebase/mmdet/models/detectors/single_stage.py中的一部分,可以看出我们使用mark
装饰器标记了__forward_impl
函数的输入输出,并在重写函数base_detector__forward
进行了调用,从而完成了对检测器输入的标记。
from mmdeploy.core import FUNCTION_REWRITER, mark
@mark(
'detector_forward', inputs=['input'], outputs=['dets', 'labels', 'masks'])
def __forward_impl(self, img, img_metas=None, **kwargs):
...
@FUNCTION_REWRITER.register_rewriter(
'mmdet.models.detectors.base.BaseDetector.forward')
def base_detector__forward(self, img, img_metas=None, **kwargs):
...
# call the mark function
return __forward_impl(...)
接下来,我们只需要对YOLOV3Head
中最后一层输出特征Tensor
进行标记就可以将整个YOLOV3
模型拆分成两部分。通过查看mmdet
源码我们可以知道YOLOV3Head
的get_bboxes
方法中输入参数pred_maps
就是我们想要的拆分点,因此可以在重写函数yolov3_head__get_bboxes
中添加内部函数对pred_mapes
进行标记,具体参考如下示例代码。值得注意的是,输入参数pred_maps
是由三个Tensor
组成的列表,所以我们在onnx模型中添加了三个Mark
标记节点。
from mmdeploy.core import FUNCTION_REWRITER, mark
@FUNCTION_REWRITER.register_rewriter(
func_name='mmdet.models.dense_heads.YOLOV3Head.get_bboxes')
def yolov3_head__get_bboxes(self,
pred_maps,
img_metas,
cfg=None,
rescale=False,
with_nms=True):
# mark pred_maps
@mark('yolo_head', inputs=['pred_maps'])
def __mark_pred_maps(pred_maps):
return pred_maps
pred_maps = __mark_pred_maps(pred_maps)
...
步骤 2: 添加部署配置文件¶
在完成模型中节点标记之后,我们需要创建部署配置文件,我们假设部署后端是onnxruntime
,并模型输入是固定尺寸608x608
,因此添加文件configs/mmdet/detection/yolov3_partition_onnxruntime_static.py
. 我们需要在配置文件中添加基本的配置信息如onnx_config
,如何你还不熟悉如何添加配置文件,可以参考write_config.md.
在这个部署配置文件中, 我们需要添加一个特殊的模型分段配置字段partition_config
. 在模型分段配置中,我们可以可以给分段策略添加一个类型名称如yolov3_partition
,设定apply_marks=True
。在分段方式partition_cfg
,我们需要指定每段模型的分割起始点start
, 终止点end
以及保存分段onnx的文件名。需要提醒的是,各段模型起始点start
和终止点end
是由多个标记节点Mark
组成,例如'detector_forward:input'
代表detector_forward
标记处输入所产生的标记节点。配置文件具体内容参考如下代码:
_base_ = ['./detection_onnxruntime_static.py']
onnx_config = dict(input_shape=[608, 608])
partition_config = dict(
type='yolov3_partition', # the partition policy name
apply_marks=True, # should always be set to True
partition_cfg=[
dict(
save_file='yolov3.onnx', # filename to save the partitioned onnx model
start=['detector_forward:input'], # [mark_name:input/output, ...]
end=['yolo_head:input'], # [mark_name:input/output, ...]
output_names=[f'pred_maps.{i}' for i in range(3)]) # output names
])
步骤 3: 拆分onnx模型¶
添加好节点标记和部署配置文件,我们可以使用tools/torch2onnx.py
工具导出带有Mark
标记的完成onnx模型并根据分段策略提取分段的onnx模型文件。我们可以执行如下脚本,得到不带后处理的YOLOV3
onnx模型文件yolov3.onnx
,同时输出文件中也包含了添加Mark
标记的完整模型文件end2end.onnx
。此外,用户可以使用网页版模型可视化工具netron来查看和验证输出onnx模型的结构是否正确。
python tools/torch2onnx.py \
configs/mmdet/detection/yolov3_partition_onnxruntime_static.py \
../mmdetection/configs/yolo/yolov3_d53_8xb8-ms-608-273e_coco.py \
https://download.openmmlab.com/mmdetection/v2.0/yolo/yolov3_d53_mstrain-608_273e_coco/yolov3_d53_mstrain-608_273e_coco_20210518_115020-a2c3acb8.pth \
../mmdetection/demo/demo.jpg \
--work-dir ./work-dirs/mmdet/yolov3/ort/partition
当得到分段onnx模型之后,我们可以使用mmdeploy提供的其他工具如mmdeploy_onnx2ncnn
, onnx2tensorrt
来进行后续的模型部署工作。
如何进行回归测试¶
这篇教程介绍了如何进行回归测试。部署配置文件由每个codebase的回归配置文件
,推理框架配置信息
组成。
1. 环境搭建¶
Python环境依赖¶
需要安装 test 的环境
pip install -r requirements/tests.txt
如果在使用过程是 numpy 报错,则更新一下 numpy
pip install -U numpy
2. 用法¶
python ./tools/regression_test.py \
--codebase "${CODEBASE_NAME}" \
--backends "${BACKEND}" \
[--models "${MODELS}"] \
--work-dir "${WORK_DIR}" \
--device "${DEVICE}" \
--log-level INFO \
[--performance 或 -p] \
[--checkpoint-dir "$CHECKPOINT_DIR"]
参数解析¶
--codebase
: 需要测试的 codebase,eg.mmdet
, 测试多个mmpretrain mmdet ...
--backends
: 筛选测试的后端, 默认测全部backend
, 也可传入若干个后端,例如onnxruntime tesnsorrt
。如果需要一同进行 SDK 的测试,需要在tests/regression/${codebase}.yml
里面的sdk_config
进行配置。--models
: 指定测试的模型, 默认测试yml
中所有模型, 也可传入若干个模型名称,模型名称可参考相关yml配置文件。例如ResNet SE-ResNet "Mask R-CNN"
。注意的是,可传入只有字母和数字组成模型名称,例如resnet seresnet maskrcnn
。--work-dir
: 模型转换、报告生成的路径,默认是../mmdeploy_regression_working_dir
,注意路径中不要含空格等特殊字符。--checkpoint-dir
: PyTorch 模型文件下载保存路径,默认是../mmdeploy_checkpoints
,注意路径中不要含空格等特殊字符。--device
: 使用的设备,默认cuda
。--log-level
: 设置日记的等级,选项包括'CRITICAL', 'FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'
。默认是INFO
。-p
或--performance
: 是否测试精度,加上则测试转换+精度,不加上则只测试转换
注意事项¶
对于 Windows 用户:
要在 shell 命令中使用
&&
连接符,需要下载并使用PowerShell 7 Preview 5+
。如果您使用 conda env,可能需要在 regression_test.py 中将
python3
更改为python
,因为%USERPROFILE%\AppData\Local\Microsoft\WindowsApps
目录中有python3.exe
。
例子¶
测试 mmdet 和 mmpose 的所有 backend 的 转换+精度
python ./tools/regression_test.py \
--codebase mmdet mmpose \
--work-dir "../mmdeploy_regression_working_dir" \
--device "cuda" \
--log-level INFO \
--performance
测试 mmdet 和 mmpose 的某几个 backend 的 转换+精度
python ./tools/regression_test.py \
--codebase mmdet mmpose \
--backends onnxruntime tensorrt \
--work-dir "../mmdeploy_regression_working_dir" \
--device "cuda" \
--log-level INFO \
-p
测试 mmdet 和 mmpose 的某几个 backend,只测试转换
python ./tools/regression_test.py \
--codebase mmdet mmpose \
--backends onnxruntime tensorrt \
--work-dir "../mmdeploy_regression_working_dir" \
--device "cuda" \
--log-level INFO
测试 mmdet 和 mmpretrain 的某几个 models,只测试转换
python ./tools/regression_test.py \
--codebase mmdet mmpose \
--models ResNet SE-ResNet "Mask R-CNN" \
--work-dir "../mmdeploy_regression_working_dir" \
--device "cuda" \
--log-level INFO
3. 回归测试配置文件¶
示例及参数解析¶
globals:
codebase_dir: ../mmocr # 回归测试的 codebase 路径
checkpoint_force_download: False # 回归测试是否重新下载模型即使其已经存在
images: # 测试使用图片
img_densetext_det: &img_densetext_det ../mmocr/demo/demo_densetext_det.jpg
img_demo_text_det: &img_demo_text_det ../mmocr/demo/demo_text_det.jpg
img_demo_text_ocr: &img_demo_text_ocr ../mmocr/demo/demo_text_ocr.jpg
img_demo_text_recog: &img_demo_text_recog ../mmocr/demo/demo_text_recog.jpg
metric_info: &metric_info # 指标参数
hmean-iou: # 命名根据 metafile.Results.Metrics
eval_name: hmean-iou # 命名根据 test.py --metrics args 入参名称
metric_key: 0_hmean-iou:hmean # 命名根据 eval 写入 log 的 key name
tolerance: 0.1 # 容忍的阈值区间
task_name: Text Detection # 命名根据模型 metafile.Results.Task
dataset: ICDAR2015 #命名根据模型 metafile.Results.Dataset
word_acc: # 同上
eval_name: acc
metric_key: 0_word_acc_ignore_case
tolerance: 0.2
task_name: Text Recognition
dataset: IIIT5K
convert_image_det: &convert_image_det # det转换会使用到的图片
input_img: *img_densetext_det
test_img: *img_demo_text_det
convert_image_rec: &convert_image_rec
input_img: *img_demo_text_recog
test_img: *img_demo_text_recog
backend_test: &default_backend_test True # 是否对 backend 进行精度测试
sdk: # SDK 配置文件
sdk_detection_dynamic: &sdk_detection_dynamic configs/mmocr/text-detection/text-detection_sdk_dynamic.py
sdk_recognition_dynamic: &sdk_recognition_dynamic configs/mmocr/text-recognition/text-recognition_sdk_dynamic.py
onnxruntime:
pipeline_ort_recognition_static_fp32: &pipeline_ort_recognition_static_fp32
convert_image: *convert_image_rec # 转换过程中使用的图片
backend_test: *default_backend_test # 是否进行后端测试,存在则判断,不存在则视为 False
sdk_config: *sdk_recognition_dynamic # 是否进行SDK测试,存在则使用特定的 SDK config 进行测试,不存在则视为不进行 SDK 测试
deploy_config: configs/mmocr/text-recognition/text-recognition_onnxruntime_static.py # 使用的 deploy cfg 路径,基于 mmdeploy 的路径
pipeline_ort_recognition_dynamic_fp32: &pipeline_ort_recognition_dynamic_fp32
convert_image: *convert_image_rec
backend_test: *default_backend_test
sdk_config: *sdk_recognition_dynamic
deploy_config: configs/mmocr/text-recognition/text-recognition_onnxruntime_dynamic.py
pipeline_ort_detection_dynamic_fp32: &pipeline_ort_detection_dynamic_fp32
convert_image: *convert_image_det
deploy_config: configs/mmocr/text-detection/text-detection_onnxruntime_dynamic.py
tensorrt:
pipeline_trt_recognition_dynamic_fp16: &pipeline_trt_recognition_dynamic_fp16
convert_image: *convert_image_rec
backend_test: *default_backend_test
sdk_config: *sdk_recognition_dynamic
deploy_config: configs/mmocr/text-recognition/text-recognition_tensorrt-fp16_dynamic-1x32x32-1x32x640.py
pipeline_trt_detection_dynamic_fp16: &pipeline_trt_detection_dynamic_fp16
convert_image: *convert_image_det
backend_test: *default_backend_test
sdk_config: *sdk_detection_dynamic
deploy_config: configs/mmocr/text-detection/text-detection_tensorrt-fp16_dynamic-320x320-2240x2240.py
openvino:
# 此处省略,内容同上
ncnn:
# 此处省略,内容同上
pplnn:
# 此处省略,内容同上
torchscript:
# 此处省略,内容同上
models:
- name: crnn # 模型名称
metafile: configs/textrecog/crnn/metafile.yml # 模型对应的 metafile 的路径,相对于 codebase 的路径
codebase_model_config_dir: configs/textrecog/crnn # `model_configs` 的父文件夹路径,相对于 codebase 的路径
model_configs: # 需要测试的 config 名称
- crnn_academic_dataset.py
pipelines: # 使用的 pipeline
- *pipeline_ort_recognition_dynamic_fp32
- name: dbnet
metafile: configs/textdet/dbnet/metafile.yml
codebase_model_config_dir: configs/textdet/dbnet
model_configs:
- dbnet_r18_fpnc_1200e_icdar2015.py
pipelines:
- *pipeline_ort_detection_dynamic_fp32
- *pipeline_trt_detection_dynamic_fp16
# 特殊的 pipeline 可以这样加入
- convert_image: xxx
backend_test: xxx
sdk_config: xxx
deploy_config: configs/mmocr/text-detection/xxx
4. 生成的报告¶
模板¶
Model | Model Config | Task | Checkpoint | Dataset | Backend | Deploy Config | Static or Dynamic | Precision Type | Conversion Result | metric_1 | metric_2 | metric_n | Test Pass | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
序号 | 模型名称 | model config 路径 | 执行的 task name | .pth 模型路径 |
数据集名称 | 后端名称 | deploy cfg 路径 | 动态 or 静态 | 测试精度 | 模型转换结果 | 指标 1 数值 | 指标 2 数值 | 指标 n 数值 | 后端测试结果 |
示例¶
这是 MMOCR 生成的报告
Model | Model Config | Task | Checkpoint | Dataset | Backend | Deploy Config | Static or Dynamic | Precision Type | Conversion Result | hmean-iou | word_acc | Test Pass | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | crnn | ../mmocr/configs/textrecog/crnn/crnn_academic_dataset.py | Text Recognition | ../mmdeploy_checkpoints/mmocr/crnn/crnn_academic-a723a1c5.pth | IIIT5K | Pytorch | - | - | - | - | - | 80.5 | - |
1 | crnn | ../mmocr/configs/textrecog/crnn/crnn_academic_dataset.py | Text Recognition | ${WORK_DIR}/mmocr/crnn/onnxruntime/static/crnn_academic-a723a1c5/end2end.onnx | x | onnxruntime | configs/mmocr/text-recognition/text-recognition_onnxruntime_dynamic.py | static | fp32 | True | - | 80.67 | True |
2 | crnn | ../mmocr/configs/textrecog/crnn/crnn_academic_dataset.py | Text Recognition | ${WORK_DIR}/mmocr/crnn/onnxruntime/static/crnn_academic-a723a1c5 | x | SDK-onnxruntime | configs/mmocr/text-recognition/text-recognition_sdk_dynamic.py | static | fp32 | True | - | x | False |
3 | dbnet | ../mmocr/configs/textdet/dbnet/dbnet_r18_fpnc_1200e_icdar2015.py | Text Detection | ../mmdeploy_checkpoints/mmocr/dbnet/dbnet_r18_fpnc_sbn_1200e_icdar2015_20210329-ba3ab597.pth | ICDAR2015 | Pytorch | - | - | - | - | 0.795 | - | - |
4 | dbnet | ../mmocr/configs/textdet/dbnet/dbnet_r18_fpnc_1200e_icdar2015.py | Text Detection | ../mmdeploy_checkpoints/mmocr/dbnet/dbnet_r18_fpnc_sbn_1200e_icdar2015_20210329-ba3ab597.pth | ICDAR | onnxruntime | configs/mmocr/text-detection/text-detection_onnxruntime_dynamic.py | dynamic | fp32 | True | - | - | True |
5 | dbnet | ../mmocr/configs/textdet/dbnet/dbnet_r18_fpnc_1200e_icdar2015.py | Text Detection | ${WORK_DIR}/mmocr/dbnet/tensorrt/dynamic/dbnet_r18_fpnc_sbn_1200e_icdar2015_20210329-ba3ab597/end2end.engine | ICDAR | tensorrt | configs/mmocr/text-detection/text-detection_tensorrt-fp16_dynamic-320x320-2240x2240.py | dynamic | fp16 | True | 0.793302 | - | True |
6 | dbnet | ../mmocr/configs/textdet/dbnet/dbnet_r18_fpnc_1200e_icdar2015.py | Text Detection | ${WORK_DIR}/mmocr/dbnet/tensorrt/dynamic/dbnet_r18_fpnc_sbn_1200e_icdar2015_20210329-ba3ab597 | ICDAR | SDK-tensorrt | configs/mmocr/text-detection/text-detection_sdk_dynamic.py | dynamic | fp16 | True | 0.795073 | - | True |
5. 支持的后端¶
[x] ONNX Runtime
[x] TensorRT
[x] PPLNN
[x] ncnn
[x] OpenVINO
[x] TorchScript
[x] SNPE
[x] MMDeploy SDK
6. 支持的Codebase及其Metric¶
Codebase | Metric | Support |
---|---|---|
mmdet | bbox | :heavy_check_mark: |
segm | :heavy_check_mark: | |
PQ | :x: | |
mmpretrain | accuracy | :heavy_check_mark: |
mmseg | mIoU | :heavy_check_mark: |
mmpose | AR | :heavy_check_mark: |
AP | :heavy_check_mark: | |
mmocr | hmean | :heavy_check_mark: |
acc | :heavy_check_mark: | |
mmagic | PSNR | :heavy_check_mark: |
SSIM | :heavy_check_mark: |
7. 注意事项¶
暂无
8. 常见问题¶
暂无
ONNX export Optimizer¶
This is a tool to optimize ONNX model when exporting from PyTorch.
Installation¶
Build MMDeploy with torchscript
support:
export Torch_DIR=$(python -c "import torch;print(torch.utils.cmake_prefix_path + '/Torch')")
cmake \
-DTorch_DIR=${Torch_DIR} \
-DMMDEPLOY_TARGET_BACKENDS="${your_backend};torchscript" \
.. # You can also add other build flags if you need
cmake --build . -- -j$(nproc) && cmake --install .
Usage¶
# import model_to_graph_custom_optimizer so we can hijack onnx.export
from mmdeploy.apis.onnx.optimizer import model_to_graph__custom_optimizer # noqa
from mmdeploy.core import RewriterContext
from mmdeploy.apis.onnx.passes import optimize_onnx
# load you model here
model = create_model()
# export with ONNX Optimizer
x = create_dummy_input()
with RewriterContext({}, onnx_custom_passes=optimize_onnx):
torch.onnx.export(model, x, output_path)
The model would be optimized after export.
You can also define your own optimizer:
# create the optimize callback
def _optimize_onnx(graph, params_dict, torch_out):
from mmdeploy.backend.torchscript import ts_optimizer
ts_optimizer.onnx._jit_pass_onnx_peephole(graph)
return graph, params_dict, torch_out
with RewriterContext({}, onnx_custom_passes=_optimize_onnx):
# export your model
第一章:模型部署简介¶
OpenMMLab 的算法如何部署?这是很多社区用户的困惑。而模型部署工具箱 MMDeploy 的开源,强势打通了从算法模型到应用程序这“最后一公里”!今天我们将开启模型部署入门系列教程,在模型部署开源库 MMDeploy 的辅助下,介绍以下内容:
中间表示 ONNX 的定义标准。
PyTorch 模型转换到 ONNX 模型的方法。
推理引擎 ONNX Runtime、TensorRT 的使用方法。
部署流水线 PyTorch - ONNX - ONNX Runtime/TensorRT 的示例及常见部署问题的解决方法。
MMDeploy C/C++ 推理 SDK。
希望通过本系列教程,带领大家学会如何把自己的 PyTorch 模型部署到 ONNX Runtime/TensorRT 上,并学会如何把 OpenMMLab 开源体系中各个计算机视觉任务的模型用 MMDeploy 部署到各个推理引擎上。
我们默认大家熟悉 Python 语言,并对 PyTorch 框架有基本的认识,除此之外不需要了解任何模型部署的知识。
在第一篇文章中,我们将部署一个简单的超分辨率模型,认识中间表示、推理引擎等模型部署中的概念。
初识模型部署¶
在软件工程中,部署指把开发完毕的软件投入使用的过程,包括环境配置、软件安装等步骤。类似地,对于深度学习模型来说,模型部署指让训练好的模型在特定环境中运行的过程。相比于软件部署,模型部署会面临更多的难题:
运行模型所需的环境难以配置。深度学习模型通常是由一些框架编写,比如 PyTorch、TensorFlow。由于框架规模、依赖环境的限制,这些框架不适合在手机、开发板等生产环境中安装。
深度学习模型的结构通常比较庞大,需要大量的算力才能满足实时运行的需求。模型的运行效率需要优化。
因为这些难题的存在,模型部署不能靠简单的环境配置与安装完成。经过工业界和学术界数年的探索,模型部署有了一条流行的流水线:
为了让模型最终能够部署到某一环境上,开发者们可以使用任意一种深度学习框架来定义网络结构,并通过训练确定网络中的参数。之后,模型的结构和参数会被转换成一种只描述网络结构的中间表示,一些针对网络结构的优化会在中间表示上进行。最后,用面向硬件的高性能编程框架(如 CUDA,OpenCL)编写,能高效执行深度学习网络中算子的推理引擎会把中间表示转换成特定的文件格式,并在对应硬件平台上高效运行模型。
这一条流水线解决了模型部署中的两大问题:使用对接深度学习框架和推理引擎的中间表示,开发者不必担心如何在新环境中运行各个复杂的框架;通过中间表示的网络结构优化和推理引擎对运算的底层优化,模型的运算效率大幅提升。
现在,让我们从一个模型部署的“Hello World”项目入手,见识一下模型部署各方面的知识吧!
部署第一个模型¶
创建 PyTorch 模型¶
仿照 PyTorch 的官方部署教程,让我们用 PyTorch 实现一个超分辨率模型,并把模型部署到 ONNX Runtime 这个推理引擎上。
首先,我们需要创建一个有 PyTorch 库的 Python 编程环境。如果你的 PyTorch 环境还没有装好,可以参考官方的入门教程。我们强烈推荐使用 conda 来管理 Python 库。使用 conda 可以靠如下的命令初始化一个 PyTorch 环境:
# 创建预安装 Python 3.7 的名叫 deploy 虚拟环境
conda create -n deploy python=3.7 -y
# 进入虚拟环境
conda activate deploy
# 安装 cpu 版本的 PyTorch
conda install pytorch torchvision cpuonly -c pytorch
如果你的设备支持 cuda 编程,我们建议你在配置 cuda 环境后使用 gpu 上的 PyTorch。比如将上面安装 PyTorch 的命令改成:
# 安装 cuda 11.3 的 PyTorch
# 如果你用的是其他版本的 cuda,请参考上面 PyTorch 的官方安装教程选择安装命令
conda install pytorch torchvision cudatoolkit=11.3 -c pytorch
本教程会用到其他一些第三方库。你可以用以下命令来安装这些库:
# 安装 ONNX Runtime, ONNX, OpenCV
pip install onnxruntime onnx opencv-python
在一切都配置完毕后,用下面的代码来创建一个超分辨率模型。
import os
import cv2
import numpy as np
import requests
import torch
import torch.onnx
from torch import nn
class SuperResolutionNet(nn.Module):
def __init__(self, upscale_factor):
super().__init__()
self.upscale_factor = upscale_factor
self.img_upsampler = nn.Upsample(
scale_factor=self.upscale_factor,
mode='bicubic',
align_corners=False)
self.conv1 = nn.Conv2d(3,64,kernel_size=9,padding=4)
self.conv2 = nn.Conv2d(64,32,kernel_size=1,padding=0)
self.conv3 = nn.Conv2d(32,3,kernel_size=5,padding=2)
self.relu = nn.ReLU()
def forward(self, x):
x = self.img_upsampler(x)
out = self.relu(self.conv1(x))
out = self.relu(self.conv2(out))
out = self.conv3(out)
return out
# Download checkpoint and test image
urls = ['https://download.openmmlab.com/mmediting/restorers/srcnn/srcnn_x4k915_1x16_1000k_div2k_20200608-4186f232.pth',
'https://raw.githubusercontent.com/open-mmlab/mmagic/master/tests/data/face/000001.png']
names = ['srcnn.pth', 'face.png']
for url, name in zip(urls, names):
if not os.path.exists(name):
open(name, 'wb').write(requests.get(url).content)
def init_torch_model():
torch_model = SuperResolutionNet(upscale_factor=3)
state_dict = torch.load('srcnn.pth')['state_dict']
# Adapt the checkpoint
for old_key in list(state_dict.keys()):
new_key = '.'.join(old_key.split('.')[1:])
state_dict[new_key] = state_dict.pop(old_key)
torch_model.load_state_dict(state_dict)
torch_model.eval()
return torch_model
model = init_torch_model()
input_img = cv2.imread('face.png').astype(np.float32)
# HWC to NCHW
input_img = np.transpose(input_img, [2, 0, 1])
input_img = np.expand_dims(input_img, 0)
# Inference
torch_output = model(torch.from_numpy(input_img)).detach().numpy()
# NCHW to HWC
torch_output = np.squeeze(torch_output, 0)
torch_output = np.clip(torch_output, 0, 255)
torch_output = np.transpose(torch_output, [1, 2, 0]).astype(np.uint8)
# Show image
cv2.imwrite("face_torch.png", torch_output)
在这份代码中,我们创建了一个经典的超分辨率网络 SRCNN。SRCNN 先把图像上采样到对应分辨率,再用 3 个卷积层处理图像。为了方便起见,我们跳过训练网络的步骤,直接下载模型权重(由于 MMagic 中 SRCNN 的权重结构和我们定义的模型不太一样,我们修改了权重字典的 key 来适配我们定义的模型),同时下载好输入图片。为了让模型输出成正确的图片格式,我们把模型的输出转换成 HWC 格式,并保证每一通道的颜色值都在 0~255 之间。如果脚本正常运行的话,一幅超分辨率的人脸照片会保存在 face_torch.png
中。
在 PyTorch 模型测试正确后,我们来正式开始部署这个模型。我们下一步的任务是把 PyTorch 模型转换成用中间表示 ONNX 描述的模型。
中间表示 —— ONNX¶
在介绍 ONNX 之前,我们先从本质上来认识一下神经网络的结构。神经网络实际上只是描述了数据计算的过程,其结构可以用计算图表示。比如 a+b
可以用下面的计算图来表示:
为了加速计算,一些框架会使用对神经网络“先编译,后执行”的静态图来描述网络。静态图的缺点是难以描述控制流(比如 if-else 分支语句和 for 循环语句),直接对其引入控制语句会导致产生不同的计算图。比如循环执行 n 次 a=a+b
,对于不同的 n,会生成不同的计算图:
ONNX(Open Neural Network Exchange)是 Facebook 和微软在 2017 年共同发布的,用于标准描述计算图的一种格式。目前,在数家机构的共同维护下,ONNX 已经对接了多种深度学习框架和多种推理引擎。因此,ONNX 被当成了深度学习框架到推理引擎的桥梁,就像编译器的中间语言一样。由于各框架兼容性不一,我们通常只用 ONNX 表示更容易部署的静态图。
让我们用下面的代码来把 PyTorch 的模型转换成 ONNX 格式的模型:
x = torch.randn(1, 3, 256, 256)
with torch.no_grad():
torch.onnx.export(
model,
x,
"srcnn.onnx",
opset_version=11,
input_names=['input'],
output_names=['output'])
其中,torch.onnx.export 是 PyTorch 自带的把模型转换成 ONNX 格式的函数。让我们先看一下前三个必选参数:前三个参数分别是要转换的模型、模型的任意一组输入、导出的 ONNX 文件的文件名。转换模型时,需要原模型和输出文件名是很容易理解的,但为什么需要为模型提供一组输入呢?这就涉及到 ONNX 转换的原理了。从 PyTorch 的模型到 ONNX 的模型,本质上是一种语言上的翻译。直觉上的想法是像编译器一样彻底解析原模型的代码,记录所有控制流。但前面也讲到,我们通常只用 ONNX 记录不考虑控制流的静态图。因此,PyTorch 提供了一种叫做追踪(trace)的模型转换方法:给定一组输入,再实际执行一遍模型,即把这组输入对应的计算图记录下来,保存为 ONNX 格式。export 函数用的就是追踪导出方法,需要给任意一组输入,让模型跑起来。我们的测试图片是三通道,256x256 大小的,这里也构造一个同样形状的随机张量。
剩下的参数中,opset_version
表示 ONNX 算子集的版本。深度学习的发展会不断诞生新算子,为了支持这些新增的算子,ONNX 会经常发布新的算子集,目前已经更新 15 个版本。 我们令 opset_version=11
,即使用第 11 个 ONNX 算子集,是因为 SRCNN 中的 bicubic(双三次插值)在 opset11 中才得到支持。剩下的两个参数 input_names
, output_names
是输入、输出 tensor 的名称,我们稍后会用到这些名称。
如果上述代码运行成功,目录下会新增一个 srcnn.onnx
的 ONNX 模型文件。我们可以用下面的脚本来验证一下模型文件是否正确。
import onnx
onnx_model = onnx.load("srcnn.onnx")
try:
onnx.checker.check_model(onnx_model)
except Exception:
print("Model incorrect")
else:
print("Model correct")
其中,onnx.load 函数用于读取一个 ONNX 模型。onnx.checker.check_model 用于检查模型格式是否正确,如果有错误的话该函数会直接报错。我们的模型是正确的,控制台中应该会打印出 “Model correct”。
接下来,让我们来看一看 ONNX 模型具体的结构是怎么样的。我们可以使用 Netron(开源的模型可视化工具)来可视化 ONNX 模型。把 srcnn.onnx
文件从本地的文件系统拖入网站,即可看到如下的可视化结果:
点击 input 或者 output,可以查看 ONNX 模型的基本信息,包括模型的版本信息,以及模型输入、输出的名称和数据类型。
点击某一个算子节点,可以看到算子的具体信息。比如点击第一个 Conv 可以看到:
每个算子记录了算子属性、图结构、权重三类信息。
算子属性信息即图中 attributes 里的信息,对于卷积来说,算子属性包括了卷积核大小(kernel_shape)、卷积步长(strides)等内容。这些算子属性最终会用来生成一个具体的算子。
图结构信息指算子节点在计算图中的名称、邻边的信息。对于图中的卷积来说,该算子节点叫做 Conv_2,输入数据叫做 11,输出数据叫做 12。根据每个算子节点的图结构信息,就能完整地复原出网络的计算图。
权重信息指的是网络经过训练后,算子存储的权重信息。对于卷积来说,权重信息包括卷积核的权重值和卷积后的偏差值。点击图中 conv1.weight, conv1.bias 后面的加号即可看到权重信息的具体内容。
现在,我们有了 SRCNN 的 ONNX 模型。让我们看看最后该如何把这个模型运行起来。
推理引擎 —— ONNX Runtime¶
ONNX Runtime 是由微软维护的一个跨平台机器学习推理加速器,也就是我们前面提到的“推理引擎”。ONNX Runtime 是直接对接 ONNX 的,即 ONNX Runtime 可以直接读取并运行 .onnx
文件,而不需要再把 .onnx
格式的文件转换成其他格式的文件。也就是说,对于 PyTorch - ONNX - ONNX Runtime
这条部署流水线,只要在目标设备中得到 .onnx
文件,并在 ONNX Runtime 上运行模型,模型部署就算大功告成了。
通过刚刚的操作,我们把 PyTorch 编写的模型转换成了 ONNX 模型,并通过可视化检查了模型的正确性。最后,让我们用 ONNX Runtime 运行一下模型,完成模型部署的最后一步。
ONNX Runtime 提供了 Python 接口。接着刚才的脚本,我们可以添加如下代码运行模型:
import onnxruntime
ort_session = onnxruntime.InferenceSession("srcnn.onnx")
ort_inputs = {'input': input_img}
ort_output = ort_session.run(['output'], ort_inputs)[0]
ort_output = np.squeeze(ort_output, 0)
ort_output = np.clip(ort_output, 0, 255)
ort_output = np.transpose(ort_output, [1, 2, 0]).astype(np.uint8)
cv2.imwrite("face_ort.png", ort_output)
这段代码中,除去后处理操作外,和 ONNX Runtime 相关的代码只有三行。让我们简单解析一下这三行代码。onnxruntime.InferenceSession 用于获取一个 ONNX Runtime 推理器,其参数是用于推理的 ONNX 模型文件。推理器的 run 方法用于模型推理,其第一个参数为输出张量名的列表,第二个参数为输入值的字典。其中输入值字典的 key 为张量名,value 为 numpy 类型的张量值。输入输出张量的名称需要和 torch.onnx.export 中设置的输入输出名对应。
如果代码正常运行的话,另一幅超分辨率照片会保存在 face_ort.png
中。这幅图片和刚刚得到的 face_torch.png
是一模一样的。这说明 ONNX Runtime 成功运行了 SRCNN 模型,模型部署完成了!以后有用户想实现超分辨率的操作,我们只需要提供一个 srcnn.onnx
文件,并帮助用户配置好 ONNX Runtime 的 Python 环境,用几行代码就可以运行模型了。或者还有更简便的方法,我们可以利用 ONNX Runtime 编译出一个可以直接执行模型的应用程序。我们只需要给用户提供 ONNX 模型文件,并让用户在应用程序选择要执行的 ONNX 模型文件名就可以运行模型了。
总结¶
在这篇教程里,我们利用成熟的模型部署工具,轻松部署了一个初始版本的超分辨率模型 SRCNN。但在实际应用场景中,随着模型结构的复杂度不断加深,碰到的困难的也会越来越多。在下一篇教程里,我们将“升级”一下这个超分辨率模型,让它支持动态的输入。
看完这篇教程,是不是感觉知识太多一下消化不过来?没关系,模型部署本身有非常多的东西要学。为了举例的方便,这篇教程包含了许多未来才会讲到的知识点。事实上,读完这篇教程后,记下以下知识点就够了:
模型部署,指把训练好的模型在特定环境中运行的过程。模型部署要解决模型框架兼容性差和模型运行速度慢这两大问题。
模型部署的常见流水线是“深度学习框架-中间表示-推理引擎”。其中比较常用的一个中间表示是 ONNX。
深度学习模型实际上就是一个计算图。模型部署时通常把模型转换成静态的计算图,即没有控制流(分支语句、循环语句)的计算图。
PyTorch 框架自带对 ONNX 的支持,只需要构造一组随机的输入,并对模型调用 torch.onnx.export 即可完成 PyTorch 到 ONNX 的转换。
推理引擎 ONNX Runtime 对 ONNX 模型有原生的支持。给定一个
.onnx
文件,只需要简单使用 ONNX Runtime 的 Python API 就可以完成模型推理。
为了实现深度学习算法的落地,充满挑战的模型部署是一个逃不开的步骤。MMDeploy 实现了 OpenMMLab 中目标检测、图像分割、超分辨率等多个视觉任务模型的部署,支持 ONNX Runtime,TensorRT,ncnn,openppl,OpenVINO 等多个推理引擎。
在后续的模型部署教程中,我们将在介绍模型部署技术的同时,介绍这些技术是如何运用在 MMDeploy 中的。
第二章:解决模型部署中的难题¶
在第一章中,我们部署了一个简单的超分辨率模型,一切都十分顺利。但是,上一个模型还有一些缺陷——图片的放大倍数固定是 4,我们无法让图片放大任意的倍数。现在,我们来尝试部署一个支持动态放大倍数的模型,体验一下在模型部署中可能会碰到的困难。
模型部署中常见的难题¶
在之前的学习中,我们在模型部署上顺风顺水,没有碰到任何问题。这是因为 SRCNN 模型只包含几个简单的算子,而这些卷积、插值算子已经在各个中间表示和推理引擎上得到了完美支持。如果模型的操作稍微复杂一点,我们可能就要为兼容模型而付出大量的功夫了。实际上,模型部署时一般会碰到以下几类困难:
模型的动态化。出于性能的考虑,各推理框架都默认模型的输入形状、输出形状、结构是静态的。而为了让模型的泛用性更强,部署时需要在尽可能不影响原有逻辑的前提下,让模型的输入输出或是结构动态化。
新算子的实现。深度学习技术日新月异,提出新算子的速度往往快于 ONNX 维护者支持的速度。为了部署最新的模型,部署工程师往往需要自己在 ONNX 和推理引擎中支持新算子。
中间表示与推理引擎的兼容问题。由于各推理引擎的实现不同,对 ONNX 难以形成统一的支持。为了确保模型在不同的推理引擎中有同样的运行效果,部署工程师往往得为某个推理引擎定制模型代码,这为模型部署引入了许多工作量。
我们会在后续教程详细讲述解决这些问题的方法。如果对前文中 ONNX、推理引擎、中间表示、算子等名词感觉陌生,不用担心,可以阅读第一章,了解有关概念。
现在,让我们对原来的 SRCNN 模型做一些小的修改,体验一下模型动态化对模型部署造成的困难,并学习解决该问题的一种方法。
问题:实现动态放大的超分辨率模型¶
在原来的 SRCNN 中,图片的放大比例是写死在模型里的:
class SuperResolutionNet(nn.Module):
def __init__(self, upscale_factor):
super().__init__()
self.upscale_factor = upscale_factor
self.img_upsampler = nn.Upsample(
scale_factor=self.upscale_factor,
mode='bicubic',
align_corners=False)
...
def init_torch_model():
torch_model = SuperResolutionNet(upscale_factor=3)
我们使用 upscale_factor 来控制模型的放大比例。初始化模型的时候,我们默认令 upscale_factor 为 3,生成了一个放大 3 倍的 PyTorch 模型。这个 PyTorch 模型最终被转换成了 ONNX 格式的模型。如果我们需要一个放大 4 倍的模型,需要重新生成一遍模型,再做一次到 ONNX 的转换。
现在,假设我们要做一个超分辨率的应用。我们的用户希望图片的放大倍数能够自由设置。而我们交给用户的,只有一个 .onnx 文件和运行超分辨率模型的应用程序。我们在不修改 .onnx 文件的前提下改变放大倍数。
因此,我们必须修改原来的模型,令模型的放大倍数变成推理时的输入。在第一章中的 Python 脚本的基础上,我们做一些修改,得到这样的脚本:
import torch
from torch import nn
from torch.nn.functional import interpolate
import torch.onnx
import cv2
import numpy as np
class SuperResolutionNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=9, padding=4)
self.conv2 = nn.Conv2d(64, 32, kernel_size=1, padding=0)
self.conv3 = nn.Conv2d(32, 3, kernel_size=5, padding=2)
self.relu = nn.ReLU()
def forward(self, x, upscale_factor):
x = interpolate(x,
scale_factor=upscale_factor,
mode='bicubic',
align_corners=False)
out = self.relu(self.conv1(x))
out = self.relu(self.conv2(out))
out = self.conv3(out)
return out
def init_torch_model():
torch_model = SuperResolutionNet()
# Please read the code about downloading 'srcnn.pth' and 'face.png' in
# https://mmdeploy.readthedocs.io/zh_CN/latest/tutorial/01_introduction_to_model_deployment.html#pytorch
state_dict = torch.load('srcnn.pth')['state_dict']
# Adapt the checkpoint
for old_key in list(state_dict.keys()):
new_key = '.'.join(old_key.split('.')[1:])
state_dict[new_key] = state_dict.pop(old_key)
torch_model.load_state_dict(state_dict)
torch_model.eval()
return torch_model
model = init_torch_model()
input_img = cv2.imread('face.png').astype(np.float32)
# HWC to NCHW
input_img = np.transpose(input_img, [2, 0, 1])
input_img = np.expand_dims(input_img, 0)
# Inference
torch_output = model(torch.from_numpy(input_img), 3).detach().numpy()
# NCHW to HWC
torch_output = np.squeeze(torch_output, 0)
torch_output = np.clip(torch_output, 0, 255)
torch_output = np.transpose(torch_output, [1, 2, 0]).astype(np.uint8)
# Show image
cv2.imwrite("face_torch_2.png", torch_output)
SuperResolutionNet 未修改之前,nn.Upsample 在初始化阶段固化了放大倍数,而 PyTorch 的 interpolate 插值算子可以在运行阶段选择放大倍数。因此,我们在新脚本中使用 interpolate 代替 nn.Upsample,从而让模型支持动态放大倍数的超分。 在第 55 行使用模型推理时,我们把放大倍数设置为 3。最后,图片保存在文件 “face_torch_2.png” 中。一切正常的话,”face_torch_2.png” 和 “face_torch.png” 的内容一模一样。
通过简单的修改,PyTorch 模型已经支持了动态分辨率。现在我们来一下尝试导出模型:
x = torch.randn(1, 3, 256, 256)
with torch.no_grad():
torch.onnx.export(model, (x, 3),
"srcnn2.onnx",
opset_version=11,
input_names=['input', 'factor'],
output_names=['output'])
运行这些脚本时,会报一长串错误。没办法,我们碰到了模型部署中的兼容性问题。
解决方法:自定义算子¶
直接使用 PyTorch 模型的话,我们修改几行代码就能实现模型输入的动态化。但在模型部署中,我们要花数倍的时间来设法解决这一问题。现在,让我们顺着解决问题的思路,体验一下模型部署的困难,并学习使用自定义算子的方式,解决超分辨率模型的动态化问题。
刚刚的报错是因为 PyTorch 模型在导出到 ONNX 模型时,模型的输入参数的类型必须全部是 torch.Tensor。而实际上我们传入的第二个参数” 3 “是一个整形变量。这不符合 PyTorch 转 ONNX 的规定。我们必须要修改一下原来的模型的输入。为了保证输入的所有参数都是 torch.Tensor 类型的,我们做如下修改:
...
class SuperResolutionNet(nn.Module):
def forward(self, x, upscale_factor):
x = interpolate(x,
scale_factor=upscale_factor.item(),
mode='bicubic',
align_corners=False)
...
# Inference
# Note that the second input is torch.tensor(3)
torch_output = model(torch.from_numpy(input_img), torch.tensor(3)).detach().numpy()
...
with torch.no_grad():
torch.onnx.export(model, (x, torch.tensor(3)),
"srcnn2.onnx",
opset_version=11,
input_names=['input', 'factor'],
output_names=['output'])
由于 PyTorch 中 interpolate 的 scale_factor 参数必须是一个数值,我们使用 torch.Tensor.item() 来把只有一个元素的 torch.Tensor 转换成数值。之后,在模型推理时,我们使用 torch.tensor(3) 代替 3,以使得我们的所有输入都满足要求。现在运行脚本的话,无论是直接运行模型,还是导出 ONNX 模型,都不会报错了。
但是,导出 ONNX 时却报了一条 TraceWarning 的警告。这条警告说有一些量可能会追踪失败。这是怎么回事呢?让我们把生成的 srcnn2.onnx 用 Netron 可视化一下:
可以发现,虽然我们把模型推理的输入设置为了两个,但 ONNX 模型还是长得和原来一模一样,只有一个叫 ” input ” 的输入。这是由于我们使用了 torch.Tensor.item() 把数据从 Tensor 里取出来,而导出 ONNX 模型时这个操作是无法被记录的,只好报了一条 TraceWarning。这导致 interpolate 插值函数的放大倍数还是被设置成了” 3 “这个固定值,我们导出的” srcnn2.onnx “和最开始的” srcnn.onnx “完全相同。
直接修改原来的模型似乎行不通,我们得从 PyTorch 转 ONNX 的原理入手,强行令 ONNX 模型明白我们的想法了。
仔细观察 Netron 上可视化出的 ONNX 模型,可以发现在 PyTorch 中无论是使用最早的 nn.Upsample,还是后来的 interpolate,PyTorch 里的插值操作最后都会转换成 ONNX 定义的 Resize 操作。也就是说,所谓 PyTorch 转 ONNX,实际上就是把每个 PyTorch 的操作映射成了 ONNX 定义的算子。
点击该算子,可以看到它的详细参数如下:
其中,展开 scales,可以看到 scales 是一个长度为 4 的一维张量,其内容为 [1, 1, 3, 3], 表示 Resize 操作每一个维度的缩放系数;其类型为 Initializer,表示这个值是根据常量直接初始化出来的。如果我们能够自己生成一个 ONNX 的 Resize 算子,让 scales 成为一个可变量而不是常量,就像它上面的 X 一样,那这个超分辨率模型就能动态缩放了。
现有实现插值的 PyTorch 算子有一套规定好的映射到 ONNX Resize 算子的方法,这些映射出的 Resize 算子的 scales 只能是常量,无法满足我们的需求。我们得自己定义一个实现插值的 PyTorch 算子,然后让它映射到一个我们期望的 ONNX Resize 算子上。
下面的脚本定义了一个 PyTorch 插值算子,并在模型里使用了它。我们先通过运行模型来验证该算子的正确性:
import torch
from torch import nn
from torch.nn.functional import interpolate
import torch.onnx
import cv2
import numpy as np
class NewInterpolate(torch.autograd.Function):
@staticmethod
def symbolic(g, input, scales):
return g.op("Resize",
input,
g.op("Constant",
value_t=torch.tensor([], dtype=torch.float32)),
scales,
coordinate_transformation_mode_s="pytorch_half_pixel",
cubic_coeff_a_f=-0.75,
mode_s='cubic',
nearest_mode_s="floor")
@staticmethod
def forward(ctx, input, scales):
scales = scales.tolist()[-2:]
return interpolate(input,
scale_factor=scales,
mode='bicubic',
align_corners=False)
class StrangeSuperResolutionNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=9, padding=4)
self.conv2 = nn.Conv2d(64, 32, kernel_size=1, padding=0)
self.conv3 = nn.Conv2d(32, 3, kernel_size=5, padding=2)
self.relu = nn.ReLU()
def forward(self, x, upscale_factor):
x = NewInterpolate.apply(x, upscale_factor)
out = self.relu(self.conv1(x))
out = self.relu(self.conv2(out))
out = self.conv3(out)
return out
def init_torch_model():
torch_model = StrangeSuperResolutionNet()
state_dict = torch.load('srcnn.pth')['state_dict']
# Adapt the checkpoint
for old_key in list(state_dict.keys()):
new_key = '.'.join(old_key.split('.')[1:])
state_dict[new_key] = state_dict.pop(old_key)
torch_model.load_state_dict(state_dict)
torch_model.eval()
return torch_model
model = init_torch_model()
factor = torch.tensor([1, 1, 3, 3], dtype=torch.float)
input_img = cv2.imread('face.png').astype(np.float32)
# HWC to NCHW
input_img = np.transpose(input_img, [2, 0, 1])
input_img = np.expand_dims(input_img, 0)
# Inference
torch_output = model(torch.from_numpy(input_img), factor).detach().numpy()
# NCHW to HWC
torch_output = np.squeeze(torch_output, 0)
torch_output = np.clip(torch_output, 0, 255)
torch_output = np.transpose(torch_output, [1, 2, 0]).astype(np.uint8)
# Show image
cv2.imwrite("face_torch_3.png", torch_output)
模型运行正常的话,一幅放大3倍的超分辨率图片会保存在”face_torch_3.png”中,其内容和”face_torch.png”完全相同。
在刚刚那个脚本中,我们定义 PyTorch 插值算子的代码如下:
class NewInterpolate(torch.autograd.Function):
@staticmethod
def symbolic(g, input, scales):
return g.op("Resize",
input,
g.op("Constant",
value_t=torch.tensor([], dtype=torch.float32)),
scales,
coordinate_transformation_mode_s="pytorch_half_pixel",
cubic_coeff_a_f=-0.75,
mode_s='cubic',
nearest_mode_s="floor")
@staticmethod
def forward(ctx, input, scales):
scales = scales.tolist()[-2:]
return interpolate(input,
scale_factor=scales,
mode='bicubic',
align_corners=False)
在具体介绍这个算子的实现前,让我们先理清一下思路。我们希望新的插值算子有两个输入,一个是被用于操作的图像,一个是图像的放缩比例。前面讲到,为了对接 ONNX 中 Resize 算子的 scales 参数,这个放缩比例是一个 [1, 1, x, x] 的张量,其中 x 为放大倍数。在之前放大3倍的模型中,这个参数被固定成了[1, 1, 3, 3]。因此,在插值算子中,我们希望模型的第二个输入是一个 [1, 1, w, h] 的张量,其中 w 和 h 分别是图片宽和高的放大倍数。
搞清楚了插值算子的输入,再看一看算子的具体实现。算子的推理行为由算子的 forward 方法决定。该方法的第一个参数必须为 ctx,后面的参数为算子的自定义输入,我们设置两个输入,分别为被操作的图像和放缩比例。为保证推理正确,需要把 [1, 1, w, h] 格式的输入对接到原来的 interpolate 函数上。我们的做法是截取输入张量的后两个元素,把这两个元素以 list 的格式传入 interpolate 的 scale_factor 参数。
接下来,我们要决定新算子映射到 ONNX 算子的方法。映射到 ONNX 的方法由一个算子的 symbolic 方法决定。symbolic 方法第一个参数必须是g,之后的参数是算子的自定义输入,和 forward 函数一样。ONNX 算子的具体定义由 g.op 实现。g.op 的每个参数都可以映射到 ONNX 中的算子属性:
对于其他参数,我们可以照着现在的 Resize 算子填。而要注意的是,我们现在希望 scales 参数是由输入动态决定的。因此,在填入 ONNX 的 scales 时,我们要把 symbolic 方法的输入参数中的 scales 填入。
接着,让我们把新模型导出成 ONNX 模型:
x = torch.randn(1, 3, 256, 256)
with torch.no_grad():
torch.onnx.export(model, (x, factor),
"srcnn3.onnx",
opset_version=11,
input_names=['input', 'factor'],
output_names=['output'])
把导出的 ” srcnn3.onnx ” 进行可视化:
可以看到,正如我们所期望的,导出的 ONNX 模型有了两个输入!第二个输入表示图像的放缩比例。
之前在验证 PyTorch 模型和导出 ONNX 模型时,我们宽高的缩放比例设置成了 3x3。现在,在用 ONNX Runtime 推理时,我们尝试使用 4x4 的缩放比例:
import onnxruntime
input_factor = np.array([1, 1, 4, 4], dtype=np.float32)
ort_session = onnxruntime.InferenceSession("srcnn3.onnx")
ort_inputs = {'input': input_img, 'factor': input_factor}
ort_output = ort_session.run(None, ort_inputs)[0]
ort_output = np.squeeze(ort_output, 0)
ort_output = np.clip(ort_output, 0, 255)
ort_output = np.transpose(ort_output, [1, 2, 0]).astype(np.uint8)
cv2.imwrite("face_ort_3.png", ort_output)
运行上面的代码,可以得到一个边长放大4倍的超分辨率图片 “face_ort_3.png”。动态的超分辨率模型生成成功了!只要修改 input_factor,我们就可以自由地控制图片的缩放比例。
我们刚刚的工作,实际上是绕过 PyTorch 本身的限制,凭空“捏”出了一个 ONNX 算子。事实上,我们不仅可以创建现有的 ONNX 算子,还可以定义新的 ONNX 算子以拓展 ONNX 的表达能力。后续教程中我们将介绍自定义新 ONNX 算子的方法。
总结¶
通过学习前两篇教程,我们走完了整个部署流水线,成功部署了支持动态放大倍数的超分辨率模型。在这个过程中,我们既学会了如何简单地调用各框架的API实现模型部署,又学到了如何分析并尝试解决模型部署时碰到的难题。
同样,让我们总结一下本篇教程的知识点:
模型部署中常见的几类困难有:模型的动态化;新算子的实现;框架间的兼容。
PyTorch 转 ONNX,实际上就是把每一个操作转化成 ONNX 定义的某一个算子。比如对于 PyTorch 中的 Upsample 和 interpolate,在转 ONNX 后最终都会成为 ONNX 的 Resize 算子。
通过修改继承自 torch.autograd.Function 的算子的 symbolic 方法,可以改变该算子映射到 ONNX 算子的行为。
至此,”部署第一个模型“的教程算是告一段落了。是不是觉得学到的知识还不够多?没关系,在接下来的几篇教程中,我们将结合 MMDeploy ,重点介绍 ONNX 中间表示和 ONNX Runtime/TensorRT 推理引擎的知识,让大家学会如何部署更复杂的模型。
第三章:PyTorch 转 ONNX 详解¶
ONNX 是目前模型部署中最重要的中间表示之一。学懂了 ONNX 的技术细节,就能规避大量的模型部署问题。从这篇文章开始,在接下来的三篇文章里,我们将由浅入深地介绍 ONNX 相关的知识。在第一篇文章里,我们会介绍更多 PyTorch 转 ONNX 的细节,让大家完全掌握把简单的 PyTorch 模型转成 ONNX 模型的方法;在第二篇文章里,我们将介绍如何在 PyTorch 中支持更多的 ONNX 算子,让大家能彻底走通 PyTorch 到 ONNX 这条部署路线;第三篇文章里,我们讲介绍 ONNX 本身的知识,以及修改、调试 ONNX 模型的常用方法,使大家能自行解决大部分和 ONNX 有关的部署问题。
在把 PyTorch 模型转换成 ONNX 模型时,我们往往只需要轻松地调用一句torch.onnx.export
就行了。这个函数的接口看上去简单,但它在使用上还有着诸多的“潜规则”。在这篇教程中,我们会详细介绍 PyTorch 模型转 ONNX 模型的原理及注意事项。除此之外,我们还会介绍 PyTorch 与 ONNX 的算子对应关系,以教会大家如何处理 PyTorch 模型转换时可能会遇到的算子支持问题。
torch.onnx.export
细解¶
在这一节里,我们将详细介绍 PyTorch 到 ONNX 的转换函数—— torch.onnx.export。我们希望大家能够更加灵活地使用这个模型转换接口,并通过了解它的实现原理来更好地应对该函数的报错(由于模型部署的兼容性问题,部署复杂模型时该函数时常会报错)。
计算图导出方法¶
TorchScript 是一种序列化和优化 PyTorch 模型的格式,在优化过程中,一个torch.nn.Module
模型会被转换成 TorchScript 的torch.jit.ScriptModule
模型。现在, TorchScript 也被常当成一种中间表示使用。我们在其他文章中对 TorchScript 有详细的介绍,这里介绍 TorchScript 仅用于说明 PyTorch 模型转 ONNX的原理。
torch.onnx.export
中需要的模型实际上是一个torch.jit.ScriptModule
。而要把普通 PyTorch 模型转一个这样的 TorchScript 模型,有跟踪(trace)和脚本化(script)两种导出计算图的方法。如果给torch.onnx.export
传入了一个普通 PyTorch 模型(torch.nn.Module
),那么这个模型会默认使用跟踪的方法导出。这一过程如下图所示:
回忆一下我们第一篇教程 知识:跟踪法只能通过实际运行一遍模型的方法导出模型的静态图,即无法识别出模型中的控制流(如循环);脚本化则能通过解析模型来正确记录所有的控制流。我们以下面这段代码为例来看一看这两种转换方法的区别:
import torch
class Model(torch.nn.Module):
def __init__(self, n):
super().__init__()
self.n = n
self.conv = torch.nn.Conv2d(3, 3, 3)
def forward(self, x):
for i in range(self.n):
x = self.conv(x)
return x
models = [Model(2), Model(3)]
model_names = ['model_2', 'model_3']
for model, model_name in zip(models, model_names):
dummy_input = torch.rand(1, 3, 10, 10)
dummy_output = model(dummy_input)
model_trace = torch.jit.trace(model, dummy_input)
model_script = torch.jit.script(model)
# 跟踪法与直接 torch.onnx.export(model, ...)等价
torch.onnx.export(model_trace, dummy_input, f'{model_name}_trace.onnx', example_outputs=dummy_output)
# 脚本化必须先调用 torch.jit.sciprt
torch.onnx.export(model_script, dummy_input, f'{model_name}_script.onnx', example_outputs=dummy_output)
在这段代码里,我们定义了一个带循环的模型,模型通过参数n
来控制输入张量被卷积的次数。之后,我们各创建了一个n=2
和n=3
的模型。我们把这两个模型分别用跟踪和脚本化的方法进行导出。
值得一提的是,由于这里的两个模型(model_trace
, model_script
)是 TorchScript 模型,export
函数已经不需要再运行一遍模型了。(如果模型是用跟踪法得到的,那么在执行torch.jit.trace
的时候就运行过一遍了;而用脚本化导出时,模型不需要实际运行)参数中的dummy_input
和dummy_output
仅仅是为了获取输入和输出张量的类型和形状。
运行上面的代码,我们把得到的4个 onnx 文件用 Netron 可视化:
首先看跟踪法得到的 ONNX 模型结构。可以看出来,对于不同的 n
,ONNX 模型的结构是不一样的。
而用脚本化的话,最终的 ONNX 模型用 Loop
节点来表示循环。这样哪怕对于不同的 n
,ONNX 模型也有同样的结构。
由于推理引擎对静态图的支持更好,通常我们在模型部署时不需要显式地把 PyTorch 模型转成 TorchScript 模型,直接把 PyTorch 模型用 torch.onnx.export
跟踪导出即可。了解这部分的知识主要是为了在模型转换报错时能够更好地定位问题是否发生在 PyTorch 转 TorchScript 阶段。
参数讲解¶
了解完转换函数的原理后,我们来详细介绍一下该函数的主要参数的作用。我们主要会从应用的角度来介绍每个参数在不同的模型部署场景中应该如何设置,而不会去列出每个参数的所有设置方法。该函数详细的 API 文档可参考 torch.onnx ‒ PyTorch 1.11.0 documentation
torch.onnx.export
在 torch.onnx.__init__.py
文件中的定义如下:
def export(model, args, f, export_params=True, verbose=False, training=TrainingMode.EVAL,
input_names=None, output_names=None, aten=False, export_raw_ir=False,
operator_export_type=None, opset_version=None, _retain_param_name=True,
do_constant_folding=True, example_outputs=None, strip_doc_string=True,
dynamic_axes=None, keep_initializers_as_inputs=None, custom_opsets=None,
enable_onnx_checker=True, use_external_data_format=False):
前三个必选参数为模型、模型输入、导出的 onnx 文件名,我们对这几个参数已经很熟悉了。我们来着重看一下后面的一些常用可选参数。
export_params¶
模型中是否存储模型权重。一般中间表示包含两大类信息:模型结构和模型权重,这两类信息可以在同一个文件里存储,也可以分文件存储。ONNX 是用同一个文件表示记录模型的结构和权重的。 我们部署时一般都默认这个参数为 True。如果 onnx 文件是用来在不同框架间传递模型(比如 PyTorch 到 Tensorflow)而不是用于部署,则可以令这个参数为 False。
input_names, output_names¶
设置输入和输出张量的名称。如果不设置的话,会自动分配一些简单的名字(如数字)。 ONNX 模型的每个输入和输出张量都有一个名字。很多推理引擎在运行 ONNX 文件时,都需要以“名称-张量值”的数据对来输入数据,并根据输出张量的名称来获取输出数据。在进行跟张量有关的设置(比如添加动态维度)时,也需要知道张量的名字。 在实际的部署流水线中,我们都需要设置输入和输出张量的名称,并保证 ONNX 和推理引擎中使用同一套名称。
opset_version¶
转换时参考哪个 ONNX 算子集版本,默认为9。后文会详细介绍 PyTorch 与 ONNX 的算子对应关系。
dynamic_axes¶
指定输入输出张量的哪些维度是动态的。
为了追求效率,ONNX 默认所有参与运算的张量都是静态的(张量的形状不发生改变)。但在实际应用中,我们又希望模型的输入张量是动态的,尤其是本来就没有形状限制的全卷积模型。因此,我们需要显式地指明输入输出张量的哪几个维度的大小是可变的。
我们来看一个dynamic_axes
的设置例子:
import torch
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
self.conv = torch.nn.Conv2d(3, 3, 3)
def forward(self, x):
x = self.conv(x)
return x
model = Model()
dummy_input = torch.rand(1, 3, 10, 10)
model_names = ['model_static.onnx',
'model_dynamic_0.onnx',
'model_dynamic_23.onnx']
dynamic_axes_0 = {
'in' : [0],
'out' : [0]
}
dynamic_axes_23 = {
'in' : [2, 3],
'out' : [2, 3]
}
torch.onnx.export(model, dummy_input, model_names[0],
input_names=['in'], output_names=['out'])
torch.onnx.export(model, dummy_input, model_names[1],
input_names=['in'], output_names=['out'], dynamic_axes=dynamic_axes_0)
torch.onnx.export(model, dummy_input, model_names[2],
input_names=['in'], output_names=['out'], dynamic_axes=dynamic_axes_23)
首先,我们导出3个 ONNX 模型,分别为没有动态维度、第0维动态、第2第3维动态的模型。 在这份代码里,我们是用列表的方式表示动态维度,例如:
dynamic_axes_0 = {
'in' : [0],
'out' : [0]
}
```
由于 ONNX 要求每个动态维度都有一个名字,这样写的话会引出一条 UserWarning,警告我们通过列表的方式设置动态维度的话系统会自动为它们分配名字。一种显式添加动态维度名字的方法如下:
```python
dynamic_axes_0 = {
'in' : {0: 'batch'},
'out' : {0: 'batch'}
}
由于在这份代码里我们没有更多的对动态维度的操作,因此简单地用列表指定动态维度即可。 之后,我们用下面的代码来看一看动态维度的作用:
import onnxruntime
import numpy as np
origin_tensor = np.random.rand(1, 3, 10, 10).astype(np.float32)
mult_batch_tensor = np.random.rand(2, 3, 10, 10).astype(np.float32)
big_tensor = np.random.rand(1, 3, 20, 20).astype(np.float32)
inputs = [origin_tensor, mult_batch_tensor, big_tensor]
exceptions = dict()
for model_name in model_names:
for i, input in enumerate(inputs):
try:
ort_session = onnxruntime.InferenceSession(model_name)
ort_inputs = {'in': input}
ort_session.run(['out'], ort_inputs)
except Exception as e:
exceptions[(i, model_name)] = e
print(f'Input[{i}] on model {model_name} error.')
else:
print(f'Input[{i}] on model {model_name} succeed.')
我们在模型导出计算图时用的是一个形状为(1, 3, 10, 10)
的张量。现在,我们来尝试以形状分别是(1, 3, 10, 10), (2, 3, 10, 10), (1, 3, 20, 20)
为输入,用ONNX Runtime运行一下这几个模型,看看哪些情况下会报错,并保存对应的报错信息。得到的输出信息应该如下:
Input[0] on model model_static.onnx succeed.
Input[1] on model model_static.onnx error.
Input[2] on model model_static.onnx error.
Input[0] on model model_dynamic_0.onnx succeed.
Input[1] on model model_dynamic_0.onnx succeed.
Input[2] on model model_dynamic_0.onnx error.
Input[0] on model model_dynamic_23.onnx succeed.
Input[1] on model model_dynamic_23.onnx error.
Input[2] on model model_dynamic_23.onnx succeed.
可以看出,形状相同的(1, 3, 10, 10)
的输入在所有模型上都没有出错。而对于batch(第0维)或者长宽(第2、3维)不同的输入,只有在设置了对应的动态维度后才不会出错。我们可以错误信息中找出是哪些维度出了问题。比如我们可以用以下代码查看input[1]
在model_static.onnx
中的报错信息:
print(exceptions[(1, 'model_static.onnx')])
# output
# [ONNXRuntimeError] : 2 : INVALID_ARGUMENT : Got invalid dimensions for input: in for the following indices index: 0 Got: 2 Expected: 1 Please fix either the inputs or the model.
这段报错告诉我们名字叫in
的输入的第0维不匹配。本来该维的长度应该为1,但我们的输入是2。实际部署中,如果我们碰到了类似的报错,就可以通过设置动态维度来解决问题。
使用技巧¶
通过学习之前的知识,我们基本掌握了 torch.onnx.export
函数的部分实现原理和参数设置方法,足以完成简单模型的转换了。但在实际应用中,使用该函数还会踩很多坑。这里我们模型部署团队把在实战中积累的一些经验分享给大家。
使模型在 ONNX 转换时有不同的行为¶
有些时候,我们希望模型在直接用 PyTorch 推理时有一套逻辑,而在导出的ONNX模型中有另一套逻辑。比如,我们可以把一些后处理的逻辑放在模型里,以简化除运行模型之外的其他代码。torch.onnx.is_in_onnx_export()
可以实现这一任务,该函数仅在执行 torch.onnx.export()
时为真。以下是一个例子:
import torch
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
self.conv = torch.nn.Conv2d(3, 3, 3)
def forward(self, x):
x = self.conv(x)
if torch.onnx.is_in_onnx_export():
x = torch.clip(x, 0, 1)
return x
这里,我们仅在模型导出时把输出张量的数值限制在[0, 1]之间。使用 is_in_onnx_export
确实能让我们方便地在代码中添加和模型部署相关的逻辑。但是,这些代码对只关心模型训练的开发者和用户来说很不友好,突兀的部署逻辑会降低代码整体的可读性。同时,is_in_onnx_export
只能在每个需要添加部署逻辑的地方都“打补丁”,难以进行统一的管理。我们之后会介绍如何使用 MMDeploy 的重写机制来规避这些问题。
利用中断张量跟踪的操作¶
PyTorch 转 ONNX 的跟踪导出法是不是万能的。如果我们在模型中做了一些很“出格”的操作,跟踪法会把某些取决于输入的中间结果变成常量,从而使导出的ONNX模型和原来的模型有出入。以下是一个会造成这种“跟踪中断”的例子:
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
x = x * x[0].item()
return x, torch.Tensor([i for i in x])
model = Model()
dummy_input = torch.rand(10)
torch.onnx.export(model, dummy_input, 'a.onnx')
如果你尝试去导出这个模型,会得到一大堆 warning,告诉你转换出来的模型可能不正确。这也难怪,我们在这个模型里使用了.item()
把 torch 中的张量转换成了普通的 Python 变量,还尝试遍历 torch 张量,并用一个列表新建一个 torch 张量。这些涉及张量与普通变量转换的逻辑都会导致最终的 ONNX 模型不太正确。
另一方面,我们也可以利用这个性质,在保证正确性的前提下令模型的中间结果变成常量。这个技巧常常用于模型的静态化上,即令模型中所有的张量形状都变成常量。在未来的教程中,我们会在部署实例中详细介绍这些“高级”操作。
使用张量为输入(PyTorch版本 < 1.9.0)¶
正如我们第一篇教程所展示的,在较旧(< 1.9.0)的 PyTorch 中把 Python 数值作为 torch.onnx.export()
的模型输入时会报错。出于兼容性的考虑,我们还是推荐以张量为模型转换时的模型输入。
PyTorch 对 ONNX 的算子支持¶
在确保torch.onnx.export()
的调用方法无误后,PyTorch 转 ONNX 时最容易出现的问题就是算子不兼容了。这里我们会介绍如何判断某个 PyTorch 算子在 ONNX 中是否兼容,以助大家在碰到报错时能更好地把错误归类。而具体添加算子的方法我们会在之后的文章里介绍。
在转换普通的torch.nn.Module
模型时,PyTorch 一方面会用跟踪法执行前向推理,把遇到的算子整合成计算图;另一方面,PyTorch 还会把遇到的每个算子翻译成 ONNX 中定义的算子。在这个翻译过程中,可能会碰到以下情况:
该算子可以一对一地翻译成一个 ONNX 算子。
该算子在 ONNX 中没有直接对应的算子,会翻译成一至多个 ONNX 算子。
该算子没有定义翻译成 ONNX 的规则,报错。
那么,该如何查看 PyTorch 算子与 ONNX 算子的对应情况呢?由于 PyTorch 算子是向 ONNX 对齐的,这里我们先看一下 ONNX 算子的定义情况,再看一下 PyTorch 定义的算子映射关系。
ONNX 算子文档¶
ONNX 算子的定义情况,都可以在官方的算子文档中查看。这份文档十分重要,我们碰到任何和 ONNX 算子有关的问题都得来”请教“这份文档。
这份文档中最重要的开头的这个算子变更表格。表格的第一列是算子名,第二列是该算子发生变动的算子集版本号,也就是我们之前在torch.onnx.export
中提到的opset_version
表示的算子集版本号。通过查看算子第一次发生变动的版本号,我们可以知道某个算子是从哪个版本开始支持的;通过查看某算子小于等于opset_version
的第一个改动记录,我们可以知道当前算子集版本中该算子的定义规则。
通过点击表格中的链接,我们可以查看某个算子的输入、输出参数规定及使用示例。比如上图是Relu在 ONNX 中的定义规则,这份定义表明 Relu 应该有一个输入和一个输入,输入输出的类型相同,均为 tensor。
PyTorch 对 ONNX 算子的映射¶
在 PyTorch 中,和 ONNX 有关的定义全部放在 torch.onnx 目录中,如下图所示:
其中,symbloic_opset{n}.py
(符号表文件)即表示 PyTorch 在支持第 n 版 ONNX 算子集时新加入的内容。我们之前讲过, bicubic 插值是在第 11 个版本开始支持的。我们以它为例来看看如何查找算子的映射情况。
首先,使用搜索功能,在torch/onnx
文件夹搜索”bicubic”,可以发现这个这个插值在第 11 个版本的定义文件中:
之后,我们按照代码的调用逻辑,逐步跳转直到最底层的 ONNX 映射函数:
upsample_bicubic2d = _interpolate("upsample_bicubic2d", 4, "cubic")
->
def _interpolate(name, dim, interpolate_mode):
return sym_help._interpolate_helper(name, dim, interpolate_mode)
->
def _interpolate_helper(name, dim, interpolate_mode):
def symbolic_fn(g, input, output_size, *args):
...
return symbolic_fn
最后,在symbolic_fn
中,我们可以看到插值算子是怎么样被映射成多个 ONNX 算子的。其中,每一个g.op
就是一个 ONNX 的定义。比如其中的 Resize
算子就是这样写的:
return g.op("Resize",
input,
empty_roi,
empty_scales,
output_size,
coordinate_transformation_mode_s=coordinate_transformation_mode,
cubic_coeff_a_f=-0.75, # only valid when mode="cubic"
mode_s=interpolate_mode, # nearest, linear, or cubic
nearest_mode_s="floor") # only valid when mode="nearest"
通过在前面提到的 ONNX 算子文档中查找 Resize 算子的定义,我们就可以知道这每一个参数的含义了。用类似的方法,我们可以去查询其他 ONNX 算子的参数含义,进而知道 PyTorch 中的参数是怎样一步一步传入到每个 ONNX 算子中的。
掌握了如何查询 PyTorch 映射到 ONNX 的关系后,我们在实际应用时就可以在 torch.onnx.export()
的opset_version
中先预设一个版本号,碰到了问题就去对应的 PyTorch 符号表文件里去查。如果某算子确实不存在,或者算子的映射关系不满足我们的要求,我们就可能得用其他的算子绕过去,或者自定义算子了。
总结¶
在这篇教程中,我们系统地介绍了 PyTorch 转 ONNX 的原理。我们先是着重讲解了使用最频繁的 torch.onnx.export
函数,又给出了查询 PyTorch 对 ONNX 算子支持情况的方法。通过本文,我们希望大家能够成功转换出大部分不需要添加新算子的 ONNX 模型,并在碰到算子问题时能够有效定位问题原因。具体而言,大家读完本文后应该了解以下的知识:
跟踪法和脚本化在导出带控制语句的计算图时有什么区别。
torch.onnx.export()
中该如何设置input_names, output_names, dynamic_axes
。使用
torch.onnx.is_in_onnx_export()
来使模型在转换到 ONNX 时有不同的行为。如何查询 ONNX 算子文档。
如何查询 PyTorch 对某个 ONNX 版本的新特性支持情况。
如何判断 PyTorch 对某个 ONNX 算子是否支持,支持的方法是怎样的。
这期介绍的知识比较抽象,大家会不会觉得有点“水”?没关系,下一篇教程中,我们将以给出代码实例的形式,介绍多种为 PyTorch 转 ONNX 添加算子支持的方法,为大家在 PyTorch 转 ONNX 这条路上扫除更多的障碍。
第四章:在 PyTorch 中支持更多 ONNX 算子¶
在上一篇教程中,我们系统地学习了 PyTorch 转 ONNX 的方法,可以发现 PyTorch 对 ONNX 的支持还不错。但在实际的部署过程中,难免碰到模型无法用原生 PyTorch 算子表示的情况。这个时候,我们就得考虑扩充 PyTorch,即在 PyTorch 中支持更多 ONNX 算子。
而要使 PyTorch 算子顺利转换到 ONNX ,我们需要保证以下三个环节都不出错:
算子在 PyTorch 中有实现
有把该 PyTorch 算子映射成一个或多个 ONNX 算子的方法
ONNX 有相应的算子
可在实际部署中,这三部分的内容都可能有所缺失。其中最坏的情况是:我们定义了一个全新的算子,它不仅缺少 PyTorch 实现,还缺少 PyTorch 到 ONNX 的映射关系。但所谓车到山前必有路,对于这三个环节,我们也分别都有以下的添加支持的方法:
PyTorch 算子
组合现有算子
添加 TorchScript 算子
添加普通 C++ 拓展算子
映射方法
为 ATen 算子添加符号函数
为 TorchScript 算子添加符号函数
封装成 torch.autograd.Function 并添加符号函数
ONNX 算子
使用现有 ONNX 算子
定义新 ONNX 算子
那么,面对不同的情况时,就需要我们灵活地选用和组合这些方法。听起来是不是很复杂?别担心,本篇文章中,我们将围绕着三种算子映射方法,学习三个添加算子支持的实例,来理清如何为 PyTorch 算子转 ONNX 算子的三个环节添加支持。
支持 ATen 算子¶
实际的部署过程中,我们都有可能会碰到一个最简单的算子缺失问题: 算子在 ATen 中已经实现了,ONNX 中也有相关算子的定义,但是相关算子映射成 ONNX 的规则没有写。在这种情况下,我们只需要为 ATen 算子补充描述映射规则的符号函数就行了。
ATen 是 PyTorch 内置的 C++ 张量计算库,PyTorch 算子在底层绝大多数计算都是用 ATen 实现的。
上期习题中,我们曾经提到了 ONNX 的 Asinh
算子。这个算子在 ATen 中有实现,却缺少了映射到 ONNX 算子的符号函数。在这里,我们来尝试为它补充符号函数,并导出一个包含这个算子的 ONNX 模型。
获取 ATen 中算子接口定义¶
为了编写符号函数,我们需要获得 asinh
推理接口的输入参数定义。这时,我们要去 torch/_C/_VariableFunctions.pyi
和 torch/nn/functional.pyi
这两个文件中搜索我们刚刚得到的这个算子名。这两个文件是编译 PyTorch 时本地自动生成的文件,里面包含了 ATen 算子的 PyTorch 调用接口。通过搜索,我们可以知道 asinh
在文件 torch/_C/_VariableFunctions.pyi
中,其接口定义为:
def asinh(input: Tensor, *, out: Optional[Tensor]=None) -> Tensor: ...
经过这些步骤,我们确认了缺失的算子名为 asinh
,它是一个有实现的 ATen 算子。我们还记下了 asinh
的调用接口。接下来,我们要为它补充符号函数,使它在转换成 ONNX 模型时不再报错。
添加符号函数¶
到目前为止,我们已经多次接触了定义 PyTorch 到 ONNX 映射规则的符号函数了。现在,我们向大家正式介绍一下符号函数。
符号函数,可以看成是 PyTorch 算子类的一个静态方法。在把 PyTorch 模型转换成 ONNX 模型时,各个 PyTorch 算子的符号函数会被依次调用,以完成 PyTorch 算子到 ONNX 算子的转换。符号函数的定义一般如下:
def symbolic(g: torch._C.Graph, input_0: torch._C.Value, input_1: torch._C.Value, ...):
其中,torch._C.Graph
和 torch._C.Value
都对应 PyTorch 的 C++ 实现里的一些类。我们在这篇文章不深究它们的细节,只需要知道第一个参数就固定叫 g
,它表示和计算图相关的内容;后面的每个参数都表示算子的输入,需要和算子的前向推理接口的输入相同。对于 ATen 算子来说,它们的前向推理接口就是上述两个 .pyi
文件里的函数接口。
g
有一个方法 op
。在把 PyTorch 算子转换成 ONNX 算子时,需要在符号函数中调用此方法来为最终的计算图添加一个 ONNX 算子。其定义如下:
def op(name: str, input_0: torch._C.Value, input_1: torch._C.Value, ...)
其中,第一个参数是算子名称。如果该算子是普通的 ONNX 算子,只需要把它在 ONNX 官方文档里的名称填进去即可(我们稍后再讲其他情况)。
在最简单的情况下,我们只要把 PyTorch 算子的输入用g.op()
一一对应到 ONNX 算子上即可,并把g.op()
的返回值作为符号函数的返回值。在情况更复杂时,我们转换一个 PyTorch 算子可能要新建若干个 ONNX 算子。
补充完了背景知识,让我们回到 asinh
算子上,来为它编写符号函数。我们先去翻阅一下 ONNX 算子文档,学习一下我们在符号函数里的映射关系 g.op()
里应该怎么写。Asinh
的文档写道:该算子有一个输入 input
,一个输出 output
,二者的类型都为张量。
到这里,我们已经完成了信息收集环节。我们在上一小节得知了 asinh
的推理接口定义,在这一小节里收集了 ONNX 算子 Asinh
的定义。现在,我们可以用代码来补充这二者的映射关系了。在刚刚导出 asinh
算子的代码中,我们添加以下内容:
from torch.onnx.symbolic_registry import register_op
def asinh_symbolic(g, input, *, out=None):
return g.op("Asinh", input)
register_op('asinh', asinh_symbolic, '', 9)
这里的asinh_symbolic
就是asinh
的符号函数。从除g
以外的第二个输入参数开始,其输入参数应该严格对应它在 ATen 中的定义:
def asinh(input: Tensor, *, out: Optional[Tensor]=None) -> Tensor: ...
在符号函数的函数体中,g.op("Asinh", input)
则完成了 ONNX 算子的定义。其中,第一个参数"Asinh"
是算子在 ONNX 中的名称。至于第二个参数 input
,如我们刚刚在文档里所见,这个算子只有一个输入,因此我们只要把符号函数的输入参数 input
对应过去就行。ONNX 的 Asinh
的输出和 ATen 的 asinh
的输出是一致的,因此我们直接把 g.op()
的结果返回即可。
定义完符号函数后,我们要把这个符号函数和原来的 ATen 算子“绑定”起来。这里,我们要用到 register_op
这个 PyTorch API 来完成绑定。如示例所示,只需要一行简单的代码即可把符号函数 asinh_symbolic
绑定到算子 asinh
上:
register_op('asinh', asinh_symbolic, '', 9)
register_op
的第一个参数是目标 ATen 算子名,第二个是要注册的符号函数,这两个参数很好理解。第三个参数是算子的“域”,对于普通 ONNX 算子,直接填空字符串即可。第四个参数表示向哪个算子集版本注册。我们遵照 ONNX 标准,向第 9 号算子集注册。值得注意的是,这里向第 9 号算子集注册,不代表较新的算子集(第 10 号、第 11 号……)都得到了注册。在示例中,我们先只向第 9 号算子集注册。
整理一下,我们最终的代码如下:
import torch
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return torch.asinh(x)
from torch.onnx.symbolic_registry import register_op
def asinh_symbolic(g, input, *, out=None):
return g.op("Asinh", input)
register_op('asinh', asinh_symbolic, '', 9)
model = Model()
input = torch.rand(1, 3, 10, 10)
torch.onnx.export(model, input, 'asinh.onnx')
成功导出的话,asinh.onnx
应该长这个样子:
测试算子¶
在完成了一份自定义算子后,我们一定要测试一下算子的正确性。一般我们要用 PyTorch 运行一遍原算子,再用推理引擎(比如 ONNX Runtime)运行一下 ONNX 算子,最后比对两次的运行结果。对于我们刚刚得到的 asinh.onnx
,可以用如下代码来验证:
import onnxruntime
import torch
import numpy as np
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return torch.asinh(x)
model = Model()
input = torch.rand(1, 3, 10, 10)
torch_output = model(input).detach().numpy()
sess = onnxruntime.InferenceSession('asinh.onnx')
ort_output = sess.run(None, {'0': input.numpy()})[0]
assert np.allclose(torch_output, ort_output)
在这份代码里,我们用 PyTorch 做了一遍推理,并把结果转成了 numpy 格式。之后,我们又用 ONNX Runtime 对 onnx 文件做了一次推理。最后,我们使用 np.allclose
来保证两个结果张量的误差在一个可以允许的范围内。一切正常的话,运行这段代码后,assert
所在行不会报错,程序应该没有任何输出。
支持 TorchScript 算子¶
对于一些比较复杂的运算,仅使用 PyTorch 原生算子是无法实现的。这个时候,就要考虑自定义一个 PyTorch 算子,再把它转换到 ONNX 中了。新增 PyTorch 算子的方法有很多,PyTorch 官方比较推荐的一种做法是添加 TorchScript 算子 。
由于添加算子的方法较繁琐,我们今天跳过新增 TorchScript 算子的内容,以可变形卷积(Deformable Convolution)算子为例,介绍为现有 TorchScript 算子添加 ONNX 支持的方法。
可变形卷积(Deformable Convolution)是在 Torchvision 中实现的 TorchScript 算子,虽然尚未得到广泛支持,但是出现在许多模型中。
有了支持 ATen 算子的经验之后,我们可以知道为算子添加符号函数一般要经过以下几步:
获取原算子的前向推理接口。
获取目标 ONNX 算子的定义。
编写符号函数并绑定。
在为可变形卷积添加符号函数时,我们也可以尝试走一遍这个流程。
使用 TorchScript 算子¶
和之前一样,我们首先定义一个包含了算子的模型,为之后转换 ONNX 模型做准备。
import torch
import torchvision
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
self.conv1 = torch.nn.Conv2d(3, 18, 3)
self.conv2 = torchvision.ops.DeformConv2d(3, 3, 3)
def forward(self, x):
return self.conv2(x, self.conv1(x))
其中,torchvision.ops.DeformConv2d
就是 Torchvision 中的可变形卷积层。相比于普通卷积,可变形卷积的其他参数都大致相同,唯一的区别就是在推理时需要多输入一个表示偏移量的张量。
然后,我们查询算子的前向推理接口。DeformConv2d
层最终会调用 deform_conv2d
这个算子。我们可以在 torchvision/csrc/ops/deform_conv2d.cpp
中查到该算子的调用接口:
m.def(TORCH_SELECTIVE_SCHEMA(
"torchvision::deform_conv2d(Tensor input,
Tensor weight,
Tensor offset,
......
bool use_mask) -> Tensor"));
那么接下来,根据之前的经验,我们就是要去 ONNX 官方文档中查找算子的定义了。
自定义 ONNX 算子¶
很遗憾的是,如果我们去 ONNX 的官方算子页面搜索 “deform”,将搜不出任何内容。目前,ONNX 还没有提供可变形卷积的算子,我们要自己定义一个 ONNX 算子了。
我们在前面讲过,g.op()
是用来定义 ONNX 算子的函数。对于 ONNX 官方定义的算子,g.op()
的第一个参数就是该算子的名称。而对于一个自定义算子,g.op()
的第一个参数是一个带命名空间的算子名,比如:
g.op("custom::deform_conv2d, ...)
其中,”::”前面的内容就是我们的命名空间。该概念和 C++ 的命名空间类似,是为了防止命名冲突而设定的。如果在 g.op()
里不加前面的命名空间,则算子会被默认成 ONNX 的官方算子。
PyTorch 在运行 g.op()
时会对官方的算子做检查,如果算子名有误,或者算子的输入类型不正确, g.op()
就会报错。为了让我们随心所欲地定义新 ONNX 算子,我们必须设定一个命名空间,给算子取个名,再定义自己的算子。
我们在第一篇教程学过:ONNX 是一套标准,本身并不包括实现。在这里,我们就简略地定义一个 ONNX 可变形卷积算子,而不去写它在某个推理引擎上的实现。在之后的教程中,我们再学习在各个推理引擎中添加新 ONNX 算子支持的方法。此处,我们只关心如何导出一个包含新 ONNX 算子节点的 onnx 文件。因此,我们可以为新算子编写如下简单的符号函数:
@parse_args("v", "v", "v", "v", "v", "i", "i", "i", "i", "i", "i", "i", "i", "none")
def symbolic(g,
input,
weight,
offset,
mask,
bias,
stride_h, stride_w,
pad_h, pad_w,
dil_h, dil_w,
n_weight_grps,
n_offset_grps,
use_mask):
return g.op("custom::deform_conv2d", input, offset)
在这个符号函数中,我们以刚刚搜索到的算子输入参数作为符号函数的输入参数,并只用 input
和 offset
来构造一个简单的 ONNX 算子。
这段代码中,最令人疑惑的就是装饰器 @parse_args
了。简单来说,TorchScript 算子的符号函数要求标注出每一个输入参数的类型。比如”v”表示 Torch 库里的 value
类型,一般用于标注张量,而”i”表示 int 类型,”f”表示 float 类型,”none”表示该参数为空。具体的类型含义可以在 torch.onnx.symbolic_helper.py中查看。这里输入参数中的 input, weight, offset, mask, bias
都是张量,所以用”v”表示。后面的其他参数同理。我们不必纠结于 @parse_args
的原理,根据实际情况对符号函数的参数标注类型即可。
有了符号函数后,我们通过如下的方式注册符号函数:
register_custom_op_symbolic("torchvision::deform_conv2d", symbolic, 9)
和前面的 register_op
类似,注册符号函数时,我们要输入算子名、符号函数、算子集版本。与前面不同的是,这里的算子集版本是最早生效版本,在这里设定版本 9,意味着之后的第 10 号、第 11 号……版本集都能使用这个新算子。
最后,我们完整的模型导出代码如下:
import torch
import torchvision
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
self.conv1 = torch.nn.Conv2d(3, 18, 3)
self.conv2 = torchvision.ops.DeformConv2d(3, 3, 3)
def forward(self, x):
return self.conv2(x, self.conv1(x))
from torch.onnx import register_custom_op_symbolic
from torch.onnx.symbolic_helper import parse_args
@parse_args("v", "v", "v", "v", "v", "i", "i", "i", "i", "i", "i", "i", "i", "none")
def symbolic(g,
input,
weight,
offset,
mask,
bias,
stride_h, stride_w,
pad_h, pad_w,
dil_h, dil_w,
n_weight_grps,
n_offset_grps,
use_mask):
return g.op("custom::deform_conv2d", input, offset)
register_custom_op_symbolic("torchvision::deform_conv2d", symbolic, 9)
model = Model()
input = torch.rand(1, 3, 10, 10)
torch.onnx.export(model, input, 'dcn.onnx')
代码成功运行的话,我们应该能得到如下的 ONNX 模型:
可以看到,我们自定义的 ONNX 算子 deform_conv2d
包含了两个输入,一个输出,和我们预想得一样。
使用 torch.autograd.Function¶
最后,我们来学习一种简单的为 PyTorch 添加 C++ 算子实现的方法,来代替较为复杂的新增 TorchScript 算子。同时,我们会用 torch.autograd.Function 封装这个新算子。torch.autograd.Function 能完成算子实现和算子调用的隔离。不管算子是怎么实现的,它封装后的使用体验以及 ONNX 导出方法会和原生的 PyTorch 算子一样。这是我们比较推荐的为算子添加 ONNX 支持的方法。
为了应对更复杂的情况,我们来自定义一个奇怪的 my_add
算子。这个算子的输入张量 a, b ,输出 2a + b
的值。我们会先把它在 PyTorch 中实现,再把它导出到 ONNX 中。
为 PyTorch 添加 C++ 拓展¶
为 PyTorch 添加简单的 C++ 拓展还是很方便的。对于我们定义的 my_add 算子,可以用以下的 C++ 源文件来实现。我们把该文件命名为 “my_add.cpp”:
// my_add.cpp
#include <torch/torch.h>
torch::Tensor my_add(torch::Tensor a, torch::Tensor b)
{
return 2 * a + b;
}
PYBIND11_MODULE(my_lib, m)
{
m.def("my_add", my_add);
}
由于在 PyTorch 中添加 C++ 拓展和模型部署关系不大,这里我们仅给出这个简单的示例,并不对其原理做过多讲解。
在这段代码中,torch::Tensor 就是 C++ 中 torch 的张量类型,它的加法和乘法等运算符均已重载。因此,我们可以像对普通标量一样对张量做加法和乘法。
轻松地完成了算子的实现后,我们用 PYBIND11_MODULE
来为 C++ 函数提供 Python 调用接口。这里的 my_lib
是我们未来要在 Python 里导入的模块名。双引号中的 my_add
是 Python 调用接口的名称,这里我们对齐 C++ 函数的名称,依然用 “my_add”这个名字。
之后,我们可以编写如下的 Python 代码并命名为 “setup.py”,来编译刚刚的 C++ 文件:
from setuptools import setup
from torch.utils import cpp_extension
setup(name='my_add',
ext_modules=[cpp_extension.CppExtension('my_lib', ['my_add.cpp'])],
cmdclass={'build_ext': cpp_extension.BuildExtension})
这段代码使用了 Python 的 setuptools 编译功能和 PyTorch 的 C++ 拓展工具函数,可以编译包含了 torch 库的 C++ 源文件。这里我们需要填写的只有模块名和模块中的源文件名。我们刚刚把模块命名为 my_lib
,而源文件只有一个 my_add.cpp
,因此拓展模块那一行要写成 ext_modules=[cpp_extension.CppExtension('my_lib', ['my_add.cpp'])],
。
之后,像处理普通的 Python 包一样执行安装命令,我们的 C++ 代码就会自动编译了。
python setup.py develop
用 torch.autograd.Function
封装¶
直接用 Python 接口调用 C++ 函数不太“美观”,一种比较优雅的做法是把这个调用接口封装起来。这里我们用 torch.autograd.Function
来封装算子的底层调用:
import torch
import my_lib
class MyAddFunction(torch.autograd.Function):
@staticmethod
def forward(ctx, a, b):
return my_lib.my_add(a, b)
@staticmethod
def symbolic(g, a, b):
two = g.op("Constant", value_t=torch.tensor([2]))
a = g.op('Mul', a, two)
return g.op('Add', a, b)
我们在前面的教程中已经见过 torch.autograd.Function
,这里我们正式地对其做一个介绍。Function
类本身表示 PyTorch 的一个可导函数,只要为其定义了前向推理和反向传播的实现,我们就可以把它当成一个普通 PyTorch 函数来使用。
PyTorch 会自动调度该函数,合适地执行前向和反向计算。对模型部署来说,Function
类有一个很好的性质:如果它定义了 symbolic
静态方法,该 Function
在执行 torch.onnx.export()
时就可以根据 symbolic
中定义的规则转换成 ONNX 算子。这个 symbolic
就是前面提到的符号函数,只是它的名称必须是 symbolic
而已。
在 forward
函数中,我们用 my_lib.my_add(a, b)
就可以调用之前写的C++函数了。这里 my_lib
是库名,my_add
是函数名,这两个名字是在前面C++的 PYBIND11_MODULE
中定义的。
在 symbolic
函数中,我们用 g.op()
定义了三个算子:常量、乘法、加法。这里乘法和加法的用法和前面提到的 asinh
一样,只需要根据 ONNX 算子定义规则把输入参数填入即可。而在定义常量算子时,我们要把 PyTorch 张量的值传入 value_t
参数中。
在 ONNX 中,我们需要把新建常量当成一个算子来看待,尽管这个算子并不会以节点的形式出现在 ONNX 模型的可视化结果里。
把算子封装成 Function 后,我们可以把 my_add
算子用起来了。
my_add = MyAddFunction.apply
class MyAdd(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, a, b):
return my_add(a, b)
在这份代码里,我们先用 my_add = MyAddFunction.apply
获取了一个奇怪的变量。这个变量是用来做什么的呢?其实,apply
是torch.autograd.Function
的一个方法,这个方法完成了 Function
在前向推理或者反向传播时的调度。我们在使用 Function
的派生类做推理时,不应该显式地调用 forward
,而应该调用其 apply
方法。
这里我们使用 my_add = MyAddFunction.apply
把这个调用方法取了一个更简短的别名 my_add
。以后在使用 my_add
算子时,我们应该忽略 MyAddFunction
的实现细节,而只通过 my_add
这个接口来访问算子。这里 my_add
的地位,和 PyTorch 的 asinh, interpolate, conv2d
等原生函数是类似的。
有了访问新算子的接口后,我们可以进一步把算子封装成一个神经网络中的计算层。我们定义一个叫做的 MyAdd
的 torch.nn.Module
,它封装了my_add
,就和封装了conv2d
的 torch.nn.Conv2d
一样。
测试算子¶
费了好大的功夫来“包装”我们的新算子后,我们终于可以来使用它了。和之前的测试流程一样,让我们用下面的代码来导出一个包含新算子的 ONNX 模型,并验证一下它是否正确。
model = MyAdd()
input = torch.rand(1, 3, 10, 10)
torch.onnx.export(model, (input, input), 'my_add.onnx')
torch_output = model(input, input).detach().numpy()
import onnxruntime
import numpy as np
sess = onnxruntime.InferenceSession('my_add.onnx')
ort_output = sess.run(None, {'a': input.numpy(), 'b': input.numpy()})[0]
assert np.allclose(torch_output, ort_output)
在这份代码中,我们直接把 MyAdd
作为要导出的模型。我们计算了一个 PyTorch 模型的运行结果,又导出 ONNX 模型,计算了 ONNX 模型在 ONNX Runtime 上的运算结果。如果一切正常的话,这两个结果是一样的,这份代码不会报任何错误,没有任何输出。
可视化一下 my_add.onnx
,可以看出,和我们设计得一样,my_add
算子被翻译成了两个 ONNX 算子节点(其中常量算子被放入了 Mul
的参数中)。
整理一下,整个流程的 Python 代码如下:
import torch
import my_lib
class MyAddFunction(torch.autograd.Function):
@staticmethod
def forward(ctx, a, b):
return my_lib.my_add(a, b)
@staticmethod
def symbolic(g, a, b):
two = g.op("Constant", value_t=torch.tensor([2]))
a = g.op('Mul', a, two)
return g.op('Add', a, b)
my_add = MyAddFunction.apply
class MyAdd(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, a, b):
return my_add(a, b)
model = MyAdd()
input = torch.rand(1, 3, 10, 10)
torch.onnx.export(model, (input, input), 'my_add.onnx')
torch_output = model(input, input).detach().numpy()
import onnxruntime
import numpy as np
sess = onnxruntime.InferenceSession('my_add.onnx')
ort_output = sess.run(None, {'a': input.numpy(), 'b': input.numpy()})[0]
assert np.allclose(torch_output, ort_output)
总结¶
在这篇教程中,我们围绕“为 ATen 算子添加符号函数”、“为 TorchScript 算子添加符号函数”、“封装成 torch.autograd.Function
并添加符号函数”这三种添加映射关系的方法,讲解了 3 个为 PyTorch 和 ONNX 添加支持的实例。在这个过程中,我们学到了很多零散的知识,来总结一下吧。
ATen 是 PyTorch 的 C++ 张量运算库。通过查询 torch/_C/_VariableFunctions.pyi 和 torch/nn/functional.pyi,我们可以知道 ATen 算子的 Python 接口定义。
用 register_op 可以为 ATen 算子补充注册符号函数
用 register_custom_op_symbolic 可以为 TorchScript 算子补充注册符号函数
如何在 PyTorch 里添加 C++ 拓展
如何用 torch.autograd.Function 封装一个自定义 PyTorch 算子
如何编写符号函数 symbolic(g, …)。
如何用 g.op() 把一个 PyTorch 算子映射成一个或多个 ONNX 算子,或者是自定义的 ONNX 算子。
这篇教程涉及的代码比较多。如果大家在阅读时碰到了问题,最好去跑一跑代码,改一改代码里的内容,实际感受一下每行代码的意义。
上期习题解答¶
PyTorch 目前没有支持 ONNX 的
Asinh
算子。我们在torch.onnx.symbolic_opset9.py
中搜索不到 Asinh 的相关内容。通过在
torch.onnx.symbolic_opset11.py
搜索BitShift
,我们可以发现 PyTorch 在__lshift_
和__rshift_
里用到了ONNX的BitShift
算子。当输入类型为Byte
时,PyTorch会把算子直接翻译翻译BitShift
,以代替乘除 2 的次幂的操作。对应
Resize
算子的第3个参数(g.op()
的第4个参数)scales
。原来的scales
传入g.op()
前会经过_interpolate_get_scales_if_available()
函数,一定会被转换成一个常量。为了让scales
由输入决定,我们直接把输入参数中的scales
传入g.op()
。
第五章:ONNX 模型的修改与调试¶
在前两期教程中,我们学习了 PyTorch 模型转 ONNX 模型的方法,了解了如何在原生算子表达能力不足时,为 PyTorch 或 ONNX 自定义算子。一直以来,我们都是通过 PyTorch 来导出 ONNX 模型的,基本没有单独探究过 ONNX 模型的构造知识。
不知道大家会不会有这样一些疑问:ONNX 模型在底层是用什么格式存储的?如何不依赖深度学习框架,只用 ONNX 的 API 来构造一个 ONNX 模型?如果没有源代码,只有一个 ONNX 模型,该如何对这个模型进行调试?这篇教程可以解答大家的这些问题。
在这期教程里,我们将围绕 ONNX 这一套神经网络定义标准本身,探究 ONNX 模型的构造、读取、子模型提取、调试。首先,我们会学习 ONNX 的底层表示方式。之后,我们会用 ONNX API 构造和读取模型。最后,我们会利用 ONNX 提供的子模型提取功能,学习如何调试 ONNX 模型。
ONNX 的底层实现¶
ONNX 的存储格式¶
ONNX 在底层是用 Protobuf 定义的。Protobuf,全称 Protocol Buffer,是 Google 提出的一套表示和序列化数据的机制。使用 Protobuf 时,用户需要先写一份数据定义文件,再根据这份定义文件把数据存储进一份二进制文件。可以说,数据定义文件就是数据类,二进制文件就是数据类的实例。 这里给出一个 Protobuf 数据定义文件的例子:
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
}
这段定义表示在 Person
这种数据类型中,必须包含 name
、id
这两个字段,选择性包含 email
字段。根据这份定义文件,用户就可以选择一种编程语言,定义一个含有成员变量 name
、id
、email
的 Person
类,把这个类的某个实例用 Protobuf 存储成二进制文件;反之,用户也可以用二进制文件和对应的数据定义文件,读取出一个 Person
类的实例。
而对于 ONNX ,它的 Protobuf 数据定义文件在其开源库中,这些文件定义了神经网络中模型、节点、张量的数据类型规范;而数据定义文件对应的二进制文件就是我们熟悉的“.onnx”文件,每一个 “.onnx” 文件按照数据定义规范,存储了一个神经网络的所有相关数据。直接用 Protobuf 生成 ONNX 模型还是比较麻烦的。幸运的是,ONNX 提供了很多实用 API,我们可以在完全不了解 Protobuf 的前提下,构造和读取 ONNX 模型。
ONNX 的结构定义¶
在用 API 对 ONNX 模型进行操作之前,我们还需要先了解一下 ONNX 的结构定义规则,学习一下 ONNX 在 Protobuf 定义文件里是怎样描述一个神经网络的。
回想一下,神经网络本质上是一个计算图。计算图的节点是算子,边是参与运算的张量。而通过可视化 ONNX 模型,我们知道 ONNX 记录了所有算子节点的属性信息,并把参与运算的张量信息存储在算子节点的输入输出信息中。事实上,ONNX 模型的结构可以用类图大致表示如下:
如图所示,一个 ONNX 模型可以用 ModelProto
类表示。ModelProto
包含了版本、创建者等日志信息,还包含了存储计算图结构的 graph
。GraphProto
类则由输入张量信息、输出张量信息、节点信息组成。张量信息 ValueInfoProto
类包括张量名、基本数据类型、形状。节点信息 NodeProto
类包含了算子名、算子输入张量名、算子输出张量名。
让我们来看一个具体的例子。假如我们有一个描述 output=a*x+b
的 ONNX 模型 model
,用 print(model)
可以输出以下内容:
ir_version: 8
graph {
node {
input: "a"
input: "x"
output: "c"
op_type: "Mul"
}
node {
input: "c"
input: "b"
output: "output"
op_type: "Add"
}
name: "linear_func"
input {
name: "a"
type {
tensor_type {
elem_type: 1
shape {
dim {dim_value: 10}
dim {dim_value: 10}
}
}
}
}
input {
name: "x"
type {
tensor_type {
elem_type: 1
shape {
dim {dim_value: 10}
dim {dim_value: 10}
}
}
}
}
input {
name: "b"
type {
tensor_type {
elem_type: 1
shape {
dim {dim_value: 10}
dim {dim_value: 10}
}
}
}
}
output {
name: "output"
type {
tensor_type {
elem_type: 1
shape {
dim { dim_value: 10}
dim { dim_value: 10}
}
}
}
}
}
opset_import {version: 15}
对应上文中的类图,这个模型的信息由 ir_version
,opset_import
等全局信息和 graph
图信息组成。而 graph
包含一个乘法节点、一个加法节点、三个输入张量 a, x, b
以及一个输出张量 output
。在下一节里,我们会用 API 构造出这个模型,并输出这段结果。
读写 ONNX 模型¶
构造 ONNX 模型¶
在上一小节中,我们知道了 ONNX 模型是按以下的结构组织起来的:
ModelProto
GraphProto
NodeProto
ValueInfoProto
现在,让我们抛开 PyTorch,尝试完全用 ONNX 的 Python API 构造一个描述线性函数 output=a*x+b
的 ONNX 模型。我们将根据上面的结构,自底向上地构造这个模型。
首先,我们可以用 helper.make_tensor_value_info
构造出一个描述张量信息的 ValueInfoProto
对象。如前面的类图所示,我们要传入张量名、张量的基本数据类型、张量形状这三个信息。在 ONNX 中,不管是输入张量还是输出张量,它们的表示方式都是一样的。因此,这里我们用类似的方式为三个输入 a, x, b
和一个输出 output
构造 ValueInfoProto
对象。如下面的代码所示:
import onnx
from onnx import helper
from onnx import TensorProto
a = helper.make_tensor_value_info('a', TensorProto.FLOAT, [10, 10])
x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 10])
b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [10, 10])
output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [10, 10])
之后,我们要构造算子节点信息 NodeProto
,这可以通过在 helper.make_node
中传入算子类型、输入张量名、输出张量名这三个信息来实现。我们这里先构造了描述 c=a*x
的乘法节点,再构造了 output=c+b
的加法节点。如下面的代码所示:
mul = helper.make_node('Mul', ['a', 'x'], ['c'])
add = helper.make_node('Add', ['c', 'b'], ['output'])
在计算机中,图一般是用一个节点集和一个边集表示的。而 ONNX 巧妙地把边的信息保存在了节点信息里,省去了保存边集的步骤。在 ONNX 中,如果某节点的输入名和之前某节点的输出名相同,就默认这两个节点是相连的。如上面的例子所示:Mul
节点定义了输出 c
,Add
节点定义了输入 c
,则 Mul
节点和 Add
节点是相连的。
正是因为有这种边的隐式定义规则,所以 ONNX 对节点的输入有一定的要求:一个节点的输入,要么是整个模型的输入,要么是之前某个节点的输出。如果我们把 a, x, b
中的某个输入节点从计算图中拿出(这个操作会在之后的代码中介绍),或者把 Mul
的输出从 c
改成 d
,则最终的 ONNX 模型都是不满足标准的。
一个不满足标准的 ONNX 模型可能无法被推理引擎正确识别。ONNX 提供了 API
onnx.checker.check_model
来判断一个 ONNX 模型是否满足标准。
接下来,我们用 helper.make_graph
来构造计算图 GraphProto
。helper.make_graph
函数需要传入节点、图名称、输入张量信息、输出张量信息这 4 个参数。如下面的代码所示,我们把之前构造出来的 NodeProto
对象和 ValueInfoProto
对象按照顺序传入即可。
graph = helper.make_graph([mul, add], 'linear_func', [a, x, b], [output])
这里 make_graph
的节点参数有一个要求:计算图的节点必须以拓扑序给出。
拓扑序是与有向图的相关的数学概念。如果按拓扑序遍历所有节点的话,能保证每个节点的输入都能在之前节点的输出里找到(对于 ONNX 模型,我们把计算图的输入张量也看成“之前的输出”)。
如果对这个概念不熟也没有关系,我们以刚刚构造出来的这个计算图为研究对象,通过下图展示的两个例子来直观理解拓扑序。
这里我们只关注 Mul
和 Add
节点以及它们之间的边 c
。在情况 1 中:如果我们的节点以 [Mul, Add]
顺序给出,那么遍历到 Add
时,它的输入 c
可以在之前的 Mul
的输出中找到。但是,如情况 2 所示:如果我们的节点以 [Add, Mul]
的顺序给出,那么 Add
就找不到输入边,计算图也无法成功构造出来了。这里的 [Mul, Add]
就是符合有向图的拓扑序的,而 [Add, Mul]
则不满足。
最后,我们用 helper.make_model
把计算图 GraphProto
封装进模型 ModelProto
里,一个 ONNX 模型就构造完成了。make_model
函数中还可以添加模型制作者、版本等信息,为了简单起见,我们没有添加额外的信息。如下面的代码所示:
model = helper.make_model(graph)
构造完模型之后,我们用下面这三行代码来检查模型正确性、把模型以文本形式输出、存储到一个 “.onnx” 文件里。这里用 onnx.checker.check_model
来检查模型是否满足 ONNX 标准是必要的,因为无论模型是否满足标准,ONNX 都允许我们用 onnx.save 存储模型。我们肯定不希望生成一个不满足标准的模型。
onnx.checker.check_model(model)
print(model)
onnx.save(model, 'linear_func.onnx')
成功执行这些代码的话,程序会以文本格式输出模型的信息,其内容应该和我们在上一节展示的输出一样。
整理一下,用 ONNX Python API 构造模型的代码如下:
import onnx
from onnx import helper
from onnx import TensorProto
# input and output
a = helper.make_tensor_value_info('a', TensorProto.FLOAT, [10, 10])
x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 10])
b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [10, 10])
output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [10, 10])
# Mul
mul = helper.make_node('Mul', ['a', 'x'], ['c'])
# Add
add = helper.make_node('Add', ['c', 'b'], ['output'])
# graph and model
graph = helper.make_graph([mul, add], 'linear_func', [a, x, b], [output])
model = helper.make_model(graph)
# save model
onnx.checker.check_model(model)
print(model)
onnx.save(model, 'linear_func.onnx')
老规矩,我们可以用 ONNX Runtime 运行模型,来看看模型是否正确:
import onnxruntime
import numpy as np
sess = onnxruntime.InferenceSession('linear_func.onnx')
a = np.random.rand(10, 10).astype(np.float32)
b = np.random.rand(10, 10).astype(np.float32)
x = np.random.rand(10, 10).astype(np.float32)
output = sess.run(['output'], {'a': a, 'b': b, 'x': x})[0]
assert np.allclose(output, a * x + b)
一切顺利的话,这段代码不会有任何报错信息。这说明我们的模型等价于执行 a * x + b 这个计算。
读取并修改 ONNX 模型¶
通过用 API 构造 ONNX 模型,我们已经彻底搞懂了 ONNX 由哪些模块组成。现在,让我们看看该如何读取现有的”.onnx”文件并从中提取模型信息。
首先,我们可以用下面的代码读取一个 ONNX 模型:
import onnx
model = onnx.load('linear_func.onnx')
print(model)
之前在输出模型时,我们传给 onnx.save
的是一个 ModelProto
的对象。同理,用上面的 onnx.load
读取 ONNX 模型时,我们收获的也是一个 ModelProto
的对象。输出这个对象后,我们应该得到和之前完全相同的输出。
接下来,我们来看看怎么把图 GraphProto
、节点 NodeProto
、张量信息 ValueInfoProto
读取出来:
graph = model.graph
node = graph.node
input = graph.input
output = graph.output
print(node)
print(input)
print(output)
使用如上这些代码,我们可以分别访问模型的图、节点、张量信息。这里大家或许会有疑问:该怎样找出 graph.node,graph.input
中 node, input
这些属性名称呢?其实,属性的名称就写在每个对象的输出里。我们以 print(node)
的输出为例:
[input: "a"
input: "x"
output: "c"
op_type: "Mul"
, input: "c"
input: "b"
output: "output"
op_type: "Add"
]
在这段输出中,我们能看出 node
其实就是一个列表,列表中的对象有属性 input, output, op_type
(这里 input
也是一个列表,它包含的两个元素都显示出来了)。我们可以用下面的代码来获取 node
里第一个节点 Mul
的属性:
node_0 = node[0]
node_0_inputs = node_0.input
node_0_outputs = node_0.output
input_0 = node_0_inputs[0]
input_1 = node_0_inputs[1]
output = node_0_outputs[0]
op_type = node_0.op_type
print(input_0)
print(input_1)
print(output)
print(op_type)
# Output
"""
a
x
c
Mul
"""
当我们想知道 ONNX 模型某数据对象有哪些属性时,我们不必去翻 ONNX 文档,只需要先把数据对象输出一下,然后在输出结果找出属性名即可。
读取完 ONNX 模型的信息后,修改 ONNX 模型就是一件很轻松的事了。我们既可以按照上一小节的模型构造方法,新建节点和张量信息,与原有模型组合成一个新的模型,也可以在不违反 ONNX 规范的前提下直接修改某个数据对象的属性。
这里我们来看一个直接修改模型属性的例子:
import onnx
model = onnx.load('linear_func.onnx')
node = model.graph.node
node[1].op_type = 'Sub'
onnx.checker.check_model(model)
onnx.save(model, 'linear_func_2.onnx')
在读入之前的 linear_func.onnx
模型后,我们可以直接修改第二个节点的类型 node[1].op_type
,把加法变成减法。这样,我们的模型描述的是 a * x - b
这个线性函数。大家感兴趣的话,可以用 ONNX Runtime 运行新模型 linear_func_2.onnx
,来验证一下它和 a * x - b
是否等价。
调试 ONNX 模型¶
在实际部署中,如果用深度学习框架导出的 ONNX 模型出了问题,一般要通过修改框架的代码来解决,而不会从 ONNX 入手,我们把 ONNX 模型当成一个不可修改的黑盒看待。 现在,我们已经深入学习了 ONNX 的原理,可以尝试对 ONNX 模型本身进行调试了。在这一节里,让我们看看该如何巧妙利用 ONNX 提供的子模型提取功能,对 ONNX 模型进行调试。
子模型提取¶
ONNX 官方为开发者提供了子模型提取(extract)的功能。子模型提取,顾名思义,就是从一个给定的 ONNX 模型中,拿出一个子模型。这个子模型的节点集、边集都是原模型中对应集合的子集。让我们来用 PyTorch 导出一个复杂一点的 ONNX 模型,并在它的基础上执行提取操作:
import torch
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
self.convs1 = torch.nn.Sequential(torch.nn.Conv2d(3, 3, 3),
torch.nn.Conv2d(3, 3, 3),
torch.nn.Conv2d(3, 3, 3))
self.convs2 = torch.nn.Sequential(torch.nn.Conv2d(3, 3, 3),
torch.nn.Conv2d(3, 3, 3))
self.convs3 = torch.nn.Sequential(torch.nn.Conv2d(3, 3, 3),
torch.nn.Conv2d(3, 3, 3))
self.convs4 = torch.nn.Sequential(torch.nn.Conv2d(3, 3, 3),
torch.nn.Conv2d(3, 3, 3),
torch.nn.Conv2d(3, 3, 3))
def forward(self, x):
x = self.convs1(x)
x1 = self.convs2(x)
x2 = self.convs3(x)
x = x1 + x2
x = self.convs4(x)
return x
model = Model()
input = torch.randn(1, 3, 20, 20)
torch.onnx.export(model, input, 'whole_model.onnx')
这个模型的可视化结果如下图所示(提取子模型需要输入边的序号,为了大家方面阅读,这幅图标出了之后要用到的边的序号):
在前面的章节中,我们学过,ONNX 的边用同名张量表示的。也就是说,这里的边序号,实际上是前一个节点的输出张量序号和后一个节点的输入张量序号。由于这个模型是用 PyTorch 导出的,这些张量序号都是 PyTorch 自动生成的。
接着,我们可以下面的代码提取出一个子模型:
import onnx
onnx.utils.extract_model('whole_model.onnx', 'partial_model.onnx', ['22'], ['28'])
子模型的可视化结果如下图所示:
通过观察代码和输出图,应该不难猜出这段代码的作用是把原计算图从边 22 到边 28 的子图提取出来,并组成一个子模型。onnx.utils.extract_model
就是完成子模型提取的函数,它的参数分别是原模型路径、输出模型路径、子模型的输入边(输入张量)、子模型的输出边(输出张量)。
直观地来看,子模型提取就是把输入边到输出边之间的全部节点都取出来。那么,这个功能在使用上有什么限制呢?基于 whole_model.onnx
, 我们来看一看三个子模型提取的示例。
添加额外输出¶
我们在提取时新设定了一个输出张量,如下面的代码所示:
onnx.utils.extract_model('whole_model.onnx', 'submodel_1.onnx', ['22'], ['27', '31'])
我们可以看到子模型会添加一条把张量输出的新边,如下图所示:
添加冗余输入¶
如果我们还是像开始一样提取边 22 到边 28 之间的子模型,但是多添加了一个输入 input.1,那么提取出的子模型会有一个冗余的输入 input.1,如下面的代码所示:
onnx.utils.extract_model('whole_model.onnx', 'submodel_2.onnx', ['22', 'input.1'], ['28'])
从下图中可以看出:无论给这个输入传入什么值,都不会影响子模型的输出。可以认为如果只用子模型的部分输入就能得到输出,那么那些”较早“的多出来的输入就是冗余的。
输入信息不足¶
这次,我们尝试提取的子模型输入是边 24,输出是边 28。如下面的代码和图所示:
# Error
onnx.utils.extract_model('whole_model.onnx', 'submodel_3.onnx', ['24'], ['28'])
从图中可以看出,想通过边 24 计算边 28 的结果,至少还需要输入边 26,或者更上面的边。仅凭借边 24 是无法计算出边 28 的结果的,因此这样提取子模型会报错。
通过上面几个使用示例,我们可以整理出子模型提取的实现原理:新建一个模型,把给定的输入和输出填入。之后把图的所有有向边反向,从输出边开始遍历节点,碰到输入边则停止,把这样遍历得到的节点做为子模型的节点。
如果还没有彻底弄懂这个提取原理,没关系,我们只要尽量保证在填写子模型的输入输出时,让输出恰好可以由输入决定即可。
输出 ONNX 中间节点的值¶
在使用 ONNX 模型时,最常见的一个需求是能够用推理引擎输出中间节点的值。这多见于深度学习框架模型和 ONNX 模型的精度对齐中,因为只要能够输出中间节点的值,就能定位到精度出现偏差的算子。我们来看看如何用子模型提取实现这一任务。
在刚刚的第一个子模型提取示例中,我们添加了一条原来模型中不存在的输出边。用同样的原理,我们可以在保持原有输入输出不变的同时,新增加一些输出,提取出一个能输出中间节点的”子模型“。例如:
onnx.utils.extract_model('whole_model.onnx', 'more_output_model.onnx', ['input.1'], ['31', '23', '25', '27'])
在这个子模型中,我们在保持原有的输入 input.1
,输出 31
的同时,把其他几个边加入了输出中。如下图所示:
这样,用 ONNX Runtime 运行 more_output_model.onnx
这个模型时,我们就能得到更多的输出了。
为了方便调试,我们还可以把原模型拆分成多个互不相交的子模型。这样,在每次调试时,可以只对原模型的部分子模块调试。比如:
onnx.utils.extract_model('whole_model.onnx', 'debug_model_1.onnx', ['input.1'], ['23'])
onnx.utils.extract_model('whole_model.onnx', 'debug_model_2.onnx', ['23'], ['25'])
onnx.utils.extract_model('whole_model.onnx', 'debug_model_3.onnx', ['23'], ['27'])
onnx.utils.extract_model('whole_model.onnx', 'debug_model_4.onnx', ['25', '27'], ['31'])
在这个例子中,我们把原来较为复杂的模型拆成了四个较为简单的子模型,如下图所示。在调试时,我们可以先调试顶层的子模型,确认顶层子模型无误后,把它的输出做为后面子模型的输入。
比如对于这些子模型,我们可以先调试第一个子模型,并存储输出 23。之后把张量 23 做为第二个和第三个子模型的输入,调试这两个模型。最后用同样方法调试第四个子模型。可以说,有了子模型提取功能,哪怕是面对一个庞大的模型,我们也能够从中提取出有问题的子模块,细致地只对这个子模块调试。
子模型提取固然是一个便利的 ONNX 调试工具。但是,在实际的情况中,我们一般是用 PyTorch 等框架导出 ONNX 模型。这里有两个问题:
一旦 PyTorch 模型改变,ONNX 模型的边序号也会改变。这样每次提取同样的子模块时都要重新去 ONNX 模型里查序号,如此繁琐的调试方法是不会在实践中采用的。
即使我们能保证 ONNX 的边序号不发生改变,我们也难以把 PyTorch 代码和 ONNX 节点对应起来——当模型结构变得十分复杂时,要识别 ONNX 中每个节点的含义是不可能的。
MMDeploy 为 PyTorch 模型添加了模型分块功能。使用这个功能,我们可以通过只修改 PyTorch 模型的实现代码来把原模型导出成多个互不相交的子 ONNX 模型。我们会在后续教程中对其介绍。
总结¶
在这篇教程中,我们抛开了 PyTorch,学习了 ONNX 模型本身的知识。老规矩,我们来总结一下这篇教程的知识点:
ONNX 使用 Protobuf 定义规范和序列化模型。
一个 ONNX 模型主要由
ModelProto
,GraphProto
,NodeProto
,ValueInfoProto
这几个数据类的对象组成。使用
onnx.helper.make_xxx
,我们可以构造 ONNX 模型的数据对象。onnx.save()
可以保存模型,onnx.load()
可以读取模型,onnx.checker.check_model()
可以检查模型是否符合规范。onnx.utils.extract_model()
可以从原模型中取出部分节点,和新定义的输入、输出边构成一个新的子模型。利用子模型提取功能,我们可以输出原 ONNX 模型的中间结果,实现对 ONNX 模型的调试。
至此,我们对 ONNX 相关知识的学习就告一段落了。回顾一下,我们先学习了 PyTorch 转 ONNX 有关 API 的用法;接着,我们学习了如何用自定义算子解决 PyTorch 和 ONNX 表达能力不足的问题;最后我们单独学习了 ONNX 模型的调试方法。通过对 ONNX 由浅入深的学习,我们基本可以应对模型部署中和 ONNX 有关的绝大多数问题了。
如果大家想了解更多有关 ONNX API 的知识,可以去阅读 ONNX 的官方 Python API 文档。
第六章: TensorRT 模型构建与推理¶
模型部署入门教程继续更新啦!相信经过前几期的学习,大家已经对 ONNX 这一中间表示有了一个比较全面的认识,但是在具体的生产环境中,ONNX 模型常常需要被转换成能被具体推理后端使用的模型格式。本篇教程我们就和大家一起来认识大名鼎鼎的推理后端 TensorRT。
TensorRT 简介¶
TensorRT 是由 NVIDIA 发布的深度学习框架,用于在其硬件上运行深度学习推理。TensorRT 提供量化感知训练和离线量化功能,用户可以选择 INT8 和 FP16 两种优化模式,将深度学习模型应用到不同任务的生产部署,如视频流、语音识别、推荐、欺诈检测、文本生成和自然语言处理。TensorRT 经过高度优化,可在 NVIDIA GPU 上运行, 并且可能是目前在 NVIDIA GPU 运行模型最快的推理引擎。关于 TensorRT 更具体的信息可以访问 TensorRT官网 了解。
安装 TensorRT¶
Windows¶
默认在一台有 NVIDIA 显卡的机器上,提前安装好 CUDA 和 CUDNN,登录 NVIDIA 官方网站下载和主机 CUDA 版本适配的 TensorRT 压缩包即可。
以 CUDA 版本是 10.2 为例,选择适配 CUDA 10.2 的 zip 包,下载完成后,有 conda 虚拟环境的用户可以优先切换到虚拟环境中,然后在 powershell 中执行类似如下的命令安装并测试:
cd \the\path\of\tensorrt\zip\file
Expand-Archive TensorRT-8.2.5.1.Windows10.x86_64.cuda-10.2.cudnn8.2.zip .
$env:TENSORRT_DIR = "$pwd\TensorRT-8.2.5.1"
$env:path = "$env:TENSORRT_DIR\lib;" + $env:path
pip install $env:TENSORRT_DIR\python\tensorrt-8.2.5.1-cp36-none-win_amd64.whl
python -c "import tensorrt;print(tensorrt.__version__)"
上述命令会在安装后检查 TensorRT 版本,如果打印结果是 8.2.5.1,说明安装 Python 包成功了。
Linux¶
和在 Windows 环境下安装类似,默认在一台有 NVIDIA 显卡的机器上,提前安装好 CUDA 和 CUDNN,登录 NVIDIA 官方网站下载和主机 CUDA 版本适配的 TensorRT 压缩包即可。
以 CUDA 版本是 10.2 为例,选择适配 CUDA 10.2 的 tar 包,然后执行类似如下的命令安装并测试:
cd /the/path/of/tensorrt/tar/gz/file
tar -zxvf TensorRT-8.2.5.1.linux.x86_64-gnu.cuda-10.2.cudnn8.2.tar.gz
export TENSORRT_DIR=$(pwd)/TensorRT-8.2.5.1
export LD_LIBRARY_PATH=$TENSORRT_DIR/lib:$LD_LIBRARY_PATH
pip install TensorRT-8.2.5.1/python/tensorrt-8.2.5.1-cp37-none-linux_x86_64.whl
python -c "import tensorrt;print(tensorrt.__version__)"
如果发现打印结果是 8.2.5.1,说明安装 Python 包成功了。
Jetson¶
对于 Jetson 平台,我们有非常详细的安装环境配置教程,可参考 MMDeploy 安装文档。需要注意的是,在 Jetson 上配置的 CUDA 版本 TensorRT 版本与 JetPack 强相关的,我们选择适配硬件的版本即可。配置好环境后,通过 python -c "import tensorrt;print(tensorrt.__version__)"
查看TensorRT版本是否正确。
模型构建¶
我们使用 TensorRT 生成模型主要有两种方式:
直接通过 TensorRT 的 API 逐层搭建网络;
将中间表示的模型转换成 TensorRT 的模型,比如将 ONNX 模型转换成 TensorRT 模型。
接下来,我们将用 Python 和 C++ 语言分别使用这两种方式构建 TensorRT 模型,并将生成的模型进行推理。
直接构建¶
利用 TensorRT 的 API 逐层搭建网络,这一过程类似使用一般的训练框架,如使用 Pytorch 或者TensorFlow 搭建网络。需要注意的是对于权重部分,如卷积或者归一化层,需要将权重内容赋值到 TensorRT 的网络中。本文就不详细展示,只搭建一个对输入做池化的简单网络。
使用 Python API 构建¶
首先是使用 Python API 直接搭建 TensorRT 网络,这种方法主要是利用 tensorrt.Builder
的 create_builder_config
和 create_network
功能,分别构建 config 和 network,前者用于设置网络的最大工作空间等参数,后者就是网络主体,需要对其逐层添加内容。
此外,需要定义好输入和输出名称,将构建好的网络序列化,保存成本地文件。值得注意的是:如果想要网络接受不同分辨率的输入输出,需要使用 tensorrt.Builder
的 create_optimization_profile
函数,并设置最小、最大的尺寸。
实现代码如下:
import tensorrt as trt
verbose = True
IN_NAME = 'input'
OUT_NAME = 'output'
IN_H = 224
IN_W = 224
BATCH_SIZE = 1
EXPLICIT_BATCH = 1 << (int)(
trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
TRT_LOGGER = trt.Logger(trt.Logger.VERBOSE) if verbose else trt.Logger()
with trt.Builder(TRT_LOGGER) as builder, builder.create_builder_config(
) as config, builder.create_network(EXPLICIT_BATCH) as network:
# define network
input_tensor = network.add_input(
name=IN_NAME, dtype=trt.float32, shape=(BATCH_SIZE, 3, IN_H, IN_W))
pool = network.add_pooling(
input=input_tensor, type=trt.PoolingType.MAX, window_size=(2, 2))
pool.stride = (2, 2)
pool.get_output(0).name = OUT_NAME
network.mark_output(pool.get_output(0))
# serialize the model to engine file
profile = builder.create_optimization_profile()
profile.set_shape_input('input', *[[BATCH_SIZE, 3, IN_H, IN_W]]*3)
builder.max_batch_size = 1
config.max_workspace_size = 1 << 30
engine = builder.build_engine(network, config)
with open('model_python_trt.engine', mode='wb') as f:
f.write(bytearray(engine.serialize()))
print("generating file done!")
使用 C++ API 构建¶
对于想要直接用 C++ 语言构建网络的小伙伴来说,整个流程和上述 Python 的执行过程非常类似,需要注意的点主要有:
nvinfer1:: createInferBuilder
对应 Python 中的tensorrt.Builder
,需要传入ILogger
类的实例,但是ILogger
是一个抽象类,需要用户继承该类并实现内部的虚函数。不过此处我们直接使用了 TensorRT 包解压后的 samples 文件夹 ../samples/common/logger.h 文件里的实现Logger
子类。设置 TensorRT 模型的输入尺寸,需要多次调用
IOptimizationProfile
的setDimensions
方法,比 Python略繁琐一些。IOptimizationProfile
需要用createOptimizationProfile
函数,对应 Python 的create_builder_config
函数。
实现代码如下:
#include <fstream>
#include <iostream>
#include <NvInfer.h>
#include <../samples/common/logger.h>
using namespace nvinfer1;
using namespace sample;
const char* IN_NAME = "input";
const char* OUT_NAME = "output";
static const int IN_H = 224;
static const int IN_W = 224;
static const int BATCH_SIZE = 1;
static const int EXPLICIT_BATCH = 1 << (int)(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
int main(int argc, char** argv)
{
// Create builder
Logger m_logger;
IBuilder* builder = createInferBuilder(m_logger);
IBuilderConfig* config = builder->createBuilderConfig();
// Create model to populate the network
INetworkDefinition* network = builder->createNetworkV2(EXPLICIT_BATCH);
ITensor* input_tensor = network->addInput(IN_NAME, DataType::kFLOAT, Dims4{ BATCH_SIZE, 3, IN_H, IN_W });
IPoolingLayer* pool = network->addPoolingNd(*input_tensor, PoolingType::kMAX, DimsHW{ 2, 2 });
pool->setStrideNd(DimsHW{ 2, 2 });
pool->getOutput(0)->setName(OUT_NAME);
network->markOutput(*pool->getOutput(0));
// Build engine
IOptimizationProfile* profile = builder->createOptimizationProfile();
profile->setDimensions(IN_NAME, OptProfileSelector::kMIN, Dims4(BATCH_SIZE, 3, IN_H, IN_W));
profile->setDimensions(IN_NAME, OptProfileSelector::kOPT, Dims4(BATCH_SIZE, 3, IN_H, IN_W));
profile->setDimensions(IN_NAME, OptProfileSelector::kMAX, Dims4(BATCH_SIZE, 3, IN_H, IN_W));
config->setMaxWorkspaceSize(1 << 20);
ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);
// Serialize the model to engine file
IHostMemory* modelStream{ nullptr };
assert(engine != nullptr);
modelStream = engine->serialize();
std::ofstream p("model.engine", std::ios::binary);
if (!p) {
std::cerr << "could not open output file to save model" << std::endl;
return -1;
}
p.write(reinterpret_cast<const char*>(modelStream->data()), modelStream->size());
std::cout << "generating file done!" << std::endl;
// Release resources
modelStream->destroy();
network->destroy();
engine->destroy();
builder->destroy();
config->destroy();
return 0;
}
IR 转换模型¶
除了直接通过 TensorRT 的 API 逐层搭建网络并序列化模型,TensorRT 还支持将中间表示的模型(如 ONNX)转换成 TensorRT 模型。
使用 Python API 转换¶
我们首先使用 Pytorch 实现一个和上文一致的模型,即只对输入做一次池化并输出;然后将 Pytorch 模型转换成 ONNX 模型;最后将 ONNX 模型转换成 TensorRT 模型。
这里主要使用了 TensorRT 的 OnnxParser
功能,它可以将 ONNX 模型解析到 TensorRT 的网络中。最后我们同样可以得到一个 TensorRT 模型,其功能与上述方式实现的模型功能一致。
实现代码如下:
import torch
import onnx
import tensorrt as trt
onnx_model = 'model.onnx'
class NaiveModel(torch.nn.Module):
def __init__(self):
super().__init__()
self.pool = torch.nn.MaxPool2d(2, 2)
def forward(self, x):
return self.pool(x)
device = torch.device('cuda:0')
# generate ONNX model
torch.onnx.export(NaiveModel(), torch.randn(1, 3, 224, 224), onnx_model, input_names=['input'], output_names=['output'], opset_version=11)
onnx_model = onnx.load(onnx_model)
# create builder and network
logger = trt.Logger(trt.Logger.ERROR)
builder = trt.Builder(logger)
EXPLICIT_BATCH = 1 << (int)(
trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
network = builder.create_network(EXPLICIT_BATCH)
# parse onnx
parser = trt.OnnxParser(network, logger)
if not parser.parse(onnx_model.SerializeToString()):
error_msgs = ''
for error in range(parser.num_errors):
error_msgs += f'{parser.get_error(error)}\n'
raise RuntimeError(f'Failed to parse onnx, {error_msgs}')
config = builder.create_builder_config()
config.max_workspace_size = 1<<20
profile = builder.create_optimization_profile()
profile.set_shape('input', [1,3 ,224 ,224], [1,3,224, 224], [1,3 ,224 ,224])
config.add_optimization_profile(profile)
# create engine
with torch.cuda.device(device):
engine = builder.build_engine(network, config)
with open('model.engine', mode='wb') as f:
f.write(bytearray(engine.serialize()))
print("generating file done!")
IR 转换时,如果有多 Batch、多输入、动态 shape 的需求,都可以通过多次调用 set_shape
函数进行设置。set_shape
函数接受的传参分别是:输入节点名称,可接受的最小输入尺寸,最优的输入尺寸,可接受的最大输入尺寸。一般要求这三个尺寸的大小关系为单调递增。
使用 C++ API 转换¶
介绍了如何用 Python 语言将 ONNX 模型转换成 TensorRT 模型后,再介绍下如何用 C++ 将 ONNX 模型转换成 TensorRT 模型。这里通过 NvOnnxParser
,我们可以将上一小节转换时得到的 ONNX 文件直接解析到网络中。
实现代码如下:
#include <fstream>
#include <iostream>
#include <NvInfer.h>
#include <NvOnnxParser.h>
#include <../samples/common/logger.h>
using namespace nvinfer1;
using namespace nvonnxparser;
using namespace sample;
int main(int argc, char** argv)
{
// Create builder
Logger m_logger;
IBuilder* builder = createInferBuilder(m_logger);
const auto explicitBatch = 1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
IBuilderConfig* config = builder->createBuilderConfig();
// Create model to populate the network
INetworkDefinition* network = builder->createNetworkV2(explicitBatch);
// Parse ONNX file
IParser* parser = nvonnxparser::createParser(*network, m_logger);
bool parser_status = parser->parseFromFile("model.onnx", static_cast<int>(ILogger::Severity::kWARNING));
// Get the name of network input
Dims dim = network->getInput(0)->getDimensions();
if (dim.d[0] == -1) // -1 means it is a dynamic model
{
const char* name = network->getInput(0)->getName();
IOptimizationProfile* profile = builder->createOptimizationProfile();
profile->setDimensions(name, OptProfileSelector::kMIN, Dims4(1, dim.d[1], dim.d[2], dim.d[3]));
profile->setDimensions(name, OptProfileSelector::kOPT, Dims4(1, dim.d[1], dim.d[2], dim.d[3]));
profile->setDimensions(name, OptProfileSelector::kMAX, Dims4(1, dim.d[1], dim.d[2], dim.d[3]));
config->addOptimizationProfile(profile);
}
// Build engine
config->setMaxWorkspaceSize(1 << 20);
ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);
// Serialize the model to engine file
IHostMemory* modelStream{ nullptr };
assert(engine != nullptr);
modelStream = engine->serialize();
std::ofstream p("model.engine", std::ios::binary);
if (!p) {
std::cerr << "could not open output file to save model" << std::endl;
return -1;
}
p.write(reinterpret_cast<const char*>(modelStream->data()), modelStream->size());
std::cout << "generate file success!" << std::endl;
// Release resources
modelStream->destroy();
network->destroy();
engine->destroy();
builder->destroy();
config->destroy();
return 0;
}
模型推理¶
前面,我们使用了两种构建 TensorRT 模型的方式,分别用 Python 和 C++ 两种语言共生成了四个 TensorRT 模型,这四个模型的功能理论上是完全一致的。 接下来,我们将分别使用 Python 和 C++ 两种语言对生成的 TensorRT 模型进行推理。
使用 Python API 推理¶
首先是使用 Python API 推理 TensorRT 模型,这里部分代码参考了 MMDeploy。运行下面代码,可以发现输入一个 1x3x224x224
的张量,输出一个 1x3x112x112
的张量,完全符合我们对输入池化后结果的预期。
from typing import Union, Optional, Sequence,Dict,Any
import torch
import tensorrt as trt
class TRTWrapper(torch.nn.Module):
def __init__(self,engine: Union[str, trt.ICudaEngine],
output_names: Optional[Sequence[str]] = None) -> None:
super().__init__()
self.engine = engine
if isinstance(self.engine, str):
with trt.Logger() as logger, trt.Runtime(logger) as runtime:
with open(self.engine, mode='rb') as f:
engine_bytes = f.read()
self.engine = runtime.deserialize_cuda_engine(engine_bytes)
self.context = self.engine.create_execution_context()
names = [_ for _ in self.engine]
input_names = list(filter(self.engine.binding_is_input, names))
self._input_names = input_names
self._output_names = output_names
if self._output_names is None:
output_names = list(set(names) - set(input_names))
self._output_names = output_names
def forward(self, inputs: Dict[str, torch.Tensor]):
assert self._input_names is not None
assert self._output_names is not None
bindings = [None] * (len(self._input_names) + len(self._output_names))
profile_id = 0
for input_name, input_tensor in inputs.items():
# check if input shape is valid
profile = self.engine.get_profile_shape(profile_id, input_name)
assert input_tensor.dim() == len(
profile[0]), 'Input dim is different from engine profile.'
for s_min, s_input, s_max in zip(profile[0], input_tensor.shape,
profile[2]):
assert s_min <= s_input <= s_max, \
'Input shape should be between ' \
+ f'{profile[0]} and {profile[2]}' \
+ f' but get {tuple(input_tensor.shape)}.'
idx = self.engine.get_binding_index(input_name)
# All input tensors must be gpu variables
assert 'cuda' in input_tensor.device.type
input_tensor = input_tensor.contiguous()
if input_tensor.dtype == torch.long:
input_tensor = input_tensor.int()
self.context.set_binding_shape(idx, tuple(input_tensor.shape))
bindings[idx] = input_tensor.contiguous().data_ptr()
# create output tensors
outputs = {}
for output_name in self._output_names:
idx = self.engine.get_binding_index(output_name)
dtype = torch.float32
shape = tuple(self.context.get_binding_shape(idx))
device = torch.device('cuda')
output = torch.empty(size=shape, dtype=dtype, device=device)
outputs[output_name] = output
bindings[idx] = output.data_ptr()
self.context.execute_async_v2(bindings,
torch.cuda.current_stream().cuda_stream)
return outputs
model = TRTWrapper('model.engine', ['output'])
output = model(dict(input = torch.randn(1, 3, 224, 224).cuda()))
print(output)
使用 C++ API 推理¶
最后,在很多实际生产环境中,我们都会使用 C++ 语言完成具体的任务,以达到更加高效的代码运行效果,另外 TensoRT 的用户一般也都更看重其在 C++ 下的使用,所以我们也用 C++ 语言实现一遍模型推理,这也可以和用 Python API 推理模型做一个对比。
实现代码如下:
#include <fstream>
#include <iostream>
#include <NvInfer.h>
#include <../samples/common/logger.h>
#define CHECK(status) \
do\
{\
auto ret = (status);\
if (ret != 0)\
{\
std::cerr << "Cuda failure: " << ret << std::endl;\
abort();\
}\
} while (0)
using namespace nvinfer1;
using namespace sample;
const char* IN_NAME = "input";
const char* OUT_NAME = "output";
static const int IN_H = 224;
static const int IN_W = 224;
static const int BATCH_SIZE = 1;
static const int EXPLICIT_BATCH = 1 << (int)(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
void doInference(IExecutionContext& context, float* input, float* output, int batchSize)
{
const ICudaEngine& engine = context.getEngine();
// Pointers to input and output device buffers to pass to engine.
// Engine requires exactly IEngine::getNbBindings() number of buffers.
assert(engine.getNbBindings() == 2);
void* buffers[2];
// In order to bind the buffers, we need to know the names of the input and output tensors.
// Note that indices are guaranteed to be less than IEngine::getNbBindings()
const int inputIndex = engine.getBindingIndex(IN_NAME);
const int outputIndex = engine.getBindingIndex(OUT_NAME);
// Create GPU buffers on device
CHECK(cudaMalloc(&buffers[inputIndex], batchSize * 3 * IN_H * IN_W * sizeof(float)));
CHECK(cudaMalloc(&buffers[outputIndex], batchSize * 3 * IN_H * IN_W /4 * sizeof(float)));
// Create stream
cudaStream_t stream;
CHECK(cudaStreamCreate(&stream));
// DMA input batch data to device, infer on the batch asynchronously, and DMA output back to host
CHECK(cudaMemcpyAsync(buffers[inputIndex], input, batchSize * 3 * IN_H * IN_W * sizeof(float), cudaMemcpyHostToDevice, stream));
context.enqueue(batchSize, buffers, stream, nullptr);
CHECK(cudaMemcpyAsync(output, buffers[outputIndex], batchSize * 3 * IN_H * IN_W / 4 * sizeof(float), cudaMemcpyDeviceToHost, stream));
cudaStreamSynchronize(stream);
// Release stream and buffers
cudaStreamDestroy(stream);
CHECK(cudaFree(buffers[inputIndex]));
CHECK(cudaFree(buffers[outputIndex]));
}
int main(int argc, char** argv)
{
// create a model using the API directly and serialize it to a stream
char *trtModelStream{ nullptr };
size_t size{ 0 };
std::ifstream file("model.engine", std::ios::binary);
if (file.good()) {
file.seekg(0, file.end);
size = file.tellg();
file.seekg(0, file.beg);
trtModelStream = new char[size];
assert(trtModelStream);
file.read(trtModelStream, size);
file.close();
}
Logger m_logger;
IRuntime* runtime = createInferRuntime(m_logger);
assert(runtime != nullptr);
ICudaEngine* engine = runtime->deserializeCudaEngine(trtModelStream, size, nullptr);
assert(engine != nullptr);
IExecutionContext* context = engine->createExecutionContext();
assert(context != nullptr);
// generate input data
float data[BATCH_SIZE * 3 * IN_H * IN_W];
for (int i = 0; i < BATCH_SIZE * 3 * IN_H * IN_W; i++)
data[i] = 1;
// Run inference
float prob[BATCH_SIZE * 3 * IN_H * IN_W /4];
doInference(*context, data, prob, BATCH_SIZE);
// Destroy the engine
context->destroy();
engine->destroy();
runtime->destroy();
return 0;
}
总结¶
通过本文的学习,我们掌握了两种构建 TensorRT 模型的方式:直接通过 TensorRT 的 API 逐层搭建网络;将中间表示的模型转换成 TensorRT 的模型。不仅如此,我们还分别用 C++ 和 Python 两种语言完成了 TensorRT 模型的构建及推理,相信大家都有所收获!在下一篇文章中,我们将和大家一起学习何添加 TensorRT 自定义算子,敬请期待哦~
FAQ¶
Could not find: cudnn64_8.dll. Is it on your PATH? 首先检查下自己的环境变量中是否包含 cudnn64_8.dll 所在的路径,若发现 cudnn 的路径在 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\bin 中,但是里面只有 cudnn64_7.dll。解决方法是去 NVIDIA 官网下载 cuDNN zip 包,解压后,复制其中的 cudnn64_8.dll 到 CUDA Toolkit 的 bin 目录下。这时也可以复制一份 cudnn64_7.dll,然后将复制的那份改名成 cudnn64_8.dll,同样可以解决这个问题。
第七章: TensorRT 自定义插件¶
介绍¶
在前面的模型部署入门系列文章中,我们介绍了部署一个 PyTorch 模型到推理后端,如 ONNXRuntime,这其中可能遇到很多工程性的问题。
有些可以通过创建 ONNX 节点来解决,该节点仍然使用后端原生的实现进行推理。而有些无法导出到后端的算法,可以通过重写代码改变算法的实现过程,同样可以导出到 ONNX ,达到一致的效果。以上两种方式一般可以处理绝大多数的部署问题,同时也不需要向推理框架引入新的内容,是我们进行模型部署时候的优先选择。
然而,仍然存在部分模型,模型中某些算子无法通过上述两种方式绕过问题,这时候,如何对特定后端实现对应代码就极为重要。这也是本文将介绍的第三种方式——自定义插件。
自定义插件是很多推理框架支持用户自定义算子的方式,以 MMDeploy 为例,它是一个支持多种推理后端的算法库。目前支持的后端有:
ONNXRuntime
TensorRT
ncnn
openvino
PPLNN 其中,前三种后端均实现了一些自定义的算子。例如 ONNXRuntime 中的调制可变性卷积,ncnn 中的topk 算子,TensorRT 中的 MultiLevelRoiAlign 。
介绍如何给后端自定义算子是一件相对复杂的事情,所以本文只针对其中一种后端 TensorRT,介绍自定义算子。如果读者对其他后端感兴趣,可以去他们的代码库查看,一般地,各个推理框架均有详细文档介绍如何添加客制化的算子实现。
在MMDeploy添加TensorRT插件¶
仍然以前面教程二中的超分辨模型SRCNN为例。在教程二中,我们用 ONNXRuntime 作为后端,通过 PyTorch 的 symbolic 函数导出了一个支持动态 scale 的 ONNX 模型,这个模型可以直接用 ONNXRuntime 运行,这是因为 NewInterpolate
类导出的节点 Resize
就是ONNXRuntime支持的节点。下面我们尝试直接将教程二导出的 srcnn3.onnx
转换到TensorRT。
from mmdeploy.backend.tensorrt.utils import from_onnx
from_onnx(
'srcnn3.onnx',
'srcnn3',
input_shapes=dict(
input=dict(
min_shape=[1, 3, 256, 256],
opt_shape=[1, 3, 256, 256],
max_shape=[1, 3, 256, 256]),
factor=dict(
min_shape=[4],
opt_shape=[4],
max_shape=[4])))
没有安装过MMDeploy的小伙伴可以先参考 build 进行安装,安装完成后执行上述脚本,会有如下报错:
RuntimeError: Failed to parse onnx, In node 1 (importResize): UNSUPPORTED_NODE: Assertion failed: mode != "cubic" && "This version of TensorRT does not support cubic interpolation!"
报错的原因有以下两方面:
srcnn3.onnx
文件中的Resize
是 ONNX 原生节点。其插值方式之一 bicubic 并不被 TensorRT 支持(TensorRT 的 Resize Layer仅支持 nearest 和 bilinear 两种插值方式)。日志的错误信息也明确提示了这点;但即便将 “bicubic” 模式改为 “bilinear” ,转换仍然失败:
RuntimeError: Failed to parse onnx, In node 1 (importResize): UNSUPPORTED_NODE: Assertion failed: scales.is_weights() && Resize scales must be initializer!"
。这是因为 TensorRT 无法接受动态 scale 导致的。
创建ONNX节点¶
为解决上述问题,我们需要创建一个新的节点替换原生 Resize 节点,并且实现新节点对应的插件代码。
继续复用同样节点名的方式已经不可取,我们需要创建新的节点。改节点名称就叫 Test::DynamicTRTResize
,这是种类C++的写法,Test
为域名,主要用于区分不同来源下的同名的节点,比如 ONNX::
和 Test::
。当然了,ONNX本身也不存在 DynamicTRTResize
的节点名。
import torch
from torch import nn
from torch.nn.functional import interpolate
import torch.onnx
import cv2
import numpy as np
import os, requests
# Download checkpoint and test image
urls = ['https://download.openmmlab.com/mmediting/restorers/srcnn/srcnn_x4k915_1x16_1000k_div2k_20200608-4186f232.pth',
'https://raw.githubusercontent.com/open-mmlab/mmagic/master/tests/data/face/000001.png']
names = ['srcnn.pth', 'face.png']
for url, name in zip(urls, names):
if not os.path.exists(name):
open(name, 'wb').write(requests.get(url).content)
class DynamicTRTResize(torch.autograd.Function):
def __init__(self) -> None:
super().__init__()
@staticmethod
def symbolic(g, input, size_tensor, align_corners = False):
"""Symbolic function for creating onnx op."""
return g.op(
'Test::DynamicTRTResize',
input,
size_tensor,
align_corners_i=align_corners)
@staticmethod
def forward(g, input, size_tensor, align_corners = False):
"""Run forward."""
size = [size_tensor.size(-2), size_tensor.size(-1)]
return interpolate(
input, size=size, mode='bicubic', align_corners=align_corners)
class StrangeSuperResolutionNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=9, padding=4)
self.conv2 = nn.Conv2d(64, 32, kernel_size=1, padding=0)
self.conv3 = nn.Conv2d(32, 3, kernel_size=5, padding=2)
self.relu = nn.ReLU()
def forward(self, x, size_tensor):
x = DynamicTRTResize.apply(x, size_tensor)
out = self.relu(self.conv1(x))
out = self.relu(self.conv2(out))
out = self.conv3(out)
return out
def init_torch_model():
torch_model = StrangeSuperResolutionNet()
state_dict = torch.load('srcnn.pth')['state_dict']
# Adapt the checkpoint
for old_key in list(state_dict.keys()):
new_key = '.'.join(old_key.split('.')[1:])
state_dict[new_key] = state_dict.pop(old_key)
torch_model.load_state_dict(state_dict)
torch_model.eval()
return torch_model
model = init_torch_model()
factor = torch.rand([1, 1, 512, 512], dtype=torch.float)
input_img = cv2.imread('face.png').astype(np.float32)
# HWC to NCHW
input_img = np.transpose(input_img, [2, 0, 1])
input_img = np.expand_dims(input_img, 0)
# Inference
torch_output = model(torch.from_numpy(input_img), factor).detach().numpy()
# NCHW to HWC
torch_output = np.squeeze(torch_output, 0)
torch_output = np.clip(torch_output, 0, 255)
torch_output = np.transpose(torch_output, [1, 2, 0]).astype(np.uint8)
# Show image
cv2.imwrite("face_torch.png", torch_output)
x = torch.randn(1, 3, 256, 256)
dynamic_axes={
'input': {
0: 'batch',
2: 'height',
3: 'width'
},
'factor': {
0: 'batch1',
2: 'height1',
3: 'width1'
},
'output': {
0: 'batch2',
2: 'height2',
3: 'width2'
},
}
with torch.no_grad():
torch.onnx.export(
model, (x, factor),
"srcnn3.onnx",
opset_version=11,
input_names=['input', 'factor'],
output_names=['output'],
dynamic_axes=dynamic_axes)
执行上述脚本,我们导出成功了一个ONNX模型 srcnn.onnx
。用netron打开这个模型可视化如下:

直接将该模型转换成TensorRT模型也是不可行的,这是因为TensorRT还无法解析 DynamicTRTResize
节点。而想要解析该节点,我们必须为TensorRT添加c++代码,实现该插件。
C++实现¶
因为MMDeploy中已经实现了Bicubic Interpolate算子,所以我们可以复用其中的CUDA部分代码,只针对TensorRT实现支持动态scale的插件即可。对CUDA编程感兴趣的小伙伴可以参考CUDA的官方教程。因为 csrc/backend_ops/tensorrt/bicubic_interpolate
中有我们需要的CUDA代码,所以我们可以直接在该文件夹加添加TensorRT相关的trt_dynamic_resize.hpp和trt_dynamic_resize.cpp文件,在这两个文件中分别声明和实现插件就可以了。我们也可以新建文件夹 csrc/backend_ops/tensorrt/dynamic_resize
,将这两个文件直接放到这个文件夹下。
对TensorRT 7+,要实现这样一个自定义插件,我们需要写两个类。
DynamicTRTResize
,继承自nvinfer1::IPluginV2DynamicExt,完成插件的具体实现DynamicTRTResizeCreator
,继承自nvinfer1::IPluginCreator,是插件的工厂类,用于创建DynamicTRTResize
插件的实例。
在MMDeploy中,由于有若干插件需要实现,所以我们在mmdeploy/csrc/backend_ops/tensorrt/common/trt_plugin_base.hpp
中实现了TRTPluginBase
和TRTPluginCreatorBase
两个类,用于管理一些所有插件共有的属性方法。其中,TRTPluginBase
是继承自nvinfer1::IPluginV2DynamicExt
,而TRTPluginCreatorBase
是继承自nvinfer1::IPluginCreator
。这样,用户实现插件时只需继承这两个新的类即可。所以我们只需在dynamic_resize
文件夹下.hpp文件中,引用trt_plugin_base.hpp
头文件,然后实现类如下:
class DynamicTRTResize : public TRTPluginBase{}
class DynamicTRTResizeCreator : public TRTPluginCreatorBase{}
在trt_dynamic_resize.hpp中,我们声明如下内容:
#ifndef TRT_DYNAMIC_RESIZE_HPP
#define TRT_DYNAMIC_RESIZE_HPP
#include <cublas_v2.h>
#include <memory>
#include <string>
#include <vector>
#include "trt_plugin_base.hpp"
namespace mmdeploy {
class DynamicTRTResize : public TRTPluginBase {
public:
DynamicTRTResize(const std::string &name, bool align_corners);
DynamicTRTResize(const std::string name, const void *data, size_t length);
DynamicTRTResize() = delete;
// IPluginV2DynamicExt Methods
nvinfer1::IPluginV2DynamicExt *clone() const TRT_NOEXCEPT override;
nvinfer1::DimsExprs getOutputDimensions(int outputIndex, const nvinfer1::DimsExprs *inputs,
int nbInputs, nvinfer1::IExprBuilder &exprBuilder)
TRT_NOEXCEPT override;
bool supportsFormatCombination(int pos, const nvinfer1::PluginTensorDesc *ioDesc, int nbInputs,
int nbOutputs) TRT_NOEXCEPT override;
void configurePlugin(const nvinfer1::DynamicPluginTensorDesc *in, int nbInputs,
const nvinfer1::DynamicPluginTensorDesc *out,
int nbOutputs) TRT_NOEXCEPT override;
size_t getWorkspaceSize(const nvinfer1::PluginTensorDesc *inputs, int nbInputs,
const nvinfer1::PluginTensorDesc *outputs,
int nbOutputs) const TRT_NOEXCEPT override;
int enqueue(const nvinfer1::PluginTensorDesc *inputDesc,
const nvinfer1::PluginTensorDesc *outputDesc, const void *const *inputs,
void *const *outputs, void *workspace, cudaStream_t stream) TRT_NOEXCEPT override;
// IPluginV2Ext Methods
nvinfer1::DataType getOutputDataType(int index, const nvinfer1::DataType *inputTypes,
int nbInputs) const TRT_NOEXCEPT override;
// IPluginV2 Methods
const char *getPluginType() const TRT_NOEXCEPT override;
const char *getPluginVersion() const TRT_NOEXCEPT override;
int getNbOutputs() const TRT_NOEXCEPT override;
size_t getSerializationSize() const TRT_NOEXCEPT override;
void serialize(void *buffer) const TRT_NOEXCEPT override;
private:
bool mAlignCorners;
};
class DynamicTRTResizeCreator : public TRTPluginCreatorBase {
public:
DynamicTRTResizeCreator();
const char *getPluginName() const TRT_NOEXCEPT override;
const char *getPluginVersion() const TRT_NOEXCEPT override;
nvinfer1::IPluginV2 *createPlugin(const char *name, const nvinfer1::PluginFieldCollection *fc)
TRT_NOEXCEPT override;
nvinfer1::IPluginV2 *deserializePlugin(const char *name, const void *serialData,
size_t serialLength) TRT_NOEXCEPT override;
};
} // namespace mmdeploy
#endif // TRT_DYNAMIC_RESIZE_HPP
在这样一份头文件中,DynamicTRTResize类进行了如下的套娃继承:

从上面的图片和代码中我们发现,插件类DynamicTRTResize
中我们定义了私有变量mAlignCorners
,该变量表示是否align corners
。此外只要实现构造析构函数和TensoRT中三个基类的方法即可。其中构造函数有二,分别用于创建插件和反序列化插件。而基类方法中:
基类
IPluginV2DynamicExt
的方法较为值得关注,getOutputDimensions
获取输出张量的形状,enqueue
真正负责执行我们的算法,内部一般会调用CUDA核函数。本文实现的插件直接调用MMDeploy已定义在csrc/backend_ops/tensorrt/bicubic_interpolate
的核函数bicubic_interpolate
。基类
IPluginV2Ext
的方法,我们只要实现获取输出数据类型的getOutputDataType
即可。基类
IPluginV2
则是些获取插件类型和版本号的方法,此外则是序列化输入插件的参数的函数serialize
和计算该参数的序列化后buffer
大小的函数getSerializationSize
,以及获取输出张量个数的方法getNbOutputs
。还有部分公共方法被定义在TRTPluginBase
类内了。
在插件工厂类 DynamicTRTResizeCreator
中,我们需要声明获取插件名称和版本的方法 getPluginName
和 getPluginVersion
。同时我们还需要声明创建插件和反序列化插件的方法 createPlugin
和 deserializePlugin
,前者调用 DynamicTRTResize
中创建插件的方法,后者调用反序列化插件的方法。
接下来,我们就实现上述声明吧。在.cpp文件中我们实现代码如下:
// Copyright (c) OpenMMLab. All rights reserved
#include "trt_dynamic_resize.hpp"
#include <assert.h>
#include <chrono>
#include "trt_plugin_helper.hpp"
#include "trt_serialize.hpp"
// 引入CUDA核函数bicubic_interpolate在的头文件,会在enqueue中使用
#include "../bicubic_interpolate/trt_bicubic_interpolate_kernel.hpp"
using namespace nvinfer1;
namespace mmdeploy {
namespace {
static const char *PLUGIN_VERSION{"1"};
static const char *PLUGIN_NAME{"DynamicTRTResize"};//插件名需和ONNX节点名一致,在转换TensorRT模型时被触发
} // namespace
DynamicTRTResize::DynamicTRTResize(const std::string &name, bool align_corners)
: TRTPluginBase(name), mAlignCorners(align_corners) {}
DynamicTRTResize::DynamicTRTResize(const std::string name, const void *data,
size_t length)
: TRTPluginBase(name) {
deserialize_value(&data, &length, &mAlignCorners);
}
nvinfer1::IPluginV2DynamicExt *DynamicTRTResize::clone() const TRT_NOEXCEPT {
DynamicTRTResize *plugin =
new DynamicTRTResize(mLayerName, mAlignCorners);
plugin->setPluginNamespace(getPluginNamespace());
return plugin;
}
nvinfer1::DimsExprs DynamicTRTResize::getOutputDimensions(
int outputIndex, const nvinfer1::DimsExprs *inputs, int nbInputs,
nvinfer1::IExprBuilder &exprBuilder) TRT_NOEXCEPT {
nvinfer1::DimsExprs ret;
ret.nbDims = 4;
// 输入张量有两个:input和size_tensor,后者只用于计算输出张量形状
ret.d[0] = inputs[0].d[0];
ret.d[1] = inputs[0].d[1];
ret.d[2] = inputs[1].d[2];
ret.d[3] = inputs[1].d[3];
return ret;
}
bool DynamicTRTResize::supportsFormatCombination(int pos,
const nvinfer1::PluginTensorDesc *ioDesc,
int nbInputs, int nbOutputs) TRT_NOEXCEPT {
if (pos == 0) {
return (ioDesc[pos].type == nvinfer1::DataType::kFLOAT &&
ioDesc[pos].format == nvinfer1::TensorFormat::kLINEAR);
} else {
return ioDesc[pos].type == ioDesc[0].type && ioDesc[pos].format == ioDesc[0].format;
}
}
void DynamicTRTResize::configurePlugin(const nvinfer1::DynamicPluginTensorDesc *inputs,
int nbInputs,
const nvinfer1::DynamicPluginTensorDesc *outputs,
int nbOutputs) TRT_NOEXCEPT {}
size_t DynamicTRTResize::getWorkspaceSize(const nvinfer1::PluginTensorDesc *inputs,
int nbInputs,
const nvinfer1::PluginTensorDesc *outputs,
int nbOutputs) const TRT_NOEXCEPT {
return 0;
}
int DynamicTRTResize::enqueue(const nvinfer1::PluginTensorDesc *inputDesc,
const nvinfer1::PluginTensorDesc *outputDesc,
const void *const *inputs, void *const *outputs, void *workSpace,
cudaStream_t stream) TRT_NOEXCEPT {
int batch = inputDesc[0].dims.d[0];
int channels = inputDesc[0].dims.d[1];
int height = inputDesc[0].dims.d[2];
int width = inputDesc[0].dims.d[3];
int height_out = outputDesc[0].dims.d[2];
int width_out = outputDesc[0].dims.d[3];
const void *x = inputs[0];
void *output = outputs[0];
// TODO: add fp16 support
auto data_type = inputDesc[0].type;
switch (data_type) {
case nvinfer1::DataType::kFLOAT:
bicubic_interpolate<float>((float *)x, (float *)output, batch, channels, height, width,
height_out, width_out, mAlignCorners, stream);
break;
default:
return 1;
break;
}
return 0;
}
nvinfer1::DataType DynamicTRTResize::getOutputDataType(int index,
const nvinfer1::DataType *inputTypes,
int nbInputs) const TRT_NOEXCEPT {
return inputTypes[0];
}
// IPluginV2 Methods
const char *DynamicTRTResize::getPluginType() const TRT_NOEXCEPT { return PLUGIN_NAME; }
const char *DynamicTRTResize::getPluginVersion() const TRT_NOEXCEPT { return PLUGIN_VERSION; }
int DynamicTRTResize::getNbOutputs() const TRT_NOEXCEPT { return 1; }
size_t DynamicTRTResize::getSerializationSize() const TRT_NOEXCEPT {
return serialized_size(mAlignCorners);
}
void DynamicTRTResize::serialize(void *buffer) const TRT_NOEXCEPT {
serialize_value(&buffer, mAlignCorners);
}
////////////////////// creator /////////////////////////////
DynamicTRTResizeCreator::DynamicTRTResizeCreator() {
mPluginAttributes.clear();
mPluginAttributes.emplace_back(nvinfer1::PluginField("align_corners"));
mFC.nbFields = mPluginAttributes.size();
mFC.fields = mPluginAttributes.data();
}
const char *DynamicTRTResizeCreator::getPluginName() const TRT_NOEXCEPT { return PLUGIN_NAME; }
const char *DynamicTRTResizeCreator::getPluginVersion() const TRT_NOEXCEPT {
return PLUGIN_VERSION;
}
nvinfer1::IPluginV2 *DynamicTRTResizeCreator::createPlugin(
const char *name, const nvinfer1::PluginFieldCollection *fc) TRT_NOEXCEPT {
nvinfer1::Dims size{2, {1, 1}};
bool align_corners = 1;
for (int i = 0; i < fc->nbFields; i++) {
if (fc->fields[i].data == nullptr) {
continue;
}
std::string field_name(fc->fields[i].name);
//获取align_corners值,用于创建插件DynamicTRTResize的实例
if (field_name.compare("align_corners") == 0) {
align_corners = static_cast<const int *>(fc->fields[i].data)[0];
}
}
// 创建插件DynamicTRTResize实例并返回
DynamicTRTResize *plugin = new DynamicTRTResize(name, align_corners);
plugin->setPluginNamespace(getPluginNamespace());
return plugin;
}
nvinfer1::IPluginV2 *DynamicTRTResizeCreator::deserializePlugin(
const char *name, const void *serialData, size_t serialLength) TRT_NOEXCEPT {
auto plugin = new DynamicTRTResize(name, serialData, serialLength);
plugin->setPluginNamespace(getPluginNamespace());
return plugin;
}
REGISTER_TENSORRT_PLUGIN(DynamicTRTResizeCreator);//真正注册了该插件
} // namespace mmdeploy
然后,我们就对MMDeploy重新build一次TensorRT的动态库build/lib/libmmdeploy_tensorrt_ops.so
。一般编译成功就表示已经注册算子了,但是我们需要进行一些测试以保证结果正确。
测试¶
我们用TensorRT的python api查看一下目前的插件列表:
import tensorrt as trt
from mmdeploy.backend.tensorrt import load_tensorrt_plugin
load_tensorrt_plugin()
def get_plugin_names():
return [pc.name for pc in trt.get_plugin_registry().plugin_creator_list]
print(get_plugin_names())
可以发现 ‘DynamicTRTResize’ 在插件列表中。然后我们对这个插件进行功能测试,看推理结果是否和PyTroch结果一致,并且可以动态控制输出尺寸。
from mmdeploy.backend.tensorrt.utils import from_onnx
engine = from_onnx(
'srcnn3.onnx',
'srcnn3',
input_shapes=dict(
input=dict(
min_shape=[1, 3, 256, 256],
opt_shape=[1, 3, 256, 256],
max_shape=[1, 3, 256, 256]),
factor=dict(
min_shape=[1, 1, 256, 256],
opt_shape=[1, 1, 512, 512],
max_shape=[1, 1, 1024, 1024])))
from mmdeploy.backend.tensorrt import TRTWrapper
trt_model = TRTWrapper('srcnn3.engine', ['output'])
factor = torch.rand([1, 1, 768, 768], dtype=torch.float)
trt_output = trt_model.forward(dict(input=x.cuda(), factor=factor.cuda()))
torch_output = model.forward(x, factor)
assert np.allclose(
trt_output['output'].cpu().numpy(),
torch_output.cpu().detach(),
rtol=1e-3,
atol=1e-5)
对比 TensorRT 的输出结果和 PyTorch 的输出结果是否一致,程序如果不报错即可说明推理正确。此外,测试时我们使用和导出时不一样的尺寸,结果也和 PyTorch 一致,说明可以支持动态的尺寸。
总结¶
本篇教程我们主要讲述如何在 MMDeploy 代码库中添加一个自定义的 TensorRT 插件,整个过程不涉及太多更复杂的 CUDA 编程,相信小伙伴们学完可以自己实现想要的插件。
Ubuntu18.04 交叉编译 NDK snpe 推理服务¶
mmdeploy 已提供预编译包,如果你想自己编译、或需要对 .proto 接口做修改,可参考此文档。
注意 gRPC 官方文档并没有对 NDK 的完整支持。
一、环境说明¶
项目 | 版本 | 备注 |
---|---|---|
snpe | 1.59 | 1.60 使用 clang-8.0,可能导致兼容问题 |
host OS | ubuntu18.04 | snpe1.59 指定版本 |
NDK | r17c | snpe1.59 指定版本 |
gRPC | commit 6f698b5 | - |
硬件设备 | qcom888 | 需要 qcom 芯片 |
二、NDK 交叉编译 gRPC¶
拉取 gRPC repo, 在 host 上编译出
protoc
和grpc_cpp_plugin
# 安装依赖
$ apt-get update && apt-get install -y libssl-dev
# 编译
$ git clone https://github.com/grpc/grpc --recursive=1 --depth=1
$ mkdir -p cmake/build
$ pushd cmake/build
$ cmake \
-DCMAKE_BUILD_TYPE=Release \
-DgRPC_INSTALL=ON \
-DgRPC_BUILD_TESTS=OFF \
-DgRPC_SSL_PROVIDER=package \
../..
# 需要安装到 host 环境
$ make -j
$ sudo make install
下载 NDK,交叉编译 android aarch64 所需静态库
$ wget https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip
$ unzip android-ndk-r17c-linux-x86_64.zip
# 设置环境变量
$ export ANDROID_NDK=/path/to/android-ndk-r17c
# 编译
$ cd /path/to/grpc
$ mkdir -p cmake/build_aarch64 && pushd cmake/build_aarch64
$ cmake ../.. \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-26 \
-DANDROID_TOOLCHAIN=clang \
-DANDROID_STL=c++_shared \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/tmp/android_grpc_install_shared
$ make -j
$ make install
此时
/tmp/android_grpc_install
应有完整的安装文件
$ cd /tmp/android_grpc_install
$ tree -L 1
.
├── bin
├── include
├── lib
└── share
三、【可跳过】自测 NDK gRPC 是否正常¶
编译 gRPC 自带的 helloworld
$ cd /path/to/grpc/examples/cpp/helloworld/
$ mkdir cmake/build_aarch64 -p && pushd cmake/build_aarch64
$ cmake ../.. \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-26 \
-DANDROID_STL=c++_shared \
-DANDROID_TOOLCHAIN=clang \
-DCMAKE_BUILD_TYPE=Release \
-Dabsl_DIR=/tmp/android_grpc_install_shared/lib/cmake/absl \
-DProtobuf_DIR=/tmp/android_grpc_install_shared/lib/cmake/protobuf \
-DgRPC_DIR=/tmp/android_grpc_install_shared/lib/cmake/grpc
$ make -j
$ ls greeter*
greeter_async_client greeter_async_server greeter_callback_server greeter_server
greeter_async_client2 greeter_callback_client greeter_client
打开手机调试模式,push 编译结果到
/data/local/tmp
目录
tips:对于国产手机,设置 - 版本号,点击 7 次可进入开发者模式,然后才能打开 USB 调试
$ adb push greeter* /data/local/tmp
adb shell
进手机,执行 client/server
/data/local/tmp $ ./greeter_client
Greeter received: Hello world
四、交叉编译 snpe 推理服务¶
打开 snpe tools 官网,下载 1.59 版本。 解压并设置环境变量
注意 snpe >= 1.60 开始使用 clang-8.0
,可能导致旧设备与 libc++_shared.so
不兼容。
$ export SNPE_ROOT=/path/to/snpe-1.59.0.3230
打开 mmdeploy snpe server 目录,使用交叉编译 gRPC 时的选项
$ cd /path/to/mmdeploy
$ cd service/snpe/server
$ mkdir -p build && cd build
$ export ANDROID_NDK=/path/to/android-ndk-r17c
$ cmake .. \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-26 \
-DANDROID_STL=c++_shared \
-DANDROID_TOOLCHAIN=clang \
-DCMAKE_BUILD_TYPE=Release \
-Dabsl_DIR=/tmp/android_grpc_install_shared/lib/cmake/absl \
-DProtobuf_DIR=/tmp/android_grpc_install_shared/lib/cmake/protobuf \
-DgRPC_DIR=/tmp/android_grpc_install_shared/lib/cmake/grpc
$ make -j
$ file inference_server
inference_server: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, BuildID[sha1]=252aa04e2b982681603dacb74b571be2851176d2, with debug_info, not stripped
最终可得到 infernece_server
,adb push
到设备上即可执行。
五、重新生成 proto 接口¶
如果改过 inference.proto
,需要重新生成 .cpp 和 .py 通信接口
$ python3 -m pip install grpc_tools --user
$ python3 -m grpc_tools.protoc -I./ --python_out=./client/ --grpc_python_out=./client/ inference.proto
$ ln -s `which protoc-gen-grpc`
$ protoc --cpp_out=./ --grpc_out=./ --plugin=protoc-gen-grpc=grpc_cpp_plugin inference.proto
参考文档¶
snpe tutorial https://developer.qualcomm.com/sites/default/files/docs/snpe/cplus_plus_tutorial.html
gRPC cross build script https://raw.githubusercontent.com/grpc/grpc/master/test/distrib/cpp/run_distrib_test_cmake_aarch64_cross.sh
stackoverflow https://stackoverflow.com/questions/54052229/build-grpc-c-for-android-using-ndk-arm-linux-androideabi-clang-compiler
FAQ¶
TensorRT¶
“WARNING: Half2 support requested on hardware without native FP16 support, performance will be negatively affected.”
Fp16 mode requires a device with full-rate fp16 support.
“error: parameter check failed at: engine.cpp::setBindingDimensions::1046, condition: profileMinDims.d[i] <= dimensions.d[i]”
When building an
ICudaEngine
from anINetworkDefinition
that has dynamically resizable inputs, users need to specify at least one optimization profile. Which can be set in deploy config:backend_config = dict( common_config=dict(max_workspace_size=1 << 30), model_inputs=[ dict( input_shapes=dict( input=dict( min_shape=[1, 3, 320, 320], opt_shape=[1, 3, 800, 1344], max_shape=[1, 3, 1344, 1344]))) ])
The input tensor shape should be limited between
min_shape
andmax_shape
.“error: [TensorRT] INTERNAL ERROR: Assertion failed: cublasStatus == CUBLAS_STATUS_SUCCESS”
TRT 7.2.1 switches to use cuBLASLt (previously it was cuBLAS). cuBLASLt is the defaulted choice for SM version >= 7.0. You may need CUDA-10.2 Patch 1 (Released Aug 26, 2020) to resolve some cuBLASLt issues. Another option is to use the new TacticSource API and disable cuBLASLt tactics if you dont want to upgrade.
Libtorch¶
Error:
libtorch/share/cmake/Caffe2/Caffe2Config.cmake:96 (message):Your installed Caffe2 version uses cuDNN but I cannot find the cuDNN libraries. Please set the proper cuDNN prefixes and / or install cuDNN.
May
export CUDNN_ROOT=/root/path/to/cudnn
to resolve the build error.
Windows¶
Error: similar like this
OSError: [WinError 1455] The paging file is too small for this operation to complete. Error loading "C:\Users\cx\miniconda3\lib\site-packages\torch\lib\cudnn_cnn_infer64_8.dll" or one of its dependencies
Solution: according to this post, the issue may be caused by NVidia and will fix in CUDA release 11.7. For now one could use the fixNvPe.py script to modify the nvidia dlls in the pytorch lib dir.
python fixNvPe.py --input=C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\torch\lib\*.dll
You can find your pytorch installation path with:
import torch print(torch.__file__)
编译时enable_language(CUDA) 报错
-- Selecting Windows SDK version 10.0.19041.0 to target Windows 10.0.19044. -- Found CUDA: C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.1 (found version "11.1") CMake Error at C:/Software/cmake/cmake-3.23.1-windows-x86_64/share/cmake-3.23/Modules/CMakeDetermineCompilerId.cmake:491 (message): No CUDA toolset found. Call Stack (most recent call first): C:/Software/cmake/cmake-3.23.1-windows-x86_64/share/cmake-3.23/Modules/CMakeDetermineCompilerId.cmake:6 (CMAKE_DETERMINE_COMPILER_ID_BUILD) C:/Software/cmake/cmake-3.23.1-windows-x86_64/share/cmake-3.23/Modules/CMakeDetermineCompilerId.cmake:59 (__determine_compiler_id_test) C:/Software/cmake/cmake-3.23.1-windows-x86_64/share/cmake-3.23/Modules/CMakeDetermineCUDACompiler.cmake:339 (CMAKE_DETERMINE_COMPILER_ID) C:/workspace/mmdeploy-0.6.0-windows-amd64-cuda11.1-tensorrt8.2.3.0/sdk/lib/cmake/MMDeploy/MMDeployConfig.cmake:27 (enable_language) CMakeLists.txt:5 (find_package)
原因: CUDA Toolkit 11.1安装在Visual Studio之前,造成VS的插件没有安装。或者VS的版本过新,使得CUDA Toolkit的安装的时候跳过了VS插件的安装
解决方法: 可以通过手工拷贝插件的方式来解决这个问题。比如将
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.1\extras\visual_studio_integration\MSBuildExtensions
中的四个文件拷贝到C:\Software\Microsoft Visual Studio\2022\Community\Msbuild\Microsoft\VC\v170\BuildCustomizations
目录下。具体路径根据实际情况进行更改。
ONNX Runtime¶
Windows系统下,转模型可视化时以及SDK推理时遇到
onnxruntime.capi.onnxruntime_pybind11_state.Fail: [ONNXRuntimeError] : 1 : FAIL : Failed to load library, error code: 193
原因: 在较新的windows系统中,系统路径下下有两个
onnxruntime.dll
,且会优先加载,造成冲突。C:\Windows\SysWOW64\onnxruntime.dll C:\Windows\System32\onnxruntime.dll
解决方法: 以下两个方案任选其一
将下载的onnxruntime中的lib目录下的dll拷贝到mmdeploy_onnxruntime_ops.dll的同级目录(推荐使用Everything 进行查找)
将系统路径下的这两个dll改名,使其加载不到,可能涉及到修改文件权限的操作
Pip¶
pip installed package but could not
import
them.Make sure your are using conda pip.
$ which pip # /path/to/.local/bin/pip /path/to/miniconda3/lib/python3.9/site-packages/pip
apis¶
- mmdeploy.apis.build_task_processor(model_cfg: mmengine.config.config.Config, deploy_cfg: mmengine.config.config.Config, device: str) → mmdeploy.codebase.base.task.BaseTask[源代码]¶
Build a task processor to manage the deployment pipeline.
- 参数
model_cfg (str | mmengine.Config) – Model config file.
deploy_cfg (str | mmengine.Config) – Deployment config file.
device (str) – A string specifying device type.
- 返回
A task processor.
- 返回类型
BaseTask
- mmdeploy.apis.create_calib_input_data(calib_file: str, deploy_cfg: Union[str, mmengine.config.config.Config], model_cfg: Union[str, mmengine.config.config.Config], model_checkpoint: Optional[str] = None, dataset_cfg: Optional[Union[str, mmengine.config.config.Config]] = None, dataset_type: str = 'val', device: str = 'cpu') → None[源代码]¶
Create dataset for post-training quantization.
- 参数
calib_file (str) – The output calibration data file.
deploy_cfg (str | Config) – Deployment config file or Config object.
model_cfg (str | Config) – Model config file or Config object.
model_checkpoint (str) – A checkpoint path of PyTorch model, defaults to None.
dataset_cfg (Optional[Union[str, Config]], optional) – Model config to provide calibration dataset. If none, use model_cfg as the dataset config. Defaults to None.
dataset_type (str, optional) – The dataset type. Defaults to ‘val’.
device (str, optional) – Device to create dataset. Defaults to ‘cpu’.
- mmdeploy.apis.extract_model(model: Union[str, onnx.onnx_ml_pb2.ModelProto], start_marker: Union[str, Iterable[str]], end_marker: Union[str, Iterable[str]], start_name_map: Optional[Dict[str, str]] = None, end_name_map: Optional[Dict[str, str]] = None, dynamic_axes: Optional[Dict[str, Dict[int, str]]] = None, save_file: Optional[str] = None) → onnx.onnx_ml_pb2.ModelProto[源代码]¶
Extract partition-model from an ONNX model.
The partition-model is defined by the names of the input and output tensors exactly.
实际案例
>>> from mmdeploy.apis import extract_model >>> model = 'work_dir/fastrcnn.onnx' >>> start_marker = 'detector:input' >>> end_marker = ['extract_feat:output', 'multiclass_nms[0]:input'] >>> dynamic_axes = { 'input': { 0: 'batch', 2: 'height', 3: 'width' }, 'scores': { 0: 'batch', 1: 'num_boxes', }, 'boxes': { 0: 'batch', 1: 'num_boxes', } } >>> save_file = 'partition_model.onnx' >>> extract_model(model, start_marker, end_marker, dynamic_axes=dynamic_axes, save_file=save_file)
- 参数
model (str | onnx.ModelProto) – Input ONNX model to be extracted.
start_marker (str | Sequence[str]) – Start marker(s) to extract.
end_marker (str | Sequence[str]) – End marker(s) to extract.
start_name_map (Dict[str, str]) – A mapping of start names, defaults to None.
end_name_map (Dict[str, str]) – A mapping of end names, defaults to None.
dynamic_axes (Dict[str, Dict[int, str]]) – A dictionary to specify dynamic axes of input/output, defaults to None.
save_file (str) – A file to save the extracted model, defaults to None.
- 返回
The extracted model.
- 返回类型
onnx.ModelProto
- mmdeploy.apis.get_predefined_partition_cfg(deploy_cfg: mmengine.config.config.Config, partition_type: str)[源代码]¶
Get the predefined partition config.
提示
Currently only support mmdet codebase.
- 参数
deploy_cfg (mmengine.Config) – use deploy config to get the codebase and task type.
partition_type (str) – A string specifying partition type.
- 返回
A dictionary of partition config.
- 返回类型
dict
- mmdeploy.apis.inference_model(model_cfg: Union[str, mmengine.config.config.Config], deploy_cfg: Union[str, mmengine.config.config.Config], backend_files: Sequence[str], img: Union[str, numpy.ndarray], device: str) → Any[源代码]¶
Run inference with PyTorch or backend model and show results.
实际案例
>>> from mmdeploy.apis import inference_model >>> model_cfg = ('mmdetection/configs/fcos/' 'fcos_r50_caffe_fpn_gn-head_1x_coco.py') >>> deploy_cfg = ('configs/mmdet/detection/' 'detection_onnxruntime_dynamic.py') >>> backend_files = ['work_dir/fcos.onnx'] >>> img = 'demo.jpg' >>> device = 'cpu' >>> model_output = inference_model(model_cfg, deploy_cfg, backend_files, img, device)
- 参数
model_cfg (str | mmengine.Config) – Model config file or Config object.
deploy_cfg (str | mmengine.Config) – Deployment config file or Config object.
backend_files (Sequence[str]) – Input backend model file(s).
img (str | np.ndarray) – Input image file or numpy array for inference.
device (str) – A string specifying device type.
- 返回
The inference results
- 返回类型
Any
- mmdeploy.apis.torch2onnx(img: Any, work_dir: str, save_file: str, deploy_cfg: Union[str, mmengine.config.config.Config], model_cfg: Union[str, mmengine.config.config.Config], model_checkpoint: Optional[str] = None, device: str = 'cuda:0')[源代码]¶
Convert PyTorch model to ONNX model.
实际案例
>>> from mmdeploy.apis import torch2onnx >>> img = 'demo.jpg' >>> work_dir = 'work_dir' >>> save_file = 'fcos.onnx' >>> deploy_cfg = ('configs/mmdet/detection/' 'detection_onnxruntime_dynamic.py') >>> model_cfg = ('mmdetection/configs/fcos/' 'fcos_r50_caffe_fpn_gn-head_1x_coco.py') >>> model_checkpoint = ('checkpoints/' 'fcos_r50_caffe_fpn_gn-head_1x_coco-821213aa.pth') >>> device = 'cpu' >>> torch2onnx(img, work_dir, save_file, deploy_cfg, model_cfg, model_checkpoint, device)
- 参数
img (str | np.ndarray | torch.Tensor) – Input image used to assist converting model.
work_dir (str) – A working directory to save files.
save_file (str) – Filename to save onnx model.
deploy_cfg (str | mmengine.Config) – Deployment config file or Config object.
model_cfg (str | mmengine.Config) – Model config file or Config object.
model_checkpoint (str) – A checkpoint path of PyTorch model, defaults to None.
device (str) – A string specifying device type, defaults to ‘cuda:0’.
- mmdeploy.apis.torch2torchscript(img: Any, work_dir: str, save_file: str, deploy_cfg: Union[str, mmengine.config.config.Config], model_cfg: Union[str, mmengine.config.config.Config], model_checkpoint: Optional[str] = None, device: str = 'cuda:0')[源代码]¶
Convert PyTorch model to torchscript model.
- 参数
img (str | np.ndarray | torch.Tensor) – Input image used to assist converting model.
work_dir (str) – A working directory to save files.
save_file (str) – Filename to save torchscript model.
deploy_cfg (str | mmengine.Config) – Deployment config file or Config object.
model_cfg (str | mmengine.Config) – Model config file or Config object.
model_checkpoint (str) – A checkpoint path of PyTorch model, defaults to None.
device (str) – A string specifying device type, defaults to ‘cuda:0’.
- mmdeploy.apis.visualize_model(model_cfg: Union[str, mmengine.config.config.Config], deploy_cfg: Union[str, mmengine.config.config.Config], model: Union[str, Sequence[str]], img: Union[str, numpy.ndarray, Sequence[str]], device: str, backend: Optional[mmdeploy.utils.constants.Backend] = None, output_file: Optional[str] = None, show_result: bool = False, **kwargs)[源代码]¶
Run inference with PyTorch or backend model and show results.
实际案例
>>> from mmdeploy.apis import visualize_model >>> model_cfg = ('mmdetection/configs/fcos/' 'fcos_r50_caffe_fpn_gn-head_1x_coco.py') >>> deploy_cfg = ('configs/mmdet/detection/' 'detection_onnxruntime_dynamic.py') >>> model = 'work_dir/fcos.onnx' >>> img = 'demo.jpg' >>> device = 'cpu' >>> visualize_model(model_cfg, deploy_cfg, model, img, device, show_result=True)
- 参数
model_cfg (str | mmengine.Config) – Model config file or Config object.
deploy_cfg (str | mmengine.Config) – Deployment config file or Config object.
model (str | Sequence[str]) – Input model or file(s).
img (str | np.ndarray | Sequence[str]) – Input image file or numpy array for inference.
device (str) – A string specifying device type.
backend (Backend) – Specifying backend type, defaults to None.
output_file (str) – Output file to save visualized image, defaults to None. Only valid if show_result is set to False.
show_result (bool) – Whether to show plotted image in windows, defaults to False.
apis/tensorrt¶
- mmdeploy.apis.tensorrt.from_onnx(onnx_model: Union[str, onnx.onnx_ml_pb2.ModelProto], output_file_prefix: str, input_shapes: Dict[str, Sequence[int]], max_workspace_size: int = 0, fp16_mode: bool = False, int8_mode: bool = False, int8_param: Optional[dict] = None, device_id: int = 0, log_level: tensorrt.Logger.Severity = tensorrt.Logger.ERROR, **kwargs) → tensorrt.ICudaEngine[源代码]¶
Create a tensorrt engine from ONNX.
- 参数
onnx_model (str or onnx.ModelProto) – Input onnx model to convert from.
output_file_prefix (str) – The path to save the output ncnn file.
input_shapes (Dict[str, Sequence[int]]) – The min/opt/max shape of each input.
max_workspace_size (int) – To set max workspace size of TensorRT engine. some tactics and layers need large workspace. Defaults to 0.
fp16_mode (bool) – Specifying whether to enable fp16 mode. Defaults to False.
int8_mode (bool) – Specifying whether to enable int8 mode. Defaults to False.
int8_param (dict) – A dict of parameter int8 mode. Defaults to None.
device_id (int) – Choice the device to create engine. Defaults to 0.
log_level (trt.Logger.Severity) – The log level of TensorRT. Defaults to trt.Logger.ERROR.
- 返回
The TensorRT engine created from onnx_model.
- 返回类型
tensorrt.ICudaEngine
示例
>>> from mmdeploy.apis.tensorrt import from_onnx >>> engine = from_onnx( >>> "onnx_model.onnx", >>> {'input': {"min_shape" : [1, 3, 160, 160], >>> "opt_shape" : [1, 3, 320, 320], >>> "max_shape" : [1, 3, 640, 640]}}, >>> log_level=trt.Logger.WARNING, >>> fp16_mode=True, >>> max_workspace_size=1 << 30, >>> device_id=0) >>> })
- mmdeploy.apis.tensorrt.is_available(with_custom_ops: bool = False) → bool¶
Check whether backend is installed.
- 参数
with_custom_ops (bool) – check custom ops exists.
- 返回
True if backend package is installed.
- 返回类型
bool
- mmdeploy.apis.tensorrt.load(path: str, allocator: Optional[Any] = None) → tensorrt.ICudaEngine[源代码]¶
Deserialize TensorRT engine from disk.
- 参数
path (str) – The disk path to read the engine.
allocator (Any) – gpu allocator
- 返回
The TensorRT engine loaded from disk.
- 返回类型
tensorrt.ICudaEngine
- mmdeploy.apis.tensorrt.onnx2tensorrt(work_dir: str, save_file: str, model_id: int, deploy_cfg: Union[str, mmengine.config.config.Config], onnx_model: Union[str, onnx.onnx_ml_pb2.ModelProto], device: str = 'cuda:0', partition_type: str = 'end2end', **kwargs)[源代码]¶
Convert ONNX to TensorRT.
实际案例
>>> from mmdeploy.backend.tensorrt.onnx2tensorrt import onnx2tensorrt >>> work_dir = 'work_dir' >>> save_file = 'end2end.engine' >>> model_id = 0 >>> deploy_cfg = ('configs/mmdet/detection/' 'detection_tensorrt_dynamic-320x320-1344x1344.py') >>> onnx_model = 'work_dir/end2end.onnx' >>> onnx2tensorrt(work_dir, save_file, model_id, deploy_cfg, onnx_model, 'cuda:0')
- 参数
work_dir (str) – A working directory.
save_file (str) – The base name of the file to save TensorRT engine. E.g. end2end.engine.
model_id (int) – Index of input model.
deploy_cfg (str | mmengine.Config) – Deployment config.
onnx_model (str | onnx.ModelProto) – input onnx model.
device (str) – A string specifying cuda device, defaults to ‘cuda:0’.
partition_type (str) – Specifying partition type of a model, defaults to ‘end2end’.
apis/onnxruntime¶
- mmdeploy.apis.onnxruntime.is_available(with_custom_ops: bool = False) → bool¶
Check whether backend is installed.
- 参数
with_custom_ops (bool) – check custom ops exists.
- 返回
True if backend package is installed.
- 返回类型
bool
apis/ncnn¶
- mmdeploy.apis.ncnn.from_onnx(onnx_model: Union[onnx.onnx_ml_pb2.ModelProto, str], output_file_prefix: str)[源代码]¶
Convert ONNX to ncnn.
The inputs of ncnn include a model file and a weight file. We need to use an executable program to convert the .onnx file to a .param file and a .bin file. The output files will save to work_dir.
示例
>>> from mmdeploy.apis.ncnn import from_onnx >>> onnx_path = 'work_dir/end2end.onnx' >>> output_file_prefix = 'work_dir/end2end' >>> from_onnx(onnx_path, output_file_prefix)
- 参数
onnx_path (ModelProto|str) – The path of the onnx model.
output_file_prefix (str) – The path to save the output ncnn file.
- mmdeploy.apis.ncnn.is_available(with_custom_ops: bool = False) → bool¶
Check whether backend is installed.
- 参数
with_custom_ops (bool) – check custom ops exists.
- 返回
True if backend package is installed.
- 返回类型
bool