TVM 微控制器实战:在 Arm Cortex-M55 与 Ethos-U55 NPU 裸机环境运行 Mobilenet v2
2026/9/23 15:48:24 网站建设 项目流程
  • 编译器
  • 深度学习
  • 模型优化

【免费下载链接】tvm

Open deep learning compiler stack for cpu, gpu and specialized accelerators

项目地址:https://gitcode.com/gh_mirrors/tvm7/tvm
点击查看免费下载

本指南围绕 Apache TVM 官方示例目录 apps/microtvm/ethosu 展开,完整讲解如何在无操作系统的裸机(bare metal)环境下,把量化(int8)MobileNet v2 图像分类模型部署到基于 Arm Corstone-300 平台的Cortex-M55 CPU + Ethos-U55 NPU + CMSIS-NN异构目标上:从环境准备、TVM 编译选项、tvmc 命令行配置,到 C 运行时桥接、链接脚本与 FVP 仿真验证的完整链路。读完本文,你将掌握用 TVM 的ethos-u/cmsis-nn/c多目标编译能力生成 AOT 模型库(MLF),并在 Fixed Virtual Platform(FVP)上驱动 NPU 完成真实推理与分类输出的整套方法。

一、示例概述:一次完整的微控制器 NPU 部署

该示例位于仓库的 apps/microtvm/ethosu 目录,展示了 TVM 微控制器(microTVM)工具链的典型工作流:

  1. 下载一个量化后的 int8 MobileNet v2 TFLite 模型;
  2. 使用tvmc将模型编译为面向Cortex-M55 CPU、Ethos-U55 NPU 与 CMSIS-NN的代码(AOT 执行器 + CRT 运行时);
  3. 下载一张企鹅图片,将其转换为 C 头文件中的字节数组;
  4. 构建 Ethos-U55 核心驱动(core driver)、CMSIS 启动代码与演示应用;
  5. 在基于 Arm Corstone-300 的 FVP 仿真器上运行,并在 UART 输出分类结果(例如The image has been classified as 'king penguin')。

示例目录中的关键文件(可对照阅读):

文件作用
run_demo.sh一键式演示脚本:下载模型与图片、调用 tvmc 编译、生成头文件、构建并运行 FVP
Makefile使用arm-none-eabi-gcc交叉编译裸机/FreRTOS 演示程序并链接 Ethos-U 驱动
convert_image.py把输入图片转换为inputs.h/outputs.hC 数组头文件
convert_labels.py把 ImageNet 标签转换为labels.h,并复制 CRT 配置头文件
src/demo_bare_metal.c裸机演示主程序,调用tvmgen_default_run执行推理
src/demo_freertos.c基于 FreeRTOS 任务与队列的演示主程序
src/tvm_ethosu_runtime.cTVM 运行时与 Ethos-U 驱动之间的桥接层
arm-none-eabi-gcc.cmake交叉编译工具链文件,指定 Cortex-M 目标
corstone300.ldCorstone-300 平台的内存布局链接脚本

二、前置条件与依赖安装

2.1 在 CI Docker 容器中运行

TVM 仓库自带的ci_cpuDocker 镜像已经预装本示例所需的全部软件,是最省事的运行方式。若使用该容器,可跳过本节剩余步骤。

2.2 手动安装依赖

若在本地环境运行,需要先安装以下软件(TVM 提供了自动化安装脚本 docker/install/ubuntu_install_ethosu_driver_stack.sh,一条命令即可装齐):

  • Arm Corstone-300 的 Fixed Virtual Platform(FVP):用于仿真 Corstone-300 子系统(Cortex-M55 + Ethos-U55),是运行裸机镜像的"虚拟机";
  • cmake 3.19.5:构建 Ethos-U 驱动栈需要;
  • Arm GCC 工具链gcc-arm-none-eabi-10-2020-q4-major,用于交叉编译 Cortex-M 目标;
  • Arm Ethos-U NPU 驱动栈(core driver):提供ethosu_init/ethosu_invoke/ethosu_reserve_driver等 NPU 操作接口;
  • CMSIS(CMSIS_5):提供 Cortex-M55 设备头文件(如ARMCM55.h)与启动代码;
  • CMSIS-NN:提供 Cortex-M 上的神经网络内核实现(示例中 Softmax 直接使用 CMSIS-NN 实现)。

此外还需安装本目录 requirements.txt 中列出的 Python 库,包括ethos-u-vela(Ethos-U 神经网络编译器)、Pillow(图片处理)、numpytflite(TFLite 模型解析)等。在示例目录下执行:

pip install -r ./requirements.txt

2.3 TVM 本身的安装方式

二选一:

  • 从源码构建:参考 TVM 官方安装文档。构建时必须在config.cmake中开启以下四个开关:
    set(USE_ETHOSU ON) # 启用 Ethos-U 编译器后端 set(USE_CMSISNN ON) # 启用 CMSIS-NN 代码生成 set(USE_MICRO ON) # 启用 microTVM / CRT 运行时 set(USE_LLVM ON) # 启用 LLVM 代码生成

    其中USE_ETHOSU在 CMake 侧的开关逻辑可见 cmake/modules/contrib/EthosU.cmake:开启后会把src/relay/backend/contrib/ethosusrc/contrib/ethosu/cascadersrc/tir/contrib/ethosu的源码编入编译器;关闭时仅保留 Python 侧 Object 定义所需的utils.cc与 cascader 代码。

  • 使用 TLCPack 预编译包:直接安装现成的二进制 wheel。

2.4 配置 PATH 环境变量

需要把cmake 3.19.5FVP加入PATH。假设二者都安装在/opt/arm下:

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

其中FVP_Corstone_SSE-300/models/Linux64_GCC-6.4是 FVP 可执行文件的目录,/opt/arm/cmake/bin是 cmake 的目录。

三、运行演示应用

3.1 裸机演示

在示例目录下直接执行:

./run_demo.sh

3.2 FreeRTOS 演示

run_demo.sh支持--freertos_path参数,指定 FreeRTOS 源码路径后,将改用 src/demo_freertos.c 作为主程序,用任务(task)与队列(queue)实现"数据采集任务 → 推理任务"的流水线:

./run_demo.sh --freertos_path /opt/freertos/FreeRTOSv202112.00/

从脚本源码看(run_demo.sh),该路径会转换为FREERTOS_PATH环境变量传给 Makefile;Makefile 检测到FREERTOS_PATH后会自动编译 FreeRTOS 内核源码(tasks.clist.cqueue.ctimers.cevent_groups.c及 ARM_CM33_NTZ 移植层的port.c/portasm.c/heap_3.c),并链接src/demo_freertos.c

3.3 自定义依赖路径

如果 Ethos-U 驱动或 CMSIS 未安装在默认位置/opt/arm/ethosu,可以用脚本参数显式指定:

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

run_demo.sh支持的完整参数(来自脚本内show_usage,见 run_demo.sh):

参数说明
-h, --help显示帮助信息
--ethosu_driver_pathEthos-U core driver 路径
--cmsis_pathCMSIS 路径
--ethosu_platform_pathEthos-U core platform 路径(提供 UART 驱动与 Corstone-300 目标文件)
--fvp_pathFVP 根路径(脚本会追加models/Linux64_GCC-6.4PATH
--cmake_path指定 cmake 可执行文件路径
--freertos_path启用 FreeRTOS 演示并指定其源码路径

3.4 脚本自动完成的完整流水线

./run_demo.sh一次完成下列全部步骤(可对照 run_demo.sh 逐行阅读):

  1. make cleanall清理旧构建产物并新建build/目录;
  2. 从 Arm ML-zoo 下载mobilenet_v2_1.0_224_INT8.tflite
  3. 调用tvmc将模型编译为 Cortex-M55 + Ethos-U55 + CMSIS-NN 目标,产物格式为MLF(Model Library Format),解包得到 CRT 运行时与生成的 C 代码;
  4. 下载 ImageNet 标签文件labels_mobilenet_quant_v1_224.txt
  5. 下载一张企鹅图片(kitten.jpg同为默认测试图之一);
  6. 运行 convert_image.py 生成include/inputs.h(输入图像数据)与include/outputs.h(输出缓冲区);
  7. 运行 convert_labels.py 生成include/labels.h,并把 CRT 配置头文件复制到build/crt_config/
  8. make交叉编译生成裸机可执行文件build/demo
  9. 启动FVP_Corstone_SSE-300_Ethos-U55仿真运行./build/demo,UART 输出分类结果,收到EXITTHESIM后仿真自动退出。

四、tvmc 编译命令行逐项解析

脚本核心是这条编译命令(见 run_demo.sh),它决定了整条部署链路的编译策略:

python3 -m tvm.driver.tvmc compile --target=ethos-u,cmsis-nn,c \ --target-ethos-u-accelerator_config=ethos-u55-256 \ --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 \ ./mobilenet_v2_1.0_224_INT8.tflite \ --output-format=mlf

参数含义与底层原理:

  • --target=ethos-u,cmsis-nn,c:声明三段式异构编译目标。TVM 会先让ethos-u后端(Vela 编译器 + TVM 的 Ethos-U 代码生成,源码见 src/relay/backend/contrib/ethosu)接管图中适合 NPU 加速的算子(卷积等),其余算子由cmsis-nn后端调用 CMSIS-NN 内核、最后落到c目标生成 C 代码;
  • --target-ethos-u-accelerator_config=ethos-u55-256:指定 Ethos-U 加速器配置为U55 且 MAC 数为 256(对应 FVP 启动参数ethosu.num_macs=256,两者必须一致);
  • --target-cmsis-nn-mcpu=cortex-m55--target-c-mcpu=cortex-m55:为 CMSIS-NN 和通用 C 代码指定 CPU 为 Cortex-M55;
  • --runtime=crt:使用 microTVM 的 C Runtime(CRT),让生成的代码可以在无操作系统、无标准库依赖的裸机上运行;
  • --executor=aot:采用 Ahead-of-Time 图执行器,编译期生成tvmgen_default_run()入口函数;
  • --executor-aot-interface-api=c:以纯 C 结构体(tvmgen_default_inputs/outputs/devices)传递输入输出;
  • --executor-aot-unpacked-api=1:生成"非打包"(unpacked)API,即每个算子对应独立的函数签名,便于裁剪与静态分析;
  • --pass-config tir.usmp.enable=1:启用 USMP(Unified Static Memory Planning)统一静态内存规划,在编译期确定所有中间张量的内存池布局;
  • --pass-config tir.usmp.algorithm=hill_climb:选择爬山算法进行内存池分配优化;
  • --pass-config tir.disable_storage_rewrite=1:禁用存储重写 pass(配合 USMP 使用);
  • --pass-config tir.disable_vectorize=1:禁用向量化(Cortex-M55 的 MVE 向量化在此示例中不启用);
  • --output-format=mlf:输出 Model Library Format,一个包含运行时、生成代码与元数据的标准目录结构(打包为module.tar,脚本随后tar -xf module.tar解包)。

五、输入数据与标签的 C 头文件生成

5.1 图片 → C 数组

convert_image.py 接收一个命令行参数(图片路径),完成以下转换:

  1. 使用 Pillow 把图片缩放为224×224(MobileNet v2 的标准输入尺寸);
  2. 转为 numpy 数组并增加 batch 维度,得到 NCHW 四维输入;
  3. 执行量化预处理:每个像素减去 128,再转为int8(对应 TFLite 模型输入量化参数 zero point 为 -128 的情形);
  4. 生成include/inputs.h:声明input[]数组(16 字节对齐,放置在ethosu_scratch段,供 NPU 直接访问),内容以\x..十六进制转义序列写入;
  5. 生成include/outputs.h:声明长度为1001(ImageNet 1000 类 + 1 个背景类)的int8_t output[],放置在output_data_sec段。

调用方式(脚本第 170 行实际执行的命令):

python3 ./convert_image.py ./build/kitten.jpg

5.2 标签 → C 字符串数组

convert_labels.py 把 ImageNet 标签文件逐行转为char* labels[]字符串数组,同样放置在ethosu_scratch段,并调用tvm.micro.copy_crt_config_header把 CRT 配置头文件复制到build/crt_config/供 Makefile 使用:

python3 ./convert_labels.py ./build/labels_mobilenet_quant_v1_224.txt

5.3 使用你自己的图片

要换用自定义图片,只需修改 run_demo.sh 中的下载与转换两行,指向你的图片文件即可:

curl -sS https://upload.wikimedia.org/wikipedia/commons/1/18/Falkland_Islands_Penguins_29.jpg -o penguin.jpg python3 ./convert_image.py ./build/penguin.jpg

注意convert_image.py只接受单个图片路径参数,且图片会被强制缩放到 224×224,因此任意分辨率图片均可使用。

六、裸机运行时与 NPU 驱动的桥接

6.1 应用主程序如何调用模型

编译产物中,TVM 的 AOT 执行器生成了tvmgen_default_run(&inputs, &outputs, &devices)入口。裸机主程序 src/demo_bare_metal.c 的调用流程为:

UartStdOutInit(); // 初始化 Corstone-300 UART(输出 printf) EthosuInit(); // 初始化 Ethos-U NPU 驱动 struct tvmgen_default_inputs inputs = { .tfl_quantize = input, // 来自 inputs.h 的图像数据 }; struct tvmgen_default_outputs outputs = { .MobilenetV2_Predictions_Reshape_11 = output, // 来自 outputs.h }; struct ethosu_driver* driver = ethosu_reserve_driver(); // 独占 NPU struct tvmgen_default_devices devices = { .ethos_u = driver, // 把 NPU 驱动句柄交给运行时 }; tvmgen_default_run(&inputs, &outputs, &devices); ethosu_release_driver(driver); // 在 1001 个输出中找最大值的索引,即为预测类别 printf("The image has been classified as '%s'\n", labels[max_index]); printf("EXITTHESIM\n"); // FVP 收到该字符串后结束仿真

6.2 NPU 初始化与中断

NPU 的初始化位于 apps/microtvm/ethosu/include/ethosu_mod.h:

if (ethosu_init(&ethosu0_driver, (void*)ETHOSU_BASE_ADDRESS, NULL, 0, 1, 1)) { printf("Failed to initialize NPU.\n"); return -1; } NVIC_SetVector(ETHOSU_IRQ, (uint32_t)&ethosuIrqHandler0); NVIC_EnableIRQ(ETHOSU_IRQ);

而 Ethos-U55 的硬件地址与中断号定义在 include/ethosu_55.h:

#define ETHOSU_IRQ ((IRQn_Type)56) #define ETHOSU_BASE_ADDRESS ((void*)0x48102000)

即 NPU 寄存器基地址为0x48102000,中断号为 56,这与 Corstone-300 SSE-300 子系统的硬件映射一致。

6.3 TVM 运行时与 Ethos-U 驱动的桥接层

AOT 生成的 C 代码会调用形如TVMEthosULaunch(...)的设备回调,其实现位于 src/tvm_ethosu_runtime.c,核心是把 TVM 传入的 NPU command stream 与张量基地址直接转交 Ethos-U 驱动的ethosu_invoke

int32_t TVMEthosULaunch(tvm_device_ethos_u_t* context, void* cms_data, size_t cms_data_size, uint64_t* base_addrs, size_t* base_addrs_size, int num_tensors) { struct ethosu_driver* driver = (struct ethosu_driver*)context; int32_t result = ethosu_invoke(driver, cms_data, cms_data_size, base_addrs, base_addrs_size, num_tensors); if (result != 0) return -1; return 0; }

此外还提供了TVMDeviceEthosUActivate/Open/Close/Deactivate四个空实现(返回 0),保持设备生命周期接口完整。从源码结构可以推断,ethosu_scratch/output_data_sec/rodata.tvm这些段正是为了让 TVM 生成的 command stream(.rodata.tvm)与输入输出数据落在NPU 可访问的 DDR/SRAM中。

6.4 FreeRTOS 版本的差异

src/demo_freertos.c 采用经典的生产者-消费者模式:

  • 数据采集任务prvDataCollectionTask:延迟 100ms 后将&input指针通过xQueueSend发送到队列;
  • 推理任务prvInferenceTaskxQueueReceive阻塞等待数据,收到后构造tvmgen_default_inputs并执行tvmgen_default_run,最后同样打印分类结果与EXITTHESIM

队列长度为 1,推理任务优先级高于数据任务(tskIDLE_PRIORITY + 3vs+ 2),推理任务栈大小 4096 字。由于 Ethos-U 驱动是不可重入的共享资源,两个任务间通过队列串行化推理访问。

七、构建系统与内存布局

7.1 交叉编译工具链

arm-none-eabi-gcc.cmake 是面向 Cortex-M 的 CMake 工具链文件,关键点:

  • 默认目标cortex-m55,可通过CMAKE_SYSTEM_PROCESSOR覆盖;
  • 根据目标自动推导硬浮点/软浮点:cortex-m33cortex-m55默认-mfloat-abi=hard
  • 统一添加-mcpu/-mfloat-abi编译与链接选项,并默认生成output.map链接映射文件;
  • C 标准为 C99、C++ 标准为 C++14。

7.2 Makefile 的构建组成

Makefile 默认路径约定:Ethos-U 驱动、CMSIS、core platform 默认都在/opt/arm/ethosu下(对应脚本参数--ethosu_driver_path等),最终可执行文件build/demo由以下部分链接而成:

  • AOT 生成的 C 代码build/codegen/host/src/*.c,打包为libcodegen.a
  • CRT 运行时stack_allocator.ccrt_backend_api.c(来自 MLF 解包出的 CRT);
  • CMSIS 启动代码CMSIS/Device/ARM/ARMCM55/Source/*.c
  • CMSIS-NN SoftmaxCMSIS-NN/Source/SoftmaxFunctions/*.c(模型的最后分类层);
  • Ethos-U core driver:通过 CMake + 工具链文件构建为静态库;
  • UART 驱动core_platform/drivers/uart
  • Corstone-300 平台代码core_platform/targets/corstone-300/*.c
  • 演示主程序:默认src/demo_bare_metal.c,传入FREERTOS_PATH时替换为src/demo_freertos.c并加入 FreeRTOS 内核源码。

CPU 编译选项为-mcpu=cortex-m55 -mthumb -mfloat-abi=hard -std=gnu99,链接选项为-lm -specs=nosys.specs -static -T corstone300.ld

7.3 Corstone-300 内存布局

corstone300.ld 定义了五块内存区域:

内存基地址大小说明
ITCM0x00000000512KB存放代码(.text),CPU 只读,NPU 不可访问
DTCM0x20000000512KB数据与栈/堆
SSE-300 SRAM0x210000002MBCPU 与 NPU 均可读写(.sram段,存放 fast memory)
Data SRAM0x010000002MBCPU 与 NPU 均可读写
DDR0x6000000032MBCPU 与 NPU 均可读写(.ddr段)

链接脚本的核心段划分:

  • .ddr段依次放置ethosu_scratch(输入数据、标签)、output_data_sec(输出)与*.rodata.tvm(NPU command stream),全部落在DDR,这是 NPU 可访问的共享内存;
  • .sram段放置*.bss.ethosu_fast_memory*.data.tvm,落在 SSE-300 SRAM,供 NPU 快速访问;
  • 栈与堆各 32KB(__STACK_SIZE/__HEAP_SIZE=0x8000),位于 DTCM,并在脚本末尾用ASSERT检查栈不溢出 DTCM。

八、FVP 仿真与结果验证

run_demo.sh最后一行启动仿真器(见 run_demo.sh):

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 \ -C mps3_board.telnetterminal1.start_telnet=0 -C mps3_board.telnetterminal2.start_telnet=0 -C mps3_board.telnetterminal5.start_telnet=0 \ -C ethosu.extra_args="--fast" \ -C ethosu.num_macs=256 ./build/demo

关键 FVP 参数说明:

  • cpu0.CFGDTCMSZ=15/cpu0.CFGITCMSZ=15:把 DTCM/ITCM 配满(2^15 = 32KB,对应 512KB 需要结合平台默认值理解,此处是 Corstone-300 的标准配置);
  • mps3_board.uart0.out_file="-":UART 输出到终端,便于观察 printf 结果;
  • mps3_board.uart0.shutdown_tag="EXITTHESIM":UART 收到EXITTHESIM字符串即关闭仿真——这正是 demo 程序最后打印该串的原因;
  • mps3_board.visualisation.disable-visualisation=1:关闭可视化窗口(无头运行);
  • ethosu.num_macs=256:Ethos-U55 配置为 256 MAC,必须与编译时accelerator_config=ethos-u55-256保持一致,否则 NPU 执行可能出错。

成功运行时,终端会依次输出:

Starting Demo Running inference The image has been classified as 'king penguin' EXITTHESIM

随后仿真器自动退出,证明整条"TVM 编译 → 交叉编译 → NPU 驱动 → 推理 → 结果输出"链路完全打通。

九、总结:一条可复用的 microNPU 部署范式

从 apps/microtvm/ethosu 示例可以提炼出在 TVM 中部署"CPU + Ethos-U NPU + CMSIS-NN"异构微控制器目标的通用范式:

  1. 环境层:FVP 仿真 +arm-none-eabi-gcc工具链 + Ethos-U core driver + CMSIS/CMSIS-NN,保证编译与运行目标一致;
  2. 编译层tvmc compile--target=ethos-u,cmsis-nn,c做算子级异构切分,--executor=aot+--runtime=crt生成无 OS 依赖的静态入口,tir.usmp系列 pass 负责统一静态内存规划;
  3. 数据层convert_image.py/convert_labels.py把图片与标签固化为放置在 NPU 可访问内存段的 C 数组;
  4. 运行时桥接层TVMEthosULaunch把 TVM 的 command stream 交给ethosu_invoke,通过ethosu_reserve_driver/ethosu_release_driver管理 NPU 独占访问;
  5. 验证层:FVP 以--fast模式加速 NPU 仿真,通过 UART 输出分类结果并自动关机。

基于这一范式,你可以替换模型(任意 TFLite 量化模型)、替换图片(convert_image.py单参数即可)、甚至替换操作系统(裸机 / FreeRTOS 双版本主程序),快速在 Corstone-300 平台上验证自己的嵌入式 AI 方案。

  • 编译器
  • 深度学习
  • 模型优化

【免费下载链接】tvm

Open deep learning compiler stack for cpu, gpu and specialized accelerators

项目地址:https://gitcode.com/gh_mirrors/tvm7/tvm
点击查看免费下载

相关推荐

上一篇:DJITelloPy安装与配置指南
下一篇:Ruff VSCode 扩展插件使用教程

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询