tensorflow部署方式: 将tensorflow编译成独立的so包,任意引擎调用

线上环境为linux的系统为 centoos6.2, glibc2.12, gcc4.4.7。我们需要在这种在线环境上部署的已有在线引擎代码中嵌入tensorflow模块,用于线上引擎调用tensorflow相关功能。

最后将tf1.8编译成了独立的.so包,通过接口函数调用该包所包含的功能。


遇到问题如下:

1、tensorflow要求bazel版本,以及gcc版本,protobuf版本均较高,而线上引擎编译一般会选择较为稳定的旧版本,无法满足安装要求。

2、tensorflow 训练的时候是多batch进行训练,而打分的时候是过来一条请求进行一次打分。那么就会出现训练产生的图与打分用到的图不一致的问题,这里的不一致主要是指输入输出不一致,而中间的图结构是相同的(要都不相同那改玩儿个哈士奇啊)。在python训练以及打分时候我们可以通过参数传入,走不同的代码,但是如果c++进行load图的时候应该如何解决该问题。

3、由高版本编译环境产生的.so包,该如何由低版本的gcc进行编译。如果线上也使用到protobuf,那么新旧版本的protobuf该如何兼容。

4、模型调用类该怎么写,怎么与线上引擎代码结合。


解决方法:

关于问题1:

首先我们找一台新的机器,首先配置环境与线上引擎代码编译环境完全相同。然后安装高版本的gcc,pb,jdk等等依赖软件,但此时切记,不要覆盖已有版本,而需要多版本共存。实在无法共存的,那么采用这种方式。

eg:/usr/bin/gcc,系统默认的版本较低,而在编译其他依赖的时候系统会默认调用/usr/bin/gcc进行编译。那么在此我们选择将系统的gcc备份,然后用软链链接到我们的新版本gcc。

mv gcc gcc.bak

ln -s /usr/local/gcc-4.9.4/bin/gcc gcc

关于问题2:

有两种方式可以解决该问题:

tensorflow加载离线训练的模型有两种方式,一种直接加载checkpoint文件,另一种为通过freeze_graph.py程序(位置为:tensorflow/python/tools/free_graph.py)将checkpoint模型文件转化为pb文件。下面我们针对这两种方式进行讲解。


way_a:  将模型转换为protobuf文件,然后进行加载。

我们以预测的参数加载模型,session加载checkpoint成功

saver.restore(sess, ckp_file)

tf.train.write_graph(sess.graph.as_graph_def(),graph_struct, 'graph.pb', as_text=False)  -----------  point_1

tf.train.write_graph(sess.graph.as_graph_def(),graph_struct, 'graph.txt', as_text=True)   -----------  point_2

将模型中图节点通过pb格式提取,运行freeze_graph.py,将整个模型load出来。在此。freeze_graph.py需要指定的参数如下:

python freeze_graph.py --input_graph="graph.pb"--input_checkpoint=model.ckpt --output_graph="frozen_graph.pb"--output_node_names="target_output_node" --input_binary=True

input_graph   我们产生的pb图节点文件。

input_checkpoint  训练生成的checkpoint文件(上文中提到的point_1)

output_graph    输出文件的file_path

output_node_names  这个是非常重要的,指定输出节点的名称。

首先我们要清楚我们我们训练的图的输出节点是哪个,比如我们是通过softmax来进行输出(打分的python代码中可能是这样的softmax_outputs

= tf.nn.softmax(model_outputs, name='this_softmax_output'),其中name是自己指定的),然后通过point_2产生的那个graph.txt文件,查看与this_softmax_output有关的节点,然后选择你要输出的节点的名称。

最终要c++预测代码想要使用的图模型就是新产生的pb文件(不是point_1产生的那个pb)

way_b: 将离线生成的模型通过预测代码重新load起来,然后再次落地到checkpoint里面。这样就可以产生预测图的结构。运行时候加载。



关于问题3:

这个是最棘手的问题,被tf这破玩意折腾了好几个礼拜就是在配破环境,缺了这个包缺那个包,这个不兼容那个不兼容,还要tprotobuf3.5.1。真尼玛先进,用了你的影响了线上引擎已有代码使用protobuf,用了线上已有protobuf你有不能用了,真是矫情。要是tensorflow化身为人,我得拿电脑砸死他,用硬盘砸,用机箱的棱角砸,然后在用主板一条一条的往下拉他肉。


解决方式,所有的依赖 c++,protobuf,全部的全部,以静态库的形式加载,并且编译的时候将生成的.so包中所有的函数名啦什么的都隐藏起来。这样就保证libtf_fun.so中相关调用就调用这个.so包里的内容。而外面调用该引擎的c++代码看不到libtf_fun.so中的任何接口,只能调用引擎原本用的包。ps,要真都这样了,那在引擎里还咋用tensorflow啊。关于隐藏函数名啥的,是要选择暴露一部分,这部分就是我们提供的接口。比如自己写一个类,这个类就是要调用tensorflow相关的接口,load_model, predict 等等功能封装起来,然后把这个类里的函数都暴露出来,这样的话,我们就可以在任何场景下调用tensorflow了。具体验证方式:

ldd  libtf_fun.so  出来的依赖,除了最基本最基本的c库(由于c库是linux最底层的库,不要更改,即使更改了也无法通过多版本加载的方式进行调用)以外,没有任何其他依赖,比如我这里是这样的。

libdl.so.2 =>/lib64/libdl.so.2

libpthread.so.0 =>/lib64/libpthread.so.0

libm.so.6 =>/lib64/libm.so.6

libc.so.6 => /lib64/libc.so.6

/lib64/ld-linux-x86-64.so.2

这样就达到了无依赖。

nm tf_lib/naive_lib/libtf_fnn.so   这个命令是要输出某些文件中的符号(如函数,全局变量等等)。主要看第二个字段与第三个字段,第二个字段大写的T话就表示是全局可见的,如果是小写t,那就是隐藏的。而我们就需要只让我们想要暴露出来的函数是T,而其他所有的所有都是隐藏的

nm tf_lib/naive_lib/libtf_rnn.so  | awk -F

" " '{if($2=="T")print $0}'   这么输出出来以后如果只有想要暴露出来的接口,那么大功告成了。


关于问题4:

首先,我们需要写一个使用模型预估的接口。我这边是写了个结构体(包括了,输入输出参数,以及load起来的session)。这样的话,首先我么可以通过本地写一个main函数来进行调用调试代码,这样的话就能排除线上引擎代码的兼容问题进行调试。真正和线上引擎代码融合的时候,写一个TensorFlowFun的类,包含init函数,predict函数。这样线上调用的时候不就爽歪歪了么。


ok,解决方式说完了,我们接下来看具体步骤:


1、安装gcc的时候需要安装.a的版本

原因:tensorflow的c++对gcc版本要求较高,至少需要gcc4.8。在此我们选择了比较新的4.9.4。而且安装的时候需要生成静态库,即/usr/local/gcc-4.9.4/lib64/libstdc++.a


下载方法:

http://mirrors.kernel.org/gnu/gcc/   选择4.9.4版本  解压到任意位置,例如~/gcc-4.9.4

运行 sh ./contrib/download_prerequisites   

  由于有个别文件总是无法自动下载成功。所以先手动下载所有的依赖吧,修改该脚本,直接使用本地的包进行解压,链软连工作。

由于线上引擎的gcc版本不可能为4.9.4这么新,那么我们安装gcc4.9.4的时候需要多版本安装(也就是说安装4.9.4以后对已有gcc版本不会造成任何影响)

mkdir build-gcc-4.9.4

cd build-gcc-4.9.4

进行配置:

../gcc-4.9.4/configure --prefix=/usr/local/gcc-4.9.4/--enable-checking=release --enable-languages=c,c++ --disable-multilib--enable-host-shared

编译gcc

make -j

安装(该步需要root权限)

make install


ok,gcc安装成功,收工

2、安装bazel

需要实现安装jdk1.8

然后下载源码,此处安装的是bazel-0.12.0  在此我们通过复制git上的包地址进行下载,千万注意下载这个包的时候很容易下载出来个半成品。要注意下载数据的完整性,建议可通过迅雷下载,很稳。

记得下载bazel-0.12.0-dist.zip 这个,不要用tar.gz的,太多依赖了。不太清楚具体是因为什么。这个坑蹚了很久,mlgb,一个解压包,mlgb的这么多门门道道,搞什么搞。真是日了吃金坷垃的猪了。用tar.gz的包各种依赖,比如GRPC_JAVA_PLUGIN。而用   bazel-0.12.0-dist.zip真的很顺畅


但是pb应该也要依赖,这个是蹚tar.gz包安装的,就这么装着吧。

protobuf  3.5.1安装  官方包     耗时略长

tar -xvf  protobuf-3.5.1.tar.gz

cd protobuf-3.5.1

sh autogen.sh

./configure --prefix=/usr/local/protobuf

make -j

make install (此不需要root权限)


然后继续安装bazel,发现还有一个问题。我们在此这么指定PROTOC

export PROTOC=/usr/local/protobuf/bin/protoc

我们继续填坑


3、安装anaconda2的python。


ok,所有的依赖都下载结束,接下来就应该配置tensorflow的安装环境了

我们从github上下载tf 1.8的代码然后解压

tensorflow-1.8.0 ./configure





修改情况:

1、安装gcc的时候需要安装.a的版本

configure --prefix=/usr/local/gcc-4.9.4/--enable-checking=release --enable-languages=c,c++ --disable-multilib--enable-host-shared



2、tensorflow/tensorflow.bzl

@@ -264,7 +264,7 @@

name,

srcs=[],

deps=[],

-    linkopts=[],

+    linkopts=['-lrt'],

framework_so=tf_binary_additional_srcs(),

**kwargs):

native.cc_binary(

@@ -1264,7 +1264,7 @@

)


def tf_extension_linkopts():

-  return []  # No extension linkopts

+  return ["-lrt"]  # Noextension link opts

def tf_extension_copts():

return []  # No extension copts






3、/bazel-tensorflow-master/external/local_config_cc/CROSSTOOL

#linker_flag:"-lstdc++"

linker_flag:"-l:libstdc++.a"


4、编译的时候需要指定

tf_cc_binary(

name = "libtf_rnn.so",

srcs =["tf_rnn/tf_rnn.cc",

"tf_rnn/tf_rnn.h",

],

linkshared=1,

linkstatic=1,

deps = [

":cc_ops",

":client_session",

"//tensorflow/core:all_kernels",

"//tensorflow/core:core_cpu_internal",

"//tensorflow/core:framework",

"//tensorflow/core:framework_internal",

"//tensorflow/core:protos_all_cc",

"//tensorflow/core:lib",

"//tensorflow/core:tensorflow",

"//tensorflow/c:checkpoint_reader",

],

linkopts = [

#"-static-libstdc++",

"-Wl,--exclude-libs,ALL",

"-static-libgcc",

]

)


5、编译时候命令:

~/xuhongbin/dep_lib/bazel-0.12.0/output/bazel build --defineframework_shared_object=false  //tensorflow/cc:libtf_rnn.so


6、线上代码使用,切记图的相关信息不能放到master里初始化,然后再fork出进程。必须要放到worker里面初始化。

这种方式部署起来比较麻烦,本人不太建议通过这种方式进行部署,而是推荐通过tf_serving的方式来进行部署。如果有人感兴趣,可加博主的微信进行讨论 395339042。谢谢。还有一些必要的软件,本人已经下载好,可通过邮箱发布,感谢阅读。

你可能感兴趣的:(tensorflow部署方式: 将tensorflow编译成独立的so包,任意引擎调用)