7,994
社区成员
发帖
与我相关
我的任务
分享承接上一篇《在 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 主机。
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,也能把模型先跑起来。
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
sudo apt install -y git-lfs
git lfs install
git clone https://www.modelscope.cn/radxa/resnet50_qairt.git
需要用到的只有 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_00003441 | 402 acoustic guitar | 原声吉他 |
| ILSVRC2012_val_00008465 | 927 trifle | 松糕甜点 |
| ILSVRC2012_val_00010218 | 281 tabby | 虎斑猫 |
| ILSVRC2012_val_00044076 | 376 proboscis monkey | 长鼻猴 |

与 Radxa 官方文档给出的结果完全一致,说明模型移植正确。
仓库自带的 4 张图跑通后,我又换了另外 4 张真实照片验证一下泛化能力。
这是最容易出错的地方。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。
把上面的流程包成了一个脚本 ~/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 → 拼一张结果图。
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 量化几乎没有损失判别能力。
仓库自带的 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 图执行还要更快一些。
| 现象 | 原因 | 解决 |
|---|---|---|
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 binary | context binary 的 dsp_arch/soc_id 与设备不符 | 用 6490/ 目录(v68/soc_id 35)的模型 |
error while loading shared libraries: libQnnHtpV68Stub.so | 没设 LD_LIBRARY_PATH | export LD_LIBRARY_PATH=$PWD |
| NPU 报 skel 加载失败 | 没设 ADSP_LIBRARY_PATH | export ADSP_LIBRARY_PATH="$PWD;/dsp" |
| 想换 SoC | 目录对应关系 | 6490 → v68,8280 → v68,9075 → v73 |
| SoC | 目录 | NPU 架构 |
|---|---|---|
| QCS6490 | 6490/ | v68 |
| SC8280XP | 8280/ | v68 |
| QCS9075 | 9075/ | 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 的命令推理