首先要能编译、能打印log
编译的方法有两种,1) bazel 2) make
可以在本地编译调试使用bazel test
bazel test //tensorflow/lite/experimental/micro/examples/gesture_recognition:gesture_recognition_test
到路径下bazel-bin/tensorflow/lite/experimental/micro/examples/gesture_recognition/找到对应的bin,然后运行得到log.
使用make
make -f tensorflow/lite/experimental/micro/tools/make/Makefile test
当前makefile有问题,更改一点或者不该都是全编译,太慢,推荐用bazel编译
1. 为什么引入tflite for mcu
tflite for mcu因硬件的限制如内存, 使用和tflite不同的interpreter和算子等。因内存的限制使tflite不能使用c++的高级用法
如hastable等。tflite仍使用c++作为主要编程语言,但主要是使用c with class。
我的体会是因为内存的限制导致tflite for mcu只能用c++的部分功能或者说库,导致tflite for mcu从tflite 中分离出来。
tflite for mcu的目标是没有库支持、没有操作系统,完全裸的情况,实际中还是会用一些库,看内存的大小而定。
2. tflite 和tflite for mcu的关系
两者都是用tflite格式的模型文件,
但是否通用要看算子是否都实现了
两者使用相同的核心数据结构 TfLitexxx(TfLiteContext, TfLiteTensor, TfLiteNode等等)
lite/c/
├── builtin_op_data.h
├── c_api_internal.c
├── c_api_internal.h
两者使用相同的接口(C++虚类), tflite和tflite for mcu给出具体的实现
lite/core/
├── api
│ ├── BUILD
│ ├── error_reporter.cc
│ ├── error_reporter.h
│ ├── flatbuffer_conversions.cc
│ ├── flatbuffer_conversions.h
│ ├── op_resolver.cc
│ ├── op_resolver.h
│ ├── profiler.h
│ ├── tensor_utils.cc
│ └── tensor_utils.h
├── subgraph.cc
└── subgraph.h
使用tflite for mcu进行推断的流程:

1] 根据如下元素创建micro_interpreter
error_report(打印log的实现), op_resolve(算子的实现)和 内存(初始地址和大小), 还有最重要的 tflite格式的模型文件
因为内存等限制MCU平台可能没有实现文件系统,tflite for mcu直接使用全局变量数组保存模型文件; 内存的分配也直接使用从栈里分配的unsinged char数组。
tflite::MicroErrorReporter继承自core/api 接口ErrorReporter, tflite::MicroMutableOpResolver继承自core/api接口OpResolver。
MicroErrorReporter
MicroErrorReporter要实现虚函数 int Report(const char* format, va_list args) override; 调用DebugLogPrintf(format, args)也就是log的一般形式(模式和内容),DebugLogPrintf会根据format打印int/float/string等,不管什么数据类型都是以字符串的形式展示,所以最终依赖DebugLog.而最基础的DebugLog实现是平台相关的如不同MCU平台串口打印log设置的寄存器是不同的。
DebugLog的实现提供了统一头文件 debug_log.h: extern "C" void DebugLog(const char* s);
而具体的实现和平台相关当前micro目录下的debug_log.cc实现为,extern "C" void DebugLog(const char* s) { fprintf(stderr, "%s", s); }。
其他具体平台的实现在micro路径下平台相关文件加如
➜ apollo3evb git:(master) ✗ tree
└── debug_log.cc
MicroMutableOpResolve
MicroMutalbOpResolve要实现通过BuiltinOprator 或custom name得到TfLiteRegistration. 不同模型使用的oprator也就需要不同的OpResolve。在不知道具体模型是系统提供了一个能解析所有operator的OpResolve: AllOpsResolver
class AllOpsResolver : public MicroMutableOpResolver {
public:
AllOpsResolver();//这个构造函数注册了所有支持的OpsReolver
}.
使用tflite::ops::micro::AllOpsResolver micro_mutable_op_resolver; 解生成了一个解析所有ops的resolver
使用AllOpsResolver的缺点时把许多不用的resolver注册进去使代码体积变大。
MutableOpResolver:根据模型使用的ops通过AddBuiltin创建MicroMutableOpResolver
namespace tflite {
namespace ops {
namespace micro {
TfLiteRegistration* Register_DEPTHWISE_CONV_2D();
TfLiteRegistration* Register_MAX_POOL_2D();
} // namespace micro
} // namespace ops
} // namespace tflite
static tflite::MicroMutableOpResolver micro_mutable_op_resolver; // NOLINT
micro_mutable_op_resolver.AddBuiltin(
tflite::BuiltinOperator_DEPTHWISE_CONV_2D,
tflite::ops::micro::Register_DEPTHWISE_CONV_2D());
xxxx.....
MicroInterrupter构造函数
microinterpreter的构造函数会引发成员变量:microallocator, simplememoryAllocator的构造函数,从model文件中解析出
tensors_, operators_等,并为说有的tensor分配 TfLiteTensor结构体关联tensor,但此时的tensor保存数据需要的内存没有分配。
2] 为TfLiteTensor分配内存
这里要区分下flatbuffer tensor和runtime tensor就是TfLiteTensor. 就是从模型文件中分析出数据
赋值TfLiteTensor, 有些tensor的数据如weight\bias就在model中, 这时只把数据指针指向对应的内容就行。
如果数据如input\output Tensor没有在model中,就要分配内存存储数据。具体看下面的策略
// Allocate memory from the tensor_arena for the model's tensors
interpreter.AllocateTensors();
microinterpreter的成员函数AllocateTensors通过成员变量allocator_调到另一个类MicroAllocator, 而MicroAllocator分配内存的实现又调用其成员变量SimpleMemoryAllocator实现.
TfLiteStatus MicroInterpreter::AllocateTensors() {
TfLiteStatus status = allocator_.AllocateTensors();
TF_LITE_ENSURE_OK(&context_, status);
tensors_allocated_ = true;
return kTfLiteOk;
}
上图展示了整个过程,TfLiteTensor变量被赋值,data需要的内存被分配或者指向flatbuffer中具体位置。
3] 为model的输入Tensor赋值
4] 运行model
有输入Tensor提供数据,从模型中解析出ops, 找到对应的处理函数,数据依次流过各个ops
首先关注flatbuffer中数据结构Operator其opcode_index可以找到对应的opcode, inputs/outputs是operator的输入出tensor索引号BuiltinOptionsUnion是所有类型operator的init_data共同体,根据具体的operator可得到对应的init_data. custom_option类似。operatorCode指的是用到的所有ops。
TensorNode的tempTensor是什么是否存在依赖registration的prepare函数等。
5] 得到model的输出
就是得到输出Tensor, 从中得到数据也就是结果。