一、Ray介绍
1.Ray是什么
Ray是一个开源的分布式计算框架,它提供了一套简单、灵活和通用的****API来构建和运行分布式应用程序。
Ray出身于UC Berkeley RISELab实验室,而RISELab实验室前身就是设计了Apache Spark、Apache Mesos和Alluxio的大名鼎鼎AMPLab实验室。Ray的核心作者就是Spark的核心作者, RISELab实验室主管教授Ion Stoica。
2.为什么有Ray
Ray设计之初主要用以解决增强学习(Reinforcement Learing, RL)中遇到的问题。
RL学习是机器学习中的一个领域,强调如何基于环境状态变动而执行具体动作,以取得最大化的预期收益。RL是监督学习和非监督学习之外的第三种基本机器学习方法。
RL应用的核心是能够通过学习得到一个策略(Policy),这个策略就是从环境状态(State)到动作(Action)的映射,通过映射来取得最大化的预期收益。而为了寻找这个有效的策略就需要应用程序具备三个主要能力:
-
模拟仿真能力(Simulations)。依赖模拟仿真(Simulations)来评估策略,通过Simulations探索更多Action选择,来感知Action选择所对应的预期收益。
-
分布式训练( Distributed training)。和监督学习类似,RL算法也需要基于Simulations或实际环境交互生成的数据集进行分布式训练,从而改进策略。
-
策略服务(Serve)。对训练出的策略,最终要能够以Serving能力对外提供策略服务(根据环境状态给出Action决策)。
而上面这些特性也对系统提出了新的需求:
-
细粒度计算(Fine-grained)。RL系统需要支持细粒度计算,比如在与现实世界交互时要能够以毫秒为单位给出Action,并能够执行大量模拟。
-
异构能力(Heterogeneity)。在时间和资源使用上支持异构,比如模拟任务可能需要几毫秒或几小时(支持多种运行时长的任务),GPU一般用于Training而CPU用于Simulations(支持多种资源)。
-
动态执行(Dynamic)。在模拟或与环境实际交互中的结果可以改变之后的计算(动态任务执行)。
综上,Ray的作者们认为:需要一个动态计算框架,能够以毫秒的延迟每秒处理数百万的异构任务。
更详细的背景,可以阅读Ray在发表的论文:Ray: A Distributed Framework for Emerging AI Applications。
3.Ray架构
我们一般所说的Ray架构是指Ray-core的架构。随着Ray的发展,Ray的整体架构从最早论文提出到现在已经有很多改变。下图是Ray最新整体架构:
Ray整体架构采用了Master-Slave架构。Ray中Master节点称为Head node负责全局协调和状态维护,比如调度任务、维护meta data等;Slave节点称为Worker node,负责具体任务执行。
Ray主要包括以下组件:
-
Global Control Store(GCS):Head node核心,主要有两个作用,全局调度任务和维护集群状态信息。
-
Raylet:Raylet是每个node上的核心进程,主要用来维护LocalScheduler和ObjectStore。
Ray调度
Ray采用的是两层调度架构,也就是全局调度器Global Scheduler和每个节点的LocalScheduler。为了避免GlobalScheduler负载过重(想象一下每秒百万任务调度),在Worker node上面的task会优先使用LocalScheduler将task调度在本节点,当本节点过载(超过预定义阈值)或者不满足任务需求(比如,缺少GPU)时才会将调度请求转发给GlobalScheduler。
这种优先尝试本地调度任务的机制,Ray称为Bottom-Up Distributed Scheduler。
但在实践中发现,在很多调度场景这种调度机制是无法满足情况的,比如想要亲和或反亲和调度一些task,这种调度机制就无法满足需求了。
所以蚂蚁又在当前双层调度框架之外增加了PlacementGroup全局调度策略,能够对绑定的一组actor进行亲和或反亲和调度。(该调度机制也已经开贡献到社区了)
4.Ray API
Ray core的核心就是Ray API,就像Ray官网介绍Ray称:Ray provides a simple, universal API for building distributed applications.
Ray API设计目标是致力于提供一个分布式系统系统的API。通过Ray API来编写的分布式应用程序就像开发一个本机应用一样。
Task和Actor概念
在具体看Ray API前,我们先了解一下Ray中的两个核心概念:Task和Actor。
-
Task提供轻量级的无状态计算,能够高效、动态执行任务,但是容错和load balance都非常简单。
-
Actor能够支持有状态计算,例如模型训练,并且可以将共享的状态的给客户端使用。
Task和Actor都代表Ray中的任务(task),Ray中调度和执行的任务就是Task和Actor。
Ray编程原语
Ray提供了编程的基础原语(Primitives),我们可以使用Ray提供的编程原语来构建分布式任务。
-
ray.init():启动一个ray集群,默认会在本地启动一个Ray runtime(可以指定远端分布式集群),可以通过参数指定一个远端Ray runtime。(也可以在init内指定集群资源)
-
@ray.remote():声明一个remote function,使用@ray.remote()注解可以修饰方法或类,而remote function就是定义Ray中执行的任务。它在定义后会立马被序列化存储到RedisServer中,并且分配一个唯一的ID,这样就能保证集群中所有节点都能看到这个函数的定义。 remote内可以指定计算资源,比如@ray.remote(num_cpus=4)。
- Ray Actor:使用@ray.remote()注解修饰的Class/Object就是我们上面说的有状态计算单元Actor,Ray Actor会把Class/Oject分布式化。
-
ray.put():可以将对象(自定义对象或计算对象)写到本地的ObjectStore中,并返回一个唯一的ObjectRef,通过这个ObjectRef可以在Ray集群的任一节点获取该对象。
-
ray.get():通过ObjectRef可以获取存储在任意节点的对象。
Ray task demo:
# A regular Python function.
def my_function():
return 1
# By adding the `@ray.remote` decorator, a regular Python function
# becomes a Ray remote function.
@ray.remote
def my_function():
return 1
# To invoke this remote function, use the `remote` method.
# This will immediately return an object ref (a future) and then create
# a task that will be executed on a worker process.
obj_ref = my_function.remote()
# The result can be retrieved with ``ray.get``.
assert ray.get(obj_ref) == 1
@ray.remote
def slow_function():
time.sleep(10)
return 1
# Invocations of Ray remote functions happen in parallel.
# All computation is performed in the background, driven by Ray's internal event loop.
for _ in range(4):
# This doesn't block.
slow_function.remote()
Ray actor demo:
#The ray.remote decorator indicates that instances of the Counter class will be actors.
# Each actor runs in its own Python process.
@ray.remote(num_cpus=2, num_gpus=0.5)
class Counter(object):
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
return self.value
# Create an actor from this class.
counter = Counter.remote()
# Call the actor.
obj_ref = counter.increment.remote()
流行的分布式框架,都有比较基础的分布式原语:任务、对象和服务。而编程语言中,也刚好有三个基本概念:函数、变量和类,这三个编程语言的基本概念刚好可以和分布式框架原语对应起来。这也是Ray API在设计之初的考虑。
5.Ray执行过程
通过上面的Ray API定义了一个应用程序后,我们接下来可以看一下Ray是如何调度执行这个任务的。
0. 首先将定义的add方法注册到GCS中,这样每个节点就都能感知这个function了。
-
执行 id = add.remote(a,b)(这里可以看到参数a和b已经存储在了ObjectStore中)。首先在Local Scheduler(现在称为Raylet)进行调度。
-
N1的Local Scheduler发现本地ObjectStore只有a,这时候向Global Scheduler(GCS)求助。
-
Global Scheduler查询 GCS的Object Table获取b的位置,发现在N2节点。
-
Global Schedule决定将任务调度到N2执行(并不是b在N2就将任务调度到N2,比如N1负载过高)。
-
N2的Local Scheduler发现本地没有执行所需要的a。
-
N2向GCS的Object Table获取a的位置。
-
N2从N1的Object Store中把a拷贝到N2本地的Object Store。
-
在N2上执行任务。
接下来我们再看获取执行结果语句c = ray.get(id)执行流程。
-
首先使用Object ref c来请求本地LocalScheduler(Raylet)。
-
N1没有缓存Object ref c,则向GCS申请。但是此时c还没有计算完成,此时N1会在GCS的Object table注册一个callback。
-
N2 add任务执行完成,会把结果c存储到本地的Object store中。
-
N2 在本地存储完c时,也会把结果同步给GCS,告知GCS 现在c的结果存储在N2节点中。
-
GCS触发刚才N1注册的callback。
-
N1从N2中将结果c拷贝到本地的object store中。
-
返回结果。
6.Ray生态体系
Ray除了Ray-core层的通用分布式计算框架外,在上层还具有丰富的用于构建AI和机器学习应用库。比如:
-
用于增强学习的库RLib。
-
超参数调整的Ray Tune。
-
用于深度学习的Ray Train。
-
用于模型服务的Ray Server。
随着Ray的发展,Ray生态不仅有自身提供的各种库,还有很多第三方应用集成到Ray。比如:
-
Python中用于分析计算的并行计算库Dask on Ray。
-
基于NumPy强大的数据分析包Pandas on Ray。
Ray在蚂蚁的应用
与国外主要围绕Ray来建设AI、机器学习等方向不同,国内参与Ray建设的主力军蚂蚁更多的是围绕在Ray core和基于Ray core建设上层引擎框架。
-
Ray core:蚂蚁在Ray core上投入很多,因为早期Berkeley主要发展方向是Ray上的增强学习,而蚂蚁想要用Ray做分布式计算底盘,自然而然在Ray core上的投入更多(Ray 目前商业化公司Anyscale也越来越多投入到Ray core上)。
-
Ray deploy:ray集群部署在Yarn、Kubernetes之上。
-
Ray Serving:提供在线服务化能力,更多的是继承蚂蚁内部sofa体系(和社区实现的Ray Sever有所不同)。
-
Ray Streaming:基于Ray core而建设的流计算引擎,主要用于在线学习、在线决策计算等。
-
Ray MPP:基于Ray core而建设的大规模数据处理引擎,主要向olap方向发展(早期和Ray Streaming融合提供在线决策能力)。
-
图计算框架:基于Ray的实时图计算框架。
下图是蚂蚁基于Ray打造的金融智能技术架构,整体运行在Kubernetes集群之上,Ray作为分布式计算的基础设施,支持包括图计算、在线机器学习等应用框架,驱动上层的实施风控、知识图谱等应用。
之所以蚂蚁在Ray core的上层计算引擎框架上投入很多,一个主要的原因就是蚂蚁想要做下一代计算体系:融合计算。
7.融合计算
为什么要做融合计算
当我们解决一个问题时,可能需要多项技术进行深度融合(OLAP、图计算、流计算、深度学习等),但在不同技术结合到一起时候,会遇到多方面挑战:
-
复杂系统的协调:当多个计算引擎组合到一起,系统复杂性大大上升,也加大了工程师对复杂系统的协调难度。
-
性能优化:在进行性能优化时,工程师需要面对多种分属不同领域的计算引擎,导致系统难以突破性能瓶颈。
-
开发效率:工程师需要熟悉多个引擎如何协同工作,出现问题也难以定位和排查,导致开发效率低下。
为什么要用Ray做融合计算
Ray天然具备以下优势:
-
简单易用的API。
-
同时支持多个编程语言。(跨语言能力极强)
-
弹性且可自定义的任务调度。
-
分布式状态管理。
-
易用的错误处理和故障恢复。
-
低成本的DevOps。
融合计算-在线机器学习
传统的在线机器学习系统是一系列子系统组成的任务Pipeline,原始的实时数据要经过过滤、数据聚合、样本拼接、采样、模型训练和模型部署等系统才能被使用。这就为数据一致性、系统稳定性和多平台等挑战。总结下来主要挑战如下:
-
SLA:整个链路的SLA会受到某个模块SLA的影响,并随着模块的增多而放大,稳定性成为制约业务发展的重要因素。
-
系统效率:模块中间的衔接多数通过数据落盘来进行,模块间的调度通过系统调度来实现,造成不必要的I/O、计算和网络开销。
-
开发和运维成本:各个模块风格迥异,开发模式、计算框架、甚至代码风格都不一致,开发和运维对接时需要花很多时间去熟悉系统,降低业务开发效率。
融合如何去做?
核心解决逻辑就是将系统间的衔接转换为系统内的衔接,将作业的调度转换为任务的调度,这样把计算与计算之间进行协同调度,从而提高系统效率和降低网络带宽。
基于Ray开发的端到端在线机器学习架构如下,主要包括实时数据处理、分布式训练和模型部署三大组件。通过Ray的分布式计算特性,支持跨语言编程(java实现流处理算子、python做模型开发)、exactly once、自动训练和模型更新等。
在线机器学习,最核心需要解决的问题就是打通流计算和模型训练,而使用Ray来做衔接这个是非常天然的(多语言、灵活调度)。数据流处理的最后一个节点是流计算的输出,worker节点消费数据,是模型训练的输入。Ray可以通过调度机制把两个计算调度在一个node上,实现数据共享从而实现两个模式的打通。
这个方式不仅可以兼容流计算和机器学习,也可以将其它模式进行衔接,比如streaming+mpp提供在线计算能力。
参考
-
Ray paper:www.usenix.org/system/file…
-
Ray 最近生态体系:www.anyscale.com/blog/whats-…
-
Ray1.0 白皮书:docs.google.com/document/d/…
-
Ray官网:www.ray.io/
-
Ray project:github.com/ray-project…
-
Ray商业化公司:www.anyscale.com/
-
蚂蚁在ray上的探索:mp.weixin.qq.com/s/MHEGQS81m…