如何使用 tensorflowServing 进行模型部署
TensorFlow Serving 是用于机器学习的灵活,高性能的服务系统,针对生产环境而设计。 TensorFlow 服务 可以轻松部署新算法和实验,同时保持不变服务器体系结构和 API。TensorFlow Serving 开箱即用 与 TensorFlow 模型集成,但可以轻松扩展以服务于其他 模型类型
关键概念
Key Conception
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)。
广义地说:
- Sources create Loaders for Servable Versions.
- Loaders are sent as Aspired Versions to the Manager, which loads and serves them to client requests.
例子:
- Source 为指定的服务 (磁盘中检测模型权重的新版本) 创建 Loader,Loader 里包含了服务所需要的元数据(模型);
- Source 使用回调函数通知 Manager 的 Aspired Version (Servable version 的集合);
- Manager 根据配置的 Version Policy 决定下一步的操作(是否 unload 之前的 Servable,或者 load 新的 Servable);
- 如果 Manager 判定是操作安全的,就会给 Loader 要求的 resource 并让 Loader 加载新的版本;
- 客户端向 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 | from keras import backend as K |
导出之后,有以下目录结构
导出之后,使用以下命令查看模型的 signature、input、output,后续客户端调用需要这些信息。
1 | saved_model_cli show --dir tfs/0/ --all |
1 | signature_def['serving_default']: |
以上可以确定,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 | sudo nvidia-docker run -p 8500:8500 \ |
其中 9e73a1470b72 为 tfs 镜像的 id,可通过 docker image ls 查看
tfs 容器可用参数解释:
1 | --port = 8500 用于侦听gRPC API的端口 |
客户端的编写
基于 Python 编写客户端时,需要安装 tensorflow_serving、grpc 库包
1 | import cv2 |
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 博客