README_zh.md
1# TensorFlow Lite接入NNRt Delegate开发指南
2
3## 概述
4神经网络运行时部件(NNRt)是跨设备的AI运行时框架,作为端侧推理框架和专用加速芯片的中间桥梁,为端侧推理框架提供了统一的Native接口。
5
6本demo旨在介绍TensorFlow Lite推理框架如何接入NNRt,并在专有芯片上加速推理,接入OpenHarmony社区生态。
7
8本demo根据用户输入参数(模型、标签、模型输入shape、循环浮点推理次数、是否允许动态尺寸推理、以及是否打印结果等)完成标签分类模型推理,用户可通过打印信息观察在不同条件下的模型推理性能、精度和预测类别。
9
10## 基本概念
11在开发前,开发者需要先了解以下概念,以便更好地理解全文内容:
12- NNRt:Neural Network Runtime,神经网络运行时,是本指导主要介绍的部件。
13- OHOS:OpenHarmony Operating System,OpenHarmony操作系统。
14
15## 约束与限制
161. 系统版本:OpenHarmony master分支。
172. 开发环境:Ubuntu 18.04及以上。
183. 接入设备:OpenHarmony定义的标准设备。
194. 其他开发依赖:
20获取TensorFlow Lite头文件,[获取链接](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite)。
21 - tensorflow-lite.so及其依赖库(参考[编译指导](#调测命令)编译生成),目前完成在tensorflow lite 2.6版本上的测试。
22 - NNRt动态库libneural_network_runtime.z.so,参考[编译指导](https://gitee.com/openharmony-sig/neural_network_runtime/blob/master/README_zh.md)编译生成。
23 - mobilenetv2的Tensorflow lite模型,[获取链接](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.4_224.tgz)。
24 - 标签文件labels.txt,可从[压缩包](https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_1.0_224_frozen.tgz)中解压得到。
25 - 测试图片grace_hopper.bmp,[获取链接](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/examples/label_image/testdata/grace_hopper.bmp)。
26
27## 运作机制
28
29**图1** 运作机制
30
31
32
33以TensorFlow lite的MobileNetv2模型进行标签分类任务为例,实现调用NNRt API在指定芯片上加速推理,主要有以下三个部分:
341. 通过TFLite delegate机制,创建NNRt Delegate,并接入TFLite的Hardware Accelerator中。
352. 将TensorFlow lite模型中能在NNRt上运行的op kernels,替换成NNRt Delegate kernels。
363. 推理过程中,被替换成NNRt Delegate Kernel的op kernel会调用Neural Network Runtime中的API在指定芯片上完成模型的构图、编译和执行。
37
38## 开发流程
39
40**图2** 开发流程
41
42
43
44主要开发步骤包括命令行参数解析、创建NNRt Delegate、TFLite nodes的替换、tensors的内存分配、执行推理、结果查看等,具体如下:
451. 解析运行demoe的命令,初始化模型推理所需参数和创建NNRt Delegate的options。
462. 调用Tensorflow lite的BuildFromFile接口完成Tensorflow lite模型的构建。
473. 根据demo的运行命令解析模型是否需要动态输入,如果需要,则调用ResizeInputTensor接口更改模型输入大小。
484. 创建DelegateProviders,并调用DelegateProviders的CreateAllRankedDelegates接口创建NnrtDelegate;创建NnrtDelegate过程中,通过dlopen打开NNRt的动态库来加载NNRt API。
495. 调用ModifyGraphWithDelegate接口完成Node替换,其中该步分四个步骤:
50 - 初始化NnrtDelegate。
51 - 判断TFLite图中各node是否支持在NnrtDelegate上运行,返回支持的node集合。
52 - 调用TFLiteRegistration注册NnrtDelegate,并初始化init,prepare,invoke成员函数指针,指向NnrtDelegateKernel的Init,Prepare和run函数方法。
53 - 替换TensorFlow Delegate的node为已注册的NNrt delegate kernel,并调用Init完成构图过程。
546. 用户调用AllocateTensors,完成tensors内存分配和图编译。其中,支持在NNRtDelegate上运行的node会调用NnrtDelegateKernel的prepare接口完成编译,不支持的会调用tflite operation kernels的prepare编译。
557. 导入输入数据并调用Invoke完成图执行。
568. 结果输出。
57
58
59## 开发步骤
60本节主要描述NNRt接入TFLite的TFLite-delegate代理机制,重点对TFLite调用delegate的流程和delegate对接NNRt的方式进行了介绍。
61TensorFlow Lite Delegate有两个基类DelegateProvider、TfLiteDelegate,本节主要描述继承这两个基类得到子类NnrtDelegate和NnrtDelegateProvider。
62
63本demo主要文件目录结构如下图:
64```text
65.
66├── CMakeLists.txt
67├── delegates
68│ └── nnrt_delegate
69│ ├── CMakeLists.txt # 生成libnnrt_delegate.so的交叉编译规则文件
70│ ├── nnrt_delegate.cpp # NnrtDelegate源文件,对接到NNRt上,使TensorFlow Lite模型能运行在加速芯片上
71│ ├── nnrt_delegate.h # NnrtDelegate头文件
72│ ├── nnrt_delegate_kernel.cpp # NnrtDelegateKernel源文件,将TensorFlow Lite模型中的operators替换成Nnrt中的operators
73│ ├── nnrt_delegate_kernel.h # NnrtDelegateKernel头文件
74│ ├── nnrt_delegate_provider.cpp # 用于创建NNrtDelegate
75│ ├── nnrt_op_builder.cpp # NnrtOpBuilder源文件,给每个operators设置输入输出tensor和operation属性
76│ ├── nnrt_op_builder.h # NnrtOpBuilder头文件
77│ ├── nnrt_utils.cpp # 用于辅助创建NnrtDelegate工具方法的源文件
78│ ├── nnrt_utils.h # 用于辅助创建NnrtDelegate工具方法的头文件
79│ └── tensor_mapping.h # TensorFlow Lite Tensor到Nnrt tensor的转换头文件
80├── label_classify
81│ ├── CMakeLists.txt # 生成可执行文件label_classify的交叉编译规则文件
82│ ├── label_classify.cpp # 生成可执行文件label_classify的源文件
83│ └── label_classify.h # 生成可执行文件label_classify的头文件
84├── nnrt
85│ ├── CMakeLists.txt # 生成libnnrt_implementation.so的交叉编译规则文件
86│ ├── nnrt_implementation.cpp # 生成libnnrt_implementation.so的源文件,用于加载NNRt Api
87│ └── nnrt_implementation.h # 生成libnnrt_implementation.so的头文件
88└── tools
89 ├── bitmap_helpers.cpp # 用于读取输入的bmp格式图片源文件
90 ├── bitmap_helpers.h # 用于读取输入的bmp格式图片头文件
91 ├── get_topn.h # 用于返回推理的top N结果
92 ├── log.h # 日志模块文件
93 ├── utils.cpp # 用于辅助模型推理输入和输出工具方法的源文件
94 └── utils.h # 用于辅助模型推理输入和输出工具方法的头文件
95```
961. 创建NnrtDelegate类。
97
98 NnrtDelegate类定义在nnrt_delegate文件中,用于对接NNRt,使TensorFlow Lite模型能运行在加速芯片上。用户需要实现DoPrepare接口、GetSupportedNodes接口、GetDelegateKernelRegistration接口,详细代码参考[链接](https://gitee.com/openharmony-sig/neural_network_runtime/blob/master/example/deep_learning_framework/tflite/delegates/nnrt_delegate/nnrt_delegate.cpp)。主要步骤有以下两点:
99 - 获取TensorFlow Lite中能替换的nodes。
100 ```cpp
101 TfLiteNode* node = nullptr;
102 TfLiteRegistration* registration = nullptr;
103 for (auto nodeIndex : TfLiteIntArrayView(executionPlan)) {
104 node = nullptr;
105 registration = nullptr;
106 TF_LITE_ENSURE_STATUS(context->GetNodeAndRegistration(context, nodeIndex, &node, ®istration));
107 if (NnrtDelegateKernel::Validate(registration->builtin_code)) {
108 supportedNodes.emplace_back(nodeIndex);
109 } else {
110 TFLITE_LOG_PROD(TFLITE_LOG_WARNING,
111 "[NNRT-DELEGATE] Get unsupportted node: %d.", registration->builtin_code);
112 }
113 }
114 ```
115
116 - 注册的Delegate kernel,初始化TfLiteRegistration的init,prepare,invoke成员函数指针,指向NnrtDelegateKernel的Init,Prepare和run函数方法。
117 ```cpp
118 nnrtDelegateKernel.init = [](TfLiteContext* context, const char* buffer, size_t length) -> void* {
119 if (buffer == nullptr) {
120 return nullptr;
121 }
122
123 const TfLiteDelegateParams* params = reinterpret_cast<const TfLiteDelegateParams*>(buffer);
124 auto* delegateData = static_cast<Data*>(params->delegate->data_);
125 NnrtDelegateKernel* state = new (std::nothrow) NnrtDelegateKernel(delegateData->nnrt);
126 if (state == nullptr) {
127 TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "Failed to create NnrtDelegateKernel instance.");
128 return state;
129 }
130
131 TfLiteStatus status = state->Init(context, params);
132 if (status != kTfLiteOk) {
133 TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "Failed to init NnrtDelegateKernel.");
134 delete state;
135 state = nullptr;
136 }
137 return state;
138 };
139
140 nnrtDelegateKernel.prepare = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
141 if (node == nullptr) {
142 TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "Failed to prepare delegate kernels, the node is nullptr.");
143 return kTfLiteError;
144 }
145
146 NnrtDelegateKernel* state = reinterpret_cast<NnrtDelegateKernel*>(node->user_data);
147 return state->Prepare(context, node);
148 };
149
150 nnrtDelegateKernel.invoke = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
151 if (node == nullptr) {
152 TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "Failed to invoke delegate kernels, the node is nullptr.");
153 return kTfLiteError;
154 }
155
156 NnrtDelegateKernel* state = reinterpret_cast<NnrtDelegateKernel*>(node->user_data);
157 return state->Invoke(context, node);
158 };
159 ```
160
1612. 创建NnrtDelegateProvider。
162
163 NnrtDelegateProvider定义在nnrt_delegate_provider文件中,用于创建NNrtDelegate,完成与TFLite的对接。主要步骤有以下两点:
164 - 注册NnrtDelegateProvider
165 ```cpp
166 REGISTER_DELEGATE_PROVIDER(NnrtDelegateProvider);
167 ```
168
169 - 创建CreateTfLiteDelegate主要有以下几步
170 ```cpp
171 NnrtDelegate::Options options;
172
173 const auto* nnrtImpl = NnrtImplementation();
174 if (!nnrtImpl->nnrtExists) {
175 TFLITE_LOG(WARN) << "NNRT acceleration is unsupported on this platform.";
176 return delegate;
177 }
178
179 Interpreter::TfLiteDelegatePtr TfLiteDelegatePtr(new (std::nothrow) NnrtDelegate(nnrtImpl, options),
180 [](TfLiteDelegate* delegate) { delete reinterpret_cast<NnrtDelegate*>(delegate); });
181 ```
182
1833. label_classify.cpp中加载NnrtDelegate,并完成node的替换。
184 ```cpp
185 interpreter->ModifyGraphWithDelegate(std::move(delegate.delegate))
186 ```
187
188
189## 调测命令
1901. 编译生成Tensorflow Lite库及其依赖库。
191
192 请参考[Tensorflow Lite交叉编译指南](https://www.tensorflow.org/lite/guide/build_cmake_arm),同时在```tensorflow/lite/CMakeLists.txt```中增加以下内容:
193 ```text
194 list(APPEND TFLITE_EXTERNAL_DELEGATE_SRC
195 ${TFLITE_SOURCE_DIR}/tools/delegates/delegate_provider.cc
196 # ${TFLITE_SOURCE_DIR}/tools/delegates/external_delegate_provider.cc
197 ${TFLITE_SOURCE_DIR}/tools/tool_params.cc
198 ${TFLITE_SOURCE_DIR}/tools/command_line_flags.cc
199 )
200 ```
201 ```text
202 target_link_libraries(tensorflow-lite
203 PUBLIC
204 Eigen3::Eigen
205 NEON_2_SSE
206 absl::flags
207 absl::hash
208 absl::status
209 absl::strings
210 absl::synchronization
211 absl::variant
212 farmhash
213 fft2d_fftsg2d
214 flatbuffers
215 gemmlowp
216 ruy
217 ${CMAKE_DL_LIBS}
218 ${TFLITE_TARGET_DEPENDENCIES}
219 )
220 ```
221
2222. 编译生成NNRt库libneural_network_runtime.z.so。
223
224 请参考[编译指导](https://gitee.com/openharmony-sig/neural_network_runtime/blob/master/README_zh.md),编译命令如下:
225 ```shell
226 ./build.sh --product-name rk3568 –ccache --jobs=16 --build-target=neural_network_runtime
227 ```
228
2293. 用cmake编译北向demo。
230
231 - TensorFlow Lite头文件和依赖库配置。
232
233 将[TensorFlow Lite头文件](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite)和编译生成的TensorFlow Lite库,分别放在```deep_learning_framework/lib_3rd_nnrt_tflite/include/tensorflow/lite/```和```deep_learning_framework/lib_3rd_nnrt_tflite/com/arm64-v8a/lib/```下。
234
235 - 交叉编译工具配置。
236
237 在社区的[每日构建](http://ci.openharmony.cn/dailys/dailybuilds)下载对应系统版本的ohos-sdk压缩包,从压缩包中提取对应平台的Native开发套件;指定ohos的cmake, ohos.toolchain.cmake路径,在```foundation/ai/neural_network_runtime/example/cmake_build/build_ohos_tflite.sh```中替换以下两行:
238 ```shell
239 ./tool_chain/native/build-tools/cmake/bin/cmake \
240 -DCMAKE_TOOLCHAIN_FILE=./tool_chain/native/cmake_build/cmake/ohos.toolchain.cmake \
241 ```
242
243 - 修改交叉编译文件。
244
245 进入```foundation/ai/neural_network_runtime/example/cmake_build```,执行以下修改:
246
247 如果需要在arm32架构的CPU上运行:
248
249 # 修改```tflite/CMakeLists.txt```
250 ```text
251 set(CMAKE_CXX_FLAGS "-pthread -fstack-protector-all -fPIC -D_FORTIFY_SOURCE=2 -march=armv7-a")
252 ```
253 # 执行编译命令
254 ```shell
255 bash build_ohos_tflite.sh armeabi-v7a
256 ```
257
258 如果需要在arm64架构的CPU上运行:
259
260 # 修改```tflite/CMakeLists.txt```
261 ```text
262 set(CMAKE_CXX_FLAGS "-pthread -fstack-protector-all -fPIC -D_FORTIFY_SOURCE=2 -march=armv8-a")
263 ```
264
265 # 执行编译命令
266 ```shell
267 bash build_ohos_tflite.sh arm64-v8a
268 ```
269
270 - 创建目录
271
272 在```example/deep_learning_framework/```目录下创建lib和output两个文件夹:
273 ```shell
274 mkdir lib output
275 ```
276
277 - 执行链接命令
278
279 进入```foundation/ai/neural_network_runtime/example/cmake_build```,执行链接命令:
280 ```shell
281 make
282 ```
283
284 - 结果查看
285
286 北向demo成功编译完成后会在```deep_learning_framework/lib```生成libnnrt_delegate.so和libnnrt_implementation.so,在```deep_learning_framework/output```下生成label_classify可执行文件,目录结构体如下所示:
287
288 ```text
289 deep_learning_framework
290 ├── lib
291 │ ├── libnnrt_delegate.so # 生成的TensorFlow Lite nnrt delegate库
292 │ └── libnnrt_implementation.so # 生成的nnrt在TensorFlow Lite中接口实现库
293 └── output
294 └── label_classify # 生成的可执行文件
295 ```
296
2974. 在开发板上运行北向demo。
298
299 - 推送文件至开发板
300
301 将步骤1生成的libnnrt_implementation.so、libnnrt_delegate.so和可执行文件label_classify,libneural_network_runtime.z.so、tensorflow-lite.so及其依赖的库、mobilenetv2.tflite模型、标签labels.txt、测试图片grace_hopper.bmp推送到开发板上:
302 ```shell
303 # 假设上述待推送文件均放在push_files/文件夹下
304 hdc_std file send push_files/ /data/demo/
305 ```
306
307 - 执行demo
308
309 进入开发板,执行demo前需要添加环境变量,文件执行权限等:
310 ```shell
311 # 进入开发板
312 hdc_std shell
313
314 # 进入推送文件目录,并增加可执行文件权限
315 cd /data/demo
316 chmod +x ./label_classify
317
318 # 添加环境变量
319 export LD_LIBRARY_PATH=/data/demo:$LD_LIBRARY_PATH
320
321 # 执行demo,-m tflite模型, -i 测试图片, -l 数据标签, -a 1表示使用nnrt, 0表示不使用nnrt推理,-z 1 表示打印输出张量大小的结果
322 ./label_classify -m mobilenetv2.tflite -i grace_hopper.bmp -l labels.txt -a 1 -z 1
323 ```
324
325 - 结果查看
326
327 demo成功执行后,可以看到以下运行结果:
328 ```text
329 INFO: invoked, average time: 194.972 ms
330 INFO: 0.536433: 653 653:military uniform
331 INFO: 0.102077: 835 835:suit, suit of clothes
332 INFO: 0.0398081: 466 466:bulletproof vest
333 INFO: 0.0251576: 907 907:Windsor tie
334 INFO: 0.0150422: 440 440:bearskin, busby, shako
335 ```
336
337## 开发实例
338完整demo可以参考社区实现[Demo实例](https://gitee.com/openharmony-sig/neural_network_runtime/tree/master/example/deep_learning_framework)。
339