而我们今天要介绍的TensorFlow Lite for Microcontrollers(TFLM)则是 TensorFlow Lite的微控制器版本。这里是官网上的一段介绍:
TensorFlow Lite for Microcontrollers (以下简称TFLM)是 TensorFlow Lite 的一个实验性移植版本,它适用于微控制器和其他一些仅有数千字节内存的设备。 它可以直接在“裸机”上运行,不需要操作系统支持、任何标准 C/C++ 库和动态内存分配。核心运行时(core runtime)在 Cortex M3 上运行时仅需 16KB,加上足以用来运行语音关键字检测模型的操作,也只需 22KB 的空间。
这三者一脉相承,都出自谷歌,区别是TensorFlow同时支持训练和推理,而后两者只支持推理。TFLite主要用于支持手机、平台等移动设备,TFLM则可以支持单片机。从发展历程上来说,后两者都可以说是TensorFlow项目的“支线项目”。或者说这三者是一个树形的发展过程,目前是三个并进发展的。
二、TFLM开源项目
TFLM代码仓链接:https://github.com/tensorflow/tflite-micro
2.1 下载TFLM源代码
下载TFLM需要使用如下git命令:
git clone https://github.com/tensorflow/tflite-micro.git
TFLM顶层目录下的文件和目录,如下图所示:
2.2 TFLM基准测试说明
TFLM顶层目录有README.md文件,其Additional Documentation节列出来Benchmark说明,Benchmark(基准测试)用于衡量关键模型和工作负载的性能。
README.md前一半内容为:
完整内容参考: tflite-micro/tensorflow/lite/micro/benchmarks/README.md at main · tensorflow/tflite-micro (github.com)
2.3 TFLM基准测试命令
从README的”Run on x86”可以看到,在x86 PC上运行关键词基准测试的命令是:
make -f tensorflow/lite/micro/tools/make/Makefile run_keyword_benchmark
在x86 PC上运行人体检测基准测试的命令是:
make -f tensorflow/lite/micro/tools/make/Makefile run_person_detection_benchmark
以上两个命令都会调用make命令,并以tensorflow/lite/micro/tools/make/Makefile
为构建规则,分别构建run_keyword_benchmark
和run_person_detection_benchmark
两个目标。
查阅tensorflow/lite/micro/tools/make/Makefile
文件夹内容,可以看到:
这段代码中的 595行、601行、605行、612行、613行 分别会下载一些文件,具体下载的是tflm依赖的库和测试数据集;
而执行上面两个make命令,实际上会依次执行如下步骤:
- 下载依赖库和数据集;
- 编译测试程序;
- 运行测试程序;
必须至少一遍make命令,才会下载测试数据集,才能进行后续的移植步骤。
三、TFLM初步体验
由于TFLM开源项目依赖部分三方软件代码没有直接放在TFLM源码仓中,需要运行一次基准测试才会下载下拉进行编译。因此,我们需要先在PC上体验一下TFLM基准测试。
PC上运行TFLM推荐使用Ubuntu系统,其他操作系统运行可能会有些问题。
3.1 PC上运行Keyword基准测试
PC Linux系统上,运行如下命令,可以执行Keyword基准测试:
make -f tensorflow/lite/micro/tools/make/Makefile run_keyword_benchmark
命令执行完毕,最后输出如下:
PC上运行10次耗时3毫秒。
3.2 PC上运行Person detection基准测试
make -f tensorflow/lite/micro/tools/make/Makefile run_person_detection_benchmark
命令执行完毕,最后输出如下:
PC上运行10次,有人的耗时343毫秒,无人的耗时337毫秒。
3.3 No module named 'numpy'问题解决
make命令报错:
解决方法:
pip install numpy
四、TFLM源码浅析
开始移植TFLM之前,需要清楚TFLM整个源码项目是如何构建的(也就是构建规则)。
4.1 编译生成的.o文件
PC上运行完基准测试命令过程中,会执行源码编译命令。运行完成后,使用如下命令,可以找到所有.o文件:
find . -name '*.o'
部分输出如下图所示:
通过该命令的输出,我们可以知道刚刚的两个命令一共有多少源文件参与了编译。
4.2 基准测试的构建目标
要移植TFLM,仅仅知道有多少源文件参与编译还不够,我们需要知道具体的构建规则。本节将通过分析Makefile解读基准测试的具体构建目标(target)。
从前面的PC端运行Keyword基准测试的输出可以看到,可执行程序名称为keyword_benchmark
,通过搜索源码,可以找到对应的Makefile构建规则代码为:
这里调用了Makefile的宏函数microlite_test
,并传递了4个参数,分别为:
- 参数1:
keyword_benchmark
- 参数2:
$(KEYWORD_BENCHMARK_SRCS)
- 参数3:
$(KEYWORD_BENCHMARK_HDRS)
- 参数4:
$(KEYWORD_BENCHMARK_GENERATOR_INPUTS)
4.3 基准测试的构建规则
下面以keyword_benchmark
为例分析具体构建规则。
宏函数microlite_test
的具体定义为:
前面我们执行了如下命令:
make -f tensorflow/lite/micro/tools/make/Makefile run_keyword_benchmark
这个命令指定的目标名称为run_keyword_benchmark
,对应到helper_function.inc
文件中的84行,参数1为keyword_benchmark
,下方规则的TEST_SCRIPT
变量的值定义在tensorflow/lite/micro/tools/make/Makefile
文件中为空白字符串,因此不起作用;
84行还可以看到run_keyword_benchmark
目标依赖$(keyword_benchmark_BINARY)
目标,回看到52行,可以知道:
-
$(keyword_benchmark_BINARY)
目标表示可执行程序文件路径,:
-
$(keyword_benchmark_BINARY)
目标依赖$(keyword_benchmark_LOCAL_OBJS)
目标;
-
$(keyword_benchmark_BINARY)
目标依赖$(MICROLITE_LIB_PATH)
目标;
-
$(keyword_benchmark_BINARY)
目标的构建规则为:
$$(CXX) $$(CXXFLAGS) $$(INCLUDES) \
-o $$(keyword_benchmark_BINARY) $$(keyword_benchmark_LOCAL_OBJS) \
$$(MICROLITE_LIB_PATH) $$(LDFLAGS) $$(MICROLITE_LIBS)
根据传参,可以知道,一些变量的值为:
-
keyword_benchmark_LOCAL_SRCS
的初始值为: tensorflow/lite/micro/benchmarks/keyword_benchmark.cc
-
keyword_benchmark_LOCAL_HDRS
的值为:tensorflow/lite/micro/benchmarks/micro_benchmark.h
-
得到GEN_RESULTS
的命令为: python3 tensorflow/lite/micro/tools/generate_cc_arrays.py gen/linux_x86_64_default_gcc/genfiles tensorflow/lite/micro/models/keyword_scrambled.tflite
,该命令会将模型文件转为.h
和.cc
文件,其中.h
为声明,.cc
为数据:
#include <cstdint>
constexpr unsigned int g_keyword_scrambled_model_data_size = 34576;
extern const unsigned char g_keyword_scrambled_model_data[];
命令的输出为:gen/linux_x86_64_default_gcc/genfiles/tensorflow/lite/micro/models/keyword_scrambled_model_data.cc
。
-
keyword_benchmark_LOCAL_SRCS
值会变为包含刚刚生成的模型keyword_scrambled_model_data.cc
;
-
keyword_benchmark_LOCAL_OBJS
的值是:先将keyword_benchmark_LOCAL_SRCS
值中所有.cc
替换为.o
,再给每个值添加前缀gen/linux_x86_64_default_gccobj/core/
;
-
MICROLITE_LIB_PATH
的值为库文件libtensorflow-microlite.a
的完整路径: gen/linux_x86_64_default_gcc/lib/libtensorflow-microlite.a
好了,到这里就可以看到$(keyword_benchmark_BINARY)
目标构建规则下方命令的主要参数了:
-
-o
选项为gen/linux_x86_64_default_gcc/bin/keyword_benchmark
;
-
$(keyword_benchmark_LOCAL_OBJS)
为所有目标文件(.o)列表;
-
$(MICROLITE_LIB_PATH)
是链接的库文件(.a)路径;
-
$(LDFLAGS)
是链接器命令行选项;
-
$(MICROLITE_LIBS)
是额外库选项,实际为 -lm
;
到这里,Keyword基准测试的构建规则以及分析清楚了。
4.4 TFLM库的构建规则
接下来,我们分析keyword链接的库文件libtensorflow-microlite.a
(简称TFLM库)的链接规则。
该目标的定义为:
这里可以看到,该库是由以下目标文件列表归档(使用ar命令)而来:
$(MICROLITE_LIB_OBJS)
$(MICROLITE_KERNEL_OBJS)
$(MICROLITE_THIRD_PARTY_OBJS)
$(MICROLITE_THIRD_PARTY_KERNEL_OBJS)
$(MICROLITE_CUSTOM_OP_OBJS)
其中,前四个变量的值来自:
这里可以看到,前面的几个目标文件列表分别来自:
$(MICROLITE_LIB_OBJS)
来自于$(MICROLITE_CC_SRCS)
$(MICROLITE_KERNEL_OBJS)
来自于$(THIRD_PARTY_CC_SRCS)
$(MICROLITE_THIRD_PARTY_OBJS)
来自于$(THIRD_PARTY_KERNEL_CC_SRCS)
$(MICROLITE_THIRD_PARTY_KERNEL_OBJS)
来自于$(MICROLITE_CC_KERNEL_SRCS)
$(MICROLITE_CUSTOM_OP_OBJS)
没有在Makefile中定义,默认为空,可以忽略(实际使用时,可以通过命令行参数指定);
经过在Makefile中加入日志打印,发现上述几个xxx_SRCS
变量的值为:
五、TFLM主体移植
从TFLM官方介绍文档、测试命令以及源码分析可以知道,在STM32H7S78-DK上移植TFLM可以分解为以下几个主要任务:
- 实现TFLM库的构建
- 实现CMake版的辅助函数microlite_test
- 实现keyword基准测试的构建
- 实现person detection基准测试(可选)的构建
- 实现基准测试依赖的功能——计时和日志
接下来分别介绍,如何完成上述任务。
5.1 实现TFLM库的构建
有了以上分析之后,我们就可以进行今天最重要的工作,将Makefile转换为CMake构建规则文件(CMakeLists.txt)。
这部分是整个移植过程中工作量和难度最大的部分,涉及到很多CMake的语法细节,这里不详细介绍。
实现TFLM库构建的CMake代码为:
5.2 实现辅助函数microlite_test
TFLM源码中,构建基准测试使用了GNU Make的microlite_test宏函数,实现了代码的复用和逻辑精简。和GNU Make类似的,CMake也支持函数。本节我们实现GNU Make的microlite_test宏函数的CMake移植版。
CMake具体代码如下:
这部分做了几个特殊处理:
- 目标类型从可执行程序修改为静态库,即生成文件类型由.elf文件改为.a文件;
- 使用宏将main函数重命名为
${NAME}_main
;
做出如上两处修改的原因是——由CubeMX生成的基础已经有main函数,并且可以生成elf文件。通过上述两个修改,我们可以将基准测试代码链接到CubeMX生成的代码中去,进而实现整个项目可以在STM32H7S78-DK上运行。
5.3 实现keyword基准测试的构建
有了CMake辅助函数microlite_test
之后,实现keyword基准测试的构建就很简单了,直接看代码:
5.4 实现Person detection基准测试的构建
同样的,有了CMake辅助函数microlite_test
之后,实现Person detection基准测试的构建也很简单,直接看代码:
5.5 实现基准测试依赖的功能——计时和日志
TFLM本身是一个边缘AI推理库,可以理解为一个举证向量计算库,纯CPU计算不依赖任何外设功能。但是,TFLM的基准测试则依赖计时和日志功能。在不同平台上,计时和日志功能的实现方式有所不同,对应的代码也不同(例如Linux、Windows、MacOS操作系统、STM32单片机、ESP32单片机上,实现计时和日志的代码是不是一样的)。
TFLM源码中,已经为移植进行了设计,其中两个文件名分别对应计时和日志:
debug_log.cc
,用于实现计时功能,不同平台有不同版本;
micro_time.cc
,用于实现计时功能,不同平台有不同版本;
默认的debug_log.cc
实现为tensorflow/lite/micro/debug_log.cc
,其主要代码内容为:
这里实现了——使用vfprintf
向stderr
输出。借助过往经验,我们知道CubeMX生成的项目稍加修改就能够支持printf,这种方式应该也可以支持。
因此,对于STM32单片机这部分可以不用修改!
默认的micro_time.cc
实现为tensorflow/lite/micro/micro_time.cc
,其主要代码内容为:
这个文件里面提供了两种实现,通过TF_LITE_USE_CTIME
宏进行切换:
- 如果没定义
TF_LITE_USE_CTIME
宏,则为空实现,提供空壳函数,可以编译通过,无法正常计时;
- 如果定义了
TF_LITE_USE_CTIME
宏,则为基于C标准库clock()
和CLOCKS_PER_SEC
的计时;
对于STM32单片机,C标准库的clock()
的计时不能直接使用。
因此,对于STM32单片机,需要单独实现一个micro_time.cc
文件,具体代码为:
到这里,TFLM移植的主体内容基本已经完成了,还有一些问题需要解决,我能将在下篇进行介绍,欢迎关注。
本篇内容到此为止,感谢阅读!
六、参考链接
- TensorFlow Lite for Microcontrollers介绍: TensorFlow Lite for Microcontrollers (google.cn)
- TensorFlow Lite for Microcontrollers入门: 微控制器入门 | TensorFlow (google.cn)
- tflite-micro 源码GitHub仓: https://github.com/tensorflow/tflite-micro
- CMake最新文档: CMake Reference Documentation — CMake 3.30.3 Documentation