news 2026/8/27 1:08:28

mmdeploy+onnxruntime实现Windows GPU推理:C++部署全流程指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
mmdeploy+onnxruntime实现Windows GPU推理:C++部署全流程指南

简介:深度学习模型部署是将训练好的模型在目标环境中高效运行的关键工程环节。PyTorch等库虽适合训练,但生产环境推理更依赖轻量级、高性能的执行引擎。通过将模型转换为统一的中间表示,并利用运行时优化,可实现跨平台加速。onnxruntime作为跨平台推理引擎,支持CPU、GPU等多种后端,并能利用CUDA在Windows上实现显著加速。在工程实践中,C++接口能提供低延迟与高稳定性,而mmdeploy作为OpenMMLab的部署工具箱,能简化从PyTorch到ONNX的转换,并封装预处理、推理与后处理完整流水线。面对Windows环境下的C++推理需求,合理配置mmdeploy与onnxruntime版本、编译SDK以及处理DLL依赖事项是成功部署的关键。从模型导出到C++端最终运行,整个流程涉及环境搭建、代码集成和性能调优,为图像分类等模型在实际业务中的落地提供了一条经过验证的路径。 先把结论放前面:这个项目我前后折腾了一周,从模型导出到C++端跑通GPU推理,中间踩了无数坑,尤其是mmdeploy在Windows下的编译和onnxruntime的DLL依赖问题。这篇文章把整个链路完整捋一遍,从环境搭建到代码实现再到性能优化,全部基于我实测跑通的版本,你可以直接照着抄。

先说下项目背景。我手里有一个基于PyTorch训练好的图像分类模型,需要部署到Windows服务器上做实时预测。最初的方案是直接用LibTorch调用,但有几个问题:一是LibTorch体积太大,二是模型导出后还想用onnxruntime做量化等优化,三是后续可能要切换不同硬件平台。所以最后选型定为:mmdeploy作为部署框架,onnxruntime作为推理后端,C++作为最终调用语言,目标平台Windows + NVIDIA GPU。mmdeploy是OpenMMLab开源的部署工具箱,能把PyTorch模型导出为onnx、tensorrt等多种中间表示,并生成一套统一的SDK接口供C++调用,非常适合这种需要跨平台且要保留优化空间的项目。

如果你正好也在做类似的事,或者准备用mmdeploy跑Windows端C++推理,这篇内容应该能帮你少走不少弯路。

1. 技术选型与整体方案设计

1.1 为什么选mmdeploy而不是直接调onnxruntime

很多人会有疑问:既然模型都导成onnx了,为什么不直接用onnxruntime的C++ API推理,还要引入mmdeploy这一层?我最初也是这么想的,但真正做起来才发现直接调onnxruntime有几个麻烦的地方:

第一,预处理和后处理要自己写。PyTorch模型里的Normalize、Resize、argmax这些操作在onnx里虽然能导出,但中间层的输入输出往往需要特定格式的tensor,我们自己写的C++代码和Python端的处理逻辑稍不一致,结果就完全对不上。mmdeploy会把完整的图像预处理、推理、后处理包装成一条Pipeline,C++端只需要传入cv::Mat就能拿到最终结果。

第二,模型管理不规范。直接用onnxruntime加载单个onnx文件,模型版本更新、多模型切换都要自己去管理。mmdeploy生成一套带配置文件的模型目录结构,后续换模型只需要换目录,代码不用动。

第三,生态兼容性。mmdeploy是OpenMMLab官方维护的,对于ResNet、YOLO、SegFormer这些常见模型的导出和推理路径都验证过。我用的就是mmclassification训练的分类模型,走官方路径最稳妥。

当然,直接引onnxruntime也不是不行,如果你的模型非常简单,预处理后处理都是标准的resize加normalize,那自己写也没问题。但如果你打算长期维护一个部署项目,或者后续要做量化、换TensorRT后端,那我建议还是用mmdeploy这套框架。

1.2 推理后端选择:onnxruntime GPU版

mmdeploy支持多个推理后端:ONNX Runtime、TensorRT、OpenVINO、NCNN,还有TorchScript。我最终选ONNX Runtime GPU的决定因素有三个:

  • 依赖最少。TensorRT虽然性能更好,但需要额外安装TensorRT库,对CUDA版本要求也更严格;OpenVINO对NVIDIA显卡的支持历史上有段时间比较尴尬;ONNX Runtime的GPU版就是一个NuGet包或者zip包,拷到工程里就能用。
  • 通用性强。一个onnx模型既能用CPU后端跑,也能用GPU后端跑,切换成本极低。
  • 性能足够。对多数CNN模型来说,ONNX Runtime在NVIDIA GPU上的推理速度和TensorRT差距在20%以内,但对开发效率的提升是实打实的。

不过这里有个前提:如果你追求极致性能,或者模型特别大、对延迟特别敏感,那还是建议直接用TensorRT后端。我们在后面性能优化章节再细说差距。

1.3 整体架构与数据流

整个部署架构可以拆成四个环节:

  1. 训练端:PyTorch训练分类模型,保存为pth权重文件。
  2. 导出端:用mmdeploy的tools/deploy.py把pth转成onnx模型,同时生成推理所需的config.json。
  3. 部署端:C++工程调用mmdeploy SDK,加载onnx模型和配置文件,通过Pipeline接口完成推理。
  4. 应用端:拿到分类结果,接入业务逻辑,比如HTTP服务、消息队列等。

数据流是这样的:cv::Mat读入图像,mmdeploy内部完成resize、normalize、通道转换,输入到onnxruntime的GPU执行推理,输出经过softmax得到各类别概率,最终返回标签id和置信度。比较关键的是第四步,mmdeploy生成的config.json里包含了完整的预处理参数和后处理逻辑,C++端不需要关心这些细节,只管拿结果。

2. 环境准备与依赖安装

2.1 版本匹配是最大的坑,没有之一

我第一天的精力几乎全耗在版本搭配上。mmdeploy、onnxruntime、CUDA、cuDNN、VS版本不对应,编译根本过不去,就算编译过了,运行的时候也会报各种奇怪的DLL错误。下面是我实测可用的版本组合:

组件版本备注
操作系统Windows 10 21H2+Win11也行,建议64位
Visual Studio2019 16.11+ 或 2022需要Desktop development with C++
CUDA11.810.2太老,12.x和部分onnxruntime不兼容
cuDNN8.9.x for CUDA 11.x一定要和CUDA版本匹配
CMake3.20+建议3.24以上
Python3.8-3.10mmdeploy对3.11支持较晚,别冒险
PyTorch1.13-2.0对应CUDA 11.8版本
mmdeploy1.0+master分支或release版
onnxruntime1.14-1.16一定要下GPU版
OpenCV4.x我用的是4.8.0

这里重点说下onnxruntime版本的选择。mmdeploy编译的时候会去读取你本机安装的onnxruntime,如果你装了CPU版,后续推理就完全没有GPU加速。一定要去onnxruntime的GitHub Releases页面下载名称为onnxruntime-win-x64-gpu-1.14.1.zip这样的包,里面有CUDA和cudnn的DLL,删掉了就没法用GPU。

还有一个容易忽略的点:CUDA版本。onnxruntime GPU版对不同CUDA版本有单独的包,1.14.x对应CUDA 11.8,你需要先确认自己机器装的是不是11.8,再下载对应的onnxruntime包。我一开始用的是CUDA 12.0,结果onnxruntime直接不认。

2.2 安装与验证的实操步骤

我建议按下面的顺序安装,减少冲突:

  1. 安装VS2019或2022,勾选C++桌面开发工作负载,SDK和CMake工具链一并装上。
  2. 安装CUDA 11.8,命令就一条:setup.exe跑完,注意不要勾选“Driver components”里已经存在的同名驱动,避免把已有显卡驱动覆盖了。
  3. 安装cuDNN:把解压后的bin、include、lib目录里的文件分别复制到CUDA安装目录对应文件夹里。
  4. 用conda创建Python环境:conda create -n mmdeploy python=3.9 -y,然后装PyTorch 1.13.1+cu117或2.0+cu118。
  5. 下载onnxruntime GPU版zip,解压到指定目录,比如D:/libs/onnxruntime
  6. 下载OpenCV 4.8的Windows包,解压到D:/libs/opencv

验证环境是否正常,可以在Python里跑一下:

import onnxruntime as ort print(ort.get_available_providers())

如果输出里有CUDAExecutionProvider,说明onnxruntime能识别你的GPU环境。如果只有CPUExecutionProvider,说明CUDA/cuDNN没配对,或者onnxruntime版本和CUDA不匹配,先别往下走,把这块解决了再说。

2.3 mmdeploy源码获取与编译准备

mmdeploy的源码要从OpenMMLab的GitHub仓库拉,建议直接clone到本地,因为编译需要用它的源码目录。我用的命令是:

git clone https://github.com/open-mmlab/mmdeploy.git cd mmdeploy git checkout main

接下来要在Python环境里安装mmdeploy的依赖。mmdeploy本身是一个Python包,里面包含了模型转换脚本和推理SDK的Python接口。安装方式:

pip install -r requirements/runtime.txt pip install -r requirements/build.txt python setup.py install

编译SDK的话需要cmake。在mmdeploy根目录下创建一个build目录,然后执行cmake配置。这里重点说一下cmake的参数,很多人就是这里出了问题。我的配置命令是:

cmake .. \ -DMMDEPLOY_BUILD_SDK=ON \ -DMMDEPLOY_BUILD_EXAMPLES=ON \ -DMMDEPLOY_TARGET_BACKENDS=ort \ -DONNXRUNTIME_DIR=D:/libs/onnxruntime \ -DMMDEPLOY_BUILD_SDK_PYTHON_API=OFF \ -DMMDEPLOY_BUILD_TEST=OFF \ -DCMAKE_BUILD_TYPE=Release

这个含义说明一下:-DMMDEPLOY_TARGET_BACKENDS=ort表示只编译onnxruntime后端,如果你要支持TensorRT,就改成trt,或者写ort,trt也可以,但编译时间会变长。-DONNXRUNTIME_DIR指向onnxruntime的解压目录,cmake会自动去找include和lib。-DMMDEPLOY_BUILD_SDK=ON是必须的,只有开了这个才会生成C++ SDK的lib和头文件。

配置成功后,在build目录里执行:

cmake --build . --config Release

这一步要看机器性能,我第一次编译用了大概十分钟左右。编译完成后在build/install目录下会生成include、lib、bin三个文件夹,这就是我们要用的mmdeploy SDK,包括mmdeploy的dll、onnxruntime的dll等。这里要特别注意,bin目录里有很多DLL,后续C++工程运行时需要把这些DLL拷到exe旁边或者加到系统PATH里。

3. 模型导出与转换流程

3.1 从PyTorch导出onnx的核心参数解析

模型导出是整个链路里最容易出错但最不花时间的一步。我这里以ResNet50分类模型为例,其他模型大同小异。mmdeploy官方推荐使用tools/deploy.py来做导出,它的好处是会同时生成onnx模型和mmdeploy推理所需的config.json,并且自动完成模型验证。

先看下deploy.py的参数结构:

python tools/deploy.py \ deploy_cfg \ model_cfg \ checkpoint.pth \ test.jpg \ --work-dir 输出目录 \ --device cuda:0

其中deploy_cfg是描述部署策略的配置文件,在mmdeploy的configs目录下。针对onnxruntime后端,分类模型对应的是configs/mmcls/classification_onnxruntime_dynamic.py。这个配置里定义了输入尺寸是动态还是静态、是否做TRT优化等信息。

keypoint是model_cfg,也就是PyTorch模型训练时的配置,例如mmclassification的resnet50.py。checkpoint就是训练保存的pth文件。test.jpg是一张测试图,mmdeploy会用它做一次前向验证,确保导出的onnx结果和PyTorch一致。

我实际执行的命令:

python tools/deploy.py \ configs/mmcls/classification_onnxruntime_dynamic.py \ mmclassification/configs/resnet/resnet50_8xb32_in1k.py \ resnet50.pth \ test.jpg \ --work-dir deploy_resnet50 \ --device cuda:0

执行成功后deploy_resnet50目录下会生成一个resnet50.onnx和一个deploy.json,另外还有一个pipeline.json。这三个文件就是mmdeploy的完整模型包。我们要把它整个放到一个model_dir目录里,后续C++端通过这个目录加载模型。

注意这里选择的分类配置是dynamic版本,意味着onnx的输入尺寸是动态的,可以用任意尺寸的输入图像。如果你的业务场景输入尺寸固定,建议用static版本的配置,推理速度会稍快一些,显存占用也略低。

3.2 动态输入尺寸的取舍与验证方法

动态输入听起来很美好,但实际使用中有一点要注意:动态尺寸的onnx模型在GPU上推理时,会频繁触发CUDA的显存分配和释放,尤其在并发场景下可能导致性能抖动。我最后实际部署时改成了固定尺寸,输入统一resize到224x224,这样对分类任务完全够用,性能也更稳定。

导出之后一定要做一步验证,用onnxruntime直接加载导出的onnx模型,拿同一张测试图跑一遍,对比PyTorch原始模型的输出。mmdeploy的deploy.py在导出后会自动做一次前向对比,输出结果会显示“Tensor matches”或者“Tensor mismatches”。如果显示mismatches,说明导出过程中数值精度出了问题,常见原因是动态尺寸设置不当或者某些算子被错误替换。

另外我还习惯用python的onnx库做一次严格检查,确保onnx模型结构没损坏:

import onnx model = onnx.load("deploy_resnet50/resnet50.onnx") onnx.checker.check_model(model)

这个检查会报告算子版本、输入输出形状等基础问题。如果检查错误,多半是opset版本太低或太高,mmdeploy在导出时会自动设置opset,但如果有自定义op,可能需要手动调整。

3.3 mmdeploy模型目录结构说明

导出的模型目录最好保持这样的结构,C++端才能正确加载:

model_dir/ ├── resnet50.onnx ├── deploy.json └── pipeline.json

deploy.json里记录了模型元信息,包括输入名称、输出名称、预处理参数、类别数量等。pipeline.json是推理pipeline的定义,比如输入节点做什么预处理、中间经过哪个网络、最后做什么后处理。mmdeploy的C++ SDK启动时会先读这两个json,解析出完整的推理流程。

这里有一个常见问题:如果你手动修改了deploy.json里的某些参数,但pipeline.json没同步改,会导致推理结果异常或者直接报错。比如我把输入尺寸从224改成256,结果忘了改pipeline.json里的同步参数,结果模型加载没问题,但推理完的结果全是乱的。所以改配置时务必两个文件一起改。

4. C++工程搭建与推理代码实现

4.1 工程目录结构与CMake配置

C++工程的关键是头文件路径、库路径、DLL路径三件套。我的工程目录结构如下:

MNIST_Deploy_Demo/ ├── CMakeLists.txt ├── include/ │ └── mmdeploy/ │ ├── model.h │ ├── classifier.h │ └── ... ├── lib/ │ ├── mmdeploy.lib │ └── onnxruntime.lib ├── src/ │ └── main.cpp └── models/ └── resnet50/ ├── resnet50.onnx ├── deploy.json └── pipeline.json

include和lib目录里的内容直接从mmdeploy编译好的install目录拷贝过来,注意是install/include和install/lib,不是build目录下的中间产物。

CMakeLists.txt是重点,我直接贴出来,按我的路径改就能用:

cmake_minimum_required(VERSION 3.20) project(mmdeploy_demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # mmdeploy SDK set(MMDEPLOY_DIR "D:/libs/mmdeploy_install") include_directories(${MMDEPLOY_DIR}/include) # OpenCV set(OpenCV_DIR "D:/libs/opencv/build") find_package(OpenCV REQUIRED) # onnxruntime set(ORT_DIR "D:/libs/onnxruntime") include_directories(${ORT_DIR}/include) add_executable(demo src/main.cpp) target_link_libraries(demo PRIVATE ${MMDEPLOY_DIR}/lib/mmdeploy.lib ${ORT_DIR}/lib/onnxruntime.lib ${OpenCV_LIBS} ) file(COPY ${MMDEPLOY_DIR}/bin/ ALL_DLL DESTINATION ${CMAKE_BINARY_DIR}/Release/)

这里有个细节:mmdeploy的lib目录下面可能有多个lib文件,比如mmdeploy.lib是主SDK库,但onnxruntime.lib需要单独链接。如果onnxruntime的lib路径不对,cmake能过,但链接阶段会报一堆无法解析的外部符号。检查方式是在VS里看输出窗口,如果出现LNK2019错误,基本都是lib没链全。

4.2 推理核心代码逐段解析

我用的是mmdeploy SDK的C API,因为C API最稳定,不受C++ ABI变化影响。代码是从官方example改的,针对分类任务做了简化,加上错误处理。

第一段是创建模型和推理器:

#include "mmdeploy/apis/c/mmdeploy/model.h" #include "mmdeploy/apis/c/mmdeploy/classifier.h" #include <opencv2/opencv.hpp> int main() { // 模型目录路径,需要包含onnx和json配置文件 const char* model_path = "models/resnet50"; // 设备类型cuda,设备id是0号卡 const char* device_name = "cuda"; int device_id = 0; // 创建模型实例 mmdeploy_model_t model = {}; int ret = mmdeploy_model_create_by_path(model_path, &model); if (ret != MMDEPLOY_SUCCESS) { printf("load model failed: %d\n", ret); return -1; } // 创建分类器 mmdeploy_classifier_t classifier = {}; ret = mmdeploy_classifier_create(model, device_name, device_id, &classifier); if (ret != MMDEPLOY_SUCCESS) { printf("create classifier failed: %d\n", ret); return -1; }

注意mmdeploy_model_create_by_path的第一个参数是模型目录的路径,不是onnx文件的路径。如果只给onnx文件路径,会报找不到deploy.json的错误。

第二段是图像读取与推理调用。mmdeploy需要把cv::Mat转换成mmdeploy_mat_t格式:

// 读取图像 cv::Mat img = cv::imread("test.jpg"); if (img.empty()) { printf("image not found\n"); return -1; } // 构造mmdeploy的mat结构体 mmdeploy_mat_t mm_mat{ img.data, img.rows, img.cols, img.channels(), MMDEPLOY_PIXEL_FORMAT_BGR, MMDEPLOY_DATA_TYPE_UINT8 }; // 调用推理,一次可以传入多张图像,这里只传一张 mmdeploy_classification_t* results = nullptr; int* result_count = nullptr; ret = mmdeploy_classifier_apply(classifier, &mm_mat, 1, &results, &result_count); if (ret != MMDEPLOY_SUCCESS) { printf("inference failed: %d\n", ret); return -1; }

这里有个关键点:mmdeploy_mat_t中的MMDEPLOY_PIXEL_FORMAT_BGR要和OpenCV默认的BGR通道顺序对应。如果用了RGB格式,图像颜色会翻掉,分类结果可能直接错得离谱。我第一次没注意这个,结果一张猫的图被分类成了狗。

第三段是结果解析:

// 输出结果的label_id和score for (int i = 0; i < result_count[0]; i++) { printf("top-%d: label=%d, score=%.6f\n", i + 1, results[i].label_id, results[i].score); } // 释放资源 mmdeploy_classifier_release_result(results, result_count); mmdeploy_classifier_destroy(classifier); mmdeploy_model_destroy(model); return 0; }

result_count是一个数组,每个元素对应一张输入图的结果数量,默认是top5。如果只想拿top1,可以改deploy.json里的后处理参数,或者在拿到结果后只取第一个。这里要注意,结果释放的顺序是先results后result_count,如果颠倒会崩溃。

4.3 编译运行与常见编译错误处理

VS里新建一个空项目,把CMakeLists.txt加载进去,选中Release x64配置,build一下。如果一切正常,exe会生成在build/Release目录下。运行前必须把DLL都放到exe同目录,否则会报“找不到mmdeploy.dll”之类的错误。需要拷贝的DLL包含:

  • mmdeploy.dll
  • onnxruntime.dll
  • opencv_world4xx.dll
  • 如果用了onnxruntime GPU版,还有onnxruntime_providers_cuda.dll、onnxruntime_providers_shared.dll
  • CUDA的cudart64_.dll和cudnn64_.dll

如果懒得手动拷贝,在CMake里我已经写了file(COPY ... ALL_DLL)指令,它会自动把安装目录bin下的DLL拷到输出目录。

运行后如果程序直接崩溃,大概率是DLL版本冲突。最常见的是onnxruntime_providers_cuda.dll和onnxruntime.dll版本不一致,比如onnxruntime是1.14.1,而cuda provider是1.13的,就会在初始化CUDAExecutionProvider时崩溃。解决办法很简单,把onnxruntime官网的GPU包里的所有文件整体解压,保证两个DLL都是同一个zip包里的。

5. GPU版预测性能实测与优化

5.1 CPU与GPU推理速度对比

跑通之后第一件事就是验证GPU到底有没有用上。我第一个测试就是同时用CPU和GPU跑同一批图,看耗时差距。测试环境是i5-10400 + RTX 3060,模型是ResNet50,输入224x224,50张图取平均:

后端平均耗时(ms)备注
CPU(onnxruntime)35.2不算预处理
GPU(CUDAExecutionProvider)6.8不算预处理
GPU + 静态输入6.1显存分配次数减少

从35毫秒降到6毫秒,效果非常明显。如果你的模型是输入更大的检测模型,比如YOLO系列,差距只会更大。有一点要提醒:GPU推理在小模型(比如MobileNet)上的优势没那么大,因为kernel启动和显存拷贝的开销可能就占了大头。我试过MobileNetV3,GPU只比CPU快30%左右。这说明GPU不是万能的,小模型在CPU上单线程跑也不差。

5.2 显存优化与输入尺寸固定的影响

onnxruntime GPU版默认会在第一次推理时分配一批显存,包括workspace和中间tensor的buffer。如果输入尺寸不固定,每次尺寸变化都会触发重新分配,导致显存碎片化和推理延迟抖动。我把模型导出为static版本后,显存占用从2.1GB降到了1.4GB,速度也稳定了。

如果你要在同一块显卡上同时跑多个模型,可以在初始化onnxruntime的时候传入session options,限制可用的显存比例。但这需要通过mmdeploy的配置注入到onnxruntime sessions里,比较麻烦。我的做法是直接在C++里换一个更精细的显存管理策略:使用固定尺寸输入,并且开启mmdeploy的“reuse intermidiate buffer”特性,方法是修改deploy.json中对应的fusenode选项。具体参数名在不同mmdeploy版本里不一样,我用的版本是直接默认开启的,所以没有额外配置。

5.3 多线程调用与并发推理的实测心得

做服务化部署时还需要考虑并发。onnxruntime的GPUsession是线程安全的,也就是说多个线程可以同时调用同一个session的Run方法。但mmdeploy的classifier接口不是线程安全的,每个线程需要创建自己的classifier实例,不过它们可以共享同一个mmdeploy_model_t模型对象。

实测在8线程并发推理时,RTX 3060的利用率能跑到90%以上,单张图延迟从6.8毫秒上升到9.5毫秒左右,总体吞吐从147FPS提升到接近400FPS。这个数据说明后端的瓶颈已经不再是GPU,而是图像解码、预处理和显存copy的时间。如果做高并发服务,建议用OpenCV的并行解码或者直接使用GPU图像解码库,这样吞吐还能再上一个台阶。

5.4 TensorRT后端真的有必要吗

既然性能这么重要,要不要再折腾一轮TensorRT?我简单对比了一下同一模型在onnxruntime GPU和TensorRT FP32下的延迟,大约差了15%。TensorRT FP16的延迟大概是onnxruntime GPU的60%左右,效果确实好。但我最后没切TensorRT,原因有三个:一是TensorRT需要额外的编译和序列化时间,模型变了就要重来;二是TensorRT对Windows的支持没有onnxruntime顺手,尤其是在VS2019下编译mmdeploy的trt backend时依赖特别多;三是onnxruntime的精度和TensorRT一致,不会因为优化导致结果偏差。如果是对延迟极致敏感的场景,比如实时视频流分析,那可以上TensorRT,否则onnxruntime GPU已经足够。

6. 常见问题与排查技巧实录

6.1 运行时报DLL缺失或版本冲突

这个错误见得最多,报错形式一般是“找不到onnxruntime.dll”或者“无法定位程序输入点”。出现这个问题基本只有两种原因:DLL不在exe目录下,或者DLL版本对不上。

第一种好解决,把DLL拷贝到exe目录,或者在系统PATH里添加DLL所在目录。第二种更隐蔽,比如你机器上同时装了多个版本的onnxruntime,程序加载时优先加载了PATH里更早版本的DLL,就会出现“无法定位程序输入点”。排查方法是用Process Explorer之类的工具看进程加载了哪些DLL,或者直接用Dependencies工具打开exe查依赖图。

我建议把工程里所有依赖统一管理,不要既用NuGet的onnxruntime又用本地下载的onnxruntime,也不要出现两个版本的onnxruntime同时存在。我自己在D盘同时下载了1.14和1.16两个版本的onnxruntime,结果cmake配置的时候指向了1.16,运行的时候又从系统PATH里加载到了1.14的DLL,导致程序崩溃在初始化阶段。浪费了一个下午才定位到。

6.2 模型加载失败与json配置文件错误

mmdeploy_model_create_by_path返回失败时,如果错误码是MMDEPLOY_E_FAIL或者MMDEPLOY_E_INVALID_ARG,常见原因有三个:

  • 路径不对。mmdeploy要的是模型目录,路径末尾不要加onnx文件名。
  • deploy.json缺失或格式错误。mmdeploy会严格解析deploy.json和pipeline.json,任何一个字段不合法都会失败。可以用json解析工具打开看看是否符合JSON格式。
  • 模型和后端不匹配。比如你把一个为TensorRT导出的模型配上onnxruntime后端,就会报错。

我在调试时还被一个中文路径坑过。模型目录放在带中文的路径下,比如D:/部署/模型,结果加载失败。后来把路径改成全英文就正常了。mmdeploy在Windows下对中文路径的支持不太好,这应该是和底层onnxruntime的文件读取接口有关,建议工程路径和模型路径都不要有中文和空格。

6.3 推理结果全是乱码或置信度异常低

如果代码能跑,但结果明显不对,比如置信度极低或者label_id恒为0,八成是预处理参数和模型训练时不匹配。mmdeploy虽然在config.json里带了预处理参数,但如果你的自己改了image size或normalize参数,会导致输入数据分布完全不对。

排查方法很简单,把同一张测试图分别用Python的mmdeploy API和C++跑一遍,对比输出。如果Python正常C++不对,说明C++传入的mat格式有问题,重点检查通道顺序和数据类型。如果两者都不对,说明导出时的部署配置有问题,回到deploy.py的配置文件检查preprocess参数。

6.4 显存不足与OOM的处理经验

第一次跑GPU推理时可能会遇到显存不足,特别是显存只有4G的卡。报错信息一般是“CUDA out of memory”。我的解决办法是:

  1. 把输入尺寸固定成最小可接受的尺寸,比如从256降到224。
  2. 在mmdeploy编译时开启内存复用选项,对onnxruntime后端尤其有效。
  3. 分批推理,每次不要传太多图像,尤其在并发场景下降batch size。
  4. 如果是Windows环境,检查是否有其他程序占用了显存,比如浏览器和视频播放器都可能吃到GPU显存。

另外还要注意一个细节:onnxruntime GPU在创建session时分配的workspace是固定大小,如果你同时开了多个C++ classifier实例,每个实例都有自己的workspace,显存会成倍增长。实测4G显存在两个进程各跑一个ResNet50时已经非常紧张,最后我把所有推理收敛到一个进程内,用线程并发才解决。

6.5 乱码问题:Windows控制台的编码大坑

调试C++程序时,如果printf输出中文label,控制台经常出现乱码。这是因为Windows控制台默认代码页是GBK,而mmdeploy返回的label_name是UTF-8编码。解决方法是程序启动时调用SetConsoleOutputCP(CP_UTF8),或者干脆不要直接输出中文,输出label_id后在业务层做映射。这个小问题浪费了我不少时间,顺便提一下,方便后来人避坑。

7. 后续扩展方向

项目跑通之后,可以往这几个方向扩展:

第一,切换后端做性能调优。上面说过TensorRT的性能更好,mmdeploy也支持,只需要重新编译对应后端,模型导出时指定trt配置文件即可。C++端代码不需要大改,因为mmdeploy的接口是统一的。

第二,做模型量化。onnxruntime GPU版支持FP16和INT8量化,mmdeploy也有对应的量化工具链。INT8量化后模型体积能缩到1/4,推理速度在部分算子上有明显提升,但代价是精度会掉一些,需要做验证。我实测ResNet50做INT8量化后准确率下降了0.7个百分点,速度提升约18%,这个幅度在业务场景里可以接受。

第三,接入真实业务场景。现在代码只是单张图推理的demo,如果要接到生产环境,还需要封装成服务接口,比如用gRPC或HTTP暴露API,再接个消息队列处理并发请求。我自己的经验是把推理封装成独立进程,用共享内存或者Redis和主业务进程通信,避免主进程崩溃导致推理服务不可用。

第四,考虑多GPU并行。onnxruntime GPU版支持指定device_id,如果有多张卡,可以创建多个classifier实例分别绑定不同GPU,再用线程池调度。这里要注意每个线程独立绑定device,避免CUDA context冲突,我第一次多卡部署时没做线程隔离,导致两张卡交替调用,性能反而下降了不少。

从整个项目来看,mmdeploy在Windows + C++ + onnxruntime这条链路上的成熟度已经很高,真正花时间的地方主要是版本匹配和工程配置。只要把环境按本文的方式搭好,后面替换模型和算法都很顺。我在实际项目中已经跑了近两个月,稳定性没问题,推理耗时稳定在6毫秒左右,显存占用不超过1.5GB,在24小时不间断运行下没有出现内存泄漏或性能衰减。如果你在部署过程中遇到问题,重点排查环境和DLL依赖,多半问题都不在代码上。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/26 23:57:11

FPGA逻辑验证神器:VSCode + Icarus Verilog + GTKWave 高效工作流

1. 项目概述&#xff1a;为什么选择这套“轻量级”组合&#xff1f; 如果你是一名FPGA开发者&#xff0c;或者正在学习数字电路设计&#xff0c;那么你一定对Vivado、Quartus这些“庞然大物”又爱又恨。它们功能强大&#xff0c;但启动慢、占用资源多&#xff0c;写个简单的Ve…

作者头像 李华
网站建设 2026/8/26 23:56:24

传送带破损检测实战:YOLOv11数据集构建与模型训练全流程

简介&#xff1a;机器视觉技术在工业设备状态监测中应用日益广泛&#xff0c;目标检测作为核心方法&#xff0c;其准确性高度依赖数据标注质量与训练配置。传送带作为连续运输关键设备&#xff0c;表面破损缺陷形态多样&#xff0c;检测需求迫切&#xff0c;但真实的工业场景数…

作者头像 李华
网站建设 2026/8/26 23:53:44

PlumeLog分布式日志系统:从零搭建到生产环境部署指南

1. 项目概述&#xff1a;为什么我们需要PlumeLog&#xff1f;如果你负责过线上系统的运维&#xff0c;或者参与过稍微有点规模的分布式项目开发&#xff0c;一定对“找日志”这件事深恶痛绝。服务A报了个错&#xff0c;你得先登录到服务器A&#xff0c;用grep、tail -f在一堆日…

作者头像 李华
网站建设 2026/8/26 23:50:50

AI生成PPT后处理:HTML转PPTX/PDF的格式转换与优化实战

1. 项目概述&#xff1a;当AI生成PPT后&#xff0c;我们还需要做什么&#xff1f;“AI帮我生成了PPT&#xff0c;然后呢&#xff1f;” 这大概是很多初次接触AI生成PPT工具的朋友&#xff0c;在短暂的兴奋过后&#xff0c;会立刻涌上心头的困惑。无论是Midjourney、DALLE 3生成…

作者头像 李华
网站建设 2026/8/26 23:50:31

蓝桥杯环境治理题解:二分答案+最小瓶颈路径Floyd

1. 这道题不是考图论&#xff0c;是考你敢不敢把“治理成本”当答案来二分 2022年蓝桥杯国赛那道标着“环境治理”的题&#xff0c;标题里写着Floyd二分&#xff0c;但几乎所有刚看完题干的选手第一反应都是——“这不就是个最短路问题吗&#xff1f;跑一遍Floyd完事”。我当年…

作者头像 李华