SNPE 2.21:VGG16 Demo 交叉编译到 ARM64 Linux 并推理

📅 2026/7/11 5:04:17 👁️ 阅读次数 📝 编程学习
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 是合理的猫类别。