Jetson TX1上安装Tensorflow Serving遇到的问题总结

2021-08-21 13:43:19 浏览数 (1)

本文的目的是分享在TX1上安装Tensorflow Serving时遇到的主要问题,避免重复踩坑。

Jetson TX1是一块带GPU的板子,预装了ubuntu系统,ARM架构,详情可参考NVidia官网。对于Tensorflow训练出来的模型,工程部署一般都采用Tensorflow Serving。目前在网络上暂时没找到Jetson系列板子上成功安装Tensorflow Serving的案例,而本人在安装过程中遇到的很多问题都搜不到解决方法,只能自己摸索,最终成功安装并运行。当前TX1环境是用的JetPack 3.2.1(CUDA9 cuDNN7)。

这里安装步骤主要参考官网的指导:https://www.tensorflow.org/serving/setup,但有些细节会不一样,比如安装命令是bazel build -c opt --config=cuda tensorflow_serving/... --action_env=PYTHON_BIN_PATH=/usr/bin/python,这里config=cuda是必要的,否则即使在bazel.rc中指定了gpu,生成出的Tensorflow Serving也不能用GPU。

原本想尝试交叉编译的,因为板子上编译很慢,但考虑板子上编译坑会少点,所以这里只尝试了在板子上编译的方式。安装遇到的问题这里只列出主要的。

问题1:

ERROR: no such target '@org_tensorflow//third_party/gpus/crosstool:crosstool': target 'crosstool' not declared in package 'third_party/gpus/crosstool' defined by /data/rootcache/bazel/_bazel_root/46688ad2577b25fcaed4521437622fa6/external/org_tensorflow/third_party/gpus/crosstool/BUILD

分析与解决:

这个报错的含义是:crosstool的定义找不到,而根据bazel.rc中的配置,crosstool期望在下面列出的那个BUILD文件里定义,而那个BUILD文件里没定义crosstool。

事实上,这个BUILD文件是空的。网上能搜到的解决方案比较多,下面的方案至少是验证可行的

(1) 修改serving/tools/bazel.rc文件,将@org_tensorflow//third_party/gpus/crosstool替换成@local_config_cuda//crosstool:toolchain。

(2) 执行bazel clean --expunge && export TF_NEED_CUDA=1

(3) 再执行bazel query 'kind(rule, @local_config_cuda//...)',这样相当于在定义local_config_cuda这个rule

问题2:

磁盘满

分析与解决:

嵌入式设备存储往往并不大,而Tensorflow安装过程需要的存储空间非常大,很容易遇到磁盘空间不够。目前能想到的办法就是用NFS来扩展存储空间,至于NFS具体操作不难搜到。这里使用NFS时会出现如下告警

root@tegra-ubuntu:/data/serving# /data/bazel/output/bazel build -c opt --config=cuda tensorflow_serving/... --action_env=PYTHON_BIN_PATH=/usr/bin/python

WARNING: Output base '/data/rootcache/bazel/_bazel_root/46688ad2577b25fcaed4521437622fa6' is on NFS. This may lead to surprising failures and undetermined behavior.

曾经在网上搜过这个问题,但能找到的答复大意是“既然明确提示了,就不要用NFS”。因为没有别的选择,所以我还是用的NFS,并安装成功了,且能跑通GoogLeNet和Resnet,说明NFS并非不能用。实际可以在编译完成后,把需要的可执行文件保留,其它中间文件都可以删掉,这样就不需要太多存储(放NFS上运行也没问题,就是加载会很慢)。

问题3:

提示大意是编译失败,cc1被kill了

分析与解决:

其实被这个问题难到了一段时间,后来偶然在串口终端上发现了内存不足并kill进程的打印,于是立即就明白了问题的原因,就是内存不足导致编译进程被kill。

(1) 一般直接重试就好,会继续编译,而不是老失败在一个地方

(2) 如果经常编译没多久就出现这个问题,说明内存很不够用,可以考虑加swap,我加的2GB(TX1本身有4GB的内存)。这个加太多也不好,因为大量的swap IO会导致编译很慢

(3) 多次尝试发现这个问题都出在同一个地方时,可以在bazel编译时加个--jobs=2的选项,来限制同时编译任务数量,默认是用CPU核数(TX1上是4)。建议挺过那几个比较耗内存的编译任务后,再停掉编译并重新用之前的方式编译,这样会快一些,不然一天一夜都编译不完。

问题4:

找不到cudnn的报错;提示找不到nccl

分析与解决:

这个两个问题其实是独立的,但比较类似,所以放一起了。cudnn的问题只需要编译前执行下面的命令

export CUDNN_INSTALL_PATH=/usr/lib/aarch64-linux-gnu

nccl的问题需要先安装nccl,然后在编译前执行下面的命令

export TF_NCCL_VERSION='1.3'

export NCCL_INSTALL_PATH=/data/nccl/build

需要说明的是,安装nccl只能用源码安装,因为安装包没有编译aarch64架构的。源码可从github上下载。

问题5:

unrecognized command line option '-mfpu=neon'

分析与解决:

修改报错时提示的那个BUILD文件,将里面包含'-mfpu=neon'的代码行都删掉。

这个问题不难找到解决方法,只是主要针对gcc而不是bazel的,所以这里还是给出针对bazel的解决方法。

问题6:

ERROR: /data/serving/tensorflow_serving/model_servers/BUILD:308:1: Linking of rule '//tensorflow_serving/model_servers:tensorflow_model_server' failed (Exit 1)

bazel-out/arm-opt/bin/external/aws/libaws.a(ClientConfiguration.o): In function `Aws::Client::ComputeUserAgentString()':

ClientConfiguration.cpp:(.text._ZN3Aws6ClientL22ComputeUserAgentStringEv 0x170): undefined reference to `Aws::OSVersionInfo::ComputeOSVersionString[abi:cxx11]()'

bazel-out/arm-opt/bin/external/aws/libaws.a(DateTimeCommon.o): In function `Aws::Utils::DateTime::ToLocalTimeString[abi:cxx11](char const*) const':

大量AWS相关的错误

分析与解决:

先给解决方法:修改_bazel_root/46688ad2577b25fcaed4521437622fa6/external/aws/BUILD.bazel,把conditions:default后面的内容换成glob([ "aws-cpp-sdk-core/source/platform/linux-shared/*.cpp", ])。

这里也给下分析的过程。

分析直接原因,是链接tensorflow_model_server用到了libaws.a,此库里确实没定义ComputeOSVersionString,期望要定义。

进一步分析,libaws.a对应的目标文件在目录_bazel_root/46688ad2577b25fcaed4521437622fa6/execroot/tf_serving/bazel-out/arm-opt/bin/external/aws/_objs/aws/external/aws/aws-cpp-sdk-core/source中,而此目录中找不到OSVersionInfo.o。而ComputeOSVersionString这个函数就是在OSVersionInfo.cpp中定义的。

再分析进一步原因,对于aws组件,aws官方提供的是cmake编译,编译只输出动态库,不会出现libaws.a。这个.a文件其实是_bazel_root/46688ad2577b25fcaed4521437622fa6/external/aws/BUILD.bazel控制生成的。分析BUILD.bazel源码发现,有对平台的判断,针对不同平台会拷贝不同目录的代码进行编译,而平台里不包含aarch64相关的,从而不会拷相应的代码。这些没拷的代码就包含了OSVersionInfo.cpp,这就是问题的根因。

问题7:

链接tensorflow_model_server、testing等目标时,出现 external symbol `__stack_chk_guard@@GLIBC_2.17' can not be used when making a shared object; recompile with -fPIC 的错误

ERROR: /data/serving/tensorflow_serving/util/net_http/socket/testing/BUILD:21:1: Linking of rule '//tensorflow_serving/util/net_http/socket/testing:ev_fetch_client' failed (Exit 1)

/usr/bin/ld: bazel-out/arm-opt/genfiles/external/com_github_libevent_libevent/libevent/lib/libevent.a(buffer.o): relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `__stack_chk_guard@@GLIBC_2.17' can not be used when making a shared object; recompile with -fPIC

/usr/bin/ld: bazel-out/arm-opt/genfiles/external/com_github_libevent_libevent/libevent/lib/libevent.a(buffer.o)(.text 0x14): unresolvable R_AARCH64_ADR_PREL_PG_HI21 relocation against symbol `__stack_chk_guard@@GLIBC_2.17'

/usr/bin/ld: final link failed: Bad value

分析与解决:

这个问题是解决时间最长的。尝试过各种加-fPIC,还尝试fno-stack-protector等方法,发现都没用。后来发现,其实不是没用,而是没生效。因为一重新编译,之前改的Makefile之类的就又被刷回来了。

这时大致解释一下bazel编译的运作机制,在执行bazel编译后,会执行BUILD文件里定义的目标;这里我们关注的是生成libevent.a的目标,即third_party/libevent.BUILD中的libevent目标;这个目标里会执行多条shell命令,大致是先将libevent工程拷到/tmp/libevent.xxx目录,然后再在/tmp/libevent.xxx目录里编译,并将结果install到bazel-genfiles/external/com_github_libevent_libevent/libevent目录,最后删除/tmp/libevent.xxx目录;在libevent编译时,Makefile等文件是动态生成的,这也是修改Makefile等文件不生效的原因;bazel在执行每个目标(如libevent)前,会先把此目标的输出(如libevent.a)都删除,然后在执行后,再检测输出的文件是否存在(如libevent.a),如果不存在是会报错的。

所以解决方法如下

(1) 修改third_party/libevent.BUILD中的libevent目标,删除删临时文件的那一行,避免编译完后中间文件被删。然后重编译,当然,仍会失败。

(2) 进到临时文件的目录,在/tmp/libevent.*这样的目录中,修改Makefile,找到CFLAGS的定义,追加一个-fPIC选项,再make install

(3) 此时,生成的文件会输出到bazel-genfiles/external/com_github_libevent_libevent/libevent目录,通过cp -r bazel-genfiles/external/com_github_libevent_libevent/libevent /data命令将输出文件暂存一下,避免下次尝试编译时被删

(4) 然后再次修改third_party/libevent.BUILD中的libevent目标,将cmd全删掉,再加一条cp -r命令将暂存的libevent内容拷回原输出目录(注意用绝对路径),再重新编译就成功了

当然这里也可能有更简单的方法,但这里重点还是弄清楚问题原因和bazel的运作机制。

0 人点赞