首先要能编译、能打印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进行推断的流程:

TfLite: TfLite for mcu代码框架
1] 根据如下元素创建micro_interpreter


error_report(打印log的实现),  op_resolve(算子的实现)和 内存(初始地址和大小), 还有最重要的 tflite格式的模型文件

TfLite: TfLite for mcu代码框架

因为内存等限制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构造函数

TfLite: TfLite for mcu代码框架

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中,就要分配内存存储数据。具体看下面的策略

TfLite: TfLite for mcu代码框架

// 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赋值

TfLite: TfLite for mcu代码框架


4] 运行model
有输入Tensor提供数据,从模型中解析出ops, 找到对应的处理函数,数据依次流过各个ops

TfLite: TfLite for mcu代码框架

首先关注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, 从中得到数据也就是结果。

TfLite: TfLite for mcu代码框架

相关文章:

  • 2021-08-05
  • 2022-12-23
  • 2021-07-13
  • 2021-05-26
  • 2022-12-23
  • 2021-04-25
  • 2021-08-14
  • 2022-12-23
猜你喜欢
  • 2021-05-08
  • 2022-12-23
  • 2021-11-07
  • 2022-01-12
  • 2022-01-08
  • 2022-02-08
  • 2021-06-25
相关资源
相似解决方案