Help us understand the problem. What is going on with this article?

TensorFlow C++(libtensorflow_cc.so) で推論アプリを CMake でビルドする(r1.8 対象)

More than 1 year has passed since last update.

漢なら, TensorFlow C++ API で推論やりたいですよね! やりましょう!

現時点(2018 年 5 月 28 日)の最新版は r1.8 です.

TensorFlow Lite というのも出始めてきていますが, 対応する Op がまだまだという問題がありますので,
本体の libtensorflow を使うことにします.

昨今では学習したモデルをモバイルで動かしたい需要が増えてきていますが, 流石に python をモバイル上で動かすというのは面倒なので C++ でコアを書き, 周りは Kotlin, Swift, ReactNative あたりで叩きたいですよね.

ここでの目的は TensorFlow C++ API(CPU 版) を PC Linux(Ubuntu 16.04, x86)で動かすことにより, モバイルでの Tensorflow C++ API での推論の開発とデバッグをやりやすくすることです.

libtensorflow をビルドする方法のは, 主に以下の三つがあります.

  • bazel : 標準のビルド. しかし bazel を使っているのは TensorFlow くらいであるし, bazel 内部は Java が動いていたりしてなるべく使いたくない...
  • cmake : Contrib で提供. Windows 向けで, PC Linux 環境向けには用意されていない. 一応ビルドはできるが, PC 環境だと libtensorflow を作ってくれないので自前で .o をリストアップしてユーザプロジェクトでリンクしないといけなくて大変. また後述するリンクの問題の対応が入っていないため実質使い物にならない.
  • makefile : Contrib で提供. モバイル(Android, iOS, RaspberryPi)向けで, PC Linux(x86) 環境向けには用意されていない. 一応ビルドはできるが, これも後述する問題により Op のリストを自前でまとめないとならず大変.

従って現状 Bazel 一択です.

そして, いろいろ試したところ Bazel で libtensorflow_cc-config monolithic でビルドが唯一の解です

Docker で libtensorflow をビルドしてくれるのもあります. こちらも検討したいですね.

https://github.com/FloopCZ/tensorflow_cc

libtensorflow リンク時の問題

自前のアプリのビルドには Bazel は使いたくありませんから, CMake で書き, libtensorflow をリンクするようにしたいですね.

しかし, とりあえず bazel や makefile でビルドした libtensorflow.so をリンクして動かしても, Session の初期化時に, No session factory registered for the given session options で失敗とよくわからないエラーが出たり, Op が無いとか言われてしまいます...:dizzy_face:

原因はこれです.

contrib/makefile: No session factory registered for the given session options #3308
https://github.com/tensorflow/tensorflow/issues/3308

TensorFlow では, ライブラリのロード時にグローバルコンストラクタで Op を初期化みたいな変な C++ テクニックを使っています.
こういう変な C++ テクニックつかうの, 本当にやめてほしいですね! :no_good:

少なくとも, Linux 環境では, makefile ベースでビルドした場合は, この問題を解決するために, リンク時にその辺りの情報を保持するために -Wl,--allow-multiple-definition -Wl,--whole-archive を指定する必要があります!

さらに, -fPIC or -fPIEposition independent にしてコンパイルすると確実かもしれません!

CMake ですと

set(CMAKE_POSITION_INDEPENDENT_CODE ON)

で有効になります.

上の makefile での問題は, Op を全部入れるとライブラリが肥大化するので, 選別した Op だけ使うようにしていて, 必要なら Op を追加してね, とあります. 従って makefile で作った libtensorflow-core.a だと実行時に Op が足りないというエラーも出ますので注意ください. これを直すのも面倒ですね.

Bazel で libtensorflow_cc をビルドする.

libstensorflow_cc.somonolithic 有効でビルドします. monolithic 有効にしないとアプリとリンクしたときにうまく動きません.

$ bazel build --config opt --config monolithic tensorflow:libtensorflow_cc.so

アプリ側での CMake 設定.

TENSORFLOW_DIR(git repo ディレクトリ)と, TENSORFLOW_EXTERNAL_DIR(third party パッケージ), TENSORFLOW_BUILD_DIR(ビルドディレクトリ)を cmake のパラメータとして設定できるようにします.

bazel だと, external パッケージの場所は, repo ディレクトリ名が tensorflow ではなく別の名前, たとべあ tensorflow-cmake だと bazel-tensorflow-cmake と repo 名がプレフィックスとして付くので注意ください.

以下のようにして bootstrap するのを想定します.

# source code directory of tensorflow
TF_DIR=`pwd`/../tensorflow-cmake

# external source code directory of tensorflow
TF_EXTERNAL_DIR=`pwd`/../bazel-tensorflow-cmake

# bazel build directory of tensorflow where `libtensorflow.so` exists.
# Please specify absolute path, otherwise cmake cannot find lib**.a
TF_BUILD_DIR=`pwd`/../tensorflow-cmake/bazel-bin/tensorflow

cmake -DTENSORFLOW_DIR=${TF_DIR} \
      -DTENSORFLOW_EXTERNAL_DIR=${TF_EXTERNAL_DIR} \
      -DTENSORFLOW_BUILD_DIR=${TF_BUILD_DIR} \
      -DSANITIZE_ADDRESS=On \
      -Bbuild \
      -H.

include の設定

eigen, protocol buffers あたりのヘッダーパスを設定します.

array_ops.h などの一部ファイルは libtensorflow のビルド時に作成されますので, それらへのパスを通します. bazel ビルドの場合は bazel-genfiles にあります.

tensorflow/third_party は実体の無いヘッダーがあるだけなので, ここにパスを通してもヘッダが見つからない(or ヘッダファイルが循環している)エラーがでるので注意です!

target_include_directories(myapp
    PUBLIC ${TENSORFLOW_DIR}

    # for array_ops.h
    PUBLIC ${TENSORFLOW_DIR}/bazel-genfiles

    # headers for external packages
    PUBLIC ${TENSORFLOW_EXTERNAL_DIR}/external/protobuf_archive/src
    PUBLIC ${TENSORFLOW_EXTERNAL_DIR}/external/eigen_archive
    PUBLIC ${TENSORFLOW_EXTERNAL_DIR}/external/nsync/public

    # this project
    PUBLIC ${CMAKE_SOURCE_DIR}/src
)

補足: リンカの設定

Bazel で monolithic 有効の場合は以下のリンカ設定は不要のようですが, 参考のために記録しておきます.

Makefile ベース, もしくは monolithic オフで bazel でビルドした場合は, libtensorflow**.so をリンクする前に -Wl,--allow-multiple-definition -Wl,--whole-archive をつけます.

# Fix for "No session factory registered for the given session" error in the runtime.
if (UNIX AND NOT APPLE)
  # https://github.com/tensorflow/tensorflow/issues/3308
  TARGET_LINK_LIBRARIES(myapp -Wl,--allow-multiple-definition -Wl,--whole-archive "${TENSORFLOW_DIR}/bazel-bin/tensorflow/libtensorflow_cc.so" -Wl,--no-whole-archive)
endif ()

できれば libtensorflow_cc.so のリンク行のあとに -Wl,--no-whole-archive をいれて後続にリストアップするライブラリのリンク設定をリセットしたいですが, これは課題とします.

最終的に, ユーザアプリでの CMakeLists.txt は以下のようになります.

make_minimum_required(VERSION 3.5.1)

project(myapp)

# threads
find_package(Threads)

# C++11(or C++14)
set (CMAKE_CXX_STANDARD 11)

# PIC
set (CMAKE_POSITION_INDEPENDENT_CODE ON)

set (CORE_SOURCE
    ${CMAKE_SOURCE_DIR}/src/main.cc
    )

link_directories(
    ${TENSORFLOW_BUILD_DIR}
    )

add_executable( myapp
    ${CORE_SOURCE}
    )

target_include_directories(myapp
    PUBLIC ${TENSORFLOW_DIR}

    # for array_ops.h
    PUBLIC ${TENSORFLOW_DIR}/bazel-genfiles

    # headers for external packages
    PUBLIC ${TENSORFLOW_EXTERNAL_DIR}/external/protobuf_archive/src
    PUBLIC ${TENSORFLOW_EXTERNAL_DIR}/external/eigen_archive
    PUBLIC ${TENSORFLOW_EXTERNAL_DIR}/external/nsync/public

    # this project
    PUBLIC ${CMAKE_SOURCE_DIR}/src
)

target_link_libraries( myapp
    tensorflow_cc
    ${CMAKE_THREAD_LIBS_INIT}
    ${CMAKE_DL_LIBS}
    )

...

これでとりあえずユーザアプリ(Linux + C++)で推論を動かすことができるようになるはずです!(少なくとも freeze された .pb を読み込めるところまで確認しました)

Happy TensorFlow C++ Inference! :nerd:

参考情報

Exporting trained TensorFlow models to C++ the RIGHT way!
https://medium.com/@hamedmp/exporting-trained-tensorflow-models-to-c-the-right-way-cf24b609d183

TensorFlow : Mobile : TensorFlow ライブラリを統合する
http://tensorflow.classcat.com/2017/12/13/tensorflow-mobile-linking_libs/
(No session factory registered for the given session options について)

Tensorflowをc++のcmakeしてるプロジェクトで使いたい
https://qiita.com/Tacha-S/items/5bb18c631868b6b15517

TODO

  • 学習部分もモバイル(エッジ)で動かしたい.
  • TensorFlow C++, ビルドがめんどくさいし Bazel 使いたくないので PyTorch 1.0 に早く移行したい. PyTorch で幸せになりたい.
  • 優秀な TensorFlow C++ 若人が, 日々切磋琢することで, 人類史上最速で優秀な C++ 機械学習若人へと昇華なされるスキームを確立する旅に出たい.
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした