首先感谢“2026华秋杯——AI开源硬件创新设计大赛”项目组让我参加比赛
一、先说下计划
目标链路:USB 摄像头 → NPU 推理 → 检测框叠加 → 显示/落盘。
准备清单:
部件 | 具体型号 | 说明 |
|---|---|---|
开发板 | 昉·星光2 | JH7110,riscv64,4 GB 内存 |
加速卡 | Hailo-8L M.2 B+M Key | 13 TOPS INT8 |
摄像头 | USB UVC 免驱摄像头 | 我用的是 Realtek 0bda:5846,插上就认 |
其他 | 网线、5V 电源 |
图 1:硬件上只有两段链路(USB 2.0 进 SoC、PCIe 出到 NPU),软件上只有一条 GStreamer 流水线、三个关键元件:hailonet(推理)、hailofilter(后处理)、hailooverlay(画框)。
这里先记一个后面排查时会用到的关键信息:这块板子的 M.2 槽挂在 PCIe 的 domain 0001 上,不是常见的 0000。加速卡的真实地址是 0001:01:00.0。查设备时一定要带 domain 前缀,否则很容易找到 domain 0000 里编号相同的另一个设备(那里是 USB 控制器),然后对着完全无关的芯片查半天。
二、第一步:让板子能操作(SSH + 图形桌面)
板子没接显示器,日常全靠 SSH。先在电脑上确认板子的 IP:
ping -c 3 192.168.31.102 # 换成你自己网段的地址
看到有回包就可以登录:
ssh user@192.168.31.102
登录后先确认基本信息,这几条命令的输出后面写文章、报问题都用得上:
uname -srm
lsb_release -ds
free -h | head -2
预期输出(实测):
Linux 6.12.5-starfive riscv64
Ubuntu 24.04.3 LTS
total used free shared buff/cache available
Mem: 3.8Gi ...
接下来必须做的一件事:GStreamer 要往 X 画面输出,所以得有个可用的图形通道。我用的是 XFCE + TigerVNC + noVNC,浏览器打开 http://<板子IP>:6080/vnc.html 就能看到桌面。

三、第二步:装 NPU 软件栈
我的第一反应是从源码编译 HailoRT——官方只发 x86 安装包,riscv64 得自己编。折腾半天确实编出来了,但自编的 HailoRT 没有 TAPPAS,也就没有 GStreamer 集成,等于有引擎没方向盘。
后来翻 StarFive 的 apt 源才发现厂商早打包好了整栈,一条命令搞定:
sudo apt update
sudo apt install libhailort tappas hailort-driver-module
这一条命令带来三样东西:HailoRT 4.19.0 设备运行时、TAPPAS 3.30.0、内核驱动 + 固件。装完立刻验证:
hailortcli --version # 看运行时版本
ls -l /lib/firmware/hailo/ # 看固件在不在
ls /usr/share/tappas/apps/h8/gstreamer/libs/post_processes/*.so | wc -l
实测输出:
HailoRT-CLI version 4.19.0
-rw-r--r-- 1 root root 166260 Sep 15 2025 hailo8_fw.bin
21
最后那行是后处理库的数量——检测、姿态、人脸、OCR、车牌全在里面,这 21 个库就是现成的视觉能力。
包名有个坑:全小写 hailort-driver-module,别按习惯写成 hailoRT-driver-module。另外驱动包描述里明确写着 "The driver only for 6.12.5 kernel version"——它只针对这块板的出厂内核编译,这也是它能开箱即用的原因:别随手升级内核,否则驱动要重新编译。
四、第三步:插卡(必须断电)
硬约束:这块板子不支持 PCIe 热插拔,插卡必须断电操作。 带电插 M.2 是在赌运气。
插好上电,用两条命令确认卡在不在:
lspci -nn | grep -i hailo
hailortcli fw-control identify
实测输出:
$ lspci -nn | grep -i hailo
0001:01:00.0 Co-processor [0b40]: Hailo Technologies Ltd. Hailo-8 AI Processor [1e60:2864] (rev 01)
$ hailortcli fw-control identify
Executing on device: 0001:01:00.0
Identifying board
Control Protocol Version: 2
Firmware Version: 4.19.0 (release,app,extended context switch buffer)
Device Architecture: HAILO8L
Serial Number: HLDDLBB241902591
Part Number: HM21LB1C2LAE
Product Name: HAILO-8L AI ACC M.2 B+M KEY MODULE EXT TMP
看到 Device Architecture: HAILO8L,说明卡、驱动、固件三者都正常。以后任何时候想判断卡是否可用,跑这一条就够了。
五、第四步:卡认不到怎么办
我第一次插好卡,lspci 能看到设备,但工具就是找不到它:hailortcli scan 报 Hailo devices not found,内核日志里是 Failed reading device BARs, device may be disconnected。字面意思像"卡坏了",但先别下这个结论。
按下面这个顺序查,每一步只回答一个是非题:

图 3:四步排查顺序。顺序别乱,也别一上来就拆硬件。
第 ① 步 看总线认不认:lspci -nn | grep -i hailo 看不到设备,基本是接触问题——断电重插、拧紧固定螺丝、确认槽位确实在 domain 0001。
第 ② 步 看固件读不读得回:hailortcli fw-control identify 如果报固件超时(Timeout waiting for firmware file),去查驱动参数。这里有个必须实测验证的坑:网上教程常建议设 no_power_mode=1 禁止卡进低功耗,我在本平台实测这个参数有害——设上之后固件直接加载超时,卡彻底起不来;改回 0 立刻恢复。
cat /etc/modprobe.d/*.conf | grep no_power_mode # 查看当前设置
# 本平台必须为 0
第 ③ 步 读链路错误位(这一步要 root):
sudo lspci -vv -s 0001:01:00.0 | grep -E 'DevSta|LnkSta|LnkCap'
实测的正常状态是这样的:
DevSta: CorrErr- NonFatalErr- FatalErr- UnsupReq- AuxPwr- TransPend-
LnkCap: Port #0, Speed 8GT/s, Width x4, ...
LnkSta: Speed 5GT/s (downgraded), Width x1 (downgraded)
判读方法:正常是一排干净的负号。出问题时 DevSta 里会出现 CorrErr+,同时 LnkSta 里 DLActive-(数据链路不再活动)。这类故障的表现是间歇性掉线:空闲几十分钟会掉,跑负载时几十秒就掉。恢复用这三步:
sudo modprobe -r hailo_pci
echo 1 | sudo tee /sys/bus/pci/devices/0001:01:00.0/remove
echo 1 | sudo tee /sys/bus/pci/rescan
第 ④ 步 软恢复连续失败 2 次,就断电冷启动。
这是我在这个问题上最重要的一条经验:软恢复(卸载重载驱动、重新扫描总线)得到的是中间状态,不是真正的复位。 我遇到过"软恢复后能识别,但一上负载就崩、性能只剩一半"的情况,而断电冷启动之后所有异常全部消失、性能完全恢复。
所以现在的规则是:掉线就直接断电,别反复软恢复。 反复折腾不仅浪费时间,还有把整机搞到失联的风险(我用 modprobe -r 时遇到过整机掉线)。
六、第五步:接好摄像头
USB 摄像头插上后,先确认系统认到了,以及用哪个节点:
ls /dev/video*
v4l2-ctl --list-devices
实测输出(板载 CSI 接口占了 6 个节点,别认错):
/dev/video0 ... /dev/video7
USB Camera: USB Camera (usb-0000:01:00.0-1.1):
/dev/video4
/dev/video5
/dev/media0
规律:USB 摄像头会占两个节点,编号小的那个用来采集,另一个是 metadata。 我这颗就是 /dev/video4 采集、/dev/video5 是元数据。认错了会报一堆"参数不支持"。
再看它支持什么格式:
v4l2-ctl -d /dev/video4 --list-formats-ext | grep -A4 MJPG
[0]: 'MJPG' (Motion-JPEG, compressed)
Size: Discrete 1280x720
Interval: Discrete 0.033s (30.000 fps)
如果访问 /dev/video* 提示权限不足,把当前用户加进 video 组:
sudo usermod -aG video $USER
七、第六步:跑出第一个检测框
卡和摄像头都就绪,就可以跑了。先试试官方封装好的脚本:
cd /usr/share/tappas/apps/h8/gstreamer/vf2/detection
export DISPLAY=:10
./detection.sh --help # 先看支持的模型
./detection.sh --network yolov8 --input /dev/video4
注意 --network 的取值是 yolov8 / yolov5 / nanodet / mobilenet_ssd,不是 HEF 文件名。
脚本跑起来后画面里就能看到检测框:
图 4:实际推理输出。车辆(car 78%)与行人(person 86.7%)被正确检出并标注,图像由流水线直接落盘,未经修饰。
想自己控制每一环,就直接写 GStreamer 命令。下面这条是我一直在用的完整流水线(换个模型只改 hef-path 和 function-name):
export DISPLAY=:10
gst-launch-1.0 -e \
v4l2src device=/dev/video4 num-buffers=200 ! \
videoflip video-direction=horiz ! \
videoscale qos=false n-threads=2 ! \
videoconvert n-threads=2 qos=false ! \
hailonet hef-path=/usr/share/tappas/apps/h8/gstreamer/vf2/detection/resources/yolov8m.hef \
batch-size=1 nms-score-threshold=0.3 nms-iou-threshold=0.45 \
output-format-type=HAILO_FORMAT_TYPE_FLOAT32 ! \
hailofilter function-name=yolov8m \
so-path=/usr/share/tappas/apps/h8/gstreamer/libs/post_processes/libyolo_hailortpp_post.so \
config-path=null qos=false ! \
hailooverlay qos=false ! \
clutterautovideosink
三个关键元件的作用:hailonet 把帧送进 NPU 推理,hailofilter 把张量结果做 NMS 变成检测框,hailooverlay 把框画回画面上。
这里有个害我卡了十分钟的坑:hailofilter 的 function-name 不等于模型文件名。我照抄 yolov5m_wo_spp 结果整条流水线段错误,终端里是:
Cannot load symbol: libyolo_hailortpp_post.so: undefined symbol: yolov5m_wo_spp
Caught SIGSEGV
Spinning. Please run 'gdb gst-launch-1.0 121946' to continue debugging, Ctrl-C to quit...
正确对应关系是(可以用 nm -D 查库导出的符号):
HEF 文件 | function-name |
|---|---|
yolov8m.hef | yolov8m |
yolov5m_wo_spp.hef | yolov5 |
nanodet_repvgg.hef | nanodet_repvgg |
ssd_mobilenet_v1.hef | ssd_mobilenet_v1 |
注意后处理库崩了之后 gst-launch 会进入自旋等待状态而不退出,脚本会一直卡住。所以做批量测试时,命令行一定要套 timeout:
timeout 60 gst-launch-1.0 -e 你的完整流水线 # 60 秒后强制结束,避免卡死
还有一种情况:跑通了却看不到框。 我第一次用自己的摄像头就遇到:
图 5:用我自己的 USB 摄像头跑出的实时画面。流水线完全正常,但画面里没有任何检测框。
没框不等于失败。 原因是这张画面里根本没有 COCO 80 类里的目标——摄像头正对着天花板,画面里是墙面和灯具。模型只认识它训练过的类别,不认识的东西不会框。新手遇到"没框"先做这一步判断:换个有明确目标的场景(放个人、摆个杯子、对着街景),再确认画面亮度和对焦正常。
八、第七步:性能摸底(先看数据从哪来)
能跑之后第一件事是搞清楚能力边界:这个 NPU 到底能跑多快、我该选哪个模型。
先说测量口径。这块板子上常规帧率统计手段大多失效:功耗查询不被固件支持、progressreport 在 live source 上取不到位置。唯一可靠的口径是"固定帧数计时法"——让流水线跑满固定帧数后自然结束,用耗时反推帧率,而耗时直接取 gst-launch 自报的 Execution ended after(不含流水线启动时间)。
先看模型本身在卡上能跑多快,一条命令测一个模型:
cd /usr/share/tappas/apps/h8/gstreamer/vf2/detection/resources
hailortcli run yolov8m.hef --measure-temp -t 30
-t 30 表示连续推理 30 秒,--measure-temp 让它顺便报芯片温度。四个模型都跑一遍的结果:
图 6:四个检测模型的真实终端输出(节选)。
整理成表:
模型 | 30 秒推理帧数 | 帧率 | 芯片最高温 |
|---|---|---|---|
yolov8m(640×640) | 193 | 6.43 FPS | 64.2 °C |
yolov5m_wo_spp(640×640) | 180 | 6.00 FPS | 65.1 °C |
nanodet_repvgg | 810 | 26.99 FPS | 65.4 °C |
ssd_mobilenet_v1(300×300) | 10991 | 366.27 FPS | 72.9 °C |
结论:中量级 640×640 模型在 6–7 FPS 这一档,轻量模型 27 FPS,极轻量模型能到 366 FPS。 这个分层决定了你的选型空间——要精度选 yolov8m,要流畅度选 nanodet。
再看端到端(摄像头进来、经过全部处理、画完框出去)能跑多少。方法是把整条流水线跑满固定帧数后自然结束,用耗时反推帧率。这条命令可以直接复制(把输出重定向到文件,方便取耗时):
export DISPLAY=:10
APP=/usr/share/tappas/apps/h8/gstreamer/vf2/detection
PP=/usr/share/tappas/apps/h8/gstreamer/libs/post_processes
QUEUE="queue leaky=no max-size-buffers=30 max-size-bytes=0 max-size-time=0"
timeout 200 gst-launch-1.0 -e \
v4l2src device=/dev/video4 num-buffers=200 ! videoflip video-direction=horiz ! $QUEUE ! \
videoscale qos=false n-threads=2 ! video/x-raw,pixel-aspect-ratio=1/1 ! $QUEUE ! \
videoconvert n-threads=2 qos=false ! $QUEUE ! \
hailonet hef-path=$APP/resources/yolov8m.hef batch-size=1 \
nms-score-threshold=0.3 nms-iou-threshold=0.45 \
output-format-type=HAILO_FORMAT_TYPE_FLOAT32 ! $QUEUE ! \
hailofilter function-name=yolov8m so-path=$PP/libyolo_hailortpp_post.so \
config-path=null qos=false ! $QUEUE ! \
hailooverlay qos=false ! fakesink > /tmp/run.log 2>&1
grep -o 'Execution ended after [0-9:.]*' /tmp/run.log # 取真实耗时
# 200 帧 ÷ 耗时 = 帧率(例如 0:00:32.88 → 6.08 FPS)
换配置只改两处:hef-path 指向的模型文件、hailofilter function-name 对应的后处理函数名(对照第七节的表)。想跑纯链路就把 hailonet 之后的四段全删掉;想只测 NPU 就把 hailofilter 之后的两段删掉。
实测四种配置的结果:
图 7:端到端实测的真实终端输出。
配置 | 帧数 | 耗时 | 帧率 |
|---|---|---|---|
纯摄像头链路(不含推理) | 300 | 12.45 s | 24.09 FPS |
yolov8m 仅 NPU(不接后处理) | 200 | 28.66 s | 6.98 FPS |
yolov8m 完整(含 NMS + 画框) | 200 | 32.88 s | 6.08 FPS |
yolov5m 完整(含 NMS + 画框) | 200 | 28.84 s | 6.93 FPS |
三个结论:
纯链路 24.09 FPS,完整流水线 6.08–6.93 FPS,差了近 4 倍——摄像头链路本身完全够用,不是瓶颈。
完整流水线的 6.08 FPS 基本等于 NPU 硬件上限(6.43 FPS),中间的后处理只吃掉不到 10%。想提帧率只能换更轻的模型。
yolov5m 的端到端(6.93)反而比 yolov8m(6.08)高,因为它的后处理更轻。如果对精度要求没那么极致,yolov5m 是更稳的实时选择。
想看全过程可以完整跑一遍脚本:
bash ~/e-e2e-bench.sh /dev/video4
九、第八步:一个反直觉的发现
这块 M.2 槽的规格比卡本身低不少,内核对这条链路有明确报告:
图 8:链路与 CPU 的真实数据。
卡本身支持 PCIe 3.0 ×4(31.5 Gb/s),实际只跑在 2.0 ×1(4.0 Gb/s)上,可用带宽只有卡能力的 1/8。 看起来像是个大问题,但算一笔账就清楚了:
YOLOv8m 的输入张量:640 × 640 × 3 字节 ≈ 1.23 MB/帧
按 7.39 FPS 发送:1.23 MB × 7.39 ≈ 9.1 MB/s(约 72.6 Mbps)
占可用带宽 4.0 Gb/s(≈500 MB/s)的 1.8%,余量 98%
所以被降级的 PCIe 根本不是瓶颈。 真正吃紧的是主机 CPU——端到端持续运行时,gst-launch 进程的多核合计 CPU 占用稳定在 74%–87%(300 秒长稳测试每 15 秒采样一次)。原因是缩放、色彩空间转换、后处理全是逐像素的 CPU 运算,而这颗 U74 的指令集里没有向量(V)扩展,干这类活效率很低。
这个结论对方案设计有直接影响:不必为"PCIe 降级"过度设计,要提性能应该往 CPU 卸载的方向走(比如把缩放和色彩转换交给 RGA 这类硬件单元),而不是去纠结链路速率。
十、第九步:稳定性验证
因为前面踩过掉线的坑,我不放心"跑通一次就算好",做了连续负载测试。方法就是把端到端流水线放着跑满 300 秒,同时每 15 秒采样一次 CPU 和温度:
# 长稳测试,核心是让完整流水线持续跑(不加 num-buffers 限制),并周期性采样
sudo bash ~/e-e2e-camera.sh 300 # 300 秒端到端连续运行
图 9:300 秒端到端连续运行的真实输出(节选)。
结果可以用四条判据概括,每条都能在终端里直接看到:
判据 | 实测结果 |
|---|---|
有没有报错 | 错误/警告区为空,全程无 ERROR |
卡还在不在 | 运行前后 卡可用=是,power_state 全程 D0 |
链路稳不稳 | DevSta / CESta 错误位一次未翻转 |
温度有没有触顶 | CPU 51.9–53.5 °C;收尾测 Hailo 芯片 71.6 °C 封顶 |
关于温度要分两个概念说清楚:CPU 温度(系统 thermal_zone0)长稳期间稳定在 51.9–53.5 °C;Hailo 芯片温度要看它自己的传感器,这次收尾短测是 71.6 °C。这块卡裸片没有散热片,实测满载也就 66–73 °C,距离结温上限还有 30 °C 以上余量——所以不必用"过热降频"去解释任何性能衰减。
这里还有个细节:Hailo 芯片有内置温度传感器,但它不会出现在系统的 hwmon 目录里。想读温度只能用官方工具:
hailortcli run yolov8m.hef --measure-temp -t 180
我一开始因为 hwmon 里只有 CPU 温度,想当然认为"卡没有温度传感器",还据此把一次性能衰减猜成"过热降频"。后来实测直接推翻了猜测——系统里读不到,不代表硬件没有,先去看厂商工具。
十一、一些建议
先翻厂商软件源,再动手编译。 上游官方文档只覆盖 x86,开发板厂商的 apt 源里往往已经有整套适配好的栈。
插卡前断电。 没有 PCIe 热插拔支持的板子,带电插卡是在赌运气。
"识别不到"不等于"卡坏了"。 先读 PCIe 配置空间的错误位,从链路层开始查,别急着拆硬件。
不要照抄网上的驱动参数。 no_power_mode 就是个例子:同一个参数在不同平台上效果可能相反,必须自己实测对照。
软恢复不是真复位。 掉线时最有效的操作是断电冷启动,反复软恢复只会让状态更脏。
模型文件名不等于后处理函数名。 hailofilter 的 function-name 填错会直接段错误,而且进程不退出——批量测试一定套 timeout。
帧率要自己数。 很多现成的统计手段在嵌入式平台上会失效,"固定帧数 + 计时"这个笨办法反而最可靠。
"跑通"和"稳定"是两件事。 至少连续跑满几分钟不报错,才算真的能用。
