漢なら, 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
をビルドしてくれるのもあります. こちらも検討したいですね.
libtensorflow リンク時の問題
自前のアプリのビルドには Bazel は使いたくありませんから, CMake で書き, libtensorflow
をリンクするようにしたいですね.
しかし, とりあえず bazel や makefile でビルドした libtensorflow.so
をリンクして動かしても, Session の初期化時に, No session factory registered for the given session options
で失敗とよくわからないエラーが出たり, Op が無いとか言われてしまいます...
原因はこれです.
contrib/makefile: No session factory registered for the given session options #3308
https://github.com/tensorflow/tensorflow/issues/3308
TensorFlow では, ライブラリのロード時にグローバルコンストラクタで Op を初期化みたいな変な C++ テクニックを使っています.
こういう変な C++ テクニックつかうの, 本当にやめてほしいですね!
少なくとも, Linux 環境では, makefile ベースでビルドした場合は, この問題を解決するために, リンク時にその辺りの情報を保持するために -Wl,--allow-multiple-definition -Wl,--whole-archive
を指定する必要があります!
さらに, -fPIC
or -fPIE
で position independent にしてコンパイルすると確実かもしれません!
CMake ですと
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
で有効になります.
上の makefile での問題は, Op を全部入れるとライブラリが肥大化するので, 選別した Op だけ使うようにしていて, 必要なら Op を追加してね, とあります. 従って makefile で作った libtensorflow-core.a
だと実行時に Op が足りないというエラーも出ますので注意ください. これを直すのも面倒ですね.
Bazel で libtensorflow_cc をビルドする.
libstensorflow_cc.so
を monolithic 有効でビルドします. 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!
参考情報
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++ 機械学習若人へと昇華なされるスキームを確立する旅に出たい.