昉星光2开发板 + Hailo-8L NPU 第一次实操记录

原创2026-09-17 10:40:36浏览38
38
Star
1

首先感谢“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

三个结论:

  1. 纯链路 24.09 FPS,完整流水线 6.08–6.93 FPS,差了近 4 倍——摄像头链路本身完全够用,不是瓶颈。

  2. 完整流水线的 6.08 FPS 基本等于 NPU 硬件上限(6.43 FPS),中间的后处理只吃掉不到 10%。想提帧率只能换更轻的模型。

  3. 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 温度,想当然认为"卡没有温度传感器",还据此把一次性能衰减猜成"过热降频"。后来实测直接推翻了猜测——系统里读不到,不代表硬件没有,先去看厂商工具。

十一、一些建议

  1. 先翻厂商软件源,再动手编译。 上游官方文档只覆盖 x86,开发板厂商的 apt 源里往往已经有整套适配好的栈。

  2. 插卡前断电。 没有 PCIe 热插拔支持的板子,带电插卡是在赌运气。

  3. "识别不到"不等于"卡坏了"。 先读 PCIe 配置空间的错误位,从链路层开始查,别急着拆硬件。

  4. 不要照抄网上的驱动参数。 no_power_mode 就是个例子:同一个参数在不同平台上效果可能相反,必须自己实测对照。

  5. 软恢复不是真复位。 掉线时最有效的操作是断电冷启动,反复软恢复只会让状态更脏。

  6. 模型文件名不等于后处理函数名。 hailofilter 的 function-name 填错会直接段错误,而且进程不退出——批量测试一定套 timeout。

  7. 帧率要自己数。 很多现成的统计手段在嵌入式平台上会失效,"固定帧数 + 计时"这个笨办法反而最可靠。

  8. "跑通"和"稳定"是两件事。 至少连续跑满几分钟不报错,才算真的能用。