Skip to content

Commit a88c2d7

Browse files
TLLite Micro Architecture Analysis and Integration
1 parent ba5ed2e commit a88c2d7

File tree

1 file changed

+320
-0
lines changed

1 file changed

+320
-0
lines changed
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
# TFLite Micro 架构解析与集成
2+
3+
在 openvela 平台上集成 TensorFlow Lite for Microcontrollers (TFLite Micro),要求开发者深入理解其分层软件架构、组件依赖关系及硬件加速机制。本文档将详细介绍 TFLite Micro 在 openvela 平台上的完整架构设计,指导开发者完成高效集成。
4+
5+
## 一、前置概念与术语
6+
7+
为了更好地理解 TFLite Micro 在嵌入式环境下的工作原理,开发者需先理解以下核心概念,这些术语贯穿于整个集成流程中。
8+
9+
| **术语** **(Term)** | **解释 (Definition)** | **openvela 平台****上下文** |
10+
| :--------------------------------- | :-------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------- |
11+
| **TFLite Micro (TFLM)** | TensorFlow 的微控制器版本,专为资源受限(KB级内存)设备设计的轻量级推理框架。 | 运行在 openvela 上的核心推理引擎。 |
12+
| **Tensor** **Arena** | 一块预先分配的大型连续内存区域。TFLM 不使用 `malloc/free`,而是将模型输入、输出及中间计算数据全部放置在此区域。 | 决定了系统能运行多大的模型,需根据 SRAM 大小谨慎配置。 |
13+
| **FlatBuffers** | 一种高效的序列化格式。模型文件以该格式存储,允许直接从 Flash 读取数据。 | 模型数据通常直接编译进固件或存储在文件系统中。 |
14+
| **Operator (****Op****) / Kernel** | 神经网络中的具体算子实现(如 Conv2D, Softmax)。Kernel 是 Op 的具体 C++ 代码。 | 可通过 **CMSIS-NN** 替换标准 Kernel 以利用 openvela 硬件加速特性。 |
15+
| **Op** **Resolver** | 算子解析器。用于在运行时查找并注册模型所需的算子实现。 | 推荐使用 `MicroMutableOpResolver` 按需注册,避免引入无用代码导致固件体积膨胀。 |
16+
| **Quantization (量化)** | 将 32 位浮点数转换为 8 位整数的技术,旨在减少模型体积并加速计算。 | openvela 推荐运行 `int8` 量化模型以获得最佳性能。 |
17+
18+
## 二、软件栈层次
19+
20+
openvela 平台的 TFLite Micro 软件栈采用模块化分层设计,实现了从底层硬件抽象到上层应用接口的解耦。
21+
22+
### 1、整体架构概览
23+
24+
```Plain
25+
┌─────────────────────────────────────────────────────────────┐
26+
│ 应用层 (Application Layer) │
27+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
28+
│ │ 语音识别应用 │ │ 图像检测应用 │ │ 传感器分析 │ │
29+
│ └──────────────┘ └──────────────┘ └──────────────┘ │
30+
└─────────────────────────────────────────────────────────────┘
31+
32+
33+
┌─────────────────────────────────────────────────────────────┐
34+
│ 推理 API 层 (Inference API) │
35+
│ ┌──────────────────────────────────────────────────────┐ │
36+
│ │ Model Loading │ Tensor Management │ Inference API │ │
37+
│ └──────────────────────────────────────────────────────┘ │
38+
└─────────────────────────────────────────────────────────────┘
39+
40+
41+
┌─────────────────────────────────────────────────────────────┐
42+
│ 框架层 (TFLite Micro Framework) │
43+
│ ┌─────────────────┐ ┌────────────────────────────────┐ │
44+
│ │ Micro Interpreter│ │ Operator Kernels (含 CMSIS-NN │ │
45+
│ ├─────────────────┤ │ / 自定义加速内核) │ │
46+
│ │ Memory Planner │ ├────────────────────────────────┤ │
47+
│ ├─────────────────┤ │ CONV │ FC │ POOL │ RELU │ ... │ │
48+
│ │ FlatBuffer Parser│ └────────────────────────────────┘ │
49+
│ └─────────────────┘ │
50+
└─────────────────────────────────────────────────────────────┘
51+
52+
53+
┌─────────────────────────────────────────────────────────────┐
54+
│ RTOS / 平台服务层 (NuttX 驱动、内存、文件系统等) │
55+
│ ┌──────────────────────────────────────────────────────┐ │
56+
│ │ Task Scheduler │ Memory Mgmt │ Drivers │ File Sys │ │
57+
│ └──────────────────────────────────────────────────────┘ │
58+
└─────────────────────────────────────────────────────────────┘
59+
60+
61+
┌─────────────────────────────────────────────────────────────┐
62+
│ 硬件平台 (Hardware) │
63+
│ ARM Cortex-M │ RISC-V │ ESP32 │ Custom SoC │
64+
└─────────────────────────────────────────────────────────────┘
65+
```
66+
67+
### 2、应用层:推理 API
68+
69+
应用层通过 C/C++ API 封装模型加载、推理执行和结果获取等核心功能。开发者应关注如何初始化解释器并高效处理张量数据。
70+
71+
#### 推理程序实现示例
72+
73+
以下代码展示了在 openvela 环境下执行一次完整推理的标准流程:
74+
75+
```C++
76+
static void test_inference(void* file_data, size_t arenaSize) {
77+
// 1. 加载模型
78+
const tflite::Model* model = tflite::GetModel(file_data);
79+
printf("arenaSize: %d\n", (int)arenaSize);
80+
81+
// 2. 手动添加算子
82+
tflite::MicroMutableOpResolver<1> resolver;
83+
resolver.AddFullyConnected(tflite::Register_FULLY_CONNECTED());
84+
85+
// 3. 准备 Tensor Arena (内存池)
86+
std::unique_ptr<uint8_t[]> pArena(new uint8_t[arenaSize]);
87+
88+
// 4. 创建解释器实例
89+
// 解释器需要模型、算子解析器、内存缓冲区作为输入
90+
tflite::MicroInterpreter interpreter(model,
91+
resolver, pArena.get(), arenaSize);
92+
93+
// 5. 分配张量内存
94+
interpreter.AllocateTensors();
95+
96+
// 6. 填充输入数据
97+
TfLiteTensor* input_tensor = interpreter.input(0);
98+
float* input_tensor_data = tflite::GetTensorData<float>(input_tensor);
99+
100+
// 示例:测试输入 x = π/2, expect y ≈ 1.0
101+
float x_value = 1.5708f;
102+
input_tensor_data[0] = x_value;
103+
104+
// 7. 执行推理
105+
interpreter.Invoke();
106+
107+
// 8. 获取输出结果
108+
TfLiteTensor* output_tensor = interpreter.output(0);
109+
float* output_tensor_data = tflite::GetTensorData<float>(output_tensor);
110+
syslog(LOG_INFO, "Output value after inference: %f\n", output_tensor_data[0]);
111+
}
112+
```
113+
114+
### 3、框架层:TFLite Micro 核心组件
115+
116+
框架层是 TFLite Micro 的核心,负责模型解析、内存管理、算子调度等关键功能。该层通过静态内存分配和精简的运行时环境,确保在 openvela 平台上实现极低的系统开销。
117+
118+
#### Micro Interpreter(微型解释器)
119+
120+
解释器是框架的中枢,负责协调模型加载、内存分配、算子执行等流程。它包含三个核心子组件:
121+
122+
1. Model Parser(模型解析器)
123+
124+
- 解析 FlatBuffers 格式的模型文件。
125+
- 提取模型元数据:算子类型、张量维度、量化参数。
126+
- 构建计算图数据结构。
127+
128+
2. Subgraph Manager(子图管理器)
129+
130+
- 管理模型的计算子图(针对大多数嵌入式模型,通常仅含有一个子图)。
131+
- 维护节点(算子)和边(张量)的拓扑关系。
132+
133+
3. Invocation Engine(调用引擎)
134+
135+
- 按拓扑顺序执行算子。
136+
- 管理算子的输入/输出张量绑定。
137+
- 处理算子执行错误和异常。
138+
139+
**解释器执行流程如下**:
140+
141+
```Plain
142+
初始化阶段(Setup):
143+
1. AllocateTensors() → 规划并分配所有张量所需的内存空间 (Tensor Arena)
144+
145+
146+
推理阶段 (Inference):
147+
1. interpreter.input() → 填充输入张量并填充数据
148+
2. Invoke() → 触发推理循环
149+
├─ for each node in execution_plan(遍历执行计划中的每个节点 (Node)):
150+
│ ├─ 获取算子注册信息(Registration)
151+
│ ├─ 绑定输入/输出张量
152+
│ └─ 调用算子的 Invoke 函数
153+
└─ 返回执行状态
154+
3. interpreter.output() → 读取输出张量结果
155+
```
156+
157+
#### Operator Kernels Library(算子内核库)
158+
159+
算子内核是执行数学运算(如卷积、全连接)的具体实现。TFLite Micro 采用注册机制来解耦框架与具体算法实现,这使得在 openvela 上替换特定算子(例如使用硬件加速的卷积)变得非常容易。
160+
161+
##### **算子接口规范**
162+
163+
开发者若需自定义算子或封装硬件加速驱动,需遵循 `TfLiteRegistration` 接口定义:
164+
165+
```C++
166+
typedef struct {
167+
168+
// [可选] 初始化:分配算子所需的持久化内存(如滤波器系数表)
169+
void* (*init)(TfLiteContext* context, const char* buffer, size_t length);
170+
171+
// [可选] 释放:清理 init 分配的资源
172+
void (*free)(TfLiteContext* context, void* buffer);
173+
174+
// [必须] 准备:校验张量维度、类型,计算临时缓冲区(Scratch Buffer)大小
175+
TfLiteStatus (*prepare)(TfLiteContext* context, TfLiteNode* node);
176+
177+
// [必须] 执行:核心计算逻辑,从 Input Tensor 读取数据,写入 Output Tensor
178+
TfLiteStatus (*invoke)(TfLiteContext* context, TfLiteNode* node);
179+
} TfLiteRegistration;
180+
```
181+
182+
##### **算子实现参考:ReLU**
183+
184+
以下代码展示了一个标准 ReLU 激活函数的实现逻辑,体现了 TFLite Micro 对类型安全和内存操作的封装:
185+
186+
```C++
187+
// 1. 准备阶段:校验数据类型与维度
188+
TfLiteStatus ReluPrepare(TfLiteContext* context, TfLiteNode* node)
189+
{
190+
// 校验:输入/输出张量数量
191+
TF_LITE_ENSURE_EQ(context, node->inputs->size, 1);
192+
TF_LITE_ENSURE_EQ(context, node->outputs->size, 1);
193+
194+
const TfLiteTensor* input = GetInput(context, node, 0);
195+
TfLiteTensor* output = GetOutput(context, node, 0);
196+
197+
// 校验:张量类型
198+
TF_LITE_ENSURE_TYPES_EQ(context, input->type, kTfLiteFloat32);
199+
200+
// 配置:调整输出张量形状与输入一致
201+
return context->ResizeTensor(context, output, TfLiteIntArrayCopy(input->dims));
202+
}
203+
204+
// 2. 执行阶段:数值计算
205+
TfLiteStatus ReluInvoke(TfLiteContext* context, TfLiteNode* node)
206+
{
207+
const TfLiteTensor* input = GetInput(context, node, 0);
208+
TfLiteTensor* output = GetOutput(context, node, 0);
209+
210+
const float* input_data = GetTensorData<float>(input);
211+
float* output_data = GetTensorData<float>(output);
212+
213+
// 获取数据总长度
214+
const int flat_size = MatchingFlatSize(input->dims, output->dims);
215+
216+
// 执行 ReLU: output = max(0, input)
217+
for (int i = 0; i < flat_size; ++i) {
218+
output_data[i] = (input_data[i] > 0.0f) ? input_data[i] : 0.0f;
219+
}
220+
221+
return kTfLiteOk;
222+
}
223+
224+
// 3. 注册阶段:返回函数指针结构体
225+
TfLiteRegistration* Register_RELU()
226+
{
227+
static TfLiteRegistration r = {
228+
nullptr, // init
229+
nullptr, // free
230+
ReluPrepare, // prepare
231+
ReluInvoke // invoke
232+
};
233+
return &r;
234+
}
235+
```
236+
237+
##### **算子库源码目录结构**
238+
239+
`tensorflow/lite/micro/kernels/` 目录下,代码按算子功能组织:
240+
241+
```Plain
242+
tensorflow/lite/micro/kernels/
243+
├── conv.cc # 卷积算子
244+
├── depthwise_conv.cc # 深度可分离卷积
245+
├── fully_connected.cc # 全连接层
246+
├── pooling.cc # 池化算子
247+
├── activations.cc # 激活函数(ReLU, Sigmoid 等)
248+
├── softmax.cc # Softmax
249+
├── add.cc, mul.cc, sub.cc # 逐元素运算
250+
├── reshape.cc, transpose.cc # 张量变换
251+
└── ...
252+
```
253+
254+
#### Memory Planner(内存规划器)
255+
256+
内存规划器是 TFLite Micro 实现低内存占用的关键技术。与桌面端 TensorFlow 动态分配内存不同,Micro 通过分析张量生命周期实现内存复用。
257+
258+
## 三、平台依赖与集成
259+
260+
在 openvela 平台上运行 TFLite Micro 并非孤立存在,它深度依赖底层的 OS 服务与硬件库。理解这些依赖关系,对于性能调优和故障排查至关重要。
261+
262+
### 1、NuttX 内核服务
263+
264+
TFLite Micro 通过平台抽象层与 NuttX RTOS 交互。尽管 TFLite Micro 设计为无 OS 依赖,但在 openvela 上,合理的 OS 配置能显著提升系统稳定性。
265+
266+
#### 任务调度与同步
267+
268+
NuttX 提供了完整的 POSIX 标准支持,TFLite Micro 的推理任务通常封装在标准的 `pthread` 或 NuttX 任务(Task)中。
269+
270+
#### 内存分配器
271+
272+
TFLite Micro 推荐使用 **Tensor Arena** 机制进行内存管理,但在初始化阶段或处理非张量数据时,仍可能与 NuttX 的内存管理器(Mm)交互。
273+
274+
**Tensor Arena 分配策略**
275+
276+
虽然可以使用 `malloc` 动态申请 Arena,但强烈建议采用静态分配。
277+
278+
```C++
279+
// 推荐:编译时确定大小,放置于 BSS 段或特定内存段(如 CCM)
280+
// 预估大小方法:先分配大空间,运行 Interpreter::ArenaUsedBytes() 获取实际用量后调整
281+
#define ARENA_SIZE (100 * 1024)
282+
static uint8_t tensor_arena[ARENA_SIZE] __attribute__((aligned(16)));
283+
```
284+
285+
### 2、硬件加速:CMSIS-NN 集成
286+
287+
为提升在 ARM Cortex-M 核心(openvela 的主要计算单元)上的推理性能,必须集成 CMSIS-NN 库。该库利用 SIMD(单指令多数据)指令集,可将卷积和矩阵乘法的性能提升 4-5 倍。
288+
289+
#### 构建系统配置 (Makefile)
290+
291+
在集成 CMSIS-NN 时,核心逻辑是**替换**:引入优化版本的源文件,同时从编译列表中剔除 TFLite 自带的通用参考实现(Reference Kernels),以避免符号定义冲突。
292+
293+
以下是针对 NuttX 构建系统的配置范本:
294+
295+
```Makefile
296+
# 检测是否在 Kconfig 中开启了 CMSIS-NN 选项
297+
ifneq ($(CONFIG_MLEARNING_CMSIS_NN),)
298+
299+
# 1. 定义宏:告知 TFLite Micro 启用 CMSIS-NN 路径
300+
COMMON_FLAGS += -DCMSIS_NN
301+
302+
# 添加头文件搜索路径
303+
COMMON_FLAGS += ${INCDIR_PREFIX}$(APPDIR)/mlearning/cmsis-nn/cmsis-nn
304+
305+
# 2. 寻找优化源文件:获取 cmsis_nn 目录下的所有 .cc 文件
306+
CMSIS_NN_SRCS := $(wildcard $(TFLM_DIR)/tensorflow/lite/micro/kernels/cmsis_nn/*.cc)
307+
308+
# 3. 排除冲突文件:
309+
# 计算需要排除的通用实现文件名(例如 conv.cc, fully_connected.cc)
310+
# 逻辑:取 CMSIS_NN_SRCS 的文件名,对应到 kernels/ 根目录
311+
UNNEEDED_SRCS := $(addprefix $(TFLM_DIR)/tensorflow/lite/micro/kernels/, $(notdir $(CMSIS_NN_SRCS)))
312+
313+
# 4. 从原始编译列表 CXXSRCS 中过滤掉这些通用实现
314+
CXXSRCS := $(filter-out $(UNNEEDED_SRCS), $(CXXSRCS))
315+
316+
# 5. 将优化后的源文件加入编译列表
317+
CXXSRCS += $(CMSIS_NN_SRCS)
318+
319+
endif
320+
```

0 commit comments

Comments
 (0)