
简介深度学习模型部署是将训练好的模型在目标环境中高效运行的关键工程环节。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会把完整的图像预处理、推理、后处理包装成一条PipelineC端只需要传入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 整体架构与数据流整个部署架构可以拆成四个环节训练端PyTorch训练分类模型保存为pth权重文件。导出端用mmdeploy的tools/deploy.py把pth转成onnx模型同时生成推理所需的config.json。部署端C工程调用mmdeploy SDK加载onnx模型和配置文件通过Pipeline接口完成推理。应用端拿到分类结果接入业务逻辑比如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 21H2Win11也行建议64位Visual Studio2019 16.11 或 2022需要Desktop development with CCUDA11.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.0master分支或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 安装与验证的实操步骤我建议按下面的顺序安装减少冲突安装VS2019或2022勾选C桌面开发工作负载SDK和CMake工具链一并装上。安装CUDA 11.8命令就一条setup.exe跑完注意不要勾选“Driver components”里已经存在的同名驱动避免把已有显卡驱动覆盖了。安装cuDNN把解压后的bin、include、lib目录里的文件分别复制到CUDA安装目录对应文件夹里。用conda创建Python环境conda create -n mmdeploy python3.9 -y然后装PyTorch 1.13.1cu117或2.0cu118。下载onnxruntime GPU版zip解压到指定目录比如D:/libs/onnxruntime。下载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_SDKON \ -DMMDEPLOY_BUILD_EXAMPLESON \ -DMMDEPLOY_TARGET_BACKENDSort \ -DONNXRUNTIME_DIRD:/libs/onnxruntime \ -DMMDEPLOY_BUILD_SDK_PYTHON_APIOFF \ -DMMDEPLOY_BUILD_TESTOFF \ -DCMAKE_BUILD_TYPERelease这个含义说明一下-DMMDEPLOY_TARGET_BACKENDSort表示只编译onnxruntime后端如果你要支持TensorRT就改成trt或者写ort,trt也可以但编译时间会变长。-DONNXRUNTIME_DIR指向onnxruntime的解压目录cmake会自动去找include和lib。-DMMDEPLOY_BUILD_SDKON是必须的只有开了这个才会生成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.jsondeploy.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.jsoninclude和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.dllonnxruntime.dllopencv_world4xx.dll如果用了onnxruntime GPU版还有onnxruntime_providers_cuda.dll、onnxruntime_providers_shared.dllCUDA的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输入224x22450张图取平均后端平均耗时(ms)备注CPUonnxruntime35.2不算预处理GPUCUDAExecutionProvider6.8不算预处理GPU 静态输入6.1显存分配次数减少从35毫秒降到6毫秒效果非常明显。如果你的模型是输入更大的检测模型比如YOLO系列差距只会更大。有一点要提醒GPU推理在小模型比如MobileNet上的优势没那么大因为kernel启动和显存拷贝的开销可能就占了大头。我试过MobileNetV3GPU只比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”。我的解决办法是把输入尺寸固定成最小可接受的尺寸比如从256降到224。在mmdeploy编译时开启内存复用选项对onnxruntime后端尤其有效。分批推理每次不要传太多图像尤其在并发场景下降batch size。如果是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依赖多半问题都不在代码上。本文还有配套的精品资源点击获取