在 Radxa Dragon Q6A(QCS6490)用 NPU 跑通 ResNet50

azouyana 2026-11-11 11:18:36

承接上一篇《在 Radxa Dragon Q6A 上部署 QAIRT SDK 2.42.0.251225》
平台:Radxa Dragon Q6A / QCS6490(Hexagon NPU = HTP v68)
系统:Ubuntu 24.04.4 LTS aarch64,QAIRT SDK 2.42.0.251225
实测日期:2026-11-10


一、结论先行

项目结果
模型ResNet50(AIMET 量化,INT8)
输入 / 输出UINT8 [1,3,224,224] → UINT8 [1,1000]
单张推理延迟7.15 ms(约 140 FPS)
上下文加载 + 单张0.24 s
500 张批量3.81 s
4 张测试集分类全对(与官方文档一致)
自定义 4 张真实照片全部 Top-1 正确
跨版本兼容SDK 2.42 运行时可直接加载 2.37 生成的 context binary,输出逐字节一致
Classification results
../data/test/crop/ILSVRC2012_val_00003441.raw 21.644295 402 acoustic guitar
../data/test/crop/ILSVRC2012_val_00008465.raw 22.483221 927 trifle
../data/test/crop/ILSVRC2012_val_00010218.raw 12.248322 281 tabby
../data/test/crop/ILSVRC2012_val_00044076.raw 18.456375 376 proboscis monkey

二、为什么不能照搬官方流程

Radxa 官方文档《QAIRT SDK 使用示例》的流程是:

[ x86 主机 ]  ONNX → DLC → 量化 DLC → context binary(.bin)
                                     ↓ scp
[ QCS6490 ]                       qnn-net-run 推理

但板端做不了前半段(上一篇已验证):qairt-converter 依赖原生扩展 libDlModelToolsPy,
SDK 里只提供了 linux-x86_64 版本:

lib/python/qti/aisw/dlc_utils/
├── linux-x86_64/     ← 只有这个
├── windows-x86_64/
└── windows-arm64ec/

在板子上强行运行会直接报:

ImportError: libpython3.10.so.1.0: cannot open shared object file: No such file or directory
ImportError: cannot import name 'libDlModelToolsPy' ...

好消息:Radxa 在 ModelScope 上提供了已经按 v68 / soc_id=35 编译好的整套推理资源
(模型 + 运行库 + 测试数据),可以直接在板子上跑,不需要 x86 主机。

🔗 仓库地址:https://www.modelscope.cn/radxa/resnet50_qairt


三、资源仓库结构

resnet50_qairt/
├── 6490/                            # ← QCS6490 用这个(HTP v68)
│   ├── resnet50_aimet_quantized_6490.bin   25M   context binary
│   ├── qnn-net-run                          5.9M  aarch64 推理程序
│   ├── libQnnHtp.so                         2.3M  HTP 后端
│   ├── libQnnHtpV68Stub.so                  246K  HTP stub
│   ├── libQnnHtpV68Skel.so                  7.8M  NPU 骨架库
│   └── test_list.txt                              输入文件列表
├── 8280/                            # SC8280XP(切到 8280 目录即可)
├── 9075/                            # QCS9075(HTP v73)
├── data/
│   ├── imagenet_classes.txt
│   └── test/
│       ├── *.JPEG                   # 原始图片
│       └── crop/*.raw               # 预处理好的 1x3x224x224 float32 输入
└── scripts/
    ├── create_resnet50_raws.py
    ├── create_file_list.py
    └── show_resnet50_classifications.py

注意:仓库里 6490/ 目录下自带一套 qnn-net-run + 运行库(版本 2.37.1),所以哪怕你电脑上还没装 SDK,也能把模型先跑起来。


四、获取资源(含 LFS 坑)

🕳 坑 1:直接 git clone 拿到的是 132 字节的 LFS 指针

ModelScope 仓库里的大文件(.bin、.so、qnn-net-run)都走 Git LFS。
如果机器上没装 git-lfs,git clone 后拿到的是指针文件:

git clone https://www.modelscope.cn/radxa/resnet50_qairt.git
ls -lh resnet50_qairt/6490/
-rw-rw-r-- 1 radxa radxa  132 libQnnHtp.so
-rw-rw-r-- 1 radxa radxa  132 libQnnHtpV68Skel.so
-rw-rw-r-- 1 radxa radxa 246K libQnnHtpV68Stub.so     ← 这个不是 LFS,正常
-rw-rw-r-- 1 radxa radxa  132 qnn-net-run
-rw-rw-r-- 1 radxa radxa  133 resnet50_aimet_quantized_6490.bin

打开看内容就是:

version https://git-lfs.github.com/spec/v1
oid sha256:65f6282958ab7df304261ae2fe5532cdae668ff4940d4dd7cef8e24dfa887cfd

✅ 正确做法 A:装 git-lfs 再克隆

sudo apt install -y git-lfs
git lfs install
git clone https://www.modelscope.cn/radxa/resnet50_qairt.git

✅ 正确做法 B:直接 HTTP 下载(推荐,不用装 git-lfs)

需要用到的只有 6490 目录下的 4 个 LFS 文件,其余文件 clone 下来就是正常的:

git clone --depth 1 https://www.modelscope.cn/radxa/resnet50_qairt.git
cd resnet50_qairt/6490

BASE="https://www.modelscope.cn/models/radxa/resnet50_qairt/resolve/master/6490"
for f in qnn-net-run libQnnHtp.so libQnnHtpV68Skel.so resnet50_aimet_quantized_6490.bin; do
  wget -q --show-progress -O "$f" "$BASE/$f"
done

chmod +x qnn-net-run
ls -lh

下载完成后应该是:

-rw-rw-r-- 1 radxa radxa 2.3M libQnnHtp.so
-rw-rw-r-- 1 radxa radxa 7.8M libQnnHtpV68Skel.so
-rw-rw-r-- 1 radxa radxa 246K libQnnHtpV68Stub.so
-rwxrwxr-x 1 radxa radxa 5.9M qnn-net-run
-rw-rw-r-- 1 radxa radxa  25M resnet50_aimet_quantized_6490.bin
-rw-rw-r-- 1 radxa radxa 183B test_list.txt

五、板端跑推理

cd ~/resnet50_qairt/6490

# 让程序能找到 HTP stub / skel
export LD_LIBRARY_PATH=$PWD
export ADSP_LIBRARY_PATH="$PWD;/dsp"

./qnn-net-run \
  --backend ./libQnnHtp.so \
  --retrieve_context ./resnet50_aimet_quantized_6490.bin \
  --input_list ./test_list.txt \
  --output_dir ./output_bin

实测输出:

qnn-net-run pid:16232
qnn-net-run build version: v2.37.1.250807093845_124904
 <W> Initializing HtpProvider
Processing inference input(s):
../data/test/crop/ILSVRC2012_val_00003441.raw
../data/test/crop/ILSVRC2012_val_00008465.raw
../data/test/crop/ILSVRC2012_val_00010218.raw
../data/test/crop/ILSVRC2012_val_00044076.raw
Creating context from binary file: ./resnet50_aimet_quantized_6490.bin
Executing Graphs
Finished Executing Graphs

real    0m0.257s

💡 test_list.txt 里写的是相对路径 ../data/test/crop/xxx.raw,必须在 6490/ 目录下执行,否则会报 Failed to open input file。

产物:

output_bin/
├── execution_metadata.yaml
├── Result_0/output.raw      # 4000 字节 = 1000 类 × float32
├── Result_1/output.raw
├── Result_2/output.raw
└── Result_3/output.raw

execution_metadata.yaml 记录了模型信息:

execution_mode: synchronous
inferences_completed: 4
graphs:
  - graph_name: resnet50_aimet
    input_tensors:
    - tensor_name: input
      datatype: QNN_DATATYPE_UFIXED_POINT_8
      dimensions: [1,3,224,224]
    output_tensors:
    - tensor_name: output
      datatype: QNN_DATATYPE_UFIXED_POINT_8
      dimensions: [1,1000]

六、验证推理结果

仓库自带解码脚本(需要 numpy):

cd ~/resnet50_qairt/6490
python3 ../scripts/show_resnet50_classifications.py \
  --input_list ./test_list.txt \
  -o ./output_bin \
  --labels_file ../data/imagenet_classes.txt
Classification results
../data/test/crop/ILSVRC2012_val_00003441.raw 21.644295 402 acoustic guitar
../data/test/crop/ILSVRC2012_val_00008465.raw 22.483221 927 trifle
../data/test/crop/ILSVRC2012_val_00010218.raw 12.248322 281 tabby
../data/test/crop/ILSVRC2012_val_00044076.raw 18.456375 376 proboscis monkey

对照 data/test/ 里的原图,四张全部识别正确:

图片识别结果ImageNet 类别
ILSVRC2012_val_00003441402 acoustic guitar原声吉他
ILSVRC2012_val_00008465927 trifle松糕甜点
ILSVRC2012_val_00010218281 tabby虎斑猫
ILSVRC2012_val_00044076376 proboscis monkey长鼻猴

ResNet50 on QCS6490 NPU 推理结果

与 Radxa 官方文档给出的结果完全一致,说明模型移植正确。


七、换自己的图片验证

仓库自带的 4 张图跑通后,我又换了另外 4 张真实照片验证一下泛化能力。

7.1 预处理必须与训练时一致

这是最容易出错的地方。CAIRT 官方 create_resnet50_raws.py 的预处理是:

1. 短边居中裁剪成正方形
2. resize 到 224×224(BILINEAR)
3. (x - mean) / std,其中
   mean = [123.675, 116.28, 103.53]
   std  = [58.395, 57.12, 57.375]
4. HWC → CHW,保存为 float32 raw

注意:不要做 BGR 翻转(脚本看 req_bgr_raw=False),也不要除以 255。

7.2 一键跑任意图片的脚本

把上面的流程包成了一个脚本 ~/QAIRT/demo/run_custom_images.py:

cd ~/QAIRT
PYTHONPATH=~/QAIRT/pylibs python3 demo/run_custom_images.py \
  --images ILSVRC2012_val_0000068{1,2,3,4}.JPEG

它会自动完成:预处理 → 生成 raw 和列表 → 调 qnn-net-run → 解析 Top-5 → 拼一张结果图。

7.3 实测结果

ILSVRC2012_val_00000681.JPEG   (排箫演奏者)
  1. #699  panpipe           21.477
  2. #558  flute             11.745
  3. #642  marimba            9.228

ILSVRC2012_val_00000682.JPEG   (冰球比赛)
  1. #746  puck              18.121
  2. #795  ski                8.221
  3. #462  broom              7.215

ILSVRC2012_val_00000683.JPEG   (绿鬣蜥)
  1. #39   common iguana     13.758
  2. #46   green lizard      10.403
  3. #40   American chameleon 7.550

ILSVRC2012_val_00000684.JPEG   (餐具特写)
  1. #828  strainer          11.745
  2. #758  reel              11.409
  3. #618  ladle             11.242

自定义图片验证结果

图片内容Top-1 预测评价
排箫演奏者#699 panpipe(排箫)✅
冰球比赛#746 puck(冰球)✅
绿鬣蜥#39 common iguana(普通鬣蜥)✅
金属漏勺 + 筷子#828 strainer(漏勺)✅

四张全对,而且 Top-3 都在同一个语义族里(flute/marimba → 乐器,ski/bobsled → 冰上运动,green lizard/chameleon → 蜥蜴,ladle/measuring cup → 餐厨具),
说明模型的 INT8 量化几乎没有损失判别能力。

八、用自己装的 QAIRT 2.42 跑(可选)

仓库自带的 qnn-net-run 是 2.37.1,而我在上一篇装的 SDK 是 2.42.0。
实测:SDK 2.42 的运行时可以直接加载 2.37 生成的 context binary,且输出完全一致。

source ~/QAIRT/qairt_env.sh          # 上一篇写的环境脚本

cd ~/resnet50_qairt/6490
qnn-net-run \
  --backend $QAIRT_SDK_ROOT/lib/aarch64-oe-linux-gcc11.2/libQnnHtp.so \
  --retrieve_context ./resnet50_aimet_quantized_6490.bin \
  --input_list ./test_list.txt \
  --output_dir /tmp/out242
qnn-net-run build version: v2.42.0.251225135753_193295
Creating context from binary file: ./resnet50_aimet_quantized_6490.bin
Executing Graphs
Finished Executing Graphs

逐字节比对两套运行时的输出:

for i in 0 1 2 3; do
  cmp -s output_bin/Result_$i/output.raw /tmp/out242/Result_$i/output.raw \
    && echo "Result_$i: 完全一致" || echo "Result_$i: 不同"
done
Result_0: 完全一致
Result_1: 完全一致
Result_2: 完全一致
Result_3: 完全一致

⚠️ 但这只是本例的经验。官方对 context binary 的建议仍是 生成和使用同一版本 SDK,
跨版本时请自行验证结果一致性。


九、性能实测

方法:两次不同规模的运行相减,扣掉上下文加载开销

cd ~/resnet50_qairt/6490
source ~/QAIRT/qairt_env.sh

BK=$QAIRT_SDK_ROOT/lib/aarch64-oe-linux-gcc11.2/libQnnHtp.so
echo "../data/test/crop/ILSVRC2012_val_00003441.raw" > /tmp/list1.txt
(for i in $(seq 125); do cat test_list.txt; echo; done) > /tmp/list500.txt   # 500 条

time qnn-net-run --backend $BK --retrieve_context ./resnet50_aimet_quantized_6490.bin \
     --input_list /tmp/list1.txt   --output_dir /tmp/o1
time qnn-net-run --backend $BK --retrieve_context ./resnet50_aimet_quantized_6490.bin \
     --input_list /tmp/list500.txt --output_dir /tmp/o500

结果:

规模耗时
1 张(含上下文加载)0.24 s
500 张3.81 s
单张净推理7.15 ms(≈ 140 FPS)

这个数字包含读 raw 文件、反量化和写输出的开销,纯 NPU 图执行还要更快一些。


十、FAQ

现象原因解决
Failed to open input file: ./test_list.txt不在 6490/ 目录下执行,相对路径失效cd ~/resnet50_qairt/6490
文件都是 132 字节,推理报错git clone 没装 git-lfs,拿到的是指针装 git-lfs,或按第四章 HTTP 下载
Could not create context from binarycontext binary 的 dsp_arch/soc_id 与设备不符用 6490/ 目录(v68/soc_id 35)的模型
error while loading shared libraries: libQnnHtpV68Stub.so没设 LD_LIBRARY_PATHexport LD_LIBRARY_PATH=$PWD
NPU 报 skel 加载失败没设 ADSP_LIBRARY_PATHexport ADSP_LIBRARY_PATH="$PWD;/dsp"
想换 SoC目录对应关系6490 → v68,8280 → v68,9075 → v73

目录与 SoC 对照

SoC目录NPU 架构
QCS64906490/v68
SC8280XP8280/v68
QCS90759075/v73

十一、自己做模型转换怎么办

如果你有自己的模型(ONNX / TFLite / TF / PyTorch),转换必须在 x86 Ubuntu 22.04 + Python 3.10 上完成:

# 1. 转 DLC
qairt-converter --input_network model.onnx --output_path model.dlc -d input 1,3,224,224

# 2. 量化(需校准数据 raw + 列表)
qairt-quantizer --input_dlc model.dlc --input_list calib_list.txt --output_dlc model_q.dlc

# 3. 生成 v68 context binary
cat > config_backend.json << 'EOF'
{ "graphs":[{"graph_names":["model"],"vtcm_mb":0}],
  "devices":[{"dsp_arch":"v68","soc_id":35}] }
EOF
cat > config_file.json << 'EOF'
{ "backend_extensions": {
    "shared_library_path": "libQnnHtpNetRunExtensions.so",
    "config_file_path": "config_backend.json" } }
EOF
qnn-context-binary-generator --model libQnnModelDlc.so --backend libQnnHtp.so \
    --dlc_path model_q.dlc --output_dir output --binary_file model --config_file config_file.json

# 4. scp output/model.bin 到板子,用 chapter 5 的命令推理

十二、小结

  • 板端完全可以跑真实模型,关键是要拿到针对 v68/soc_id=35 编译好的 context binary;
  • Radxa 的 ModelScope 仓库把「模型 + 运行库 + 测试数据」都打包好了,省掉了 x86 主机这一环;
  • ResNet50 INT8 在 QCS6490 的 Hexagon NPU 上约 7 ms/张;
  • SDK 2.42 运行时兼容 2.37 生成的 context binary,输出逐字节一致(仅代表本例)。

参考

...全文
14 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
这是一套基于YOLOv8n的农田病虫害目标检测系统,对叶片照片做目标检测,识别健康叶片、褐斑病、白粉病、锈病四类,并给出对应防治建议。全流程纯CPU可跑,配套交付完整Python源码、9章实战教程(md/html/pdf三种格式)、100张自造样本及YOLO标签、训练曲线与混淆矩阵截图、Tkinter界面和答辩PPT。 系统围绕五项功能展开:同一套推理逻辑覆盖图像、视频、摄像头三种来源;图形界面选文件即检测,画面叠彩色框标注病名与置信度,右侧面板同步给出症状与防治措施;检测记录自动写入CSV,统计页一键生成四类病害占比饼图;训练结束自动产出损失与精度曲线;内置防治建议文案库,检测命中即回填面板。 选型用YOLOv8n nano档,参数量约320万、权重约6.2MB,CPU单帧几十毫秒,小样本训练5到8分钟完成。数据由data_gen.py程序化绘制100张叶片图,固定随机种子,重跑分布一致,不依赖公开数据集。依赖全部锁版本,教程第7章整理了19条真实报错及排查方法,第8章给出换模型档、换PlantVillage数据集的迁移路径。 适合做计算机视觉方向课程设计或毕业设计的学生,也适合智慧农业方向想了解检测模型如何落进业务面板的学习者。不需要GPU,不需要系统学过目标检测,教程第3章会按需补齐原理。 使用时先pip install -r requirements.txt装好依赖,download_data.py下载预训练权重,data_gen.py生成样本后train.py训练,完成后用main.py infer批量检测或main.py gui启动界面。

7,994

社区成员

发帖
与我相关
我的任务
社区描述
本论坛以AI、IoT、PC 、XR、Auto等核心板块组成,为开发者提供便捷及高效的学习和交流平台。 高通开发者专区主页:https://qualcomm.csdn.net/
物联网人工智能开源 企业社区 北京·东城区
社区管理员
  • csdnsqst0050
  • chipseeker
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧