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编译中大部分的坑不是由Android 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 2019 configure.cmd
-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,会导致编译错误,并且没法人工改正,可以参考TFLite - NDK11 构建问题

常见的第二个问题是包含 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位机型的话可以这么修改。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值