引言
本文是 TalkingData 阎老师原创发表在内部资讯 Furion 平台的一篇文章,介绍了 UC Berkley 的 RISELab 面临数据科学最后一公里开源的 Clipper 项目。
面临数据科学这个挑战,我们并不孤独,UC Berkley的RISE Lab(AMPLab的继承者)也发现了这个问题,并且开源了他们应对这个问题的项目-Clipper(快船),现在就让我们来了解一下Clipper这个项目。
Clipper 是什么?
从 Clipper 的官网上,Clipper 将自己定位为处于面向客户的应用和机器学习模型与常用框架之间的一套预测服务系统。
从这个图上,我们可以清晰的理解 Clipper 所处的位置。在 Clipper 的下层,是不同类型的模型容器,包括 Spark、Caffe、TensorFlow、Scikit Learn 等等。而模型通过 Clipper 变为不同 API 接口,为上层的业务系统所调用,比如推荐、无人驾驶等等。
上层应用系统和 Clipper 之间的预测模型调用以及模型结果反馈的交互可以通过 RPC 或者 REST API,而 Clipper 与模型容器之间的交互则是通过 RPC。
Clipper 将自己定位为在一个鲁棒的、高性能的预测服务系统,通过它可以非常容易地扩展从而实现每秒钟数以千计的模型服务调用并且能够达到毫秒级别的响应时间。
另外,Clipper 支持数据科学家能够不改变代码,将训练代码直接部署到生产环境中。
Clipper 的特性
Clipper 的特性如下:
对于数据科学家在选定的框架上训练的模型,可以通过几行代码就可以部署到一个现存的模型容器上,或者开发自己的模型容器
对于正在运行的应用,可以很容易地更新和回滚模型
可以设定服务的延迟目标从而保证可靠的查询延迟
每个模型都运行在一个独立的 Docker 容器上,从而可以实现简单的集群管理和资源分配
可以将模型运行在 CPU、GPU 或者同时运行在二者之上
Clipper 的架构
在 Clipper 的架构中,包含一个模型选择层(Model Selection Layer)以及一个模型抽象层(Model Abstraction Layer)。模型选择层负责在多个竞争的模型当中根据需求动态的选择和组合模型,从而能够提供更精确、更鲁棒的预测。而模型抽象层则屏蔽底层的不同的机器学习框架,通过抽象出一个通用的 API 来方便模型上层应用对模型的调用。
Clipper 为了能够实现低延时、高吞吐率的预测,在模型抽象层引入了缓存的策略。对于每一个模型,Clipper 提供一个缓存层,并且通过 Adaptive Batching 来提高预测的吞吐率。而对于模型选择层,则引入 Straggler mitigation 技术,预测的请求不会路由到比较慢的模型执行上,从而能够降低延迟。
Clipper 集群
Clipper 集群的实现利用了现在非常流行的容器技术。一个 Clipper 集群由一组相互通讯的 Docker 容器组成。Clipper 集群的核心有三个部分组成:查询前端(Query Frontend)、管理前端(Management Frontend)以及配置数据库(configuration database),如下图:
其中:
Query Frontend 负责接收进来的预测请求,并且将这些请求路由到部署的模型上
Management Frontend 负责管理和更新 Clipper 集群的内部状态,当集群需要更新时,需要通过 Management Frontend 的 REST API 发送请求,状态会更新到 database 当中
Configuration Database 是一个运行 Redis 实例的容器,它存储了集群所有的配置信息
Clipper 中的模型
在 Clipper 环境中,每个模型都会运行在一个 Docker 容器中。Clipper 对常用的模型运行框架提供了 model deployer,从而使得常用的模型类型可以方便地进行部署。目前,Clipper 支持三种类型的模型环境:纯 Python、PySpark 和 R。在模型被部署成功之后,Clipper 利用容器管理来启动容器并且建立一个模型容器与 Query Frontend 之间的 RPC 连接。
在 Clipper 当中,模型部署好之后并不会建立一个对外的 REST 服务。Clipper 引入了一个应用层来负责将请求路由到模型容器中。这样可以使得多个应用可以路由到一个模型,也可以一个应用路由到多个模型。用户需要通过 ClipperConnection.register_application 来注册应用,应用注册成功之后,会对应用创建一个 REST 服务。
通过 ClipperConnection.link_model_to_app,可以将 model 连接到应用上,这样对于应用的访问就可以路由到模型上了。如下图:
在 Clipper 当中,模型支持不同版本,当新的版本通过 deploy_model 被部署时,应用会将预测请求路由到新版本的模型上。另外,用户可以通过 ClipperConnection.set_model_version 来回滚模型。
Clipper 支持对同一个模型复制不同的副本,从而提高模型的吞吐率。通过调用 ClipperConnection.set_num_replicas,Clipper 可以根据设置的副本的数量来启动相应数量的模型容器,如下图:
对于模型的访问,则是通过访问应用的 REST API 来完成,比如:
http://localhost:8080/wordcount-app/predict
从前面的描述我们可以看到,Clipper 的核心是实现了模型的调用与模型运行态的隔离,通过逻辑层的应用,将模型的服务以 REST API 或者 RPC 的方式对调用者开放,而 Clipper 内部通过通过应用和模型的连接来是实现灵活的应用对模型的路由,从而将模型和对外的服务解耦,为满足模型服务化的性能提供了基础。而底层则利用容器化的技术来实现从训练到运行态的转换工作,降低模型部署的成本。
整个设计的思路从架构上来讲,如果大家面向同样的问题做架构,估计大同小异。具体的实现,由于 Clipper 的目的是为了提供高性能生产环境预测能力,整个项目利用 RUST 和 C++ 来实现核心的代码,实现代码的选择非常有 Geek 范儿。想一下师出同门的 Spark 用 Scala 语言实现,在大约 6 年前,也是非常 Geek 的。不得不说,伯克利出品的东西,工程能力相对其他的美国院校,还是比较出色的。
下面我会自己搭个测试环境玩一下 Clipper,希望能够形成一篇实际操作的文章。
领取专属 10元无门槛券
私享最新 技术干货