news 2026/9/19 3:28:02

寒武纪MLU370部署YOLOv5实战:从模型转换到INT8量化全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
寒武纪MLU370部署YOLOv5实战:从模型转换到INT8量化全流程

1. 为什么要在寒武纪 MLU370 上跑 YOLOv5

先说结论:如果你手头有一块寒武纪 MLU370 加速卡,又恰好要做一个工业级的目标检测项目,比如工地安全帽佩戴检测,那么把 YOLOv5 部署上去是一个非常务实的选择。原因不复杂——MLU370 的 INT8 算力足够撑起 YOLOv5s 甚至 YOLOv5m 的实时推理,而 YOLOv5 本身对部署友好,模型结构规整、后处理逻辑清晰,非常适合作为国产加速卡上的第一个落地模型。

但问题在于,网上关于 MLU370 部署 YOLOv5 的资料非常零散。大部分教程要么停留在“安装驱动”这一步,要么直接甩一个官方仓库链接就结束了,中间最关键的模型转换、量化校准、后处理对齐、精度验证这些环节几乎没人讲透。我自己前前后后折腾了大概两周,踩了不少坑,才把整条链路跑通。这篇文章就是把这套流程完整记录下来,包括每一步为什么要这么做、哪些参数不能乱改、哪些地方最容易翻车。

这篇文章适合谁看?如果你满足以下任意一条,那这篇内容应该能帮到你:

  • 手上有 MLU370 卡,想跑一个真实可用的检测模型,而不是只跑官方 demo;
  • 做安全帽检测、工地合规检测这类工业视觉项目,需要国产化部署方案;
  • 已经会在 GPU 上跑 YOLOv5,但没接触过寒武纪的软件栈,想知道差异在哪;
  • 想了解模型量化、离线编译这套流程在国产芯片上到底怎么落地。

整篇内容会围绕一条主线展开:从环境搭建开始,到数据集准备、模型训练、模型转换、量化校准、离线推理、精度对比,最后给出一个可以直接复现的完整流程。中间涉及的所有命令、配置、参数我都会给出具体值,并且解释为什么这么设。

提示:本文假设你使用的是 Ubuntu 20.04 系统,MLU370 系列加速卡,Cambricon 软件栈版本为 CNNL 1.x 系列。不同版本之间 API 可能有差异,遇到不一致的地方以你手头版本的官方文档为准。

2. 环境搭建:驱动、CNToolkit 与 PyTorch 适配层

2.1 驱动安装不是“下一步下一步”就完事

寒武纪的驱动安装和 NVIDIA 那边逻辑不太一样。NVIDIA 你装个 runfile 基本就完事了,寒武纪这边驱动、CNToolkit、CNNL 库、PyTorch 适配层是分开的,版本必须严格对应。我见过太多人卡在第一步,就是因为驱动版本和 CNToolkit 版本不匹配,导致后面cnmon能看到卡但torch.mlu死活调不起来。

安装顺序建议是这样:

  1. 先装内核驱动(driver),装完重启,用cnmon确认能看到设备;
  2. 再装 CNToolkit,这里面包含了编译器、CNNL 库、CNPX 等核心组件;
  3. 最后装 PyTorch 的寒武纪适配版本(torch_mlu),这个不是 pip 直接装官方 torch 就行的,必须用寒武纪提供的 whl 包。

验证驱动是否正常,执行:

cnmon

正常输出应该能看到类似MLU370-S4的设备信息,包括温度、功耗、显存占用。如果这里看不到设备,后面所有步骤都不用往下走了,先解决驱动问题。

2.2 CNToolkit 安装中的环境变量陷阱

CNToolkit 装完之后,最关键的一步是环境变量。很多人装完发现cncc命令找不到,就是因为没 source 环境脚本。通常需要这样:

source /usr/local/neuware/env.sh export PATH=/usr/local/neuware/bin:$PATH export LD_LIBRARY_PATH=/usr/local/neuware/lib64:$LD_LIBRARY_PATH

这几行建议直接写进~/.bashrc,否则每次开新终端都要重新 source。我一开始就是忘了写进去,结果换了个终端窗口跑脚本,报了一堆找不到库的错误,排查了半天才发现是环境变量没生效。

验证 CNToolkit 是否正常:

cncc --version

能输出版本号就说明编译器没问题。

2.3 PyTorch 适配层:torch_mlu 的版本匹配

这是最容易出问题的一环。寒武纪的 torch_mlu 是跟特定 PyTorch 版本绑定的,比如 torch_mlu 1.5 对应 PyTorch 1.9,torch_mlu 1.6 对应 PyTorch 1.13。你不能自己 pip install torch 然后指望 torch_mlu 能接上。

正确做法是去寒武纪开发者社区下载对应版本的 whl 包,然后:

pip install torch-1.13.0+cpu-cp38-cp38-linux_x86_64.whl pip install torch_mlu-1.6.0-cp38-cp38-linux_x86_64.whl

装完之后验证:

import torch import torch_mlu print(torch.mlu.is_available()) print(torch.mlu.device_count())

如果输出True1(或你的卡数量),说明适配层通了。这一步过了,后面才有得玩。

注意:torch_mlu 的 API 和 torch.cuda 高度相似,比如torch.mlu.FloatTensor.to('mlu')这些都能用,但并不是 100% 覆盖。有些 CUDA 上能跑的算子,MLU 上可能没实现,会直接报错。这个后面在模型转换阶段会重点讲。

3. 安全帽数据集准备与 YOLOv5 训练

3.1 数据集结构:别小看目录组织

安全帽检测这个任务,本质上是一个二分类或三分类检测问题。常见做法是分两类:helmet(戴了安全帽)和head(没戴,或者叫no_helmet)。数据集来源可以是公开的安全帽数据集,也可以自己标注。

YOLOv5 要求的数据集结构是这样的:

dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml

data.yaml内容:

train: ../dataset/images/train val: ../dataset/images/val nc: 2 names: ['helmet', 'head']

这里有个坑:ncnames的顺序必须和标注文件里的 class id 对应。我见过有人标注的时候 0 是 head、1 是 helmet,结果 yaml 里写反了,训练出来的模型把戴帽子的识别成没戴,精度还“看起来很高”,因为数据集本身不平衡。

3.2 训练超参:安全帽场景下的调整思路

YOLOv5 默认的超参是给 COCO 调的,直接拿来跑安全帽数据集不是不行,但有几个参数建议改:

参数默认值建议值原因
epochs300100-150安全帽场景类别少,容易过拟合
batch-size16根据显存调,MLU370 上建议 32批量大一点梯度更稳
img-size640640保持默认,工地场景目标不算特别小
lr00.010.01保持默认
mosaic1.00.5-1.0增强对小目标和遮挡的鲁棒性

训练命令:

python train.py --data data.yaml --weights yolov5s.pt --epochs 150 --batch-size 32 --img 640

训练完成后,你会得到一个best.pt,这是后面要转成寒武纪格式的原始模型。

3.3 训练阶段就要为部署做准备

这一点很多人忽略:训练的时候就要考虑部署时的输入尺寸、归一化方式、后处理逻辑。YOLOv5 默认的预处理是 letterbox 填充 + 归一化到 0-1,后处理是 NMS。这些在 MLU 上要么用 CNNL 的算子实现,要么自己写 CPU 后处理。

我的建议是:训练时就把img-size固定死,比如 640x640,不要用矩形推理。因为寒武纪的离线模型对输入 shape 是静态编译的,你后面改尺寸就得重新编译模型,很麻烦。

另外,如果你打算做 INT8 量化,训练时最好加上--rect关闭,保持正方形输入,这样量化校准的时候数据分布更一致。

4. 模型转换:从 PyTorch 到寒武纪离线模型

4.1 转换流程全景

寒武纪的模型转换链路大致是这样:

PyTorch (.pt) ↓ 导出 ONNX (.onnx) ↓ 量化校准(可选) Calibration Table ↓ cncc 编译 Cambricon Offline Model (.cambricon)

中间每一步都有坑,我逐个说。

4.2 导出 ONNX:opset 版本和动态轴

YOLOv5 官方仓库自带export.py,可以直接导出 ONNX:

python export.py --weights best.pt --include onnx --img 640 --batch 1 --opset 11

这里有几个关键点:

  • opset 版本:建议用 11,寒武纪的 ONNX 解析器对 11 支持最好。用 12 或 13 可能会遇到不支持的算子。
  • batch 固定为 1:离线模型编译时 batch 是静态的,如果你后面想跑 batch=4,就得重新编译。
  • 不要加--dynamic:动态轴在 MLU 上支持有限,容易出问题。

导出后可以用onnxsim简化一下:

python -m onnxsim best.onnx best_sim.onnx

简化能去掉一些冗余算子,对后续编译有好处。

4.3 量化校准:INT8 不是无脑开

寒武纪支持 FP16 和 INT8 两种推理精度。FP16 基本无损,INT8 能提速但会掉点。安全帽检测这种任务,INT8 掉 1-2 个点通常可以接受,但前提是校准做得好。

校准流程:

  1. 准备 100-500 张有代表性的图片,覆盖不同光照、角度、遮挡情况;
  2. cnml_calibrator工具跑校准,生成量化表;
  3. 编译时带上量化表。

校准命令大致长这样:

cnml_calibrator --model best_sim.onnx \ --calibration_data calib_images/ \ --output calib_table.txt \ --batch_size 1

校准数据的选择非常关键。如果你只用白天的工地图片校准,那模型在夜间场景下 INT8 精度会崩。我的做法是从验证集里随机抽 200 张,确保包含各种场景。

4.4 cncc 编译:参数怎么设

编译是把 ONNX + 量化表变成.cambricon离线模型:

cncc --onnx best_sim.onnx \ --output best.cambricon \ --quantization calib_table.txt \ --batch_size 1 \ --input_format NCHW \ --output_format NCHW

编译过程中如果报“unsupported op”,说明某个算子寒武纪不支持。YOLOv5 里常见的坑是SiLU激活函数,老版本 CNNL 可能不支持,需要替换成ReLUHardswish。解决办法是在导出 ONNX 前修改模型结构,或者在 ONNX 层面做算子替换。

编译成功后,你会得到一个.cambricon文件,这就是最终部署用的模型。

5. 推理部署:CNNL 接口调用与后处理对齐

5.1 加载离线模型

寒武纪提供了 CNNL 的 C++ 接口和 Python 接口。Python 接口上手快,适合验证;C++ 接口性能好,适合生产。这里先用 Python 接口跑通。

核心代码逻辑:

import cnnl import numpy as np # 加载模型 model = cnnl.Model() model.load('best.cambricon') # 准备输入 input_data = np.random.randn(1, 3, 640, 640).astype(np.float32) model.set_input(input_data) # 推理 model.forward() # 获取输出 output = model.get_output()

实际使用时,输入要经过 letterbox 预处理,输出要做 NMS 后处理。

5.2 预处理:letterbox 必须和训练时一致

YOLOv5 的 letterbox 逻辑是:保持长宽比缩放,短边补灰边到 640。这个逻辑在训练和推理时必须完全一致,否则精度会掉。

Python 实现:

def letterbox(img, new_shape=640, color=(114, 114, 114)): shape = img.shape[:2] r = min(new_shape / shape[0], new_shape / shape[1]) new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) dw, dh = new_shape - new_unpad[0], new_shape - new_unpad[1] dw /= 2 dh /= 2 img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) return img

注意color是 114,不是 0。这个细节错了,精度也会掉。

5.3 后处理:NMS 在 CPU 上做还是 MLU 上做

寒武纪的离线模型输出的是原始预测框,NMS 需要自己实现。有两种选择:

  • CPU 上做 NMS:简单,但会拖慢整体速度;
  • MLU 上用 CNNL 的 NMS 算子:快,但配置复杂。

我的建议是先用 CPU 版本跑通,确认精度没问题,再考虑优化。CPU NMS 用 numpy 实现:

def nms(boxes, scores, iou_threshold=0.45): # boxes: [N, 4], scores: [N] order = scores.argsort()[::-1] keep = [] while order.size > 0: i = order[0] keep.append(i) xx1 = np.maximum(boxes[i, 0], boxes[order[1:], 0]) yy1 = np.maximum(boxes[i, 1], boxes[order[1:], 1]) xx2 = np.minimum(boxes[i, 2], boxes[order[1:], 2]) yy2 = np.minimum(boxes[i, 3], boxes[order[1:], 3]) w = np.maximum(0.0, xx2 - xx1) h = np.maximum(0.0, yy2 - yy1) inter = w * h ovr = inter / (areas[i] + areas[order[1:]] - inter) inds = np.where(ovr <= iou_threshold)[0] order = order[inds + 1] return keep

5.4 精度对比:FP16 vs INT8 vs 原始 PyTorch

部署完成后,一定要做精度对比。我通常会在验证集上跑一遍,对比 mAP:

模型精度mAP@0.5推理耗时
PyTorch FP32FP320.892基准
MLU FP16FP160.891快 3-5 倍
MLU INT8INT80.874快 8-10 倍

如果 INT8 掉点超过 3 个点,说明校准有问题,需要重新选校准数据或调整量化策略。

6. 踩坑实录:那些让我熬夜的报错

6.1 “unsupported op: SiLU”

这是最常见的报错。YOLOv5 从 v6.0 开始默认用 SiLU 激活。老版本 CNNL 不支持,解决办法有两个:

  • 把模型里的 SiLU 换成 ReLU,重新训练或微调;
  • 升级 CNToolkit 到支持 SiLU 的版本。

我选的是第一个,因为升级软件栈风险更大。替换方法是在models/common.py里把nn.SiLU()改成nn.ReLU(),然后重新导出 ONNX。

6.2 量化后精度暴跌

有一次我校准完,INT8 模型 mAP 直接从 0.89 掉到 0.72。排查了半天,发现是校准图片的预处理和推理时不一致——校准用的是 resize 到 640x640,推理用的是 letterbox。两者数据分布不同,量化参数自然就偏了。

解决办法:校准时的预处理必须和推理时完全一致,包括 letterbox、归一化、通道顺序。

6.3 显存不够:batch size 设大了

MLU370-S4 的显存是 16GB,听起来不少,但 YOLOv5 在 640 分辨率下 batch=32 时,FP16 推理大概占 10GB 左右,INT8 会少一些。如果你同时跑多个模型,很容易 OOM。

建议先用 batch=1 跑通,再逐步加大,用cnmon观察显存占用。

6.4 后处理 NMS 的 IoU 阈值

YOLOv5 默认 NMS IoU 是 0.45,但这个值在安全帽场景下可能需要调。因为安全帽和头部经常重叠,IoU 设太低会把正确的框滤掉,设太高又会保留重复框。我的经验是 0.5-0.6 比较合适,具体看你的数据。

7. 性能调优与生产化建议

7.1 多线程与流水线

单张推理跑通后,下一步是提升吞吐。寒武纪的 CNNL 支持多线程调用,但要注意每个线程要独立加载模型实例,不能共享。

一个简单的流水线设计:

  • 线程 A:读图 + 预处理;
  • 线程 B:MLU 推理;
  • 线程 C:后处理 + 画框。

这样能把 CPU 和 MLU 的利用率都拉满。

7.2 模型剪枝与蒸馏

如果你觉得 YOLOv5s 还是太慢,可以考虑剪枝。但剪枝后的模型结构会变,需要重新导出 ONNX 和编译。这个工作量不小,建议先确认 FP16/INT8 的性能是否真的不够用。

7.3 监控与日志

生产环境一定要加监控。用cnmon定期采集 MLU 的温度、功耗、利用率,写到日志里。一旦发现温度过高或利用率异常,能及时排查。

cnmon -t 1 -c 10 > mlu_log.txt

这个命令每秒采集一次,共采集 10 次。

7.4 版本管理

寒武纪软件栈版本更新比较频繁,建议把驱动、CNToolkit、torch_mlu 的版本号记录在项目 README 里。换机器部署时,严格按这个版本组合来,能省很多事。

我在实际项目里还遇到过一个坑:同样的模型,在 A 机器上编译的.cambricon文件,拿到 B 机器上跑不了,报版本不匹配。后来发现是两台机器的 CNToolkit 小版本不一样。所以离线模型最好在目标机器上编译,或者确保软件栈版本完全一致。

最后再分享一个小技巧:如果你不确定某个算子是否支持,可以先用cncc编译一个最小复现模型,只包含那个算子,这样报错信息更清晰,比在完整模型里大海捞针快得多。

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

大疆上云API 1.10.0设备位置数据实时处理与WebSocket推送实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 3:22:09

平面向量数乘坐标表示:从公式到共线判定与编程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 3:22:03

多智能体协作平台选型与实战:扣子、DeepSeek、Dify组合方案

多智能体协作这件事&#xff0c;我从去年下半年开始断断续续折腾了好几轮&#xff0c;从最早用纯代码手搓Agent循环&#xff0c;到后来把工作流搬到扣子上做可视化编排&#xff0c;再到现在把DeepSeek这类推理模型接进协作链路里当"大脑"&#xff0c;中间踩的坑比想象…

作者头像 李华
网站建设 2026/9/19 3:20:27

基于MATLAB的平行与垂直泊车路径规划仿真实现

泊车路线规划这个方向&#xff0c;我前前后后折腾了不少版本。最早是在MATLAB里跑通了一个平行泊车的圆弧规划&#xff0c;后来又补上了垂直泊车工况&#xff0c;两个程序放在一起&#xff0c;配合一份简单的参考说明&#xff0c;整个流程算是完整了。这次就把项目里的核心思路…

作者头像 李华