Tensroflow⼿动编译TFLite
本篇主要作为⼀个操作⼿册来介绍怎么编译Tensorflow和记录编译过程中踩过的坑
建议在编译TFLite之前通读⼀遍本⽂,可以少⾛很多弯路。
在安卓上使⽤TFLite⼀般可以通过直接在gradle中引⽤dependencies的⽅式增加TFlite依赖。
但在⼀些⾃定义场景下需要我们⼿动去编译TFlite依赖库,⽐如在C++下开发了基于Tensorflow Lite的模型或者逻辑。
google预编译的TFLite so库的C++ 符号只有基本的⼏个JNI接⼝,如果我们想在安卓的C++ 层对TFLite进⾏调⽤,则需要通过修改Tensorflow Lite源码的⽅式来构建⾃定义的TFLite so。
以下默认是以Linux或者Mac平台,win平台类推
需要的环境和⼯具
· Tensorflow 2.0.0 源码,git下载
编译过程需要以下⼯具,这些版本是编译过程中坑最少的,其他版本虽然也可以编译但坑不是⼀般的多。
· Bazel - v0.26.0, 或者 < v2.0.0
· Android NDK - ⼤于v16的版本
· Python
· g++,gcc等常⽤的编译⼯具
其中Bazel和ndk是必备的,其他的⼯具在编译过程中报错的时候根据提⽰信息再下载对应⼯具就⾏。
Bazel版本要求
Bazel⽬前已经更新到3.0.0版本,⽽构建的时候还需要指定这么低的版本,是因为Tensorflow的配置脚本最⾼只⽀持0.26.0,⽽编译的话则可以⽀持到 v2.0.0(不包含2.0.0) 最⾼。v2.0.0版本的Bazel因为取消了android_library中对 aapt_vesion的⽀持,所以不能使⽤。虽然官⽅DOC说 aapt_version 在2.0.0中还⽀持,但实测是不⽀持的(tmd让我怀疑⼈⽣)。
Android NDK版本要求
坑最少的版本是 v16以上,实测v18可以⼀次性编译通过。其他版本的ndk编译中⼤部分的坑不是由An
droid NDK引起的,主要还是Bazel 的bug。
配置
进⼊tensorflow下执⾏ configure 命令,配置你的系统环境,包括NDK,SDK,python等位置。其他选项默认值即可。
下载的Tensorflow根⽬录如下。
PhoenixdeMac-mini:~/…/tensorflow$ ll
total 432
drwxr-xr-x 36 phoenixzheng admin 1152 7 10 17:11 .
drwxr-xr-x 14 phoenixzheng admin 448 7 8 15:37 …
-rw-r–r-- 1 phoenixzheng admin 8196 7 9 14:10 .DS_Store
-rw-r–r-- 1 phoenixzheng admin 225 7 7 10:36 .bazelrc
drwxr-xr-x 3 phoenixzheng admin 96 11 12 2019 .github
安卓软件签名工具-rw-r–r-- 1 phoenixzheng admin 776 11 12 2019 .gitignore
-rw-r–r-- 1 phoenixzheng admin 797 7 13 16:51 .tf_configure.bazelrc
-rwxr-xr-x 1 phoenixzheng admin 285 11 12 2019 configure
-rw-r–r-- 1 phoenixzheng admin 780 11 12 d
-rw-r–r-- 1 phoenixzheng admin 57803 11 12 2019 configure.py
不想配置环境也可以通过修改WORKSPACE⽂件内容,增加 android_ndk_repository()的⽅式指定NDK和SDK的位置。
执⾏完configrue之后会有 tf_configure.bazelrc 隐藏⽂件,所需要的配置信息在这⾥⾯有,⽽且可以修改。这些配置信息是从 .bazelrc ⾥抽取出来的。现在你可以⼿动修改 tf_configure.bazelrc ⽂件来指定不同的NDK路径。
build --action_env ANDROID_NDK_HOME="/Users/phoenixzheng/Library/Android/sdk/ndk-bundle-r18b/"
编译
在Tensorflow⽬录下执⾏以下命令构建CPU版本
bazel build --cxxopt=’-std=c++11’ -c opt
–fat_apk_cpu=armeabi-v7a,arm64-v8a
//tensorflow/lite/java:tensorflow-lite
在Tensorflow⽬录下执⾏以下命令构建GPU版本
bazel build --cxxopt=’-std=c++11’ -c opt
–fat_apk_cpu=armeabi-v7a,arm64-v8a
//tensorflow/lite/java:tensorflow-lite-gpu
严格按照上⾯的流程执⾏的话就可以正确地编出 tensorflow-lite.aar了。编译过程会需要从官⽅镜像拉取bazel的构建依赖⼯具,如果发⽣IO 失败的话⼋成是你⽹络问题,建议梯⼦处理。
Android NDK版本的影响
C++函数签名
不同版本的NDK主要区别是C++标准的不同。v16以后的C++标准只⽀持 libc++ 标准库,v16之前的包括v16⽀持gnustl / libc++ / STLport 三个标准。
不同C++标准的影响主要是在动态库链接的时候,可能会导致 undefined reference 错误。
⽐如⽤了 libc++ 标准库构建了tensorflow lite so,但在安卓上构建的时候⽤的是另外⼀个标准的c++,例如gnu stl,那么就会引起链接错误的问题。
因为不同的C++标准的函数签名是不⼀样的。⽐如cmath 的 std::round() 函数,
gnu stl 签名
std::round()
libc++ 签名
std::__ndk1::round()
所以如果tflite so是⽤libc++标准编出来的,那么它应该要 std::__ndk1::round() 签名的函数。但如果
在安卓构建的时候⽤gnustl去链接tflite so,因为在gnustl标准下,符号表⾥的函数签名是 std::round(),⾃然就不到了。
和第三⽅库混编
另外⼀些经常引起问题的情况是和第三⽅库混编。⽐如特化的tflite 模型需要结合OpenCV库使⽤,C++代码既链接了tflite so,⼜链接了opencv so。
引起问题的原因是,opencv so是⽤⼀种C++标准,⽽tflite so⼜是⽤另外⼀种标准。甚⾄还存在opencv 和tflite⽤的是不同版本的ndk编译的情况。因为最终⼤家都需要编到同⼀个so⾥,他们需要链接同⼀个c++标准库,⽽如果opencv和tflite的so在编译的时候就依赖了不同的c++标准库,那么最终是没办法链接到同⼀个so⾥的。
因此⽐较复杂的构建依赖下,必须保证⼤家都是在同⼀个ndk版本和c++标准下编译不同的动态库,才能保证在最后的链接阶段没问题。
修改导出符号表
默认的TFLite只导出了部分C++函数符号,我们可以通过修改 version_script.lds 来增加需要导出的符号。
.../TFLite/tensorflow/tensorflow/lite/java/src/main/native/version_script.lds
修改后的如下
VERS_1.0{
# Export JNI symbols.
global:
Java_*;
JNI_OnLoad;
JNI_OnUnload;
*FlatBufferModel*;
*BuiltinOpResolver*;
*Interpreter*;
*MutableOpResolver*;
*TfLite*;
*ErrorReporter*;
# Hide everything else.
local:
*;
};
疑难杂症
最经常遇到的问题是需要⽤低版本的ndk去构建 so。因为很多现有的⼯程会依赖⼤量已经编译好的第三⽅so,⽽他们通常是在低版本下⽤默认的 gnustl标准 编译的。如果⽤⾼于16的NDK版本编译tflite so的话,默认只有libc++标准库。libc++标准的函数签名跟低版本的默认c++库 gnustl 标准不⼀样,所以会有各种莫名其妙的问题。
这也就导致必须要⽤低版本的ndk去编译tflite。然⽽低版本的不⽌bazel本⾝有bug,ndk也有bug。
另外个办法是把现在⼯程中的所有第三⽅so库都⽤跟tflite so统⼀的ndk版本编⼀遍,这是下策。
需要注意的是低版本编译tflite so需要避免使⽤ndk-r11,bazel对ndk-r11的解析有bug,会导致编译错误,并且没法⼈⼯改正,可以参考。
常见的第⼆个问题是包含 armeabi-v7a 架构的编译失败,但单独编arm64-v8a是OK的。具体报错信息是std::round() not defined。std::round()是cmath下的标准接⼝,它的定义在cmath头⽂件⾥,经过分析发现它受到_GLIBCXX_USE_C99_MATH_TR1宏限制。所以暴⼒修改的话可以通过修改ndk的cmath头⽂件来解决。
路径:.../sdk/ndk-bundle-r14b/sources/cxx-stl/gnu-libstdc++/4.9/include/cmath
line: 925
#if __cplusplus >= 201103L
#define _GLIBCXX_USE_C99_MATH_TR1 1 // << 增加
#ifdef _GLIBCXX_USE_C99_MATH_TR1
#undef acosh
#undef acoshf
#undef acoshl
...
进⼀步搜索这个宏会发现在armeabi-v7a下的c++config.h和arm64-v8a下的有差异,
⽬前没有仔细研究_GLIBCXX_USE_C99_MATH_TR1在32bit机器上为什么被关掉,但打开了这个宏之后实测也没有问题。所以如果⾮得要⽀持32位机型的话可以这么修改。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。