TVM 裸机部署实战:基于 Cortex-M55 与 CMSIS-NN 运行人员检测模型

TVM 裸机部署实战:基于 Cortex-M55 与 CMSIS-NN 运行人员检测模型

【免费下载链接】tvm Open deep learning compiler stack for cpu, gpu and specialized accelerators 【免费下载链接】tvm 项目地址: https://gitcode.com/gh_mirrors/tvm7/tvm

导读

本指南讲解如何在 TVM 开源仓库自带的示例工程中,把量化(int8)神经网络模型编译到 Arm Cortex-M55 裸机(bare metal)环境,并借助 CMSIS-NN 库在 Arm Corstone-300 固定虚拟平台(FVP)上完成一次完整的人员检测推理。读者将掌握从环境准备、tvmc 编译参数、C 头文件数据生成到裸机应用构建与仿真运行的完整链路,并了解 TVM 中 cmsis-nn 目标后端的源码级工作原理。

示例工程位于 apps/microtvm/cmsisnn,核心文件包括一键脚本 run_demo.sh、裸机主程序 src/demo_bare_metal.c、图像转换脚本 convert_image.py 与构建规则 Makefile。

前置条件(Prerequisites)

在 TVM 官方 Docker 容器内运行

如果直接在 TVM 自带的 ci_cpu Docker 容器中运行本示例,下列软件均已被预先安装,可跳过本节的安装步骤。

自行搭建环境时需要的软件

不在 ci_cpu 容器中运行时,需要手动安装以下软件:

  • 基于 Arm Corstone-300 的固定虚拟平台(FVP):用于在没有真实硬件的情况下仿真 Cortex-M55 + Ethos-U55 系统。
  • cmake 3.19.5:构建 UART 驱动等子组件所需的最低版本。
  • Arm 提供的 GCC 工具链(arm-none-eabi):交叉编译裸机程序。
  • Arm Ethos-U NPU 驱动栈(core_platform):提供 Corstone-300 平台的外设驱动,如 UART。
  • CMSIS:Cortex-M 软件接口标准,提供设备启动代码与内核头文件。
  • CMSIS-NN:Arm 官方的高效神经网络内核库,本示例的算子代码最终由它执行。

上述软件均可以通过执行 docker/install/ubuntu_install_ethosu_driver_stack.sh 一键安装,这是官方 CI 镜像所采用的安装脚本。

Python 依赖

进入示例目录后,安装本目录下 requirements.txt 列出的 Python 库:

pip install -r ./requirements.txt

该文件包含了 ethos-u-vela(Ethos-U 编译器)、numpy、Pillow(图像处理)、tflite(模型解析)以及 tvm.micro 相关组件运行所需的依赖。

TVM 本身的安装

两种方式任选其一:

  1. 源码编译:按官方安装指引编译 TVM,并且在 config.cmake 中必须开启以下三个选项:

    set(USE_CMSISNN ON)
    set(USE_MICRO ON)
    set(USE_LLVM ON)
    
    • USE_CMSISNN 决定是否编译 cmsis-nn 后端。开启后,构建系统会在编译命令中加入 -DTVM_USE_CMSISNN 宏,并把 src/relay/backend/contrib/cmsisnn 下的全部源码纳入编译,详见 cmake/modules/contrib/CMSISNN.cmake。
    • USE_MICRO 启用 microTVM 支持,编译生成的模型运行时依赖 CRT(C Runtime)。
    • USE_LLVM 用于代码生成与编译基础设施。
  2. 通过 TLCPack 安装:直接使用预编译的 TVM 二进制包,跳过源码编译。

更新 PATH 环境变量

需要把 cmake 3.19.5 与 FVP 的可执行文件目录加入 PATH。假设安装在 /opt/arm 下:

export PATH=/opt/arm/FVP_Corstone_SSE-300/models/Linux64_GCC-6.4:/opt/arm/cmake/bin:$PATH

注意 FVP 目录中 Linux64_GCC-6.4 是随 FVP 发行的 GCC 版本化目录名,路径需与实际安装版本一致。

一键运行裸机演示

在 apps/microtvm/cmsisnn 目录下执行:

./run_demo.sh

如果 Ethos-U 平台与 CMSIS 并未安装在默认位置 /opt/arm/ethosu,可以通过命令行参数显式指定,例如:

./run_demo.sh --cmsis_path /home/tvm-user/cmsis \
--ethosu_platform_path /home/tvm-user/ethosu/core_platform

run_demo.sh 支持的完整参数如下(对应脚本内的参数解析逻辑):

参数作用默认值
-h, --help打印用法说明—
--cmsis_path CMSIS_PATH设置 CMSIS 根目录/opt/arm/ethosu/cmsis
--ethosu_platform_path ETHOSU_PLATFORM_PATH设置 Ethos-U core_platform 根目录/opt/arm/ethosu/core_platform
--fvp_path FVP_PATH设置 FVP 安装路径(脚本会拼接 models/Linux64_GCC-6.4 子目录加入 PATH)依赖系统 PATH
--cmake_path CMAKE指定 cmake 可执行文件cmake

脚本执行的完整流程

运行 run_demo.sh 会依次完成以下步骤:

  1. 清理并重建构建目录:执行 make cleanall 后创建 build/ 目录。
  2. 下载量化模型:从 TensorFlow 官方仓库下载 int8 量化的人员检测模型 person_detect.tflite。
  3. 使用 tvmc 编译模型:为目标 cmsis-nn,c(Cortex-M55 CPU + CMSIS-NN)编译,并以 Model Library Format(mlf)输出。
  4. 下载测试图像:下载 person.bmp 作为输入图像。
  5. 生成 C 头文件:运行 convert_image.py 生成 inputs.h(图像数据)与 outputs.h(推理结果存储区)。
  6. 构建裸机应用:执行 make 生成 build/demo。
  7. 在 FVP 上仿真运行:启动 Corstone-300 FVP 运行 demo。
  8. 输出检测结果:程序打印 Person detected. 或 No person detected.。

tvmc 编译命令解析

脚本第 121-133 行是模型编译的核心:

python3 -m tvm.driver.tvmc compile --target=cmsis-nn,c \
    --target-cmsis-nn-mcpu=cortex-m55 \
    --target-c-mcpu=cortex-m55 \
    --runtime=crt \
    --executor=aot \
    --executor-aot-interface-api=c \
    --executor-aot-unpacked-api=1 \
    --pass-config tir.usmp.enable=1 \
    --pass-config tir.usmp.algorithm=hill_climb \
    --pass-config tir.disable_storage_rewrite=1 \
    --pass-config tir.disable_vectorize=1 ./person_detect.tflite \
    --output-format=mlf \
    --module-name=detection

各参数含义如下:

  • --target=cmsis-nn,c:同时指定两个代码生成目标。cmsis-nn 目标负责把可融合的卷积、池化等算子卸载给 CMSIS-NN 内核,其余算子由 c 目标生成标准 C 代码。cmsis-nn 目标通过 TVM_REGISTER_TARGET_KIND("cmsis-nn") 注册(见 src/relay/backend/contrib/cmsisnn/target.cc),其 mcpu 属性由 src/relay/backend/contrib/cmsisnn/compiler_attrs.cc 中的配置解析逻辑消费。
  • --target-cmsis-nn-mcpu=cortex-m55 / --target-c-mcpu=cortex-m55:为两个目标分别指定 CPU 微架构,生成的代码将使用 Cortex-M55 的指令集与 DSP 扩展。
  • --runtime=crt:使用 microTVM 的 C Runtime(CRT)作为运行时。
  • --executor=aot:选择 AOT(Ahead-Of-Time)执行器,把推理逻辑静态展开为 tvmgen_detection_run 这样的入口函数。
  • --executor-aot-interface-api=c:生成 C 语言风格的结构体输入输出接口。
  • --executor-aot-unpacked-api=1:使用 unpacked API,避免装箱(boxing)开销。
  • --pass-config tir.usmp.enable=1 / tir.usmp.algorithm=hill_climb:开启 Unified Static Memory Planner(USMP)静态内存规划,并使用 hill_climb 贪心算法对内存进行复用,以降低 SRAM 占用。
  • --pass-config tir.disable_storage_rewrite=1:禁用存储重写 pass,配合 USMP 工作。
  • --pass-config tir.disable_vectorize=1:禁用向量化(CMSIS-NN 场景下不依赖 TVM 自身向量化)。
  • --output-format=mlf:以 Model Library Format 输出,产出 module.tar,随后 tar -xf module.tar 解包。
  • --module-name=detection:生成的运行时符号统一以 detection 为后缀(如 tvmgen_detection_run、tvmgen_detection_inputs)。

深入理解代码生成与 CMSIS-NN 后端

TVM 的 CMSIS-NN 支持并非简单的"调用库函数",而是一条完整的编译流水线。从源码结构看,src/relay/backend/contrib/cmsisnn 目录中的模块分别承担:

结合前文 tvmc 的 cmsis-nn,c 双目标编译参数,可以推断实际流程为:Relay 图先经算子融合 pass 把可卸载子图标记给 cmsis-nn 编译器,再由上述模块生成调用 CMSIS-NN 函数的 C 代码,其余算子落入通用 c 目标。这种分工正是"CNN 计算密集部分走 DSP 加速库、控制流与通用算子走标准 C"的典型嵌入式部署策略。

裸机主程序分析

编译产物会生成模型专属头文件 tvmgen_detection.h(声明 tvmgen_detection_run 与输入输出结构体),demo 主程序 src/demo_bare_metal.c 直接调用它:

#include <tvm_runtime.h>
#include <tvmgen_detection.h>
#include "uart_stdout.h"
#include "inputs.h"
#include "outputs.h"

int main(int argc, char** argv) {
  UartStdOutInit();
  printf("Starting Demo\n");

  struct tvmgen_detection_outputs detection_outputs = {
      .MobilenetV1_Predictions_Reshape_1 = output,
  };
  struct tvmgen_detection_inputs detection_inputs = {
      .input = input,
  };

  tvmgen_detection_run(&detection_inputs, &detection_outputs);

  if (output[1] > output[0]) {
    printf("Person detected.\n");
  } else {
    printf("No person detected.\n");
  }

  printf("EXITTHESIM\n");
  while (1 == 1)
    ;
  return 0;
}

要点:

  • 模型入口 tvmgen_detection_run 由 AOT 执行器生成,输入输出通过结构体直接传递,接口名中的 detection 来自 tvmc 的 --module-name=detection。
  • 输入 input 与输出 output 数组分别来自 convert_image.py 生成的 inputs.h 与 outputs.h。
  • 结果判断逻辑是二分类比大小:输出张量第二个元素大于第一个元素即判定"检测到人"。
  • EXITTHESIM 是 FVP 的退出约定字符串,仿真器收到该串后自动结束(见脚本中的 -C mps3_board.uart0.shutdown_tag=\"EXITTHESIM\")。

microTVM 平台抽象层

include/tvm_runtime.h 提供了 CRT 在该裸机平台上的适配实现:

  • TVMPlatformAbort:出错时打印错误码并输出 EXITTHESIM 终止仿真。
  • TVMPlatformMemoryAllocate / TVMPlatformMemoryFree:本例中返回 kTvmErrorFunctionCallNotImplemented,因为 AOT + unpacked API 下所有缓冲区均为静态分配,不需要动态内存。
  • TVMLogf:把日志重定向到 vfprintf(stdout, ...),经 UART 输出。
  • TVMFuncRegisterGlobal:AOT 场景直接返回 0(无需注册全局函数)。

这些函数是 microTVM 平台接口(Platform Abstraction Layer)在裸机上的最小实现,任何新硬件移植都要补齐它们。

图像转换:把图片变成 C 数组

convert_image.py 的作用是把输入图像转换为模型所需的 int8 数据并生成两个头文件:

python3 ./convert_image.py ./build/input_image.bmp

处理细节:

  1. 缩放:使用 Pillow 把图片缩放到 224×224(Image.open(img_path).resize((224, 224))),与 MobileNetV1 输入尺寸一致。
  2. 加批维度:np.expand_dims(img_data, axis=0) 得到 NCHW 四维张量。
  3. 量化偏移:img_data - 128 后转为 np.int8,对应模型训练时的输入量化方案(uint8 像素减去 128 映射到 int8)。
  4. 生成 inputs.h:把字节以 \xHH 十六进制转义序列写入 C 字符串字面量,并声明 input 数组:
    const size_t input_len = ...;
    __attribute__((section(".data.tvm"), aligned(16))) int8_t input[] = "...";
    

    section(".data.tvm") 配合 corstone300.ld 链接脚本,将大数组放置在 DDR 段(见下文内存布局)。

  5. 生成 outputs.h:预分配长度为 2 的 int8_t output 数组存放推理结果。
  6. 生成 crt_config.h:调用 tvm.micro.copy_crt_config_header 在 build/crt_config/ 下生成 CRT 配置头文件。

构建系统:Makefile 与交叉编译

Makefile 负责把四部分产物链接为裸机可执行文件:

  • 生成的代码:build/codegen/host/src/*.c(tvmc mlf 产物中的模型 C 代码),打包为 libcodegen.a。
  • CMSIS 启动代码:CMSIS_PATH/Device/ARM/ARMCM55/Source/*.c,打包为 libcmsis_startup.a。
  • CMSIS-NN 内核:find ${CMSIS_PATH}/CMSIS-NN/Source/*/*.c 递归收集的所有内核源码,打包为 libcmsis_nn.a。
  • UART 驱动:从 Ethos-U core_platform 的 drivers/uart 经 cmake 交叉编译生成 libethosu_uart_cmsdk_apb.a。

关键编译选项:

PKG_COMPILE_OPTS = -g -Wall -O2 -Wno-incompatible-pointer-types -Wno-format \
                   -mcpu=cortex-m55 -mthumb -mfloat-abi=hard -std=gnu99

-mcpu=cortex-m55 -mfloat-abi=hard 启用 Cortex-M55 的 FPU;链接时使用 -specs=nosys.specs -static -T corstone300.ld(无操作系统环境 + 静态链接 + 自定义链接脚本)。

交叉编译工具链文件 arm-none-eabi-gcc.cmake 把编译器固定为 arm-none-eabi-gcc/g++,处理器设为 cortex-m55,并根据 +fp/+nofp 特性后缀自动决定 -mfloat-abi 取值;同时禁用 C++ 异常与 RTTI 以适配嵌入式环境。

链接脚本中的内存布局

corstone300.ld 定义了 Corstone-300 系统的内存映射:

内存区域起始地址大小用途
ITCM0x00000000512KB存放 .text 代码(CPU 只读)
DTCM0x20000000512KB存放 .data、.bss、栈与堆
SSE-300 SRAM0x210000002MB可读写(NPU 可访问)
Data SRAM0x010000002MB可读写
DDR0x6000000032MB大容量可读写(NPU 可访问)

链接脚本把 .rodata.tvm 与 .data.tvm 段(即模型权重与输入图像所在段)单独放入 .ddr 段,规避了 ITCM/DTCM 容量限制;同时在脚本末尾用 ASSERT(__StackLimit >= __bss_end__, "region DTCM overflowed with stack") 检查 DTCM 是否溢出。

使用自己的图片

convert_image.py 接收一个命令行参数:待转换图片的路径。演示默认使用的测试图片由 run_demo.sh 从 TensorFlow 仓库下载:

curl -sS https://raw.githubusercontent.com/tensorflow/tflite-micro/main/tensorflow/lite/micro/examples/person_detection/testdata/person.bmp -o input_image.bmp

若要换成自己的图片,只需修改 run_demo.sh 中这一行为自己图片的下载/复制命令,或直接把图片放到 build/ 目录后手动执行:

python3 ./convert_image.py ./build/your_image.bmp

注意脚本会强制缩放到 224×224,因此任意分辨率的图片均可使用;而 no_person.bmp 等负样本同样被注释保留在脚本中,可用来验证"未检测到人"的输出路径。

在 FVP 上运行与结果验证

脚本末尾通过以下命令启动仿真:

FVP_Corstone_SSE-300_Ethos-U55 -C cpu0.CFGDTCMSZ=15 \
-C cpu0.CFGITCMSZ=15 -C mps3_board.uart0.out_file=\"-\" \
-C mps3_board.uart0.shutdown_tag=\"EXITTHESIM\" \
-C mps3_board.visualisation.disable-visualisation=1 \
-C mps3_board.telnetterminal0.start_telnet=0 \
... \
./build/demo

关键 FVP 配置项说明:

  • cpu0.CFGDTCMSZ=15 / cpu0.CFGITCMSZ=15:配置 DTCM 与 ITCM 的地址解码大小(0x80000,即 512KB,与链接脚本一致)。
  • mps3_board.uart0.out_file=\"-\":把 UART 输出重定向到 stdout,使 printf 结果直接显示在终端。
  • mps3_board.uart0.shutdown_tag=\"EXITTHESIM\":UART 收到 EXITTHESIM 字符串后仿真器自动退出(对应 demo 主程序最后的 printf("EXITTHESIM\n"))。
  • mps3_board.visualisation.disable-visualisation=1:关闭可视化窗口,适合无头(headless)环境。
  • telnetterminal*.start_telnet=0:关闭无关的 telnet 终端,避免干扰。

正常运行结束时,终端会依次出现 Starting Demo、Running detection inference、Person detected.(或 No person detected.)以及 EXITTHESIM,随后 FVP 自行关闭。

小结

本示例完整覆盖了 TVM 面向 Cortex-M 裸机 + CMSIS-NN 的部署链路:环境搭建(Docker 脚本或手动安装)→ tvmc 双目标编译(cmsis-nn,c + AOT/CRT/USMP 系列 pass 配置)→ 图像转 C 数组(convert_image.py)→ Makefile 交叉链接(CMSIS 启动代码 + CMSIS-NN 内核 + UART 驱动 + 模型代码)→ Corstone-300 FVP 仿真验证。通过本文涉及的源码路径,读者还可以进一步阅读 src/relay/backend/contrib/cmsisnn 下的算子映射与常量生成实现,或参考 apps/microtvm/ethosu 等相邻示例对比不同后端的部署方式。

【免费下载链接】tvm Open deep learning compiler stack for cpu, gpu and specialized accelerators 【免费下载链接】tvm 项目地址: https://gitcode.com/gh_mirrors/tvm7/tvm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值