互联网上有很多关于 TensorflowLite 的 Android JNI 编译方法,但关于 TensorflowLite 本身的编译却不太好找。因此,我把自己的相关经验做一个总结分享。分享前先感谢一下小雨同学和谷歌 TensorflowLite 团队的朋友,他们为我提供了宝贵的经验和建议。
Tensorflow 官网在 2.0 版本发布后做出相应的更新,TensorflowLite (简称 TFLite) 的资料在 For Mobile & IOT 版块,Android 的官方推荐方法在相应的 Guide 里面。由于官网登录有些技术门槛,我把官方的 TFLite 编译方式贴在下面:
执行 ./configure 脚本,设置下面几个环境变量参数:
ANDROID_SDK_HOME
ANDROID_SDK_API_LEVEL
ANDROID_NDK_HOME
ANDROID_NDK_API_LEVEL
如果这些环境变量在 configure 阶段没有配置,可以打开 Tensorflow 主目录下的 .tf_configure.bazelrc
文件,参考下面的配置方式,设定环境参数。
build --action_env ANDROID_NDK_HOME="/usr/local/android/android-ndk-r17c"
build --action_env ANDROID_NDK_API_LEVEL="18"
build --action_env ANDROID_BUILD_TOOLS_VERSION="28.0.3"
build --action_env ANDROID_SDK_API_LEVEL="23"
build --action_env ANDROID_SDK_HOME="/usr/local/android/android-sdk-linux"
只要 Bazel 配置完成,我们就可以再 Tensorflow 主目录用下面的脚本编译 TensorFlow Lite AAR。
bazel build --cxxopt='-std=c++11' -c opt \
--fat_apk_cpu=x86,x86_64,arm64-v8a,armeabi-v7a \
//tensorflow/lite/java:tensorflow-lite
最终的编译结果 TensorFlow Lite AAR 以及 libtensorflowlite_jni.so
都会在 bazel-genfiles/tensorflow/lite/java/
相应的 CPU 架构目录中。
如果直接调用 TFLite 的 JNI 接口能满足 Android 应用的业务需求,到这里已经解决问题了。
我的业务场景要求利用 TFLite 的 C 类接口封装推理引擎,支撑业务架构的算法实现。因此,libtensorflowlite_jni.so
的可见函数不足以解决我面临的问题。
下面是 TFLite_jni 的符号导出情况:
...
000076e5 g DF .text 0000000c VERS_1.0 Java_org_tensorflow_lite_nnapi_NnApiDelegate_createDelegate
00007b19 g DF .text 0000003c VERS_1.0 Java_org_tensorflow_lite_NativeInterpreterWrapper_getInputCount
0000ce8d g DF .text 00000068 VERS_1.0 Java_org_tensorflow_lite_Tensor_buffer
00007cb5 g DF .text 00000030 VERS_1.0 Java_org_tensorflow_lite_NativeInterpreterWrapper_allowFp16PrecisionForFp32
0000823d g DF .text 00000078 VERS_1.0 Java_org_tensorflow_lite_NativeInterpreterWrapper_getOutputQuantizationZeroPoint
00008351 g DF .text 000002c0 VERS_1.0 Java_org_tensorflow_lite_NativeInterpreterWrapper_resizeInput
0000d935 g DF .text 00000050 VERS_1.0 Java_org_tensorflow_lite_TensorFlowLite_schemaVersion
00007d41 g DF .text 00000034 VERS_1.0
.....
解决思路有两种,一种是修改 tensorflow/lite/java
下的 exported_symbols.lds文件:
*Java_*
*JNI_OnLoad
*JNI_OnUnload
TfLite*
*tflite*
*TFL_*
另一种思路是,直接编译 tensorflow/lite
下的 libtensorflowlite.so
:
bazel build --cxxopt='-std=c++11' -c opt \
--config=android_arm \
//tensorflow/lite:libtensorflowlite.so
其中 --config=android_arm
表示兼容 armeabi-v7a 架构的 Android 交叉编译,详情可以参考 tensorflow 主目录下的 tensorflow/BUILD
文件:
....
config_setting(
name = "android_arm",
values = {
"crosstool_top": "//external:android/crosstool",
"cpu": "armeabi-v7a",
},
visibility = ["//visibility:public"],
)
....
这个 BUILD 文件中包含了很多官方文档不容易查到编译参数。其实,这个 config_setting 相当于封装 2.0 以前 tensorflow/contrib/lite 时代的 --cpu=armeabi-v7a --crosstool_top=//external:android/crosstool
参数。现在这些常用而又繁琐的编译选项组合,已经在 BUILD 文件中通过 config_setting 函数封装好了。我们可以仔细查阅这个文件,使用业务需要的编译参数即可。
最后,展示部分 TFLite 比较完整的导出符号:
.....
00cd155 w DF .text 000000a0 VERS_1.0 _ZN6tflite3ops7builtin15maximum_minimum4EvalILNS2_10KernelTypeE0ENS2_9MinimumOpEEE12TfLiteStatusP13TfLiteContextP10TfLiteNode
0013ea65 g DF .text 0000000c VERS_1.0 _ZN6tflite8internal7MfccDctC1Ev
0004f7ed w DF .text 00000648 VERS_1.0 _ZN6tflite3ops7builtin11activations14LogSoftmaxEvalILNS2_10KernelTypeE1EEE12TfLiteStatusP13TfLiteContextP10TfLiteNode
000642bd w DF .text 0000029c VERS_1.0 _ZN6tflite13reference_ops9ArgMinMaxIhixNSt6__ndk18functionIFbhhEEEEEvRKNS_12RuntimeShapeEPKT_PKT1_S8_PT0_RKT2_
00098673 w DF .text 00000254 VERS_1.0 _ZN6tflite13optimized_ops24FloatDepthwiseConvKernelILb1ELi0ELi1EE3RunEiiiPKfiS4_Pf
000d101d g DF .text 00000006 VERS_1.0 _ZN6tflite3ops7builtin3mul4InitEP13TfLiteContextPKcj
001323b1 g DF .text 00000eb0 VERS_1.0 _ZN6tflite3ops7builtin28unidirectional_sequence_lstm26CheckInputTensorDimensionsEP13TfLiteContextP10TfLiteNodeiiib
0019bb28 w DO .data.rel.ro 00000014 VERS_1.0 _ZTVN6tflite16cpu_backend_gemm6detail14CustomGemvTaskIffffLNS0_18QuantizationFlavorE0EEE
000f9c93 w DF .text 00000008 VERS_1.0 _ZZN6tflite3ops7builtin6reduce8EvalTypeIhEE12TfLiteStatusP13TfLiteContextP10TfLiteNodePNS2_9OpContextENS2_10ReduceTypeEENUlhhE0_8__invokeEhh
000a990d w DF .text 00000998 VERS_1.0 _ZN6tflite3ops7builtin3div7EvalDivILNS2_10KernelTypeE2EEEvP13TfLiteContextP10TfLiteNodeP15TfLiteDivParamsPKNS2_6OpDataEPK12TfLiteTensorSG_PSE_
0011fa0d w DF .text 00000b02 VERS_1.0 _ZN6tflite3ops7builtin3sub13EvalQuantizedILNS2_10KernelTypeE0EEEvP13TfLiteContextP10TfLiteNodeP15TfLiteSubParamsPKNS2_6OpDataEPK12TfLiteTensorSG_PSE_
00048bc9 g DF .text 00000028 VERS_1.0 _ZN6tflite3ops7builtin11activations11SoftmaxInitEP13TfLiteContextPKcj
00094afd w DF .text 000002a4 VERS_1.0 _ZN6tflite3ops7builtin14depthwise_conv23EvalQuantizedPerChannelILNS2_10KernelTypeE0EEEvP13TfLiteContextP10TfLiteNodeP25TfLiteDepthwiseConvParamsPNS2_6OpDataEPK12TfLiteTensorSF_SF_PSD_
00108941 w DF .text 0000015c VERS_1.0 _ZN6tflite13reference_ops13RankOneSelectIbaEEvRKNS_12RuntimeShapeEPKT_S4_PKT0_S4_SA_S4_PS8_
00104965 w DF .text 00000462 VERS_1.0 _ZN6tflite13reference_ops15ReverseSequenceIsiEEvPKT0_iiRKNS_12RuntimeShapeEPKT_S7_PS8_
0010b70d w DF .text 0000019c VERS_1.0 _ZN6tflite3ops7builtin5slice22GetBeginAndSizeVectorsIxEEviPK12TfLiteTensorS6_PNSt6__ndk16vectorIiNS7_9allocatorIiEEEESC_
000887b5 w DF .text 00000004 VERS_1.0 _ZN6tflite16cpu_backend_gemm6detail14CustomGemvTaskIffffLNS0_18QuantizationFlavorE0EED0Ev
0011d101 w DF .text 000005b8 VERS_1.0 _ZN6tflite13reference_ops12StridedSliceIxEEvRKNS_18StridedSliceParamsERKNS_12RuntimeShapeEPKT_S7_PS8_
000516db w DF .text 00000006 VERS_1.0 _ZNSt6__ndk110__function6__funcIZN6tflite3ops7builtin11activations11TanhPrepareILNS5_10KernelTypeE0EEE12TfLiteStatusP13TfLiteContextP10TfLiteNodeEUlfE0_NS_9allocatorISD_EEFffEEclEOf
......
有了这个符号完整的 TFLite 动态库,我们就可以封装自己的 JNI 推理引擎了。