新一代分布式计算引擎Ray

1,865 阅读12分钟

一、Ray介绍

1.Ray是什么

Ray是一个开源的分布式计算框架,它提供了一套简单、灵活和通用的****API来构建和运行分布式应用程序。

Ray出身于UC Berkeley RISELab实验室,而RISELab实验室前身就是设计了Apache Spark、Apache Mesos和Alluxio的大名鼎鼎AMPLab实验室。Ray的核心作者就是Spark的核心作者, RISELab实验室主管教授Ion Stoica。

image

2.为什么有Ray

Ray设计之初主要用以解决增强学习(Reinforcement Learing, RL)中遇到的问题。

RL学习是机器学习中的一个领域,强调如何基于环境状态变动而执行具体动作,以取得最大化的预期收益。RL是监督学习和非监督学习之外的第三种基本机器学习方法。

RL应用的核心是能够通过学习得到一个策略(Policy),这个策略就是从环境状态(State)到动作(Action)的映射,通过映射来取得最大化的预期收益。而为了寻找这个有效的策略就需要应用程序具备三个主要能力:

  1. 模拟仿真能力(Simulations)。依赖模拟仿真(Simulations)来评估策略,通过Simulations探索更多Action选择,来感知Action选择所对应的预期收益。

  2. 分布式训练( Distributed training)。和监督学习类似,RL算法也需要基于Simulations或实际环境交互生成的数据集进行分布式训练,从而改进策略。

  3. 策略服务(Serve)。对训练出的策略,最终要能够以Serving能力对外提供策略服务(根据环境状态给出Action决策)。

image

而上面这些特性也对系统提出了新的需求:

  1. 细粒度计算(Fine-grained)。RL系统需要支持细粒度计算,比如在与现实世界交互时要能够以毫秒为单位给出Action,并能够执行大量模拟。

  2. 异构能力(Heterogeneity)。在时间和资源使用上支持异构,比如模拟任务可能需要几毫秒或几小时(支持多种运行时长的任务),GPU一般用于Training而CPU用于Simulations(支持多种资源)。

  3. 动态执行(Dynamic)。在模拟或与环境实际交互中的结果可以改变之后的计算(动态任务执行)。

综上,Ray的作者们认为:需要一个动态计算框架,能够以毫秒的延迟每秒处理数百万的异构任务。

更详细的背景,可以阅读Ray在发表的论文:Ray: A Distributed Framework for Emerging AI Applications

3.Ray架构

我们一般所说的Ray架构是指Ray-core的架构。随着Ray的发展,Ray的整体架构从最早论文提出到现在已经有很多改变。下图是Ray最新整体架构:

image

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

image

但在实践中发现,在很多调度场景这种调度机制是无法满足情况的,比如想要亲和反亲和调度一些task,这种调度机制就无法满足需求了。

所以蚂蚁又在当前双层调度框架之外增加了PlacementGroup全局调度策略,能够对绑定的一组actor进行亲和或反亲和调度。(该调度机制也已经开贡献到社区了)

image

4.Ray API

Ray core的核心就是Ray API,就像Ray官网介绍Ray称:Ray provides a simple, universal API for building distributed applications.

Ray API设计目标是致力于提供一个分布式系统系统的API。通过Ray API来编写的分布式应用程序就像开发一个本机应用一样。

image

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)。

image

  • Ray Actor:使用@ray.remote()注解修饰的Class/Object就是我们上面说的有状态计算单元Actor,Ray Actor会把Class/Oject分布式化。

image

  • 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是如何调度执行这个任务的。

image

0. 首先将定义的add方法注册到GCS中,这样每个节点就都能感知这个function了。

  1. 执行 id = add.remote(a,b)(这里可以看到参数a和b已经存储在了ObjectStore中)。首先在Local Scheduler(现在称为Raylet)进行调度。

  2. N1的Local Scheduler发现本地ObjectStore只有a,这时候向Global Scheduler(GCS)求助。

  3. Global Scheduler查询 GCS的Object Table获取b的位置,发现在N2节点。

  4. Global Schedule决定将任务调度到N2执行(并不是b在N2就将任务调度到N2,比如N1负载过高)。

  5. N2的Local Scheduler发现本地没有执行所需要的a。

  6. N2向GCS的Object Table获取a的位置。

  7. N2从N1的Object Store中把a拷贝到N2本地的Object Store。

  8. 在N2上执行任务。

接下来我们再看获取执行结果语句c = ray.get(id)执行流程。

image

  1. 首先使用Object ref c来请求本地LocalScheduler(Raylet)。

  2. N1没有缓存Object ref c,则向GCS申请。但是此时c还没有计算完成,此时N1会在GCS的Object table注册一个callback。

  3. N2 add任务执行完成,会把结果c存储到本地的Object store中。

  4. N2 在本地存储完c时,也会把结果同步给GCS,告知GCS 现在c的结果存储在N2节点中。

  5. GCS触发刚才N1注册的callback。

  6. N1从N2中将结果c拷贝到本地的object store中。

  7. 返回结果。

6.Ray生态体系

Ray除了Ray-core层的通用分布式计算框架外,在上层还具有丰富的用于构建AI和机器学习应用库。比如:

  • 用于增强学习的库RLib。

  • 超参数调整的Ray Tune。

  • 用于深度学习的Ray Train。

  • 用于模型服务的Ray Server。

image

随着Ray的发展,Ray生态不仅有自身提供的各种库,还有很多第三方应用集成到Ray。比如:

  • Python中用于分析计算的并行计算库Dask on Ray。

  • 基于NumPy强大的数据分析包Pandas on Ray。

image

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作为分布式计算的基础设施,支持包括图计算、在线机器学习等应用框架,驱动上层的实施风控、知识图谱等应用。

image

之所以蚂蚁在Ray core的上层计算引擎框架上投入很多,一个主要的原因就是蚂蚁想要做下一代计算体系:融合计算

7.融合计算

为什么要做融合计算

当我们解决一个问题时,可能需要多项技术进行深度融合(OLAP、图计算、流计算、深度学习等),但在不同技术结合到一起时候,会遇到多方面挑战:

  • 复杂系统的协调:当多个计算引擎组合到一起,系统复杂性大大上升,也加大了工程师对复杂系统的协调难度。

  • 性能优化:在进行性能优化时,工程师需要面对多种分属不同领域的计算引擎,导致系统难以突破性能瓶颈。

  • 开发效率:工程师需要熟悉多个引擎如何协同工作,出现问题也难以定位和排查,导致开发效率低下。

为什么要用Ray做融合计算

Ray天然具备以下优势:

  • 简单易用的API。

  • 同时支持多个编程语言。(跨语言能力极强)

  • 弹性且可自定义的任务调度。

  • 分布式状态管理。

  • 易用的错误处理和故障恢复。

  • 低成本的DevOps。

融合计算-在线机器学习

传统的在线机器学习系统是一系列子系统组成的任务Pipeline,原始的实时数据要经过过滤、数据聚合、样本拼接、采样、模型训练和模型部署等系统才能被使用。这就为数据一致性、系统稳定性和多平台等挑战。总结下来主要挑战如下:

  • SLA:整个链路的SLA会受到某个模块SLA的影响,并随着模块的增多而放大,稳定性成为制约业务发展的重要因素。

  • 系统效率:模块中间的衔接多数通过数据落盘来进行,模块间的调度通过系统调度来实现,造成不必要的I/O、计算和网络开销。

  • 开发和运维成本:各个模块风格迥异,开发模式、计算框架、甚至代码风格都不一致,开发和运维对接时需要花很多时间去熟悉系统,降低业务开发效率。

融合如何去做?

核心解决逻辑就是将系统间的衔接转换为系统内的衔接,将作业的调度转换为任务的调度,这样把计算与计算之间进行协同调度,从而提高系统效率和降低网络带宽。

基于Ray开发的端到端在线机器学习架构如下,主要包括实时数据处理、分布式训练和模型部署三大组件。通过Ray的分布式计算特性,支持跨语言编程(java实现流处理算子、python做模型开发)、exactly once、自动训练和模型更新等。

image

在线机器学习,最核心需要解决的问题就是打通流计算和模型训练,而使用Ray来做衔接这个是非常天然的(多语言、灵活调度)。数据流处理的最后一个节点是流计算的输出,worker节点消费数据,是模型训练的输入。Ray可以通过调度机制把两个计算调度在一个node上,实现数据共享从而实现两个模式的打通。

image

这个方式不仅可以兼容流计算和机器学习,也可以将其它模式进行衔接,比如streaming+mpp提供在线计算能力。

参考

  1. Ray paper:www.usenix.org/system/file…

  2. Ray 最近生态体系:www.anyscale.com/blog/whats-…

  3. Ray1.0 白皮书:docs.google.com/document/d/…

  4. Ray官网:www.ray.io/

  5. Ray project:github.com/ray-project…

  6. Ray商业化公司:www.anyscale.com/

  7. 蚂蚁在ray上的探索:mp.weixin.qq.com/s/MHEGQS81m…