- 编译器
- 深度学习
- 模型优化
【免费下载链接】tvm
Open deep learning compiler stack for cpu, gpu and specialized accelerators
本指南围绕 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)工具链的典型工作流:
- 下载一个量化后的 int8 MobileNet v2 TFLite 模型;
- 使用
tvmc将模型编译为面向Cortex-M55 CPU、Ethos-U55 NPU 与 CMSIS-NN的代码(AOT 执行器 + CRT 运行时); - 下载一张企鹅图片,将其转换为 C 头文件中的字节数组;
- 构建 Ethos-U55 核心驱动(core driver)、CMSIS 启动代码与演示应用;
- 在基于 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.c | TVM 运行时与 Ethos-U 驱动之间的桥接层 |
| arm-none-eabi-gcc.cmake | 交叉编译工具链文件,指定 Cortex-M 目标 |
| corstone300.ld | Corstone-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(图片处理)、numpy、tflite(TFLite 模型解析)等。在示例目录下执行:
pip install -r ./requirements.txt2.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/ethosu、src/contrib/ethosu/cascader与src/tir/contrib/ethosu的源码编入编译器;关闭时仅保留 Python 侧 Object 定义所需的utils.cc与 cascader 代码。 - 使用 TLCPack 预编译包:直接安装现成的二进制 wheel。
2.4 配置 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_Corstone_SSE-300/models/Linux64_GCC-6.4是 FVP 可执行文件的目录,/opt/arm/cmake/bin是 cmake 的目录。
三、运行演示应用
3.1 裸机演示
在示例目录下直接执行:
./run_demo.sh3.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.c、list.c、queue.c、timers.c、event_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_platformrun_demo.sh支持的完整参数(来自脚本内show_usage,见 run_demo.sh):
| 参数 | 说明 |
|---|---|
-h, --help | 显示帮助信息 |
--ethosu_driver_path | Ethos-U core driver 路径 |
--cmsis_path | CMSIS 路径 |
--ethosu_platform_path | Ethos-U core platform 路径(提供 UART 驱动与 Corstone-300 目标文件) |
--fvp_path | FVP 根路径(脚本会追加models/Linux64_GCC-6.4到PATH) |
--cmake_path | 指定 cmake 可执行文件路径 |
--freertos_path | 启用 FreeRTOS 演示并指定其源码路径 |
3.4 脚本自动完成的完整流水线
./run_demo.sh一次完成下列全部步骤(可对照 run_demo.sh 逐行阅读):
make cleanall清理旧构建产物并新建build/目录;- 从 Arm ML-zoo 下载
mobilenet_v2_1.0_224_INT8.tflite; - 调用
tvmc将模型编译为 Cortex-M55 + Ethos-U55 + CMSIS-NN 目标,产物格式为MLF(Model Library Format),解包得到 CRT 运行时与生成的 C 代码; - 下载 ImageNet 标签文件
labels_mobilenet_quant_v1_224.txt; - 下载一张企鹅图片(
kitten.jpg同为默认测试图之一); - 运行 convert_image.py 生成
include/inputs.h(输入图像数据)与include/outputs.h(输出缓冲区); - 运行 convert_labels.py 生成
include/labels.h,并把 CRT 配置头文件复制到build/crt_config/; make交叉编译生成裸机可执行文件build/demo;- 启动
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 接收一个命令行参数(图片路径),完成以下转换:
- 使用 Pillow 把图片缩放为224×224(MobileNet v2 的标准输入尺寸);
- 转为 numpy 数组并增加 batch 维度,得到 NCHW 四维输入;
- 执行量化预处理:每个像素减去 128,再转为
int8(对应 TFLite 模型输入量化参数 zero point 为 -128 的情形); - 生成
include/inputs.h:声明input[]数组(16 字节对齐,放置在ethosu_scratch段,供 NPU 直接访问),内容以\x..十六进制转义序列写入; - 生成
include/outputs.h:声明长度为1001(ImageNet 1000 类 + 1 个背景类)的int8_t output[],放置在output_data_sec段。
调用方式(脚本第 170 行实际执行的命令):
python3 ./convert_image.py ./build/kitten.jpg5.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.txt5.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(ðosu0_driver, (void*)ETHOSU_BASE_ADDRESS, NULL, 0, 1, 1)) { printf("Failed to initialize NPU.\n"); return -1; } NVIC_SetVector(ETHOSU_IRQ, (uint32_t)ðosuIrqHandler0); 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发送到队列; - 推理任务
prvInferenceTask:xQueueReceive阻塞等待数据,收到后构造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-m33、cortex-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.c、crt_backend_api.c(来自 MLF 解包出的 CRT); - CMSIS 启动代码:
CMSIS/Device/ARM/ARMCM55/Source/*.c; - CMSIS-NN Softmax:
CMSIS-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 定义了五块内存区域:
| 内存 | 基地址 | 大小 | 说明 |
|---|---|---|---|
| ITCM | 0x00000000 | 512KB | 存放代码(.text),CPU 只读,NPU 不可访问 |
| DTCM | 0x20000000 | 512KB | 数据与栈/堆 |
| SSE-300 SRAM | 0x21000000 | 2MB | CPU 与 NPU 均可读写(.sram段,存放 fast memory) |
| Data SRAM | 0x01000000 | 2MB | CPU 与 NPU 均可读写 |
| DDR | 0x60000000 | 32MB | CPU 与 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"异构微控制器目标的通用范式:
- 环境层:FVP 仿真 +
arm-none-eabi-gcc工具链 + Ethos-U core driver + CMSIS/CMSIS-NN,保证编译与运行目标一致; - 编译层:
tvmc compile用--target=ethos-u,cmsis-nn,c做算子级异构切分,--executor=aot+--runtime=crt生成无 OS 依赖的静态入口,tir.usmp系列 pass 负责统一静态内存规划; - 数据层:
convert_image.py/convert_labels.py把图片与标签固化为放置在 NPU 可访问内存段的 C 数组; - 运行时桥接层:
TVMEthosULaunch把 TVM 的 command stream 交给ethosu_invoke,通过ethosu_reserve_driver/ethosu_release_driver管理 NPU 独占访问; - 验证层:FVP 以
--fast模式加速 NPU 仿真,通过 UART 输出分类结果并自动关机。
基于这一范式,你可以替换模型(任意 TFLite 量化模型)、替换图片(convert_image.py单参数即可)、甚至替换操作系统(裸机 / FreeRTOS 双版本主程序),快速在 Corstone-300 平台上验证自己的嵌入式 AI 方案。
- 编译器
- 深度学习
- 模型优化
【免费下载链接】tvm
Open deep learning compiler stack for cpu, gpu and specialized accelerators
相关推荐
基于 TVM 与 Arm Virtual Hardware 在 Cortex(R)-M55 裸机环境运行 PaddleOCR 文本识别模型实战指南
基于 TVM 与 Arm Virtual Hardware 在 Cortex R M55 裸机环境运行 PaddleOCR 文本识别模型实战指南 导读:本文以
人工智能计算机视觉OCR深度学习大模型RAGPaddleOCR 裸机部署实战:用 TVM 与 Arm Virtual Hardware 在 Cortex-M55 上运行 PP-OCRv3 英文文字识别
PaddleOCR 裸机部署实战:用 TVM 与 Arm Virtual Hardware 在 Cortex M55 上运行 PP OCRv3 英文文字识别 本
人工智能计算机视觉深度学习PaddleOCR 边缘部署实战:基于 TVM 与 Arm 虚拟硬件在 Cortex-M55 裸机上运行文本识别模型
PaddleOCR 边缘部署实战:基于 TVM 与 Arm 虚拟硬件在 Cortex M55 裸机上运行文本识别模型 本篇技术指南围绕 PaddleOCR 仓库
人工智能计算机视觉OCR深度学习大模型RAG
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考