欢迎加入开源鸿蒙 PC 社区,获取最新资讯、教程与答疑,与开发者一起共建生态。

本文记录将腾讯 TNN 推理框架通过 lycium_plusplus 框架适配到鸿蒙PC(OpenHarmony aarch64)平台的过程,包括实际编译中遇到的问题和 HPKBUILD 编写要点。


一、项目信息说明

项目 说明
名称 TNN
开源协议 BSD-3-Clause(原项目)
源码版本 0.3.0
目标平台 鸿蒙PC
依赖项 无(内置 flatbuffers、gflags 等,无额外系统库)
操作系统平台 WSL + Ubuntu 24.04
适配仓库(lycium_plusplus / HPKBUILD) https://atomgit.com/oh-tpc/ohos-TNN

在这里插入图片描述


二、项目背景

TNN(Tencent Neural Network)是腾讯优图实验室开源的高性能轻量级神经网络推理框架,支持图像分类、人脸检测、目标检测等场景。将其移植到鸿蒙PC平台,可以让开发者在鸿蒙桌面端直接集成 AI 推理能力。

  • 上游仓库:https://github.com/Tencent/TNN
  • 适配版本:v0.3.0
  • 目标平台:OpenHarmony aarch64(arm64-v8a)
  • 构建框架:lycium_plusplus

三、环境准备

3.1 鸿蒙PC简介

鸿蒙PC(OpenHarmony PC)是华为基于 OpenHarmony 构建的桌面端操作系统平台,采用 aarch64 架构。目前鸿蒙PC处于快速发展阶段,原生应用生态正在建设中,将成熟的开源库移植到鸿蒙PC是丰富其生态的重要工作之一。

3.2 交叉编译

由于鸿蒙PC设备上的开发工具链尚不完善,通常在 x86_64 宿主机(或 WSL 环境)上使用 OpenHarmony SDK 提供的 aarch64 交叉编译工具链进行编译,生成可在鸿蒙PC上运行的 aarch64 二进制产物,再通过 hdc 等文件传输工具推送到设备运行。

3.3 编译平台:WSL + Ubuntu 24.04

本文的编译工作在 WSL(Windows Subsystem for Linux)+ Ubuntu 24.04 环境下完成。WSL 提供了完整的 Linux 环境,可以直接使用 OpenHarmony SDK 的 Linux 工具链,是在 Windows 宿主机上进行鸿蒙PC交叉编译的推荐方式。

WSL 安装与配置教程:在 Windows 10 上安装和使用 WSL 2 安装 Ubuntu24详细指南

3.4 lycium_plusplus 框架

lycium_plusplus 是专为鸿蒙PC生态设计的开源三方库编译管理框架,类似 Android 的 ndk-build 或 Alpine 的 aports。开发者只需编写 HPKBUILD 脚本(包含 prepare / build / package 等生命周期函数),框架便自动完成工具链注入、源码下载、编译和打包等全流程,最终生成可在鸿蒙PC上部署的 .hnp 包。

lycium_plusplus 环境搭建教程:【鸿蒙PC命令行适配】Ubuntu22.04 lycium_plusplus环境搭建SOP流程

搭建完成后,确保以下环境变量已配置:

# OpenHarmony SDK 根目录
export OHOS_SDK=/home/gyl/openharmony/linux

验证 SDK 中的 cmake 和编译器可用:

${OHOS_SDK}/native/build-tools/cmake/bin/cmake --version
${OHOS_SDK}/native/llvm/bin/aarch64-linux-ohos-clang --version

四、适配包目录结构与核心文件说明

4.1 创建适配包目录

lycium_plusplus/thirdparty/ 下为 TNN 新建独立目录,所有适配文件集中存放:

mkdir -p lycium_plusplus/thirdparty/TNN

完整目录结构如下:

lycium_plusplus/thirdparty/TNN/
├── HPKBUILD          # 核心构建脚本,定义编译全流程
├── SHA512SUM         # 源码包完整性校验文件
└── hnp.json          # HNP 包元数据,供 hnpcli 打包使用

4.2 HPKBUILD

HPKBUILD 是 lycium_plusplus 框架的核心构建描述文件,语法类似 Arch Linux 的 PKGBUILD。

包元数据字段
pkgname=TNN          # 包名,同时作为安装目录名
pkgver=0.3.0         # 上游版本号
pkgrel=0             # 包修订号,上游版本不变时递增
archs=("arm64-v8a")  # 支持的目标 ABI 列表
license=("BSD-3-Clause")
source="https://github.com/Tencent/TNN/archive/refs/tags/v${pkgver}.tar.gz"
downloadpackage=true # 由框架自动下载源码包
autounpack=true      # 下载后自动解压
buildtools=cmake     # 声明使用 cmake 构建
builddir=${pkgname}-${pkgver}   # 解压后的源码目录名
packagename=${builddir}.tar.gz  # 下载的压缩包文件名
prepare():编译前准备

prepare() 在源码解压后、build() 之前执行,主要完成三件事:

  1. 修复 CMakeLists.txt:将 examples/linux/cross/CMakeLists.txtCMAKE_C_FLAGS 里错误的 -std=c++11(C++ 专有选项)去除,防止 FindOpenMP 的 C 编译器探测失败。

  2. 下载模型文件:SqueezeNet 已随源码附带,另外两个模型(face_detector、mobilenet_v2-ssd)从 GitHub 下载。内层 _dl() 函数实现单文件级别的幂等性——若文件已存在则跳过,避免重复下载。

  3. 创建构建目录:分别为 TNN 库和示例程序创建 out-of-source 构建目录。

build():编译构建

build() 接收框架通过 "$@" 注入的工具链参数(toolchain file、install prefix、system name/processor),分两个阶段调用 cmake:

  • 第一阶段:编译 TNN 主库,生成 libTNN.so
  • 第二阶段:编译三个示例程序,通过 -DTNN_LIB_PATH 指向第一阶段的构建输出目录

两次 cmake 调用均复用同一个 "$@",确保工具链完全一致。

package():安装打包

TNN v0.3.0 未定义 cmake install() 目标,无法使用 make install,因此 package() 改为手动 cp 各类产物到安装目录:

产物类型 来源 安装位置
共享库 $ARCH-build/libTNN.so* lib/
头文件 include/tnn/ include/
示例程序 $ARCH-demo-build/demo_arm_* bin/
模型文件 model/SqueezeNetface_detectormobilenet_v2-ssd model/
测试资源 examples/assets/ assets/
OpenMP 运行时 SDK native/llvm/lib/aarch64-linux-ohos/libomp.so lib/

拷贝 libomp.so 时需将 ${ARCH}arm64-v8a)映射为 LLVM target triple(aarch64-linux-ohos),两者格式不同。

其他生命周期函数
  • check():打印提示,说明测试需在鸿蒙PC设备上进行,框架会在 package() 后调用。
  • cleanbuild():删除解压的源码目录,用于强制重新编译。
  • archive():在 package() 之后由框架调用,将安装目录打包为 tar.gz 并调用 hnpcli 生成 .hnp 包。

4.3 SHA512SUM

SHA512SUM 文件记录源码压缩包的 SHA-512 校验值,lycium 框架在下载完成后自动比对,防止下载损坏或被篡改。

文件格式为 sha512sum 标准输出格式——哈希值与文件名之间以两个空格分隔:

fb12bde5c7e4671749fc768dafdb223e79ce4752bab9da685336dba904a107587e8842d7df13b0e37b8af7d7b2f3197fa378a9cbb68e0bfa5e6dc2ccc642c3d1  TNN-0.3.0.tar.gz

生成方式:先手动下载一次源码包,再用以下命令生成:

sha512sum TNN-0.3.0.tar.gz > SHA512SUM

4.4 hnp.json

hnp.json 是 HNP(HarmonyOS Native Package)打包工具 hnpcli 所需的包描述文件,archive() 阶段会将其拷贝到安装目录后一并打包。

{
    "type": "hnp-config",
    "name": "TNN",
    "version": "0.3.0",
    "install": {}
}
字段 说明
type 固定为 "hnp-config",标识这是一个 HNP 包配置文件
name 包名,与 HPKBUILD 中的 pkgname 保持一致
version 版本号,与 HPKBUILD 中的 pkgver 保持一致
install 安装配置(当前为空,使用 hnpcli 默认行为)

五、TNN 源码分析

5.1 关键目录(v0.3.0)

TNN-0.3.0/
├── CMakeLists.txt
├── include/tnn/              # 公共头文件(编译产物直接拷贝此目录)
├── source/tnn/
│   ├── device/arm/           # ARM 后端(含 arm64/*.S NEON 汇编)
│   └── device/cpu/           # CPU 后端
├── examples/linux/
│   ├── cross/CMakeLists.txt  # 示例程序构建文件(prepare() 中用 sed 修复 C flags)
│   └── src/                  # 三个 demo 源码
└── third_party/              # 内置第三方依赖(flatbuffers、gflags 等)

5.2 关键 CMake 选项(TNN 库)

选项 说明 本次配置
TNN_ARM_ENABLE ARM 后端(含 NEON 汇编优化) ON
TNN_CPU_ENABLE CPU 后端 ON
TNN_OPENMP_ENABLE OpenMP 多线程 ON
TNN_BUILD_SHARED 构建共享库 ON
TNN_OPENCL_ENABLE OpenCL GPU 后端 OFF(鸿蒙PC暂不支持)
TNN_RK_NPU_ENABLE 瑞芯微 NPU OFF

六、适配过程

6.1 问题一:TNN v0.3.0 没有 cmake install 目标

现象

package() 执行 make install 时报错:

make: *** No rule to make target 'install'.  Stop.

根本原因

TNN v0.3.0 的 CMakeLists.txt 未定义 install() 指令,无法通过标准的 make install 将产物安装到 CMAKE_INSTALL_PREFIX

解决方案

package() 函数改为手动拷贝编译产物:

cp -av ${builddir}/$ARCH-build/libTNN.so*  ${INSTALL_DIR}/lib/
cp -av ${builddir}/include/tnn             ${INSTALL_DIR}/include/
cp -av ${builddir}/$ARCH-demo-build/demo_* ${INSTALL_DIR}/bin/

6.2 问题二:libomp.so 路径映射错误

现象

libomp.so 拷贝静默失败,安装目录中缺少该文件。

根本原因

${ARCH} 是 ABI 名称(arm64-v8a),而 OpenHarmony SDK 中 LLVM 库目录使用的是 LLVM target triple(aarch64-linux-ohos),两者不同:

# 错误写法(目录不存在)
${OHOS_SDK}/native/llvm/lib/arm64-v8a-linux-ohos/libomp.so
# 正确路径
${OHOS_SDK}/native/llvm/lib/aarch64-linux-ohos/libomp.so

解决方案

package() 中增加 ARCH 到 LLVM triple 的映射:

case "$ARCH" in
    arm64-v8a)   LLVM_TARGET="aarch64-linux-ohos" ;;
    armeabi-v7a) LLVM_TARGET="arm-linux-ohos" ;;
    x86_64)      LLVM_TARGET="x86_64-linux-ohos" ;;
esac
LIBOMP="${OHOS_SDK}/native/llvm/lib/${LLVM_TARGET}/libomp.so"

6.3 问题三:示例程序 CMakeLists.txt 中 CMAKE_C_FLAGS 含 -std=c++11

现象

示例程序 cmake 配置阶段报错:

Could NOT find OpenMP_C (missing: OpenMP_C_FLAGS OpenMP_C_LIB_NAMES)

根本原因

examples/linux/cross/CMakeLists.txt 中:

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=c++11 -fPIC")

cmake 的 FindOpenMP 模块会用 CMAKE_C_FLAGS 编译 .c 测试文件,-std=c++11 对 C 编译器无效,测试失败导致误判。

解决方案

prepare() 中用 sed 修复:

sed -i 's/-O3 -std=c++11 -fPIC/-O3 -fPIC/' \
    $builddir/examples/linux/cross/CMakeLists.txt

七、HPKBUILD 编写要点

7.1 "$@" 机制

lycium 框架调用 build() 时,通过 "$@" 自动注入以下 cmake 参数:

-DCMAKE_TOOLCHAIN_FILE=${OHOS_SDK}/native/build/cmake/ohos.toolchain.cmake
-DCMAKE_INSTALL_PREFIX=${LYCIUM_ROOT}/usr/${pkgname}/${ARCH}
-DCMAKE_SYSTEM_NAME=Linux
-DCMAKE_SYSTEM_PROCESSOR=aarch64

库和示例程序的 cmake 调用都复用同一个 "$@",确保工具链一致。

7.2 两阶段 cmake 构建

build() 分两次调用 cmake:第一次构建 TNN 库,第二次构建示例程序并通过 -DTNN_LIB_PATH 指向库的构建输出目录:

# 第一步:编译 TNN 库
cmake "$@" ... -B$ARCH-build -S./

# 第二步:编译示例程序
cmake "$@" -DTNN_LIB_PATH=${PWD}/$ARCH-build \
    -B$ARCH-demo-build -S./examples/linux/cross/

7.3 重新编译时清理构建记录

若需重新编译,除了删除已解压的源码目录外,还需清理 lycium/usr/hpk_build.csv 中对应条目,否则 lycium 框架会跳过该库的编译:

grep -v "^TNN," lycium/usr/hpk_build.csv > /tmp/tmp.csv && mv /tmp/tmp.csv lycium/usr/hpk_build.csv
rm -rf lycium/usr/TNN
./build.sh TNN

八、编译与部署

8.1 编译

cd lycium_plusplus/lycium
./build.sh TNN

编译产物位于:

lycium/usr/TNN/arm64-v8a/
├── include/tnn/
├── lib/
│   ├── libTNN.so -> libTNN.so.0.1.0.0
│   ├── libTNN.so.0 -> libTNN.so.0.1.0.0
│   ├── libTNN.so.0.1.0.0
│   └── libomp.so
├── bin/
│   ├── demo_arm_imageclassify
│   ├── demo_arm_facedetector
│   └── demo_arm_objectdetector
├── model/
│   ├── SqueezeNet/           # 随源码附带
│   ├── face_detector/        # prepare() 阶段下载
│   └── mobilenet_v2-ssd/     # prepare() 阶段下载
└── assets/                   # 测试图片(dog.png、test_face.jpg、synset.txt 等)

HNP 包和 tar.gz 归档位于:

lycium/output/arm64-v8a/
├── TNN.hnp
└── TNN_0.3.0.tar.gz

8.2 在应用中使用 TNN 库

set(TNN_LIB_PATH /path/to/lycium/usr/TNN/arm64-v8a/lib)
include_directories(/path/to/lycium/usr/TNN/arm64-v8a/include)
link_directories(${TNN_LIB_PATH})
target_link_libraries(my_app TNN)

8.3 鸿蒙PC真机实测

将编译产物传输到鸿蒙PC用户目录下,保持目录结构不变即可,如下所示:

~/tnn/
├── lib/
│   ├── libTNN.so.0
│   └── libomp.so
├── bin/
│   ├── demo_arm_imageclassify
│   ├── demo_arm_facedetector
│   └── demo_arm_objectdetector
├── model/
│   ├── SqueezeNet/
│   ├── face_detector/
│   └── mobilenet_v2-ssd/
└── assets/

注意事项:需要将libTNN.so.0.1.0.0 重命名为libTNN.so.0

原因说明:按照动态库版本规则,TNN库的SONAME是libTNN.so.0,依赖TNN库的程序运行时只会按SONAME来查找库,因此必须将完整版本文件libTNN.so.0.1.0.0重命名或者软链接为libTNN.so.0才能被正确加载。但是鸿蒙PC上加载动态库不会自动追踪软链接,所以只能将完整版本文件重命名为soname。

在鸿蒙PC上打开终端,并运行如下命令做好准备工作:

# 配置库查找路径
export LD_LIBRARY_PATH=~/tnn/lib:$LD_LIBRARY_PATH

# 测试程序增加可执行权限
chmod +x bin/*

# 对所有动态库和测试程序进行自签名
binary-sign-tool sign -inFile ~/tnn/lib/libTNN.so.0 -outFile ~/tnn/lib/libTNN.so.0 -selfSign "1"
binary-sign-tool sign -inFile ~/tnn/lib/libomp.so -outFile ~/tnn/lib/libomp.so -selfSign "1"
binary-sign-tool sign -inFile ~/tnn/bin/demo_arm_imageclassify -outFile ~/tnn/bin/demo_arm_imageclassify -selfSign "1"
binary-sign-tool sign -inFile ~/tnn/bin/demo_arm_facedetector-outFile ~/tnn/bin/demo_arm_facedetector -selfSign "1"
binary-sign-tool sign -inFile ~/tnn/bin/demo_arm_objectdetector -outFile ~/tnn/bin/demo_arm_objectdetector -selfSign "1"

如果找不到binary-sign-tool命令,需要先将鸿蒙PC的系统版本升级到6.0.0.115版本及以上,然后安装官方开发工具套件:DevBox

  • 测试程序1:图像分类
~/tnn/bin/demo_arm_imageclassify \
    -p ~/tnn/model/SqueezeNet/squeezenet_v1.1.tnnproto \
    -m ~/tnn/model/SqueezeNet/squeezenet_v1.1.tnnmodel \
    -i ~/tnn/assets/dog.png \
    -l ~/tnn/assets/synset.txt

在这里插入图片描述

程序对输入图片进行前向推理,输出 Top-N 分类结果,每行包含类别排名、置信度分数和对应的 ImageNet 类别名称(来自 synset.txt)。置信度最高的类别即为模型对该图片内容的预测。如上图所示,分类结果为golden retriever(金毛寻回犬),从图片可以看到分类是正确的。

  • 测试程序2:人脸检测
~/tnn/bin/demo_arm_facedetector \
    -p ~/tnn/model/face_detector/version-slim-320_simplified.tnnproto \
    -m ~/tnn/model/face_detector/version-slim-320_simplified.tnnmodel \
    -i ~/tnn/assets/test_face.jpg

在这里插入图片描述

程序对测试图片进行人脸检测,输出检测到的人脸数量及每个人脸的边界框坐标(x、y、宽、高)和置信度分数。检测框坐标为归一化值或像素值,可用于后续的人脸对齐、识别等任务。从上图可以看到,共检测到5个人脸,并且从输出的图片来看,人脸的识别位置和范围都很精准。

  • 测试程序3:目标检测
~/tnn/bin/demo_arm_objectdetector \
    -p ~/tnn/model/mobilenet_v2-ssd/mobilenetv2_ssd.tnnproto \
    -m ~/tnn/model/mobilenet_v2-ssd/mobilenetv2_ssd.tnnmodel \
    -i ~/tnn/assets/test.jpg

在这里插入图片描述

程序基于 MobileNetV2-SSD 模型对测试图片进行多目标检测,输出每个检测到的目标的类别标签、置信度分数和边界框坐标。模型支持 COCO 数据集的 80 个常见物体类别。从上图可以看到,共检测到5个物体,并且从输出的图片来看,物体的识别位置和范围也很精准。


参考资料

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐