需要在APP里使用tensorflow lite來運行PC端訓練的model.tlite,又想apk的體積最小,嘗試了如下方法:
1. 在gradle里配置
implementation("org.tensorflow:tensorflow-lite:2.16.1")
這樣會引入tensorflow.jar,最終apk的size增加大約2.2M
2. 根據tensorflow官方的優化編譯教程
https://www.tensorflow.org/lite/android/lite_build?spm=5176.28103460.0.0.73711db8niy7UE&hl=zh-cn
針對我們的模型,構建出針對性的TensorFlow Lite AAR,最后集成到apk里,體積增加約1.5M
分析TensorFlow Lite AAR的實現,發現其本質還是通過JNI調用了libtensorflowlite.so,
而這個libtensorflowlite.so,包含了tensorflow lite幾乎所有核心framework代碼,因此肯定很大。
3. 因為我們僅需要用到tensorflow lite里model 初始化,interpreter推理等基礎功能,并不需要tensorflow lite里的其他功能,因此,想要最小,直接在我們的JNI文件里,集成tensorflow lite相關類的源碼進行編譯,應該就能使得體積增加最小化了。
把我們JNI文件依賴的類,比如
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/model.h"
等.h和.cc引入我們的JNI里,一起編譯就行了。
一開始是在android studio里,導入tensorflow lite的源碼, 修改CmakeLists.txt,嘗試編譯出可以獨立運行的JNI so, 但是總是失敗。
最后,把JNI文件,放到tensorflow lite的源碼目錄里,利用tensorflow的編譯工具bazel,編譯成功。然后把生成的milc_jni.so放到app的jniLibs里,成功:
a. 在tensorflow/lite/下創建milc_jni/這個目錄,目錄下創建BUILD,milc_jni.cc,?custom_op_resolver.h和custom_op_resolver.cc
b. 根據我們的模型文件model.tflite里用到的算子,比如,我只用了FULLY_CONNECTED,RELU, LOGISTIC這3個算子,定制精簡算子的Resolver類
custom_op_resolver.h
#ifndef TENSORFLOW_LITE_CUSTOM_OP_RESOLVER_H_
#define TENSORFLOW_LITE_CUSTOM_OP_RESOLVER_H_#include "tensorflow/lite/mutable_op_resolver.h"namespace tflite {class MinimalOpResolver : public MutableOpResolver {public:MinimalOpResolver();
};} // namespace tflite#endif // TENSORFLOW_LITE_CUSTOM_OP_RESOLVER_H_
custom_op_resolver.cc
#include "tensorflow/lite/milc_jni/custom_op_resolver.h"
#include "tensorflow/lite/kernels/builtin_op_kernels.h"namespace tflite {
MinimalOpResolver::MinimalOpResolver() {// 使用 kernels::builtin:: 命名空間下的注冊函數AddBuiltin(BuiltinOperator_FULLY_CONNECTED, tflite::ops::builtin::Register_FULLY_CONNECTED());AddBuiltin(BuiltinOperator_RELU, tflite::ops::builtin::Register_RELU());AddBuiltin(BuiltinOperator_LOGISTIC, tflite::ops::builtin::Register_LOGISTIC());
}
} // namespace tflite
c. 創建JNI文件milc_jni.cc
#include <jni.h>
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/model.h"
#include "tensorflow/lite/milc_jni/custom_op_resolver.h"
#include <android/log.h>#define LOG_TAG "TensorFlowLiteJNI"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)// 移除所有日志輸出
//#define LOGI(...)
//#define LOGE(...)extern "C" JNIEXPORT jfloat JNICALL
Java_com_xm_j_milc_predictJNI(JNIEnv* env, jobject /* this */, jstring modelPath, jfloatArray inputArray) {const char* modelPathStr = env->GetStringUTFChars(modelPath, nullptr);// 獲取輸入數組jfloat* inputElements = env->GetFloatArrayElements(inputArray, nullptr);jsize inputLength = env->GetArrayLength(inputArray);if (inputLength != 31) {LOGE("Input array length must be 31");env->ReleaseStringUTFChars(modelPath, modelPathStr);env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);return -1.0;}// 加載 TensorFlow Lite 模型std::unique_ptr<tflite::FlatBufferModel> model = tflite::FlatBufferModel::BuildFromFile(modelPathStr);if (!model) {LOGE("Failed to load model from %s", modelPathStr);env->ReleaseStringUTFChars(modelPath, modelPathStr);env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);return -1.0;}// 創建解釋器//tflite::ops::builtin::BuiltinOpResolver resolver;tflite::MinimalOpResolver resolver;std::unique_ptr<tflite::Interpreter> interpreter;tflite::InterpreterBuilder(*model, resolver)(&interpreter);if (!interpreter) {LOGE("Failed to create interpreter");env->ReleaseStringUTFChars(modelPath, modelPathStr);env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);return -1.0;}// 分配張量if (interpreter->AllocateTensors() != kTfLiteOk) {LOGE("Failed to allocate tensors");env->ReleaseStringUTFChars(modelPath, modelPathStr);env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);return -1.0;}// 設置輸入float* input = interpreter->typed_input_tensor<float>(0);for (int i = 0; i < inputLength; ++i) {input[i] = inputElements[i];}// 運行推理if (interpreter->Invoke() != kTfLiteOk) {LOGE("Failed to invoke interpreter");env->ReleaseStringUTFChars(modelPath, modelPathStr);env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);return -1.0;}// 獲取輸出// 5. 獲取輸出結果float* outputTensor = interpreter->typed_output_tensor<float>(0);// 釋放資源env->ReleaseStringUTFChars(modelPath, modelPathStr);env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);return outputTensor[0]; // 直接返回標量值
}
d. 創建BUILD文件
# 自定義操作解析器(僅包含必要算子)
cc_library(name = "custom_op_resolver",srcs = ["custom_op_resolver.cc"],hdrs = ["custom_op_resolver.h"],deps = ["//tensorflow/lite/kernels:builtin_ops",],
)cc_binary(name = "milc_jni.so",srcs = ["milc_jni.cc"],linkshared = True,linkstatic = True, # 靜態鏈接所有依賴deps = [":custom_op_resolver","//tensorflow/lite:framework","//tensorflow/lite/kernels:builtin_ops","@flatbuffers//:flatbuffers",],copts = ["-Oz","-flto=thin","-ffunction-sections","-fdata-sections","-fvisibility=hidden","-fvisibility-inlines-hidden","-DFLATBUFFERS_RELEASE","-DTF_LITE_STRIP_ERROR_STRINGS=1","-DNDEBUG","-DFORCE_MINIMAL_LOGGING","-fno-exceptions","-fno-rtti","-fno-unwind-tables","-fno-asynchronous-unwind-tables","-ffreestanding",],linkopts = ["-flto=thin","-Wl,--gc-sections","-Wl,--exclude-libs,ALL","-s", "-Wl,--as-needed","-Wl,-z,norelro","-Wl,--build-id=none", # 移除構建ID"-Wl,--strip-all", # 徹底去除符號"-nostdlib","-lc","-Wl,--hash-style=gnu", # 更小的哈希表"-Wl,--compress-debug-sections=zlib", # 壓縮調試節],features = ["-layering_check",],
)
e. 在tensorflow的源碼目錄里,初始化好環境,AndroidNDK之類的,然后執行編譯
bazel build -c opt --config=android_arm64 --copt="-DFORCE_DISABLE_ALL_OPS" --linkopt="-Wl,--gc-sections" --linkopt="-Wl,--exclude-libs,ALL" --linkopt="-s" --define=tflite_with_xnnpack=false --copt="-Os" --copt="-fomit-frame-pointer" --copt="-ffunction-sections" --copt="-fdata-sections" --copt="-fvisibility=hidden" --copt="-g0" --copt="-DFLATBUFFERS_RELEASE" //tensorflow/lite/milc_jni:milc_jni.so
然后,就會生成一個milc_jni.so,大約500K,它是可以獨立運行的,不用依賴libtensorflowlite.so,因此,APK的size,也就只會增加約500K。
f.針對生成的milc_jni.so,進一步壓縮優化
sudo apt-get install upx
upx --android-shlib --best --lzma milc_jni.so -o milc_jni_upx.so
最終的milc_jni_upx.so大約200K,因此,APK的size,也就只會增加約200K。