4. 工程结构
TFLite Micro 的官方仓库(tensorflow/tflite-micro)在初次接触时往往让人感到困惑:它不像典型的嵌入式 SDK 那样将“核心库”与“示例工程”清晰分离,而是以“目标平台 + 组件”的方式组织源码。理解这套目录结构的本质,是搭建最小可运行工程的前提。本章将逐层拆解仓库布局,帮助你建立“哪些文件必须保留、哪些文件可以裁剪、示例代码如何被构建系统引用”的清晰认知。
4.1 核心原理:仓库的“三明治”式分层结构
TFLite Micro 官方仓库采用了一种“根目录-核心库-平台适配”的三层结构。最外层是 tensorflow/ 目录,它并非完整的 TensorFlow 框架,而是仅包含 Micro 运行时所需的头文件与实现。第二层是 tensorflow/lite/micro/,这是整个仓库的心脏,包含了模型解释器(Interpreter)、算子内核(Kernels)、内存规划器(MemoryPlanner)以及调试工具。第三层是 examples/ 与 third_party/,前者提供可直接编译的参考工程,后者存放依赖的第三方库(如 CMSIS、FlatBuffers)。
这种分层设计的核心思想是“核心与平台解耦”。你不需要修改 micro/ 下的任何代码,只需要在 examples/ 中新建一个工程目录,并实现几个平台相关的回调函数(如 MicroPrintf、MicroTimer),即可完成移植。理解这一点,你就不会在后续集成时误改核心文件导致运行时崩溃。
4.2 判断方法:如何快速定位你需要的组件
面对几十个子目录,初学者最容易迷失。这里给出一个“三步定位法”:
- 按功能需求定位:如果你需要跑一个图像分类模型,重点看
examples/person_detection和tensorflow/lite/micro/kernels中的卷积、池化算子;如果只是正弦波拟合,则关注hello_world与全连接算子。 - 按编译依赖定位:打开任意示例的
Makefile或CMakeLists.txt,观察它引用了哪些micro/下的源文件。这些被引用的文件就是“最小必要集”。 - 按平台适配定位:在
tensorflow/lite/micro/下找到debug_log.cc、micro_time.cc等文件,这些是平台相关的桩函数,你的工程必须提供实现。
micro/ 下的 micro_interpreter.h、micro_mutable_op_resolver.h 和 all_ops_resolver.h 这三个头文件,它们是你集成时最常打交道的接口。
4.3 案例拆解:以 hello_world 为例剖析工程骨架
我们以官方最简示例 examples/hello_world 作为解剖对象。该示例的目录结构如下:
examples/hello_world/
├── main.cc # 入口函数,包含模型推理循环
├── model.cc # 模型数据(正弦波拟合模型,以 C 数组形式存放)
├── model.h # 模型数据声明
├── constants.cc # 输入输出张量的维度常量
├── constants.h
├── output_handler.cc # 输出处理(打印结果)
├── output_handler.h
└── Makefile.inc # 构建脚本片段
这个示例的运行流程是:main.cc 中创建一个 MicroInterpreter 实例,将 model.cc 中的模型数据加载到 TensorArena(一块静态分配的内存缓冲区),然后循环执行 Invoke() 完成推理。关键点在于:整个工程没有使用任何动态内存分配,所有张量缓冲区都在编译期或初始化阶段确定。
| 文件/目录 | 作用 | 是否必须 |
|---|---|---|
| main.cc | 程序入口,初始化解释器并驱动推理 | 是 |
| model.cc / model.h | 存放转换后的模型二进制数据 | 是 |
| constants.cc / constants.h | 定义输入输出维度、量化参数 | 是 |
| output_handler.cc | 将推理结果输出到串口或日志 | 是(可自定义) |
| Makefile.inc | 声明源文件列表,供上层构建系统调用 | 是 |
从这个案例中你可以提炼出“最小可运行工程”的四个必备元素:模型数据、解释器初始化、输入输出处理、构建脚本。后续章节中,我们将逐一替换这些元素,实现自己的模型集成。
4.4 常见误区与总结复盘
hello_world 的 main.cc,然后替换模型数据,却发现编译报错或运行崩溃。原因通常是:1. 没有同步修改
constants.cc 中的张量维度,导致输入数据长度不匹配;2. 模型使用了示例中未注册的算子(如
Softmax),而 MicroMutableOpResolver 只注册了全连接算子;3. 忽略了
TensorArena 的大小设置,模型稍大就会内存溢出。
正确的做法是:先确认模型中的算子列表,然后在 main.cc 中通过 resolver.AddSoftmax() 等方式显式注册所需算子。此外,TensorArena 的大小可以通过 MicroInterpreter::arena_size() 方法获取建议值,但实际使用时建议在此基础上增加 10%~20% 的余量。