写点什么

Zendesk 的 TensorFlow 产品部署经验

2017 年 3 月 31 日

我们如何开始使用 TensorFlow

在 Zendesk,我们开发了一系列机器学习产品,比如最新的自动应答(Automatic Answers)。它使用机器学习来解释用户提出的问题,并用相应的知识库文章来回应。当用户有问题、投诉或者查询时,他们可以在线提交请求。收到他们的请求后,Automatic Answers 将分析请求,并且通过邮件建议客户阅读可能最有帮助的相关文章。

Automatic Answers 使用一类目前最先进的机器学习算法来识别相关文章,也就是深度学习。 我们使用 Google 的开源深度学习库 TensorFlow 来构建这些模型,利用图形处理单元(GPU)来加速这个过程。Automatic Answers 是我们在 Zendesk 使用 Tensorflow 完成的第一个数据产品。在我们的数据科学家付出无数汗水和心血之后,我们才有了在 Automatic Answers 上效果非常好的 Tensorflow 模型。

但是构建模型只是问题的一部分,我们的下一个挑战是要找到一种方法,使得模型可以在生产环境下服务。模型服务系统将处理大量的业务,所以需要确保为这些模型提供的软件和硬件基础架构是可扩展的、可靠的和容错的,这对我们来说是非常重要的。接下来介绍一下我们在生产环境中配置 TensorFlow 模型的一些经验。

顺便说一下我们的团队——Zendesk 的机器学习数据团队。我们团队包括一群数据科学家、数据工程师、一位产品经理、UX / 产品设计师以及一名测试工程师。

TensorFlow 模型服务

经过数据科学家和数据工程师之间一系列的讨论,我们明确了一些核心需求:

  • 预测时的低延迟
  • 横向可扩展
  • 适合我们的微服务架构
  • 可以使用 A/B 测试不同版本的模型
  • 可以与更新版本的 TensorFlow 兼容
  • 支持其他 TensorFlow 模型,以支持未来的数据产品

TensorFlow Serving

经过网上的调研之后,Google 的 TensorFlow Serving 成为我们首选的模型服务。TensorFlow Serving 用 C++ 编写,支持机器学习模型服务。开箱即用的 TensorFlow Serving 安装支持:

  • TensorFlow 模型的服务
  • 从本地文件系统扫描和加载 TensorFlow 模型

TensorFlow Serving 将每个模型视为可服务对象。它定期扫描本地文件系统,根据文件系统的状态和模型版本控制策略来加载和卸载模型。这使得可以在 TensorFlow Serving 继续运行的情况下,通过将导出的模型复制到指定的文件路径,而轻松地热部署经过训练的模型。

(点击放大图像)

根据这篇Google博客中报告的基准测试结果,他们每秒记录大约100000 个查询,其中不包括TensorFlow 预测处理时间和网络请求时间。

有关TensorFlow Serving 架构的更多信息,请参阅TensorFlow Serving文档

通信协议(gRPC)

TensorFlow Serving 提供了用于从模型调用预测的 gRPC 接口。gRPC 是一个开源的高性能远程过程调用(remote procedure call,RPC)框架,它在 HTTP/2 上运行。与 HTTP/1.1 相比,HTTP/2 包含一些有趣的增强,比如它对请求复用、双向流和通过二进制传输的支持,而不是文本。

默认情况下,gRPC 使用 Protocol Buffers (Protobuf) 作为其信息交换格式。Protocol Buffers 是 Google 的开源项目,用于在高效的二进制格式下序列化结构化数据。它是强类型,这使它不容易出错。数据结构在.proto 文件中指定,然后可以以各种语言(包括 Python,Java 和 C ++)将其编译为 gRPC 请求类。 这是我第一次使用 gRPC,我很想知道它与其他 API 架构(如 REST)相比谁性能更好。

模型训练和服务架构

我们决定将深度学习模型的训练和服务分为两个管道。下图是我们的模型训练和服务架构的概述:

(点击放大图像)

模型训练管道

模型训练步骤:

  • 我们的训练特征是从 Hadoop 中提供的数据生成的。
  • 生成的训练特征保存在 AWS S3 中。
  • 然后使用 AWS 中的 GPU 实例和 S3 中的批量训练样本训练 TensorFlow 模型。

一旦模型被构建并验证通过,它将被发布到 S3 中的模型存储库。

模型服务管道

验证的模型在生产中通过将模型从模型库传送到 TensorFlow Serving 实例来提供。

基础结构

我们在 AWS EC2 实例上运行 TensorFlow Serving。Consul 在实例之前设置,用于服务发现和分发流量。客户端连接从 DNS 查找返回的第一个可用 IP。或者弹性负载平衡可用于更高级的负载平衡。由于 TensorFlow 模型的预测本质上是无状态操作,所以我们可以通过旋转加速更多的 EC2 实例来实现横向可扩展性。

另一个选择是使用 Google Cloud 平台提供的 Cloud ML,它提供 TensorFlow Serving 作为完全托管服务。 但是,当我们在大概 2016 年 9 月推出 TensorFlow Serving 时,Cloud ML 服务处于 alpha 阶段,缺少生产使用所需的功能。因此,我们选择在我们自己的 AWS EC2 实例中托管,以实现更精细的粒度控制和可预测的资源容量。

模型服务的实现

下面是我们实现 TensorFlow Serving 部署和运行所采取的步骤:

1. 从源编译 TensorFlow Serving

首先,我们需要编译源代码来产生可执行的二进制文件。然后就可以从命令行执行二进制文件来启动服务系统。

假设你已经配置好了 Docker,那么一个好的开端就是使用提供的 Dockerfile 来编译二进制文件。请按照以下步骤:

  • 运行该 gist 中的代码以构建适合编译 TensorFlow Serving 的 docker 容器。
  • 在正在运行的 docker 容器中运行该 gist 中的代码以构建可执行二进制文件。
  • 一旦编译完成,可执行二进制文件将在你的 docker 镜像的以下路径中:/work/serving/bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server

2. 运行模型服务系统

上一步生成的可执行二进制文件(tensorflow_model_server)可以部署到您的生产实例中。 如果您使用 docker 编排框架(如 Kubernetes Elastic Container Service ),您还可以在 docker 容器中运行 TensorFlow Serving。

现在假设 TensorFlow 模型存储在目录 /work/awesome_model_directory 下的生产主机上。你可以在端口 8999 上使用以下命令来运行 TensorFlow Serving 和你的 TensorFlow 模型:

复制代码
<path_to_the_binary>/tensorflow_model_server — port=8999 — model_base_path=/work/awesome_model_directory

默认情况下,TensorFlow Serving 会每秒扫描模型基本路径,并且可以自定义。此处列出了可作为命令行参数的可选配置。

3. 从服务定义(Service Definitions)生成 Python gRPC 存根

下一步是创建可以在模型服务器上进行预测的 gRPC 客户端。这可以通过编译.proto 文件中的服务定义,从而生成服务器和客户端存根来实现。.proto 文件在 TensorFlow Serving 源码中的 tensorflow_serving_apis 文件夹中。在 docker 容器中运行以下脚本来编译.proto 文件。运行提交版本号为 46915c6 的脚本的示例:

复制代码
./compile_ts_serving_proto.sh 46915c6

运行该脚本后应该在 tensorflow_serving_apis 目录下生成以下定义文件:

  • model_pb2.py
  • predict_pb2.py
  • prediction_service_pb2.py

你还可以使用 grpc_tools Python 工具包来编译.proto 文件。

4. 从远程主机调用服务

可以使用编译后的定义来创建一个 python 客户端,用来调用服务器上的 gRPC 调用。比如这个例子用一个同步调用TensorFlow Serving 的Python 客户端。

如果您的用例支持异步调用预测,TensorFlow Serving 还支持批处理预测以达到性能优化的目的。要启用此功能,你应该运行tensorflow_model_server 同时开启flag?—enable_batching。是一个异步客户端的例子。

从其他存储加载模型

如果你的模型没有存储在本地系统中应该怎么办?你可能希望TensorFlow Serving 可以直接从外部存储系统(比如AWS S3 和Google Storage)中直接读取。

如果是这种情况,你将需要通过 Custom Source 来拓展 TensorFlow Serving 以使其可以读取这些源。TensorFlow Serving 仅支持从文件系统加载模型。

一些经验

我们在产品中已经使用 TensorFlow Serving 大概半年的时间,我们的使用体验是相当平稳。它具有良好的预测时间延迟。以下是我们的 TensorFlow Serving 实例一周内的预测时间(以秒为单位)的第 95 百分位数的图(约 20 毫秒):

(点击放大图像)

然而,在生产中使用TensorFlow Serving 的过程中,我们也有一些经验教训可以跟大家分享。

1. 模型版本化

到目前为止,我们已经在产品中使用了几个不同版本的 TensorFlow 模型,每一个版本都有不同的特性,比如网络结构、训练数据等。正确处理模型的不同版本已经是一个重要的任务。这是因为传递到 TensorFlow Serving 的输入请求通常涉及到多个预处理步骤。这些预处理步骤在不同 TensorFlow 模型版本下是不同的。预处理步骤和模型版本的不匹配可能导致错误的预测。

1a. 明确说明你想要的版本

我们发现了一个简单但有用的防止错误预测的方法,也就是使用在 model.proto 定义中指定的版本属性,它是可选的(可以编译为 model_pb2.py)。这样可以始终保证你的请求有效负载与预期的版本号匹配。

当你请求某个版本(比如从客户端请求版本 5),如果 TensorFlow Serving 服务器不支持该特定版本,它将返回一个错误消息,提示找不到模型。

1b. 服务多个模型版本

TensorFlow Serving 默认的是加载和提供模型的最新版本。

当我们在 2016 年 9 月首次应用 TensorFlow Serving 时,它不支持同时提供多个模型。这意味着在指定时间内它只有一个版本的模型。这对于我们的用例是不够的,因为我们希望服务多个版本的模型以支持不同神经网络架构的 A / B 测试。

其中一个选择是在不同的主机或端口上运行多个 TensorFlow Serving 进程,以使每个进程提供不同的模型版本。这样的话就需要:

  • 用户应用程序(gRPC 客户端)包含切换逻辑,并且需要知道对于给定的版本需要调用哪个 TensorFlow Serving 实例。这增加了客户端的复杂度,所以不是首选。
  • 一个可以将版本号映射到 TensorFlow Serving 不同实例的注册表。

更理想的解决方案是 TensorFlow Serving 可以支持多个版本的模型。

所以我决定使用一个“lab day”的时间来扩展 TensorFlow Serving,使其可以服务多个版本的时间。在 Zendesk,“lab day”就是我们可以每两周有一天的时间来研究我们感兴趣的东西,让它成为能够提高我们日常生产力的工具,或者一种我们希望学习的新技术。我已经有 8 年多没有使用 C++ 代码了。但是,我对 TensorFlow Serving 代码库的可读性和整洁性印象深刻,这使其易于扩展。支持多个版本的增强功能已经提交,并且已经合并到主代码库中。TensorFlow Serving 维护人员对补丁和功能增强的反馈非常迅速。从最新的主分支,你可以启动 TensorFlow Serving,用 model_version_policy 中附加的 flag 来服务多个模型版本:

复制代码
/work/serving/bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server
— port=8999 — model_base_path=/work/awesome_model_directory — model_version_policy=ALL_VERSIONS

一个值得注意的要点是,服务多个模型版本,需要权衡的是更高的内存占用。所以上述的 flag 运行时,记住删除模型基本路径中的过时模型版本。

2. 活用压缩

当你部署一个新的模型版本的时候,建议在复制到 model_base_path 之前,首先将导出的 TensorFlow 模型文件压缩成单个的压缩文件。Tensorflow Serving 教程中包含了导出训练好的 Tensorflow 模型的步骤。导出的检查点 TensorFlow 模型目录通常具有以下文件夹结构:

一个包含版本号(比如 0000001)和以下文件的父目录:

  • saved_model.pb:序列化模型,包括模型的图形定义,以及模型的元数据(比如签名)。
  • variables:保存图形的序列化变量的文件。

压缩导出的模型:

复制代码
tar -cvzf modelv1.tar.gz 0000001

为什么需要压缩?

  1. 压缩后转移和复制更快
  2. 如果你将导出的模型文件夹直接复制到 model_base_path 中,复制过程可能需要一段时间,这可能导致导出的模型文件已复制,但相应的元文件尚未复制。如果 TensorFlow Serving 开始加载你的模型,并且无法检测到源文件,那么服务器将无法加载模型,并且会停止尝试再次加载该特定版本。

3. 模型大小很重要

我们使用的 TensorFlow 模型相当大,在 300Mb 到 1.2Gb 之间。我们注意到,在模型大小超过 64Mb 时,尝试提供模型时将出现错误。这是由于 protobuf 消息大小的硬编码 64Mb 限制,如这个 TensorFlow Serving 在 Github 上的问题所述。

最后,我们采用 Github 问题中描述的补丁来更改硬编码的常量值。(这对我们来说还是一个问题。如果你可以找到在不改变硬编码的情况下,允许服务大于 64Mb 的模型的替代方案,请联系我们。)

4. 避免将源移动到你自己的分支下

从实现时开始,我们一直从主分支构建 TensorFlow Serving 源,最新的版本分支(v0.4)在功能和错误修复方面落后于主分支。因此,如果你只通过检查主分支来创建源,一旦新的更改被合并到主分支,你的源也可能改变。为了确保人工制品的可重复构建,我们发现检查特定的提交修订很重要:

  • TensorFlow Serving
  • TensorFlow(TensorFlow Serving 里的 Git 子模块)

期待未来加入的一些功能增强清单

这里是一些我们比较感兴趣的希望以后 TensorFlow Serving 会提供的功能:

  • 健康检查服务方法
  • 一个 TensorFlow Serving 实例可以支持多种模型类型
  • 直接可用的分布式存储(如 AWS S3 和 Google 存储)中的模型加载
  • 直接支持大于 64Mb 的模型
  • 不依赖于 TensorFlow 的 Python 客户端示例

阅读英文原文: How Zendesk Serves TensorFlow Models in Production


感谢冬雨对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017 年 3 月 31 日 17:455299

评论

发布
暂无评论
发现更多内容

《Maven实战》.pdf

田维常

程序员

.NET可视化权限功能界面设计

雯雯写代码

完美!Ali软件架构师下场“痛扁”spring源码,加入吗?

周老师

Java 编程 程序员 架构 面试

频繁操作本地缓存导致YGC耗时过长

AI乔治

Java 架构 JVM GC

15张图解Redis为什么这么快

Java架构师迁哥

面试官:面对千万级、亿级流量怎么处理?

艾小仙

Java 缓存 分布式 高并发 中间件

架构师训练营作业:第五周

m

甲方日常 40

句子

工作 随笔杂谈 日常

week1 架构方法-作业-杨斌

杨斌

【JSRC小课堂】Web安全专题(二)逻辑漏洞的burpsuite插件开发

京东智联云开发者

Web

保险区块链创新中心成立,三方面赋能行业数字化转型

CECBC区块链专委会

区块链 保险

只有基于区块链才可能实现“大众创业、万众创新”

CECBC区块链专委会

区块链 分布式技术

总结年初到10月底Java基础、架构面试题,共计1327道!涵盖蚂蚁金服、腾讯、字节跳动、美团、拼多多等等一线大厂!

Java架构追梦

Java 架构 字节跳动 面试 蚂蚁金服

求职时这样回答问题你就输了!来自IT类面试官视角的深度解读

Java架构师迁哥

中台:未到终局,焉知生死?

ToB行业头条

中台

调包侠的炼丹福利:使用Keras Tuner自动进行超参数调整

计算机与AI

学习 keras 超参数调优

通过GUI界面更改 Ubuntu 20 LTS apt 源为阿里云

jiangling500

ubuntu 阿里云 apt

分析和解决JAVA 内存泄露的实战例子

AI乔治

Java 架构 JVM 内存泄露

VRBT视频彩铃解决方案

dwqcmo

5G 解决方案 实时音视频

算法题解:Excel 工作表列标题

欧雷

Java 算法

后李健熙时代的三星,将迎来怎样变局?

脑极体

如何实现微服务架构下的分布式事务?

华为云开发者社区

架构 分布式 事务

假的数字人民币钱包已出现,真的是啥样?

CECBC区块链专委会

数字货币 数字钱包

想了解Webpack,看这篇就够了

华为云开发者社区

华为 前端 开发

智能安防的普惠密码,在华为好望手中的三根“线头”上

脑极体

USDT承兑商支付系统开发,USDT支付结算系统搭建

135深圳3055源中瑞8032

3年CRUD经验的Java程序员,金九银十想要跳槽,面试却遭到屡屡碰壁,感觉很迷茫!

Java成神之路

Java 程序员 架构 面试 编程语言

快速掌握并发编程---线程池的原理和实战

田维常

程序员

Flink在窗口上应用函数-6-9

小知识点

scala 大数据 flink

合约一键跟单软件,API跟单软件开发

135深圳3055源中瑞8032

LeetCode题解:78. 子集,递归回溯,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

Zendesk的TensorFlow产品部署经验-InfoQ