如何使用tensorflowServing进行模型部署

TensorFlow Serving是用于机器学习的灵活,高性能的服务系统,针对生产环境而设计。 TensorFlow服务 可以轻松部署新算法和实验,同时保持不变服务器体系结构和API。TensorFlow Serving开箱即用 与TensorFlow模型集成,但可以轻松扩展以服务于其他 模型类型

关键概念

Key Conception

serving_architecture

Loaders

Loaders管理Servables的生命周期。Loader API 是一种支持独立于特定算法,数据或产品用例的通用基础架构。具体来说,Loaders标准化了用于加载和卸载Servable的API。

Sources

Sources 是可以寻找和提供 Servables 的模块,每个 Source 提供了0个或者多个Servable streams,对于每个Servable stream,Source 都会提供一个Loader实例。

Managers

管理 Servable 的整个的生命周期,包括:

  • loading Servables
  • serving Servables
  • unloading Servables

Managers监听Sources并跟踪所有版本。Managers尝试满足、响应Sources的请求,但是如果所请求的资源不可用,可能会拒绝加载相应版本。Managers也可以推迟“卸载”。例如,Managers可能会等待到较新的版本完成加载之后再卸载(基于保证始终至少加载一个版本的策略)。

Servables

Servable是Tensorflow Serving的核心抽象,是客户端用于执行计算的基础对象,其大小和粒度是灵活的。Tensorflow serving可以在单个实例的生命周期内处理一个或多个版本的Servable,这样既可以随时加载新的算法配置,权重或其他数据;也能够同时加载多个版本的Servable,支持逐步发布和实验。由此产生另外一个概念:Servable stream,即是指Servable的版本序列,按版本号递增排序。Tensorflow Serving 将 model 表示为一个或者多个Servables,一个Servable可能对应着模型的一部分,例如,a large lookup table 可以被许多 Tensorflow Serving 共享。另外,Servable不管理自己的生命周期

Core

Tensorflow Serving core 负责管理Servables的Lifecycle和metrics,将Servables和loaders看作黑箱(opaque objects)。

广义地说:

  1. Sources create Loaders for Servable Versions.
  2. Loaders are sent as Aspired Versions to the Manager, which loads and serves them to client requests.

例子:

  1. Source 为指定的服务(磁盘中检测模型权重的新版本)创建Loader,Loader里包含了服务所需要的元数据(模型);
  2. Source 使用回调函数通知 Manager 的 Aspired Version(Servable version的集合);
  3. Manager 根据配置的Version Policy决定下一步的操作(是否 unload 之前的Servable,或者 load 新的Servable);
  4. 如果 Manager 判定是操作安全的,就会给 Loader 要求的resource并让 Loader 加载新的版本;
  5. 客户端向 Manager 请求服务,可以指定服务版本或者只是请求最新的版本。Manager 返回服务端的处理结果;

Extensibility

Tensorflow Serving提供了几个可扩展的entry point,用户可以在其中添加自定义功能。

Version Policy

Version Policy(版本策略)可以指定单个Servable stream中的版本加载和卸载顺序。它包括Availability Preserving Policy(在卸载旧版本之前加载并准备好新版本)和Resource Preserving Policy(在加载新版本之前先卸载旧版本)。

Source

New Sources可以支持新的文件系统,云产品和算法后端,这主要和创建自定义Source有关。

Loaders

Loaers是添加算法、数据后端的扩展点。Tensorflow就是这样一种算法后端。例如,用户将实现一个新的Loader,以便对新的Servable机器学习模型实例的访问和卸载。

Batcher

将多个请求批处理为单个请求可以显着降低计算成本,尤其是在存在诸如GPU的硬件加速器的情况下。Tensorflow Serving包括一个请求批处理小部件,它允许客户端轻松地将请求中特定类型的计算进行批量处理。

系统环境搭建

系统及软硬件说明

系统:Ubuntu16.04

软件

  • 驱动 450.23.05
  • cuda 11.1
  • cudnn 8.0.5
  • tensorflow nightly-gpu(2.4)
  • python 3.7.9

硬件

  • RTX 3090

导出Keras模型

将keras中以model.save(filepath)保存的模型h5文件,转为tensorflow的xx格式,加载模型时,使用✔️tf.keras.models.load_model而不是❌keras.models.load_model

1
2
3
4
5
6
7
8
from keras import backend as K
from keras.models import load_model
import tensorflow as tf

# 首先使用tf.keras的load_model来导入模型h5文件
model_path = 'v3_resnet50_unet.h5'
model = tf.keras.models.load_model(model_path, custom_objects=dependencies)
model.save('deploy/tfs/0', save_format='tf') # 导出tf格式的模型文件

导出之后,有以下目录结构

image-20210205165552193

导出之后,使用以下命令查看模型的signature、input、output,后续客户端调用需要这些信息。

1
saved_model_cli show --dir tfs/0/ --all
1
2
3
4
5
6
7
8
9
10
11
12
signature_def['serving_default']:
The given SavedModel SignatureDef contains the following input(s):
inputs['input_1'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 512, 1024, 3)
name: serving_default_input_1:0
The given SavedModel SignatureDef contains the following output(s):
outputs['activation_49'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 524288, 1)
name: StatefulPartitionedCall:0
Method name is: tensorflow/serving/predict

以上可以确定,signature、input、output分别为:serving_default,input_1,activation_49

saved_model_cli为tensorflow的python工具包,位于tensorflow/python/tools/saved_model_cli.py下,一般安装了tensorflow,可以直接找到该命令。

Docker部署模型

拉取tfs的docker镜像

1.安装docker

安装过程参考官方安装文档Install Docker Engine on Ubuntu

2.安装nvidia-docker

在docker上安装nvidia插件,以便使得应用在GPU上运行,安装过程参考:Installation Guide

3.拉取tfs镜像

docker官网上包含不同版本的tfs镜像,根据需求需要版本,使用以下命令拉取tfs镜像

1
sudo docker pull tensorflow/serving:nightly-gpu

启动tfs容器

使用以下命令启动tfs容器

1
2
3
4
5
sudo nvidia-docker run -p 8500:8500  \
-v "[path]/tfs:/models/resnet50_unet" \
-e MODEL_NAME=resnet50_unet \
-e CUDA_VISIBLE_DEVICES=1 \
-t 9e73a1470b72&

其中9e73a1470b72为tfs镜像的id,可通过docker image ls查看

tfs容器可用参数解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
--port = 8500                     用于侦听gRPC API的端口
--rest_api_port = 0 用于侦听HTTP / REST API的端口。如果设置为零,将不会导出HTTP
/ REST API。此端口必须与--port中指定的端口不同。
--rest_api_num_threads = 160 用于HTTP / REST API处理的线程数。如果未设置,
将根据CPU数量自动设置。
--rest_api_timeout_in_ms = 30000 HTTP / REST API调用超时。
--enable_batching = false bool 启用批处理
--batching_parameters_file =“” 字符串如果非空,请从提供的文件名读取ascii BatchingParameters
protobuf,并使用包含的值代替默认值。
--model_config_file =“” 字符串如果非空,请从提供的文件名读取ascii ModelServerConfig
协议,然后在该文件中提供模型。此配置文件可用于指定要使用的
多个模型以及其他高级参数,包括非默认版本策略。
(如果使用了--model_name和--model_base_path,则将被忽略。)
--model_name =“ default” 模型的字符串名称(如果设置了--model_config_file标志,则忽略
--model_base_path =“” 导出的字符串路径(如果设置了--model_config_file标志,
则忽略该字符串,否则为必需)
--file_system_poll_wait_seconds = 1 以秒为单位的两次新模型版本的文件系统每次轮询之间的间隔
--flush_filesystem_caches=true bool 如果为true(默认值),则在所有可服务对象的初始加载之后以及
随后的每个可服务对象重新加载之后(如果加载线程数为1),
将刷新文件系统缓存。如果在加载可服务对象之后访问模型文件,
则可以减少模型服务器的内存消耗,并以潜在的高速缓存未命中为 代价。
--tensorflow_session_parallelism=0 用于运行Tensorflow会话的线程数。默认情况下自动配置。请注意,
如果--platform_config_file为非空,则将忽略此选项。
--ssl_config_file =“” 字符串如果非空,请从提供的文件名读取ascii SSLConfig协议
并设置安全的gRPC通道
--platform_config_file =“” 字符串如果非空,请从提供的文件名读取ascii PlatformConfigMap
protobuf,然后使用该平台配置而不是Tensorflow平台。
(如果使用,则--enable_batching将被忽略。)
--per_process_gpu_memory_fraction=0.00 float每个进程占用GPU内存空间的分数,
该值介于0.0和1.0之间(默认值为0.0)。如果为1.0,则服务
器将在服务器启动时分配所有内存;如果为0.0,
则Tensorflow将自动选择一个值。
--saved_model_tags =“ serve” 字符串对应于要从SavedModel加载的元图def的逗号分隔的标记集。
--grpc_channel_arguments =“” 字符串要传递给grpc服务器的参数的逗号分隔列表。
(例如grpc.max_connection_age_ms = 2000)
--enable_model_warmup = true bool 启用模型预热,该预热在加载时触发延迟初始化(例如TF优化),
以减少第一个请求的延迟。
--version = false bool 显示版本

客户端的编写

基于Python编写客户端时,需要安装tensorflow_serving、grpc库包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import cv2
import grpc
import numpy as np
import os.path as ops
import tensorflow as tf

from tensorflow_serving.apis import predict_pb2, prediction_service_pb2_grpc

def request_server_grpc(img_resized, server_url):
'''
用于向TensorFlow Serving服务请求推理结果的函数。
:param img_resized: 经过预处理的待推理图片数组,numpy array,shape:(h, w, 3)
:param server_url: TensorFlow Serving的地址加端口,str,如:'0.0.0.0:8500'
:return: 模型返回的结果数组,numpy array
'''
# Request.
channel = grpc.insecure_channel(server_url)
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
request = predict_pb2.PredictRequest()
request.model_spec.name = "resnet50_unet" # 模型名称,启动容器命令的model_name参数
request.model_spec.signature_name = "serving_default" # 签名名称,刚才叫你记下来的
# "input_1"是你导出模型时设置的输入名称,刚才叫你记下来的
request.inputs["input_1"].CopyFrom(
tf.make_tensor_proto(img_resized, shape=[1, ] + list(img_resized.shape)))
# print(request)

response = stub.Predict(request, 5.0) # 5 secs timeout
return np.asarray(response.outputs["activation_49"].float_val) # fc2为输出名称,刚才叫你记下来的

if __name__=='__main__':
img=cv2.imread([imgpath_xx],cv2.COLOR_BGR2RGB)

#预处理
res_image=cv2.resize(img,(1024,512))
res_image = res_image / 255
res_image=res_image.astype('float32')

#向tfs发送请求
port='8500'
server_url = r'0.0.0.0:'+port
response=request_server_grpc(res_image, server_url) #调用服务端

#处理返回结果
predict=response.copy()
#....后处理

Web服务

1
TensorFlow模型的计算图,一般输入的类型都是张量,你需要提前把你的图像、文本或者其它数据先进行预处理,转换成张量才能输入到模型当中。而一般来说,这个数据预处理过程不会写进计算图里面,因此当你想使用TensorFlow Serving的时候,需要在客户端上写一大堆数据预处理代码,然后把张量通过gRPC发送到serving,最后接收结果。现实情况是你不可能要求每一个用户都要写一大堆预处理和后处理代码,用户只需使用简单POST一个请求,然后接收最终结果即可。因此,这些预处理和后处理代码必须由一个“中间人”来处理,这个“中间人”就是Web服务。

可以使用Tornado来搭建web服务

版本管理

待完善

应用例子

例子来源于美团技术团队基于TensorFlow Serving的深度学习在线预估

该例子针对广告精排的业务场景,使用tfs进行模型部署,针对高速的推断进行逐步的优化,并突破现有tfs的束缚,解决模型切换的毛刺问题,使得tfs部署后在性能上满足业务场景

性能优化措施

  • 请求端优化:使用OpenMP多线程并行处理请求,时间从5ms降低到2ms

  • 构建模型的ops优化:分析构建模型的ops中的耗时操作,将其分离出去,或者使用低阶API替代高阶API

  • XLA,JIT优化:优化Tensorflow的计算图,剪除荣誉的计算

模型切换毛刺问题

模型切换时,大量的请求超时,原因有两个:一是更新、加载模型和处理请求的线程共用线程池,切换模型时无法处理请求;二是模型采用Lazy Initialization加载,第一次请求需要等待计算图初始化。

问题一的解决办法:

uint32 num_load_threads = 0; uint32 num_unload_threads = 0;设置为1,

问题2的解决办法:

模型加载后进行一次预热

参考资料

Tensorflow Serving部署tensorflow、keras模型详解_jeffery0207的博客-CSDN博客
TensorFlow Serving + Docker + Tornado机器学习模型生产级快速部署 - 知乎
SignatureDefs in SavedModel for TensorFlow Serving | TFX
使用tensorflow serving部署keras模型(tensorflow 2.0.0) - 知乎
基于TensorFlow Serving的深度学习在线预估 - 美团技术团队
TensorFlow Serving入门 - 简书
TensorFlow Serving 入门教程(Windows)_I’m George 的博客-CSDN博客
TensorFlow Serving 使用 及 部署_Eric’s Blog-CSDN博客