Google在2016年2月开源了TensorFlow Serving,这个组件可以将TensorFlow训练好的模型导出,并部署成可以对外提供预测服务的RESTful/RPC接口。有了这个组件,TensorFlow就可以实现应用机器学习的全流程:从训练模型、调试参数,到打包模型,最后部署服务,名副其实是一个从研究到生产整条流水线都齐备的框架。TensorFlow Serving是一个为生产环境而设计的高性能的机器学习服务系统。它可以同时运行多个大规模深度学习模型,支持模型生命周期管理、算法实验,并可以高效地利用GPU资源,让TensorFlow训练好的模型更快捷方便地投入到实际生产环境。
为什么使用TensorFlow Serving而不是直接启动多个加载了模型的Python进程来提供线上服务?因为重复引入TensorFlow并加载模型的Python进程浪费资源并且运行效率不高。而且TensorFlow本身有一些限制导致并不是所有时候都能启动多个进程。TensorFlow默认会使用尽可能多的GPU并且占用所使用的GPU。因此如果有一个TensorFlow进程正在运行,可能导致其他TensorFlow进程无法启动。虽然可以指定程序使用特定的GPU,但是进程的数量也受到GPU数量的限制,总体来说不利于分布式部署。而TensorFlow Serving提供了一个高效的分布式解决方案。当新数据可用或改进模型时,加载并迭代模型是很常见的。TensorFlow Serving能够实现模型生命周期管理,它能自动检测并加载最新模型或回退到上一个模型,非常适用于高频迭代场景。
具体介绍可参考官网
目前TF Serving有Docker、APT(二级制安装)和源码编译三种方式,但考虑实际的生产环境项目部署和简单性,推荐使用Docker方式,这也是让TensorFlow服务支持GPU的最简单方法,因此首先介绍Docker的安装方法。(后续会介绍源码及APT安装的方式)
系列文章目录
(一)TensorFlow Serving系列之安装及调用方法
(二)TensorFlow Serving系列之导出自己的训练模型
(三)TensorFlow Serving系列之客户端gRPC调用
(四)TensorFlow Serving系列之gRPC基本知识
(五)TensorFlow Serving系列之源码安装服务
(六)TensorFlow Serving系列之多模型多版本控制
===================== 20200529更新 =============================
docker升级到19.03以后,nvidia将提供原生的显卡支持,只需要安装nvidia-container-toolkit工具包即可,不再像使用nvidia-docker/2那样复杂配置,而且不支持用docker-compose
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \
sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
#restart docker
sudo systemctl restart docker
============= 下边的安装方法可以不用 ==============
为了使服务能调用底层GPU,需要安装nvidia-docker,否则普通的docker是不能使用GPU的
如图所示,最下层是有NVIDIA GPU的服务器,再往上是操作系统和CUDA驱动,再往上就是不同的docker容器,里边会包含CUDA Toolkit和CUDNN,比如可以启动一个docker来跑Tensorflow Serving 1.12.0,它使用的是CUDA 9.0和CUDNN 7;也可以启动一个docker来跑Tensorflow Serving 1.6.0,它使用的是CUDA 8。
nvidia-docker的安装方法如下,适用于ubuntu系统(官方链接)
# 如果之前安装过nvidia-docker 1.0,那么我们需要卸载它以及已经创建的容器(container)
docker volume ls -q -f driver=nvidia-docker | xargs -r -I{} -n1 docker ps -q -a -f volume={} | xargs -r docker rm -f
sudo apt-get purge -y nvidia-docker
# 更新源
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | \
sudo apt-key add -
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \
sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update
# 安装nvidia-docker2并且充钱docker的daemon进程
sudo apt-get install -y nvidia-docker2
sudo pkill -SIGHUP dockerd
# 可以跳过,但是建议测试一下安装是否有问题,如果出现nvidia-smi的结果,那么说明安装成功
docker run --runtime=nvidia --rm nvidia/cuda:9.0-base nvidia-smi
默认的docker需要sudo权限才能运行,为了让当前用户可以运行docker,需要把当前用户加到docker组里:
sudo gpasswd -a $USER docker #将登陆用户加入到docker用户组中
newgrp docker #更新用户组
在docker hub中下载TFS镜像(镜像列表),镜像后缀有如下四种:
:latest
:安装TensorFlow Serving二进制文件的最小镜像。:latest-gpu
:安装了TensorFlow Serving二进制文件并可以在GPU上使用的最小镜像。:latest-devel
-包括要开发的所有源/依赖项/工具链,以及在CPU上运行的已编译二进制文件:latest-devel-gpu
-包括要开发的所有源代码依赖项/工具链(cuda9 / cudnn7),以及可在NVIDIA GPU上运行的已编译二进制文件。
因为要在GPU服务器上运行,所以这里下拉支持GPU的TFS镜像,可以指定版本,注意选择的时候要查看支持的CUDA版本及驱动版本与自己的显卡是否一致,比如,我们查看tensorflow / serving : latest-gpu的信息时,里边有一条
要求cuda>=10.0 brand=tesla,driver>=384,driver<385,那么如何查看我们显卡的驱动版本呢,输入nvidia-smi即可输出
如果自己显卡驱动不满足需求,运行时会报错requirement error: unsatisfied condition: brand = tesla\\\\n\\\"\"": unknown,由于我电脑上装的是9.0版本的cuda,因此拉了1.12.3-gpu版本的镜像
docker pull tensorflow/serving:1.12.3-gpu
TF Serving客户端和服务端的通信方式有两种分别是gRPC和RESTfull API,接下来对这两种通信的实现方法进行介绍
下载示例程序
mkdir -p /tmp/tfserving
cd /tmp/tfserving
git clone https://github.com/tensorflow/serving
运行TensorFlow Serving容器,将其指向此模型并打开REST API端口(8501):
docker run --runtime=nvidia -p 8501:8501 --mount type=bind,source=$(pwd)/serving/tensorflow_serving/servables/tensorflow/testdata/saved_model_half_plus_two_gpu,target=/models/half_plus_two -e MODEL_NAME=half_plus_two -t tensorflow/serving:1.12.3-gpu &
如果让其作为服务方式运行,可以加上参数--restart always让其自动重启。
docker run --runtime=nvidia --restart always -p 8501:8501 --mount type=bind,source=$(pwd)/serving/tensorflow_serving/servables/tensorflow/testdata/saved_model_half_plus_two_gpu,target=/models/half_plus_two -e MODEL_NAME=half_plus_two -t tensorflow/serving:1.12.3-gpu &
如果要指定GPU,则添加-e NVIDIA_VISIBLE_DEVICES=0选项
运行docker容器,并指定用nvidia-docker,表示调用GPU,启动TensorFlow服务模型,绑定REST API端口8501(gRPC端口为8500),并映射出来到宿主机的8501端口,使外部可以访问,当然也可以设置成其它端口,如1234,只需要指定-p 1234:8501即可。通过mount将我们所需的模型从主机(source)映射到容器中预期模型的位置(target)。我们还将模型的名称作为环境变量传递,这在查询模型时非常重要。提示:在查询模型之前,请务必等到看到如下所示的消息,表明服务器已准备好接收请求:
2018-07-27 00:07:20.773693: I tensorflow_serving/model_servers/main.cc:333]
Exporting HTTP/REST API at:localhost:8501 ...
如果运行时报错则是因为cuda版本问题,我服务器上默认调用的是9.0,但显示要求需要10.1版本,因此安装并切换10.1版本的cuda,切换方法参考《Linux之cuda、cudnn版本切换》
新打开一个终端,模拟客户端查询
curl -d '{"instances": [1.0, 2.0, 5.0]}' \
-X POST http://localhost:8501/v1/models/half_plus_two:predict
会有返回值
{ "predictions": [2.5, 3.0, 4.5] }
如果是在没有GPU的计算机上运行将会报错
Cannot assign a device for operation 'a': Operation was explicitly assigned to /device:GPU:0
接下来采用手写体数字识别模型的例子,来说明tf服务的过程
首先训练一个模型,如果要用GPU训练,则需要修改mnist_saved_model.py文件,在头文件处新增如下代码
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
创建mnist文件夹,然后运行脚本,将结果文件保存在mnist文件夹中
cd serving
mkdir mnist
python tensorflow_serving/example/mnist_saved_model.py mnist
输出如下
Training model...
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting /tmp/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Extracting /tmp/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting /tmp/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting /tmp/t10k-labels-idx1-ubyte.gz
2019-09-18 11:24:11.010872: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2019-09-18 11:24:11.614926: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1392] Found device 0 with properties:
name: TITAN V major: 7 minor: 0 memoryClockRate(GHz): 1.455
pciBusID: 0000:06:00.0
totalMemory: 11.78GiB freeMemory: 11.36GiB
2019-09-18 11:24:11.614978: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1471] Adding visible gpu devices: 0
2019-09-18 11:24:15.718308: I tensorflow/core/common_runtime/gpu/gpu_device.cc:952] Device interconnect StreamExecutor with strength 1 edge matrix:
2019-09-18 11:24:15.718387: I tensorflow/core/common_runtime/gpu/gpu_device.cc:958] 0
2019-09-18 11:24:15.718412: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971] 0: N
2019-09-18 11:24:15.721216: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1084] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 10974 MB memory) -> physical GPU (device: 0, name: TITAN V, pci bus id: 0000:06:00.0, compute capability: 7.0)
training accuracy 0.9092
Done training!
Exporting trained model to b'mnist/1'
Done exporting!
训练完之后会在mnist/1文件夹下生成模型文件
mnist
└── 1
├── saved_model.pb
└── variables
├── variables.data-00000-of-00001
└── variables.index
其中,saved_model.pb是序列化的tensorflow::SaveModel,它包括模型的一个或多个图形定义,以及模型的元数据(如签名);variables是包含图形的序列化变量的文件。
运行服务
CPU版
docker run -p 8500:8500 --mount type=bind,source=$(pwd)/mnist,target=/models/mnist -e MODEL_NAME=mnist -t tensorflow/serving
GPU版
docker run --runtime=nvidia -p 8500:8500 --mount type=bind,source=$(pwd)/mnist,target=/models/mnist -e MODEL_NAME=mnist -t tensorflow/serving:1.12.3-gpu
客户端验证
python tensorflow_serving/example/mnist_client.py --num_tests=1000 --server=127.0.0.1:8500
输出如下
Extracting /tmp/train-images-idx3-ubyte.gz
Extracting /tmp/train-labels-idx1-ubyte.gz
Extracting /tmp/t10k-images-idx3-ubyte.gz
Extracting /tmp/t10k-labels-idx1-ubyte.gz
W0918 12:49:20.635714 140358698280704 lazy_loader.py:50]
The TensorFlow contrib module will not be included in TensorFlow 2.0.
For more information, please see:
* https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md
* https://github.com/tensorflow/addons
* https://github.com/tensorflow/io (for I/O related ops)
If you depend on functionality not listed there, please file an issue.
........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
Inference error rate: 10.4%