如何将 TensorFlow Lite 构建为静态库并从单独的 (CMake) 项目向其 link?

How to build TensorFlow Lite as a static library and link to it from a separate (CMake) project?

我已经成功构建了一个简单的 C++ 应用程序 运行 TF Lite model by adding my sources to tensorflow/lite/examples, similarly to what the official C++ TF guide 建议使用完整的 TF。现在我想将它构建为一个单独的项目(共享库)link静态地连接到 TF Lite 并使用 CMake 作为构建系统。

我尝试将自定义目标添加到我的 CMakeLists.txt,这将使用 Bazel 构建 TF Lite:

set(TENSORFLOW_DIR ${CMAKE_SOURCE_DIR}/thirdparty/tensorflow)
add_custom_target(TFLite
    COMMAND bazel build //tensorflow/lite:framework
    COMMAND bazel build //tensorflow/lite/kernels:builtin_ops
    WORKING_DIRECTORY ${TENSORFLOW_DIR})

我选择那些 Bazel 目标是因为来自 tensorflow/lite/examples/minimalBUILD 文件将它们作为依赖项,并且它们在我构建时为我工作 我在 TF 仓库中使用 Bazel 的代码。不确定是否足够。

然后我手动收集包含目录(带有丑陋的临时硬编码路径)和库:

set(TFLite_INCLUDES
    ${TENSORFLOW_DIR}
    ~/.cache/bazel/_bazel_azymohliad/ec8567b83922796adb8477fcbb00a36a/external/flatbuffers/include)

set(TFLite_LIBS
    ${TENSORFLOW_DIR}/bazel-bin/tensorflow/lite/libframework.pic.a)
    
target_include_directories(MyLib ... PRIVATE ... ${TFLite_INCLUDES})
target_link_libraries(MyLib ... ${TFLite_LIBS})

使用此配置,我在 linkage 期间获得了很多对 TFLite 内容的未定义引用。我检查了 nmlibframework.pic.a 中确实缺少这些符号,我在 Bazel 输出的各种 .o 文件中找到了其中一些。手动选择所有这些 .o 文件似乎是错误的。

那么,是否可以像我尝试的那样从 CMake 很好地 link 到 TF Lite?也许有一些神奇的 bazel query include_dirs(//tensorflow/lite:framework) 命令可以给我所有必要的包含目录的路径,以及一个类似的库命令 link 反对以便我可以将此信息传递给 CMake?

我最终为 CMake 的 target_link_libraries(在 TFLite_LIBS)手动列出了所有必要的 TFLite object 文件并且它有效。

我使用了一个简单的 shell 脚本来获取必要的 object 文件列表。 首先,我将构建日志中所有未定义的引用收集到 bash-array 中,如下所示:

SYMBOLS=(\
    'tflite::CombineHashes('\
    'tflite::IsFlexOp('\
    'tflite::ConvertArrayToTfLiteIntArray('\
    'tflite::EqualArrayAndTfLiteIntArray('\
    ...
    'tflite::ConvertVectorToTfLiteIntArray(')

然后对于该数组中的每个符号,我遍历了 bazel 构建输出中的每个 *.o 文件:

for SYMBOL in $SYMBOLS[@]; do
    for OBJ in $(find -L /path/to/tensorflow/bazel-bin/ -name '*.o'); do
        nm -C $OBJ | grep "T $SYMBOL" > /dev/null && echo $OBJ
    done
done | sort | uniq

并将输出添加到 CMake 中的 TFLite_LIBS(当然,路径前缀正确)。之后,我得到了一个新的未定义引用部分,但经过几次迭代后,它解决了所有问题。

可能我还可以从我最初的 in-tree 构建的 *-params 文件中获取完整的依赖项列表,但快速检查显示它有一些冗余项,并且脚本只收集了必要的。

对于包含位置,我用 ${TENSORFLOW_DIR}/bazel-tensorflow/external/flatbuffers/include/ 替换了 bazel 缓存中平面缓冲区的硬编码路径。感谢 jdehesa 的提示。

更新:
all-inclusive TF Lite 静态库的本地构建可以非常类似于 RPi, iOS or ARM64 的官方构建说明,使用普通的旧 make:
1. ./tensorflow/lite/tools/make/download_dependencies.sh
2. make -f tensorflow/lite/tools/make/Makefile

输出库将存储为<tensorflow-root>/tensorflow/lite/tools/make/gen/<platform>/lib/libtensorflow-lite.a。并且带有 headers 的外部依赖项将进入 <tensorflow-root>/tensorflow/tensorflow/lite/tools/make/downloads(例如 flatbuffers headers 在 <tensorflow-root>/tensorflow/tensorflow/lite/tools/make/downloads/flatbuffers/include 中)。

指南没有提到可以直接调用make。有wrapper-scripts针对不同的cross-compilation目标,这只是设置适当的变量和运行 make。但默认情况下 make 只会进行原生构建。此 make 调用可以作为自定义命令添加到 CMakeLists.txt.