SNPE 2.21:VGG16 Demo 交叉编译到 ARM64 Linux 并推理
摘要:本文完整演示了在 Ubuntu 20.04 x86_64 主机上将 SNPE 2.21.0 SDK 的 VGG16 Demo 交叉编译到 ARM64 Linux 板端并完成 CPU 推理的全流程。涵盖主机环境准备、交叉工具链安装、ONNX 模型转换与 DLC 生成、Native C++ Demo 交叉编译、板端部署与推理执行、结果拉取与 Top-5 分类展示等核心步骤,并扩展介绍了 Yocto/OpenEmbedded 目标板适配、GPU/DSP 扩展注意事项以及常见错误排查方法,为 ARM64 Linux 平台上的 SNPE 模型部署提供完整参考。
flowchart TD A[主机环境准备] --> B[模型转换] A --> C[交叉编译] subgraph 主机环境 A1[Ubuntu 20.04 x86_64] A2[Python 3.8 + venv] A3[SNPE 2.21.0 SDK] A4[aarch64-linux-gnu-g++ 9.4] end subgraph 模型转换 B1[下载 VGG16 ONNX] B2[预处理图片 → 224×224 float32 raw] B3[snpe-onnx-to-dlc → vgg16.dlc] end subgraph 交叉编译 C1[配置 SNPE_ROOT + envsetup.sh] C2[make -f Makefile.aarch64-ubuntu-gcc9.4] C3[产物: snpe-sample] end subgraph 板端部署 D1[scp snpe-sample + libSNPE.so] D2[scp vgg16.dlc + kitten.raw] D3[设置 LD_LIBRARY_PATH] end subgraph 推理验证 E1[运行 snpe-sample CPU 推理] E2[生成 Result_0/*.raw] E3[拉回结果 → Top-5 分类] end B --> C C --> D1 D1 --> D2 D2 --> D3 D3 --> E1 E1 --> E2 E2 --> E3# SNPE 2.21:VGG16 Demo 交叉编译到 ARM64 Linux 并推理
本文针对当前 SDK 2.21.0,完成以下链路:
```text
Ubuntu 20.04 x86_64 主机
├─ VGG16 ONNX -> DLC
├─ 图片 -> 224x224 float32 raw
└─ aarch64-linux-gnu-g++ 交叉编译 snpe-sample
↓ scp
ARM64 Ubuntu Linux 板端运行 CPU 推理 -> 拉回输出 -> Top-5
```
本文默认目标板是 **Ubuntu/glibc ARM64**,所以选用 SDK 目标
`aarch64-ubuntu-gcc9.4`。如果板端是 Yocto/OpenEmbedded,请直接看第 10 节,不能混用
Ubuntu 运行库。
## 1. 准备环境
推荐配置:
- 编译主机:Ubuntu 20.04 x86_64;
- Python:3.8;
- 目标板:ARM64 Ubuntu Linux;
- 交叉编译器:`aarch64-linux-gnu-g++` 9.4;
- SDK:本目录中的 SNPE 2.21.0;
- 主机和板端可通过 `ssh/scp` 通信。
先检查目标板:
```bash
ssh root@192.168.1.100 'uname -m; ldd --version | head -1'
```
`uname -m` 必须输出 `aarch64`。下文用变量避免反复修改地址:
```bash
export BOARD_USER=root
export BOARD_IP=192.168.1.100
export TARGET_DIR=/opt/snpe_vgg
```
## 2. 主机配置 SNPE 与 Python
```bash
export SNPE_ROOT=$HOME/snpe_2.21 # 改成 SDK 实际绝对路径
cd "$SNPE_ROOT"
python3.8 -m venv .venv-vgg
source .venv-vgg/bin/activate
source bin/envsetup.sh
python --version
echo "$SNPE_ROOT"
snpe-onnx-to-dlc --help
```
SDK 的 `sdk.yaml` 指定 ONNX 1.11.0;VGG 辅助脚本还需要 NumPy、Pillow 和 SciPy:
```bash
python -m pip install --upgrade pip
python -m pip install 'onnx==1.11.0' 'numpy==1.23.5' Pillow 'scipy==1.9.1'
```
如果转换器仍提示缺包,可在虚拟环境中运行 SDK 的依赖检查:
```bash
"$SNPE_ROOT/bin/check-python-dependency"
```
## 3. 安装 ARM64 交叉工具链
Ubuntu 20.04 的交叉编译器与 SDK 的 `aarch64-ubuntu-gcc9.4` 目标相匹配:
```bash
sudo apt update
sudo apt install -y build-essential gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \
binutils-aarch64-linux-gnu wget openssh-client
aarch64-linux-gnu-g++ --version
```
确认主版本是 9.x。若主机不是 Ubuntu 20.04,建议安装独立 GCC 9.4/sysroot,不要直接用过新
的主机交叉编译器,以免目标板缺少较新的 `GLIBCXX_*` 或 `GLIBC_*`。
## 4. 下载 VGG16、标签和测试图片
```bash
export VGG="$SNPE_ROOT/examples/Models/VGG"
mkdir -p "$VGG/onnx" "$VGG/data/cropped" "$VGG/dlc"
wget -c -O "$VGG/onnx/vgg16.onnx" \
https://s3.amazonaws.com/onnx-model-zoo/vgg/vgg16/vgg16.onnx
wget -O "$VGG/data/synset.txt" \
https://s3.amazonaws.com/onnx-model-zoo/synset.txt
wget -O "$VGG/data/kitten.jpg" \
https://s3.amazonaws.com/model-server/inputs/kitten.jpg
```
VGG16 ONNX 大约 500 MB,确认没有下载成错误页面:
```bash
ls -lh "$VGG/onnx/vgg16.onnx"
```
## 5. 预处理图片并生成 DLC
随包脚本会把图片缩放到 256×256、中心裁剪到 224×224,进行 ImageNet 归一化,最后保存
为 NHWC `float32` raw:
```bash
python "$VGG/scripts/create_VGG_raws.py" \
-i "$VGG/data" \
-d "$VGG/data/cropped"
python "$VGG/scripts/create_file_list.py" \
-i "$VGG/data/cropped" \
-o "$VGG/data/cropped/raw_list.txt" \
-e '*.raw' -r
```
转换 ONNX 为 DLC:
```bash
snpe-onnx-to-dlc \
--input_network "$VGG/onnx/vgg16.onnx" \
--output_path "$VGG/dlc/vgg16.dlc"
snpe-dlc-info -i "$VGG/dlc/vgg16.dlc"
```
检查输入:
```bash
stat -c '%n %s bytes' "$VGG/data/cropped/kitten.raw"
cat "$VGG/data/cropped/raw_list.txt"
```
`kitten.raw` 应为 `224 × 224 × 3 × 4 = 602112` 字节,列表应包含相对路径
`kitten.raw`。
## 6. 交叉编译 SNPE Native C++ Demo
SDK demo 位于:
```text
examples/SNPE/NativeCpp/SampleCode
```
随包 `Makefile.aarch64-ubuntu-gcc9.4` 默认期待一个特定目录结构的工具链。直接在 make
命令行覆盖 `CXX` 最简单,也比仅 `export CXX=...` 更可靠,因为 Makefile 自身给 `CXX`
赋了值。
```bash
export SAMPLE="$SNPE_ROOT/examples/SNPE/NativeCpp/SampleCode"
cd "$SAMPLE"
rm -rf obj/local/aarch64-ubuntu-gcc9.4
make -j"$(nproc)" \
SNPE_ROOT="$SNPE_ROOT" \
CXX=aarch64-linux-gnu-g++ \
-f Makefile.aarch64-ubuntu-gcc9.4
```
不要用 `sudo make`,否则 `SNPE_ROOT` 很可能被 sudo 清理。编译前应确认头文件根目录:
```bash
test -f "$SNPE_ROOT/include/SNPE/SNPE/SNPE.hpp"
test -f "$SNPE_ROOT/include/SNPE/SNPE/SNPEFactory.hpp"
test -f "$SNPE_ROOT/include/SNPE/DlContainer/IDlContainer.hpp"
```
三条命令都应返回 0。实际编译命令中必须出现:
```text
-I <SDK绝对路径>/include/SNPE
```
产物为:
```text
$SAMPLE/obj/local/aarch64-ubuntu-gcc9.4/snpe-sample
```
验证架构和动态依赖:
```bash
file "$SAMPLE/obj/local/aarch64-ubuntu-gcc9.4/snpe-sample"
aarch64-linux-gnu-readelf -h "$SAMPLE/obj/local/aarch64-ubuntu-gcc9.4/snpe-sample" \
| grep -E 'Class:|Machine:'
aarch64-linux-gnu-readelf -d "$SAMPLE/obj/local/aarch64-ubuntu-gcc9.4/snpe-sample" \
| grep NEEDED
```
应看到 `ELF 64-bit`、`ARM aarch64/AArch64`,以及对 `libSNPE.so` 的依赖。
### 如果已有厂商 sysroot
当板端根文件系统不是标准 Ubuntu 20.04,建议用板端 BSP 配套 sysroot:
```bash
make -j"$(nproc)" \
CXX="/opt/gcc-9.4/bin/aarch64-linux-gnu-g++ --sysroot=/opt/board-sysroot" \
-f Makefile.aarch64-ubuntu-gcc9.4
```
工具链/sysroot 与板端 rootfs 一致,能显著减少 `GLIBC_* not found` 问题。
## 7. 部署到 ARM64 Linux 板端
先建目录:
```bash
ssh "$BOARD_USER@$BOARD_IP" \
"mkdir -p '$TARGET_DIR/bin' '$TARGET_DIR/lib' '$TARGET_DIR/model' '$TARGET_DIR/data'"
```
推送交叉编译出的 demo、SNPE ARM64 Ubuntu 运行库、模型和输入:
```bash
scp "$SAMPLE/obj/local/aarch64-ubuntu-gcc9.4/snpe-sample" \
"$BOARD_USER@$BOARD_IP:$TARGET_DIR/bin/"
scp "$SNPE_ROOT"/lib/aarch64-ubuntu-gcc9.4/*.so \
"$BOARD_USER@$BOARD_IP:$TARGET_DIR/lib/"
scp "$VGG/dlc/vgg16.dlc" \
"$BOARD_USER@$BOARD_IP:$TARGET_DIR/model/"
scp "$VGG/data/cropped/kitten.raw" "$VGG/data/cropped/raw_list.txt" \
"$BOARD_USER@$BOARD_IP:$TARGET_DIR/data/"
```
这里必须部署 `lib/aarch64-ubuntu-gcc9.4`,不要误用 `aarch64-android` 或
`aarch64-oe-linux-*`。
## 8. 在板端运行 VGG16 CPU 推理
先验证 demo 能加载:
```bash
ssh "$BOARD_USER@$BOARD_IP" "
export LD_LIBRARY_PATH='$TARGET_DIR/lib':\${LD_LIBRARY_PATH:-}
chmod +x '$TARGET_DIR/bin/snpe-sample'
'$TARGET_DIR/bin/snpe-sample' -h
"
```
运行 CPU 推理:
```bash
ssh "$BOARD_USER@$BOARD_IP" "
set -e
export LD_LIBRARY_PATH='$TARGET_DIR/lib':\${LD_LIBRARY_PATH:-}
cd '$TARGET_DIR/data'
rm -rf '$TARGET_DIR/output_sample'
'$TARGET_DIR/bin/snpe-sample' \
-b ITENSOR \
-r cpu \
-d '$TARGET_DIR/model/vgg16.dlc' \
-i raw_list.txt \
-o '$TARGET_DIR/output_sample'
find '$TARGET_DIR/output_sample' -type f -maxdepth 2 -ls
"
```
成功后通常会生成:
```text
/opt/snpe_vgg/output_sample/Result_0/vgg0_dense2_fwd.raw
```
也可先用 SDK 自带的 ARM64 Linux 工具排除 demo 编译问题:
```bash
scp "$SNPE_ROOT/bin/aarch64-ubuntu-gcc9.4/snpe-net-run" \
"$BOARD_USER@$BOARD_IP:$TARGET_DIR/bin/"
ssh "$BOARD_USER@$BOARD_IP" "
export LD_LIBRARY_PATH='$TARGET_DIR/lib':\${LD_LIBRARY_PATH:-}
cd '$TARGET_DIR/data'
'$TARGET_DIR/bin/snpe-net-run' \
--container '$TARGET_DIR/model/vgg16.dlc' \
--input_list raw_list.txt \
--output_dir '$TARGET_DIR/output_net_run' \
--use_cpu
"
```
如果预编译 `snpe-net-run` 能运行而自编译 `snpe-sample` 不能,问题在交叉工具链、sysroot
或 demo 链接阶段;如果二者都失败,则优先检查模型、运行库和板端系统兼容性。
## 9. 拉回结果并显示 Top-5
```bash
rm -rf "$VGG/output_sample"
scp -r "$BOARD_USER@$BOARD_IP:$TARGET_DIR/output_sample" "$VGG/output_sample"
python "$VGG/scripts/show_vgg_classifications.py" \
-i "$VGG/data/cropped/raw_list.txt" \
-o "$VGG/output_sample" \
-l "$VGG/data/synset.txt"
```
官方 kitten 图片的结果通常以 `tabby`、`tiger cat`、`Egyptian cat` 等类别为主。运行后端
不同会造成小幅概率差异。
## 10. 如果目标板是 Yocto/OpenEmbedded
先在板端确认:
```bash
cat /etc/os-release
```
若是 Yocto/OE,不要使用本文前面的 `aarch64-ubuntu-gcc9.4`。SNPE 2.21 提供以下目标:
- `aarch64-oe-linux-gcc8.2`
- `aarch64-oe-linux-gcc9.3`
- `aarch64-oe-linux-gcc11.2`(适合相应版本的 Yocto SDK,例如 Kirkstone 系)
以 BSP 提供的 GCC 11.2 SDK 为例:
```bash
source /opt/board-sdk/environment-setup-armv8a-oe-linux
cd "$SAMPLE"
make -j"$(nproc)" \
CXX="$CXX" \
-f Makefile.aarch64-oe-linux-gcc11.2
```
部署时也必须换成完全对应的库:
```bash
scp "$SNPE_ROOT"/lib/aarch64-oe-linux-gcc11.2/*.so \
"$BOARD_USER@$BOARD_IP:$TARGET_DIR/lib/"
```
注意:某些 Yocto 环境的 `$CXX` 自带多段参数。若 make 对包含空格的 `CXX` 处理不正确,
把 BSP 环境输出的完整编译命令直接写入 make 命令行,或按实际 sysroot 修改一份本地
Makefile。
## 11. GPU/DSP 注意事项
CPU 路径是基础验收路线。要跑 GPU,目标 BSP 必须包含 Qualcomm OpenCL 用户态驱动:
```bash
"$TARGET_DIR/bin/snpe-sample" -b ITENSOR -r gpu \
-d "$TARGET_DIR/model/vgg16.dlc" -i raw_list.txt -o output_gpu
```
DSP/HTP 还要求:
- 按 SoC 选择正确的 `hexagon-v68/v69/v73/v75` skeleton;
- 正确设置 `ADSP_LIBRARY_PATH`;
- 设备固件和用户态驱动与 SNPE 版本兼容;
- 通常先将模型量化成 INT8 DLC。
因此建议先把本文 CPU 路径跑通,再根据具体 SoC/BSP 配置 DSP。仅知道“ARM64 Linux”不足以
确定 HTP 架构。
## 12. 常见错误
### `libSNPE.so: cannot open shared object file`
```bash
export LD_LIBRARY_PATH=/opt/snpe_vgg/lib:$LD_LIBRARY_PATH
```
并确认库是 `aarch64-ubuntu-gcc9.4` 版本。
### `GLIBCXX_x.y.z not found` 或 `GLIBC_x.y not found`
交叉编译 sysroot 比板端系统新,或混用了 Ubuntu/OE 库。使用板端 BSP 对应的交叉工具链和
sysroot;不要靠复制主机的 `libc.so` 解决。
### `cannot execute binary file: Exec format error`
程序不是 AArch64 ELF。用 `file` 和 `readelf -h` 检查,确保调用的是
`aarch64-linux-gnu-g++`,不是主机 `g++`。
### 链接时 `cannot find -lSNPE`
确认存在:
```bash
ls "$SNPE_ROOT/lib/aarch64-ubuntu-gcc9.4/libSNPE.so"
```
并且已经 `source "$SNPE_ROOT/bin/envsetup.sh"`。
### `SNPE/SNPE.hpp: No such file or directory`
说明 `SNPE_ROOT` 为空或不是 SDK 根目录。不要用 `sudo make`,并在 make 命令行显式传入:
```bash
export SNPE_ROOT=$(realpath /path/to/snpe_2.21)
test -f "$SNPE_ROOT/include/SNPE/SNPE/SNPE.hpp" || exit 1
cd "$SNPE_ROOT/examples/SNPE/NativeCpp/SampleCode"
rm -rf obj/local/aarch64-ubuntu-gcc9.4
make -j"$(nproc)" \
SNPE_ROOT="$SNPE_ROOT" \
CXX=aarch64-linux-gnu-g++ \
-f Makefile.aarch64-ubuntu-gcc9.4
```
### 进程被 OOM killer 杀死
VGG16 很大。用 `dmesg | tail` 检查 OOM,关闭其他进程或增加 swap;也可以先用 MobileNet
验证部署链路。
### 后处理找不到 `vgg0_dense2_fwd.raw`
通过 `snpe-dlc-info` 确认实际输出 tensor 名。不同来源的 VGG 模型可能输出名不同,此时需
同步修改 `scripts/show_vgg_classifications.py` 中的结果文件名。
## 13. 验收清单
- 主机 `snpe-dlc-info` 能读取 `vgg16.dlc`;
- `kitten.raw` 为 602112 字节;
- 目标板 `uname -m` 为 `aarch64`;
- `file snpe-sample` 显示 `ELF 64-bit ... ARM aarch64`;
- 板端 `snpe-sample -h` 能正常打印;
- CPU 推理生成 `Result_0/*.raw`;
- Top-5 是合理的猫类别。