1. 从 BM1684 到 BM1688:一次真实的迁移踩坑记录
算能 BM1684 升级 BM1688 这件事,我原本以为只是把--chip bm1684改成--chip bm1688就完事了。结果从 tpu_mlir 模型转换到 SDK 更新,再到工程代码编译调试,前前后后折腾了快一周。这篇文章把整个迁移过程拆成可复制的步骤,重点讲清楚 BM1684 迁移 BM1688 时 tpu_mlir 转换参数怎么改、SDK 头文件库文件怎么对齐、以及升级后代码调试最容易卡住的几个报错。
先说清楚这套流程适合谁:如果你手上已经有跑在 BM1684 上的 YOLOv5 或其他检测模型工程,现在要迁到 BM1688(CV186AH)平台,并且用的是算能官方 sophonsdk_edge 系列 SDK + tpu_mlir 工具链,那这篇基本能覆盖你 80% 的坑。核心检索词就三个:BM1684、BM1688、tpu_mlir,围绕它们展开。
迁移的整体链路是这样的:旧平台 BM1684 上你有一套能跑的 onnx 模型和 C++ 推理工程;新平台 BM1688 需要重新用 tpu_mlir 把 onnx 转成 bmodel,同时 SDK 从旧版本升到 v1.9 系列,头文件和库文件路径全变了,工程里的 Makefile、CMakeLists、后处理代码都得跟着改。任何一环没对齐,表现就是编译报错或者模型跑起来一个框都检测不到。
我这次迁移的起点是一个 YOLOv5s 的 PCB 缺陷检测模型,四个输出头,之前在 BM1684 上跑得好好的。下面按实际操作顺序来,每一步都给可复制的命令。
2. 前置准备:SDK 下载、Docker 镜像与 tpu_mlir 安装
2.1 SDK 下载与目录结构
先去算能技术资料页面下载 sophonsdk_edge 的官方 release 包,我这次用的是sophonsdk_edge_v1.9_official_release。解压之后重点看两个目录:tpu_mlir(模型转换工具链)和sophon-img(板端运行时库和头文件)。这两个目录后面会反复用到。
这里有个容易忽略的点:BM1684 时代你可能用的是Release_v2312-LTS那套 tpu-mlir,版本号是tpu-mlir_20231116。BM1688 必须换到 v1.9 配套的 tpu_mlir,不能混用。混用的直接后果就是转换出来的 bmodel 在板端加载失败,或者精度对不上。
2.2 Docker 镜像拉取与标签管理
以前转 BM1684 模型时,拉镜像命令是:
docker pull sophgo/tpuc_dev:latest现在转 BM1688,官方给的命令还是sophgo/tpuc_dev:latest。问题来了:latest 标签会覆盖,你本地原来的 BM1684 环境就没了。我的做法是先把旧的 latest 重命名保留:
docker tag sophgo/tpuc_dev:latest sophgo/tpuc_dev:bm1684然后再拉新的 latest,并打上 bm1688 标签:
docker pull sophgo/tpuc_dev:latest docker tag sophgo/tpuc_dev:latest sophgo/tpuc_dev:bm1688这样两个环境互不干扰,随时可以切回去验证 BM1684 的老模型。
2.3 创建容器与安装 tpu_mlir
创建 BM1688 专用容器,把当前目录挂进 /workspace:
docker run --privileged --name bm1688 -v $PWD:/workspace -it sophgo/tpuc_dev:bm1688进容器后装 tpu_mlir。官方源下载慢,加清华源:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple tpu_mlir[all] pip install -i https://pypi.tuna.tsinghua.edu.cn/simple tpu_mlir[onnx,torch]装完进 tpu_mlir 目录 source 环境变量:
cd /workspace/sophonsdk_edge_v1.9_official_release/tpu_mlir source envsetup.sh这一步做完,model_transform.py、run_calibration.py、model_deploy.py这些命令才能直接用。
2.4 工作目录准备
建一个模型工作目录,把 onnx 和校准图片放进去:
mkdir -p model_yolov5s/workspace cd model_yolov5s # 把 onnx 模型和 calib 图片目录拷进来我图省事,直接把 BM1684 时代的整个model_yolov5s目录拷过来,然后清空 workspace 里的中间文件:
cp -drf /data/chw/.../model_yolov5s /data/chw/.../tpu_mlir/model_yolov5s rm -rf model_yolov5s/workspace/*这样 onnx、calib 图片、coco.names 都还在,只清掉旧的 mlir 和 bmodel,避免新旧文件混淆。
3. tpu_mlir 模型转换:onnx 转 mlir 与 int8 量化配置
3.1 onnx 转 mlir
在model_yolov5s/workspace目录下执行:
model_transform.py \ --model_name yolov5s \ --model_def ../edge_compute_best_20230821.onnx \ --input_shapes [[1,3,640,640]] \ --mean 0.0,0.0,0.0 \ --scale 0.0039216,0.0039216,0.0039216 \ --keep_aspect_ratio \ --pixel_format rgb \ --output_names 339,391,443 \ --test_input ../calib/00ca3e9473b4407bb1e72a38a7c6c69f.jpg \ --test_result yolov5s_top_outputs.npz \ --mlir yolov5s.mlir--output_names这三个名字必须用 Netron 打开你的 onnx 确认,不同训练脚本导出的节点名不一样。这一步如果名字写错,转换不会报错,但后面量化出来的模型输出维度会不对。
3.2 生成校准表
run_calibration.py yolov5s.mlir \ --dataset ../calib \ --input_num 200 \ -o yolov5s_cali_tablecalib 目录里放 200 张左右的代表性图片。数量太少校准表不准,int8 掉点严重;太多则转换时间线性增长。200 张是个比较稳的平衡点。
3.3 编译 int8 量化模型
model_deploy.py \ --mlir yolov5s.mlir \ --quantize INT8 \ --calibration_table yolov5s_cali_table \ --chip bm1688 \ --test_input yolov5s_in_f32.npz \ --test_reference yolov5s_top_outputs.npz \ --tolerance 0.85,0.45 \ --model yolov5s_pcb_4shuchu_1688_int8_sym.bmodel关键改动就一处:--chip bm1688。BM1684 时代这里是--chip bm1684。tolerance 参数如果量化后精度掉得厉害,可以适当放宽,但别放太松,否则掩盖真实问题。
3.4 混精度配置
如果 int8 精度不达标,需要混精度。混精度不能照抄 BM1684 的配置,芯片名、层名都要改。核心思路是找出量化敏感层,在model_deploy.py里通过--quantize配合混合精度配置文件指定哪些层走 fp32。具体哪些层敏感,用--debug输出每层余弦相似度,挑相似度低的层。
3.5 转换参数对照表
| 参数 | BM1684 旧值 | BM1688 新值 | 说明 |
|---|---|---|---|
| --chip | bm1684 | bm1688 | 必改 |
| --quantize | INT8 | INT8 | 不变 |
| --tolerance | 0.85,0.45 | 0.85,0.45 | 视精度调整 |
| --output_names | 旧节点名 | 新节点名 | 需 Netron 确认 |
| tpu_mlir 版本 | 20231116 | v1.9 配套 | 不可混用 |
4. SDK 更新后的库文件头文件对齐与编译验证
4.1 拷贝 libsophon 库和头文件
BM1688 的 SDK 版本是libsophon_soc_0.4.11_aarch64。在板子上建目录,从编译机 scp 过来:
mkdir -p /data1/chw/bitmain_all_1688/lib/sophon cd /data1/chw/bitmain_all_1688/lib/sophon scp -rf root@192.168.1.10:/data/chw/.../libsophon_soc_0.4.11_aarch64/opt/sophon/libsophon-0.4.11/lib/* ./头文件同理:
mkdir -p /data1/chw/bitmain_all_1688/include/sophon scp -r root@192.168.1.10:/data/chw/.../libsophon-0.4.11/include/* ./4.2 拷贝 ffmpeg 和 opencv 库头文件
先把 BM1684 时代的旧 ffmpeg、opencv 头文件和库全删掉,避免新旧混用导致链接到错误符号。然后从sophon-media-soc_1.9.0_aarch64拷贝:
scp -r root@192.168.1.10:/data/chw/.../sophon-ffmpeg_1.9.0/lib/* /data1/chw/bitmain_all_1688/lib/ffmpeg/ scp -r root@192.168.1.10:/data/chw/.../sophon-ffmpeg_1.9.0/include/* /data1/chw/bitmain_all_1688/include/ffmpeg/ scp -r root@192.168.1.10:/data/chw/.../sophon-opencv_1.9.0/include/* /data1/chw/bitmain_all_1688/include/opencv/ scp -r root@192.168.1.10:/data/chw/.../sophon-opencv_1.9.0/lib/* /data1/chw/bitmain_all_1688/lib/opencv/4.3 修改 Makefile 路径
因为删了旧文件夹、建了新文件夹,Makefile 里的 include 和 lib 路径必须同步改。重点检查-I和-L两处,确保指向bitmain_all_1688而不是旧的bitmain_all_1684。
4.4 编译报错逐项排查
直接 make,看报错一个个解决。
报错一:fatal error: bmnn_utils.h: No such file or directory
这个头文件在算能模型 demo 的 dependencies 目录里,不在标准 SDK include 里。把它拷到include/sophon下即可。
报错二:error: 'string' is not a member of 'std'
新 SDK 头文件没有隐式包含<string>,在报错的源文件顶部加:
#include <string>报错三:error: 'bmcv_padding_atrr_t' was not declared; did you mean 'bmcv_padding_attr_t'?
这是 SDK 更新后改了拼写,旧名atrr改成了attr。解决方式有两种:在公共头文件加typedef bmcv_padding_attr_t bmcv_padding_atrr_t;,或者直接包含bm_wrapper.hpp,里面已经做了兼容定义。我选后者,一行 include 搞定。
报错四:/usr/bin/ld: cannot find -lbmvideo
-lbmvideo、-lbmjpuapi、-lbmjpulite这几个库是 BM1684 时代的,BM1688 SDK 里已经没有了。直接在 Makefile 里把这几个-l去掉。
4.5 编译成功验证
改完上面四处,再 make 应该就能过。编译通过不代表模型能跑对,下一步必须验证推理结果。
5. 常见错误排查:模型跑不出框与后处理对齐
5.1 现象:程序能跑但一个框都没有
编译过了,程序也起来了,但检测结果为空。这时候别急着改代码,先用官方 demo 验证模型本身有没有问题。
把sophon-demo里的 YOLOv5 sample 整个拷到 BM1688 盒子上,直接在盒子上编译(省去交叉编译环境搭建):
mkdir build && cd build cmake -DTARGET_ARCH=soc -DSDK=/opt/sophon/ -DCMAKE_BUILD_TYPE=Debug .. make注意-DSDK=/opt/sophon/在没装 SDK 的情况下其实不生效,需要在 CMakeLists.txt 里手动把库和头文件路径指向你实际拷贝的目录。
然后跑官方 demo:
./yolov5_bmcv.soc --bmodel=./easnet_pcb_4shuchu_1688_int8_sym.bmodel --input=shigu5.mp4 --classnames=coco.names如果 demo 能出框,说明模型没问题,问题在你的工程代码。
5.2 根因:输出头选择与 sigmoid 处理不一致
我对比了自己的后处理代码和官方 demo,发现关键差异。官方 demo 在post_process里对输出做了 sigmoid 解码:
dst[0] = (sigmoid(ptr[0]) * 2 - 0.5 + i % feat_w) / feat_w * m_net_w; dst[1] = (sigmoid(ptr[1]) * 2 - 0.5 + i / feat_w) / feat_h * m_net_h; dst[2] = pow((sigmoid(ptr[2]) * 2), 2) * anchors[tidx][anchor_idx][0]; dst[3] = pow((sigmoid(ptr[3]) * 2), 2) * anchors[tidx][anchor_idx][1]; dst[4] = sigmoid(ptr[4]);而我的代码里没有 sigmoid 处理。原因是我之前 BM1684 转模型时,用的不是那三个分散输出头,而是 concat 之后的汇总输出(1×25200×8),那个输出在模型内部已经做过 sigmoid,所以后处理不需要再算。这次 BM1688 转模型时我用了三个分散输出头,后处理却没加 sigmoid,自然解不出框。
5.3 解决:重新转模型指定汇总输出
两种改法:一是后处理加 sigmoid,二是重新转模型用汇总输出。我选后者,因为改动最小。重新执行model_transform.py,把--output_names改成汇总输出节点名(Netron 里看是output),并且用混合量化:
model_transform.py \ --model_name yolov5s \ --model_def ../edge_compute_best_20230821.onnx \ --input_shapes [[1,3,640,640]] \ --mean 0.0,0.0,0.0 \ --scale 0.0039216,0.0039216,0.0039216 \ --keep_aspect_ratio \ --pixel_format rgb \ --output_names output \ --test_input ../calib/00ca3e9473b4407bb1e72a38a7c6c69f.jpg \ --test_result yolov5s_top_outputs.npz \ --mlir yolov5s.mlir然后重新走校准和model_deploy.py,chip 仍是 bm1688。转出来的 bmodel 替换进工程,检测框正常出现。
5.4 排查路径总结
| 现象 | 排查动作 | 结论 |
|---|---|---|
| 编译报错找不到头文件 | 检查 include 路径 | 补拷 bmnn_utils.h |
| 链接报错找不到库 | 检查 Makefile -l 项 | 删 BM1684 专属库 |
| 程序跑但无框 | 官方 demo 验证模型 | 模型 OK,代码问题 |
| demo 有框工程无框 | 对比后处理 | 输出头/sigmoid 不一致 |
| 宽高异常 2048 | 保存中间图 | 前处理正常,定位后处理 |
6. 迁移完成后如何继续验证与接入工具链
模型转换和代码调试都通了之后,建议做两件事巩固这次迁移成果。
第一,把 BM1684 和 BM1688 两套 bmodel 放在同一个测试集上跑,对比 mAP 和推理耗时。int8 量化后精度掉 1-2 个点是正常的,如果掉超过 5 个点,回去检查校准表质量和混精度配置。第二,把这次改动的 Makefile、CMakeLists、后处理代码差异整理成 patch,下次再迁其他模型直接套用。
如果你在迁移过程中需要频繁验证模型输出、对比不同量化配置的结果,可以用 TaoToken 的模型对话能力快速做结果比对和参数分析,省去本地写脚本的时间。API 接入地址是 https://taotoken.net/api,模型对话入口在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model_chat。需要管理多个项目的 Key 时,控制台在 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console,API Keys 页面在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api_keys。
如果你后续要做长期的边缘 AI 模型迭代和 Agent 化部署,Coding Plan 适合把模型转换、代码调试、版本管理串成流水线,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding_plan。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc,Claude Code 相关配置参考 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=claude_code。
最后留一个我踩过的坑:BM1688 的 SDK 头文件里bmcv_padding_attr_t这个拼写改动,如果你工程里有多处引用旧名,别一个个改,直接在公共头文件加 typedef 兼容,省事且不容易漏。模型转换那边,--output_names一定要用 Netron 确认,别凭记忆写,这是最容易导致「模型能转但跑不出结果」的隐形坑。