Press "Enter" to skip to content

Tensorflow Serving踩坑指南

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

介绍

 

Tensorflow Serving
是Google开源的机器学习平台 Tensorflow
生态的一部分,它的功能是将训练好的模型跑起来,提供接口给其他服务调用,以便使用模型进行推理预测。

 

由于tf-serving项目的文档极度缺失,以至于不得不使用本人擅长的野路子面向偶然/面向源代码/面向issue进行xjb尝试,因此整理了本文,希望踩坑经验对您也有所帮助。

 

项目状况

 

代码/架构

 

项目的开源代码都在 https://github.com/tensorflow/serving
项目是由C++开发,它的主要玩法就是在Tensorflow外面包了一层,加了一些访问接口和模型加载处理的东西,详细架构可以去看 官方文档的描述

 

运行环境

 

在docker容器里跑的,理论上来说也可以把二进制抠出来用,但官方不建议。

 

版本

 

目前 master
分支的代码是2.x最新,旧版本的有其独立分支如 r2.0
, r1.15
等。

 

tensorflow 1.x产出的savedmodel基本上可以在2.x的serving上使用,具体情况可以看官方 关于SavedModel的RPC

 

开发

 

当你需要魔改项目的时候,可以用 tools/run_in_docker.sh
这个脚本进行构建和运行,它会把你本地的目录挂进容器里去跑bazel,这样不用每次从头进行构建,因为他编译一次实在太慢,同时测试运行也可以用这个脚本,具体用法请面向源代码编程。

 

Tips

 

如果你在运行上述脚本中遇到了 no such package '@zlib//
这种报错,可能需要魔跑到bazel目录下手工改个东西,详细参考 Building from source error: The repository ‘@zlib’ could not be resolved

 

项目构建

 

通常情况下,为了适配你自己的硬件或你自己魔改了代码,就需要重新构建docker镜像。

 

这个项目采用启动一个docker容器,在里面跑bazel的方式进行构建,相关Dockerfile在 tensorflow_serving/tools/docker

 

Dockerfile分为两种,带 .devel
的后缀的构建结果是仅有tensorflow_serving二进制的基础镜像,而不带此后缀的是在前者的基础之上配置了模型目录和启动脚本的可供生产使用的镜像。

 

如果你需要使用gpu或者intel的mkl库,就需要用带 gpu
mkl
字样的Dockerfile进行构建,以便能够使用相应的依赖库。

 

构建Tips

 

跑构建时需要注意这些事情:

 

 

    1. 如果你自己改了源码,那幺构建时

Dockerfile.devel

    1. 这个文件是需要修改的,因为它默认是从github仓库中把代码拉下来跑构建,没有用你本地的代码。

 

    1. 为了达到最佳的性能,需要调整TF_SERVING_BUILD_OPTIONS这个参数,加一些构建参数,设置CPU支持的指令集之类的,详细参考

building-an-optimized-serving-binary

    1. 如果内存不够大之类的,也请设置TF_SERVING_BUILD_OPTIONS,还是看上面那篇文档。

 

    1. 构建请自觉科学上网,不然有一些东西会拉不下来。请注意是需要给docker容器加代理,给你宿主机加代理是没啥用的。

 

 

部署

 

主要可以参考官方的docker部署的文档 TensorFlow Serving with Docker

 

我主要设置了这些参数,供参考

 

tensorflow_model_server --port=8500 --rest_api_port=8501 --enable_batching=true --model_config_file_poll_wait_seconds=1000 --model_config_file=/data/models.conf --monitoring_config_file=/data/monitor.conf 
--batching_parameters_file=/data/batching.conf --tensorflow_intra_op_parallelism=9 --tensorflow_inter_op_parallelism=9

 

其中几个配置文件的内容

 

batching.conf

 

这个用来设置batch的大小和线程数什幺的,做性能优化的时候可以调整

 

max_batch_size { value: 512 }
batch_timeout_micros { value: 0 }
max_enqueued_batches { value: 10 }
num_batch_threads { value: 10 }

 

monitor.conf

 

开启了metrics,可以用prometheus收集数据,但是现在给的信息没有太大的用处,属于鸡肋功能

 

prometheus_config: {
enable: true,
    path: "/metrics"
}

 

models.conf

 

用来配置加载哪些模型

 

model_config_list: {
    config: {
    name:"my_model"
    base_path:"/data/models/my_model"
    model_platform: "tensorflow"
    model_version_policy: {all{}}
  },
}

 

调用

 

HTTP REST接口

 

直接参考官方文档即可

 

gRPC接口

 

如果你用python来调,官方项目里已经有生成好的客户端可用直接用了,但如果你像我一样不幸用go,或者其他语言。就比较麻烦,需要根据他项目里的proto文件用 protoc
编译出你要用的语言的客户端,其中有用的其实只有 api目录下的proto
生成出来的东西,但它依赖了一堆其他目录下的proto,甚至还有隔壁tensorflow仓库里的proto。需要按照以下步骤进行构建。

 

 

clone两个仓库到本地: serving
tensorflow
两个项目的代码。注意分支要对应。比如你想用1.15版本,那幺两个仓库都要用 r1.15
分支。

 

然后用protoc进行编译,具体可以参考 我糊出来的这个可能能跑的构建脚本

 

把构建出来的一坨东西放到你的项目下使用,grpc具体用法参考gRPC文档 Basics tutorial – Creating a stub

 

 

其他语言的使用步骤类似。

 

如果需要压测gRPC接口,请参考旧文 用 ghz 对 gRPC 服务进行压测

 

疑难杂症

 

内存泄漏

 

这里有个祖传的issue好几年了也没关
,大家遇到各种内存问题,可官方人员就是没法复现,也不给修。但我们也遇到了,一定是我太菜。迄今为止,遇到过两种内存泄漏。

 

1.随着请求数增加,内存持续增加

 

似乎他们是非batch的情况下有bug,所以启动参数加上 --enable_batching=true
可解

 

2.模型版本热更新时,某个版本已经卸载,但内存没有释放

 

经过反复尝试,发现把malloc换成jemalloc可以解决,我也不知道为啥,but it works.

 

魔改Dockerfile后重新构建即可,加上

 

RUN apt-get update && apt-get install -y libjemalloc-dev
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1

 

模型热更新的时候有抖动(请求耗时有毛刺)

 

 

在模型中增加warmup,加上之后,serving会在挂载模型前进行预热。参考文档 saved_model_warmup

 

模型配置里开启多版本同时在线。我目前的做法是配置文件里修改 model_version_policy: {all{}}
,即加载目录下所有版本(参考上面部署一节的 models.conf
配置文件),使用另外的脚本来控制目录下的版本更新(就是把新版本放进去,旧版本删掉),保证目录中在更新时,同时至少存在两个版本(已在线的原版本,和新版本)的模型,这样能在版本切换的过程更加平滑。

 

 

HTTP接口和gRPC接口的行为不一致

 

你可能注意到在调用http的predict接口的时候,少几个多几个tensor也不会报错,但通过gRPC调用时,就会报出来,这是因为里面的处理逻辑不同,http接口对这个情况对缺失tensor进行了填充,但gRPC里没有,因此在通过gRPC调用前,我们需要把tensor补全才能成功调用。

 

性能问题

 

我们的场景是推荐系统的在线预估,对延时要求比较高,目前采用了以下方法降低延时,有一定的效果,但还在继续尝试寻找更多的优化方案。

 

 

    1. 优化模型结构(可以借助tensorboard来进行分析)

 

    1. 将请求切片,分散到多个实例上并发计算(这样就可以加机器了)。仔细调整每个batch的大小和切片数,可能会找到一个效果比较好的平衡点

 

    1. 调整

tensorflow_intra_op_parallelism

tensorflow_inter_op_parallelism

    1. ,我们都设成了和核数一致,效果还行

 

    1. 如果是cpu跑,不要用mkl版,因为它跑得比普通的cpu版要慢。解释在这里

 

build with “–config=mkl” infernece-latency become longer(CPU)

 

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注