torch-onnx-ncnn 部署 yolov5:从模型导出到 ncnn 推理的完整配置骨架
1. 为什么 YOLOv5 落地总要过 torch→onnx→ncnn 这一关如果你手上有一个训练好的 YOLOv5 权重想把它塞进 C 服务、安卓 App 或者嵌入式板子里跑实时检测那基本绕不开这条链路PyTorch 训练出来的.pt先导出成 ONNX再由 ncnn 的onnx2ncnn转成.param.bin最后用 ncnn 的 C 接口加载推理。原因很直接——ncnn 没有 Python 依赖、体积小、对 ARM 和 Vulkan 支持好是移动端和端侧部署里最省心的选择之一。但这条链路真正上手时坑几乎都集中在中间那几步ONNX 导出时输出节点不对、onnx2ncnn之后 param 文件里出现一堆 ncnn 不支持的层、Focus 层被拆成 split/crop/concat、Reshape 的0xxx导致维度写死、输出层名字和 C 代码里对不上。这些问题不会在报错里直接告诉你往往要跑到推理阶段发现框全乱或者直接崩掉才回头查。这篇就按「导出 → 转换 → 改 param → 编译 → 验证」的顺序把每一步的可复制命令、参数含义和验证动作写清楚。适合已经会用 PyTorch、需要在 C/移动端落地推理的开发者。工具链调用过程中如果涉及远程脚本、模型文件分发或者多机协作可以用 TaoToken 的统一 Key/API 通道把调用串起来省去每个工具单独配鉴权的麻烦官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。2. 前置准备环境、版本与 TaoToken 通道2.1 版本选择比想象中重要YOLOv5 的仓库迭代很快v5.0、v6.0、v7.0 导出的 ONNX 结构差异不小尤其是 Focus 层和 Detect 头的输出组织方式。这篇以v5.0为基准因为它的export.py输出相对干净社区里针对 ncnn 的 param 修改资料也最多。如果你用的是更新版本Focus 层可能已经被Conv替代param 修改步骤会简化但输出层对齐的逻辑是一样的。拉代码和权重git clone -b v5.0 https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt wget https://github.com/ultralytics/yolov5/releases/download/v5.0/yolov5s.pt先跑一遍原版推理确认权重没问题python3 detect.py --source inference/images --weights yolov5s.pt --conf 0.25这一步能出图说明 PyTorch 侧环境是通的后面出问题就只可能是转换环节。2.2 ncnn 编译ncnn 本体编译不复杂关键是 protobuf 依赖和 Vulkan 选项。如果只是 CPU 推理-DNCNN_VULKANOFF能省掉 Vulkan SDK 的安装sudo apt install libprotobuf-dev protobuf-compiler git clone https://github.com/Tencent/ncnn.git cd ncnn mkdir -p build cd build cmake -DNCNN_VULKANOFF .. make -j4编译完在build/tools/下会有onnx2ncnn和ncnnoptimizebuild/example/下有yolov5示例程序后面都要用到。2.3 TaoToken 在工具链里的位置这条链路本身是本地命令但实际项目里经常要跨机器训练机导出 ONNX、转换机跑 onnx2ncnn、目标设备拉模型文件。如果这些环节用脚本自动化鉴权和调用地址分散在各处会很乱。TaoToken 提供统一的 Key 和 API 入口把模型对话、Coding Plan、控制台、API Keys 管理都收在一个通道里脚本里只需要维护一份凭证。接入文档和 Key 管理入口API Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基础地址是https://taotoken.net/api注意这个地址不带 UTM 参数脚本里直接写死即可。3. 可复制配置从 .pt 到 ncnn param/bin3.1 导出 ONNX 并做 onnxsimv5.0 的export.py直接支持 ONNX 导出固定输入尺寸 640、batch 1python3 models/export.py --weights yolov5s.pt --img 640 --batch 1导出后会得到yolov5s.onnx。但原始导出的图里有些冗余节点比如恒等算子、多余的 Cast直接喂给 onnx2ncnn 容易产生奇怪的层。先用 onnxsim 做一次图简化python3 -m onnxsim yolov5s.onnx yolov5s-sim.onnxonnxsim 会输出简化前后的节点数对比正常情况下能砍掉几十个节点。这一步不是必须但强烈建议做后面 param 文件会干净很多。3.2 onnx2ncnn 转换cd ncnn/build/tools/onnx ./onnx2ncnn yolov5s-sim.onnx yolov5s.param yolov5s.bin转换过程会打印每一层的处理情况。如果看到Unsupported slice或者Unsupported layer type之类的警告先记下来这些就是后面要手动改的地方。3.3 param 文件的三处关键修改打开yolov5s.param第一行是层数和 blob 数v5.0 原始导出通常是 309 层左右。需要改三处第一处Focus 层替换。原始 param 里 Focus 被拆成了 split、crop、concat 等十来个层ncnn 有专门的YoloV5Focus层可以一步替代。找到从输入开始的那一串 split/crop/concat整体替换成一行YoloV5Focus focus 1 1 input focus_blob层数会从 309 降到 300 左右具体数字看你替换掉了几层。第二处Reshape 的 0xxx 改成 0-1。在 Permute 层前面通常有一个 Reshape参数里写着0xxxxxx 是具体数字。这个写法把 batch 维写死了ncnn 里要改成0-1让它动态推断Reshape reshape_xxx 1 1 permute_input reshape_output 0-1 2xxx 3xxx只改0那一项后面的2、3保持不动。第三处确认输出层名字。param 文件最后几个 Permute 层的输出 blob 名字就是 C 代码里要取的输出。记下这三个名字后面yolov5.cpp里要对上。3.4 ncnnoptimize 转 fp16改完 param 后用 ncnnoptimize 过一遍顺便把权重转成 fp16 存储模型体积能减一半cd ncnn/build/tools ./ncnnoptimize yolov5s.param yolov5s.bin yolov5s-opt.param yolov5s-opt.bin 65536最后的65536是 fp16 存储的标志位116。转完之后yolov5s-opt.bin就是最终要部署的权重文件。4. 验证请求与成功结果逐阶段对齐4.1 ONNX 输出对齐导出 ONNX 后先用 onnxruntime 跑一遍和 PyTorch 的输出做数值比对。写个小脚本import onnxruntime as ort import numpy as np import torch # PyTorch 侧 model torch.hub.load(ultralytics/yolov5, custom, pathyolov5s.pt) dummy torch.zeros(1, 3, 640, 640) with torch.no_grad(): pt_out model(dummy)[0].numpy() # ONNX 侧 sess ort.InferenceSession(yolov5s-sim.onnx) onnx_out sess.run(None, {images: dummy.numpy()})[0] print(max diff:, np.abs(pt_out - onnx_out).max())max diff在 1e-4 量级以内就算对齐成功。如果差得离谱多半是导出时输出节点选错了回去检查export.py里的 output 配置。4.2 ncnn 推理结果比对ncnn 的example/yolov5.cpp里需要改两个地方模型路径和输出层名字。// 模型路径 const char* param_path yolov5s-opt.param; const char* bin_path yolov5s-opt.bin; // 输出层名字和 param 文件最后的 Permute 输出对齐 std::vectorconst char* out_names {output, 350, 367};改完重新编译 examplecd ncnn/build make -j4 cd example ./yolov5 test.jpg跑通的话会打印检测框坐标和置信度。拿同一张图分别跑 PyTorch 和 ncnn对比框的位置和类别偏差在几个像素内就说明整条链路是通的。4.3 用 TaoToken 串联验证脚本如果验证脚本要跑在多台机器上或者需要把结果回传到统一的地方可以用 TaoToken 的 API 通道做调用中转。模型对话入口适合快速验证输出格式模型对话https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content长期跑编码和 Agent 任务的话Coding Plan 更合适Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content5. 本篇常见错排查5.1 onnx2ncnn 报 Unsupported layer最常见的是Unsupported slice和Unsupported Resize。v5.0 的 Focus 层会触发 slice按 3.3 节替换成 YoloV5Focus 即可。Resize 层如果参数不对检查 ONNX 导出时的--img尺寸是否和推理时一致尺寸不匹配会导致 Resize 的 scale 算错。5.2 param 改完推理崩或者输出全零先检查 Reshape 的0-1是不是只改了第一项。如果误改了2或3维度会彻底乱掉。另外确认 YoloV5Focus 那一行的输入 blob 名字和上一层的输出名字一致名字对不上 ncnn 会静默跳过这一层导致后面全是垃圾数据。5.3 输出层名字对不上yolov5.cpp里的out_names必须和 param 文件最后三个 Permute 层的输出 blob 名字完全一致。打开 param 文件拉到最底部看最后几个 Permute 行的最后一个字段是什么直接复制过去。如果名字里有数字比如350、367那是 ncnn 自动生成的 blob 名不要手动改。5.4 编译 example 时报找不到 ncnn确认CMakeLists.txt里的 ncnn 路径指向你编译出来的build/install目录。如果之前只跑了make没跑make install需要先cd ncnn/build make install然后在 example 的 CMake 里设置ncnn_DIR为install/lib/cmake/ncnn。5.5 推理速度不达预期CPU 推理下检查是否开了 OpenMP。ncnn 编译时默认开启但如果你的 CMake 配置里关了推理会慢好几倍。另外 fp16 存储只减小体积不加速 CPU 计算要加速得用 Vulkan 或者 ARM 的 NEON 优化。移动端部署时确认编译目标架构是arm64-v8a而不是x86_64。6. 接入与排障把这条链路固化成可复用流程整条链路跑通一次之后建议把命令固化成脚本尤其是 param 修改那几步——手动改容易漏。可以写个 Python 脚本用正则匹配 split/crop/concat 段落做替换Reshape 的0xxx也用正则统一改成0-1这样换模型版本时只需要调正则规则。工具链的鉴权和调用地址统一走 TaoToken脚本里只维护一份 Key换机器或者换环境时不用到处改配置。API Keys 在控制台里管理接入文档里有各语言的调用示例API Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content如果后面要接 Claude Code 做自动化编码或者 Agent 任务Anthropic 兼容入口在这里ClaudeCodeAnthropichttps://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content最后提醒一句param 文件改完之后一定要用 ncnnoptimize 再过一遍不要直接拿手改的 param 去推理。ncnnoptimize 会做算子融合和常量折叠能提前暴露 param 里的结构错误比跑到推理阶段再 debug 省事得多。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →