##haohaohao#######蘑菇街自研服务框架如何提高在线推理效率?

Online Serving 简介

从本质而言,在线服务就是提供 (http, rpc) 等接口,用户输入 X, X 通过 pre-process 处理成符合模型输入的参数,经由模型推理后获得 Y,Y 通过 post-process 处理成符合用户认知的数据格式,最后将结果返回。golang

第 2 步和训练中的 evalute/test 相应步骤几乎同样,只是在线推理下的 batch size 每每为 1,远远小于训练过程当中的 batch size,故在线推理下的显卡和显存的利用率相对训练更低。算法

1. X = pre_process(X)
2. Y = model.predict(X)
3. Y = post_process(Y)

以 mnist 为例,其模型输入为 28 * 28 的图片,模型输出为 1 * 10 的向量。从用户体验角度来看,客户端更指望的输入是一张图片的 URL,返回结果是预测的数字,相比 10 维向量,数字更符合人类的认知。因此从易用性出发,在线服务不只须要预测模块 (2),还须要预处理 (1) 和后处理模块 (3),方能为用户提供友好的服务。很遗憾的是,当前大部分开源的在线服务框架或者云服务,仅仅提供了模型预测功能。flask

开源的 Online Serving 现状

纵观业界,开源的在线服务项目可谓形态灵活、百花齐放,但也呈现出微服务化、容器化部署的特色,本节选择几个具有表明性的开源项目进行分析。缓存

Tensorflowsession

TensorFlow Serving is a flexible, high-performance serving system for machine learning models, designed for production environments.

正如官网所述,基于 C++ 编写的 Tensorflow Serving 注定是一个高性能的在线服务系统框架,它支持 Tensorflow,Pytorch 等深度学习框架训练导出的模型 (一般基于 ONNX 完成模型转换),并且部署很是方便,支持多个版本的模型,详细的例子请见官网。特别在结合 K8S + Docker,完美的解决了资源管理和调度、服务弹性部署、服务发现等痛点,从而对外提供稳定可靠的服务。架构

Tensorflow Serving 的缺点也很明显,正如本文第一节所讲,它缺少预处理和后处理相关的逻辑,因此影响用户体验,这点由官网样例也能看出来,用户须要关注调用 API 时传入的数据及其维度和格式,解析 protobuf 等等。另一个痛点是 Tensorflow 对其它深度学习框架生成的模型不够友好,好比卷积和池化时的 padding 参数,不一样的深度学习框架的存在差别,例如博文 Running Pytorch models in Tensorflow。并发

Pytorch框架

对于 Pytorch 的 Online Serving,社区并无提供相关的框架,仅在官网文档 model-serving-in-pyorch 给出了一个概述,为不一样场景下的 Online Serving 介绍了一些可行性的方案。less

  • 云化场景:基于 AWS Sagemaker、Azure Machine Learning 等公有云服务进行部署。
  • 本地场景:采用 flask 构建本身的微服务进行部署,例如 Deploy your PyTorch model to Production。
  • 开源 Model Server:如 Kubeflow 中的 KFServing 等等,详情请见下文。

KFServing机器学习

KFServing 是 Kubeflow 的子项目,旨在 Kubernetes 的基础上提供 Serverless 的在线推理服务,它支持多种机器学习框架,具有弹性伸缩、服务治理、A/B Test、金丝雀发布等重要功能,容许用户扩展 pre-processing、post-processing 逻辑,从而对外提供完整易用的服务,它主要有两部分组成:

  • KFServing Controller:基于 K8S 定义了在线推理服务相关资源 K8S CRD(Custom Resource Definition) 和生命周期管理 API,这部分主要为 golang 代码。
  • Model Server: 基于 tornado 提供了一套 Restful Web 框架,支持 Tensorflow、Pytorch 等深度学习框架,这部分为 Python 代码。

 

如上图所示,KFServing 的弱点也很明显,深度依赖 Knative、Istio 等模块,这些模块比较复杂,且稳定性有待商榷,其次 200+Stars 的 KFServing 的成熟度亦欠佳。但长远来看,待 Knative、Istio 稳定和普及后,KFServing 或将释放更大能力。

蘑菇街 Online Serving 实践

蘑菇街计算视觉有大几十个在线服务 (模型),其中很多运行在 GPU 容器上,每一个服务的业务代码一般在数百行之内,依赖 Pytorch、Tensorflow 和 Openvino 等框架完成推理。所以,建设一个易用、支持多深度学习框架、支持异构计算的统一在线服务,将大大提高效率和下降维护成本。

基本需求

易用性主要体如今两方面,一方面是对客户端要提供简单易用的 API,即 HTTP Restful API,因为公司全部图片均存储在对象存储上,故用户只须要在 body 中提供图片的 URL 便可,图片的下载和解析在服务端完成;另一方面是在线服务模块对算法同窗要简单易用,以便快速迭代上线。

此外,在线服务模块不只须要支持 Tensorflow、Pytorch 和 Openvino 等深度学习框架,还须要同时支持 GPU 和 CPU 计算资源。Batch size 为 1 下的推理没法充分利用 GPU 资源,咱们经过单卡运行多个服务以提高 GPU 资源利用率。

最后,支持弹性部署、完善的业务层面的监控和服务发现等功能也很是重要。

技术选型和实现

本节介绍蘑菇街计算视觉在线推理的实践,包括技术选型、架构和一些经验等。

语言和 Web 框架

Java 是公司的"标准"语言,几乎全部的基础技术体系均是基于 Java 建设。可是在机器学习领域,Python 有着无可比拟的丰富的生态,包括主流的深度学习框架和大量的图像数学库。采用 Java 实如今线推理一般碰到对应库和函数缺失的状况,甚至致使准确性降低的问题。其次 Java 并不是算法同窗经常使用的语言,致使常常须要工程同窗协助实现对应的逻辑,带来大量的沟通成本,影响上线效率。虽然 Java 性能较 Python 高,但权衡准确性和效率等因素,Python 是更好的选择。

如今分析 Python 下的并发方式,主要从进程、线程和协程择优用之。因为 CUDA 对多进程支持不够成熟,容易出现耗尽显存等异常状况,故排除多进程;受 GIL(Global Interpreter Lock) 锁的限制,线程的性能要低于协程,故最终采用高效和成熟的 Gevent 协程库提供并发,选择简单易用的 Flask 做为 WSGI Web 框架,对外提供 Restful API。

 

 

为了进一步提高易用性,可将下载图片、统计等通用功能放置在 WSGI 的 Middleware 中。每当新增一个在线服务,只需增长一个继承基础类的新类,并在新类中实现 load 和 handler 两个方法便可。其中 load 方法仅在服务启动时执行一次,用于完成下载和加载模型、建立 session 等初始化工做;handler 方法实现具体的业务逻辑,一般包含预处理、预测和后处理三个步骤,每当一个请求到来,启动一个协程执行对应在线服务的 handler 方法,并将结果返回给客户端。经过给每一个 handler 方法加上各自的互斥锁,使得同一个服务只能串行执行,以免相同服务并行带来的可能的 GPU 异常,可是不一样服务之间可并行执行。

容器化部署

咱们采用容器化部署在线服务,针对 CPU 和 GPU 异构资源制做了对应的两个镜像。以 GPU 场景为例,全部服务所依赖的深度学习框架库、图像库、数学库和 GPU 驱动相关已在镜像中安装,虽然这个镜像高达十几个 GB,可是便于使用,特别是算法同窗新增一个模型时,极少会碰到库缺失的状况。在线推理的逻辑较训练相比简单许多,对各类库的版本要求比较宽松,一般只需一个版本的镜像便可知足全部 GPU 在线服务的依赖要求。

 

 

K8S 不支持显卡虚拟化,且业内暂无开源的解决方案,只能为每一个 GPU 容器分配一张真实的显卡,每一个容器运行一个在线服务进程,它支持加载多个服务 (模型),其中服务的数量由模型大小和显存大小决定。一般而言,在线推理的显卡,如 P4 一般为 8GB,计算视觉的模型大小广泛在小几百 MB,一张 8G 的 P4 GPU 可加载十几个服务,且不一样服务之间能够并行执行,故 GPU 资源能够获得较为充分的利用,使用率可达到 40%,单卡能支持几十 QPS。

借助 K8S 的 StatefulSet,进一步简化部署和弹性扩缩,同时提高可靠性。

架构

每当在线推理的容器启动时,它根据环境变量决定加载哪些服务 (模型),依次从模型管理中心下载和加载模型,完成初始化工做后再注册路由和 API,最后成功启动的服务注册到注册中心。客户端根据服务名称从注册中心获取 IP 信息,而后访问对应的服务。Middleware 的统计模块会采集 QPS 等数据,并上报到监控中心。

 

 

模型管理主要用于存储和管理模型,经常使用的存储媒介有对象存储、HDFS 等,蘑菇街采用 HDFS 做为模型的存储中心。监控模块因不一样公司而异,一般的作法是经过 Kafka 或者监控服务的 API 上传统计数据。注册中心主要用于服务发现,Zookeeper 或 Redis 是经常使用的方案。咱们之因此须要注册中心,是由于有大几十个服务 (模型),同时单个 pod 加载多个服务而且这些服务常常增删,因此 K8S 原生的服务发现功能没法知足需求。可是若是您的服务数量少或者极少变动,那么采用 K8S 的服务发现便可,甚至可部署在虚拟机上,不须要注册中心。

为了提高性能,能够在多个层次增长 Cache。对于业务存在周期性调用的服务,能够将结果缓存在 Redis;计算视觉一般依赖多个在线服务完成一件事情,好比搭配购业务,它须要依次调用目标检测、服装粗分类和服装细分类的服务,将图片缓存在内存可在必定几率上避免重复下载,下降 RTT。对于视频类服务,因为视频较大,能够采用本地磁盘缓存视频和拆帧后的图片。

注意事项

在单卡支持运行多个服务 (模型) 时,主要遇到两个问题:

 

  • 同一个 GPU 上运行同时不一样框架的服务时,例如 Tensorflow 和 Pytorch,容易形成异常,故同一个 Pod 只能加载相同框架的服务。
  • 同一个进程下 Tensorflow 的 config.gpu_options.per_process_gpu_memory_fraction 仅在第一次初始化 session 时生效,为了不该值设置太小引发异常,建议 TF Pod 下的全部服务的参数均设置为 0.95。

做者简介

  • 范德良,花名揽胜,蘑菇街技术专家,在 IaaS、PaaS 和机器学习工程领域具备丰富经验。
  • 杨立,花名浮尘,蘑菇街智能投放平台小组负责人,深度参与了蘑菇街算法在线服务体系 0 到 1 的建设,主导了搜索推荐链路算法相关核心系统平台的建设与持续演进。