Ray:基于python的高性能实时并行机器学习框架

时间:2018-01-08 15:43:04   来源:lck5602   阅读:

前言

加州大学伯克利分校实时智能安全执行实验室(RISELab)的研究人员已开发出了一种新的分布式框架,该框架旨在让基于Python的机器学习和深度学习工作负载能够实时执行,并具有类似消息传递接口(MPI)的性能和细粒度。这种框架名为Ray,看起来有望取代Spark,业界认为Spark对于一些现实的人工智能应用而言速度太慢了;过不了一年,Ray应该会准备好用于生产环境。 
目前ray已经发布了0.3.0版本,可以查看代码

本文主要翻译自官方文档的介绍

介绍

为了使用ray,你需要了解以下内容:

  • Ray是如何异步执行任务,从而获得并行效果的
  • Ray是如何利用对象IDs来表示不可变的远程对象

概述

Ray是一个基于Python的分布式执行引擎。相同的代码可以在单个机器上运行以实现高效的多处理,并且可以在群集上用于大量的计算。

使用Ray时,涉及以下几个过程

  • 多个工作进行执行任务,并将结果村存储在对象库中,每个进程是一个独立的处理单位。
  • 每个节点的存储不可变的对象在共享内存中,并允许进程在相同节点上高效复制和反序列化对象
  • 一个全局调度器调度接收任务,并将它们分配到其他地方节点运行
  • 一个driver是用户控制的python程序。例如,如果用户正在运行脚本或使用python shell,那么driver就是运行的脚本或者python进程。driver与工作程序类似,都可以将任务提交给本地调度程序,并从对象存储中获取对象,但不同之处在于本地调度程序不会讲任务分配给要执行的driver
  • 一个Redis服务器维护大量的系统状态,例如,他跟踪哪些对象在哪些机器上以及任务规范(而不是数据)上,他可以直接用于调试目的的查询。

开始使用Ray

开始使用Ray,只需要执行下面的命令

import ray
ray.init()
  • 1
  • 2

不可变远程对象

在Ray中,我们可以创建和计算对象。我们将这些对象称为 远程对象,并使用对象ID来引用它们。远程对象存储在对象存储中,并且群集中每个节点都有一个对象存储。在集群设置中,我们可能实际上并不知道每个对象所在的机器。

一个对象ID本质上是一个唯一的ID可以被用来指代一个远程对象。如果您对Futures熟悉,我们的对象ID在概念上是相似的。

我们假设远程对象是不可变的。也就是说,它们的值在创建后不能改变。这允许远程对象在多个对象存储中被复制,而不需要同步副本。

Put 和 Get

命令ray.get和ray.put可用于Python对象之间进行转换和对象ID,如示于以下的例子。

x  =  "example"
ray.put (x )  #ObjectID(b49a32d72057bdcfc4dda35584b3d838aad89f5d)
  • 1
  • 2

该命令ray.put(x)将由工作进程或驱动程序进程运行(驱动程序进程是运行脚本的进程)。它需要一个Python对象,并将其复制到本地对象存储区(这里的本地手段在同一个节点上)。一旦对象被存储在对象存储中,其值就不能被改变。

另外,ray.put(x)返回一个对象ID,它本质上是一个可以用来引用新创建的远程对象的ID。如果我们把对象ID保存在一个变量中,那么我们就可以传入远程函数,这些远程函数将在相应的远程对象上运行

ray.x_id = ray.put(x)
  • 1

该命令ray.get(x_id)获取一个对象ID,并从相应的远程对象中创建一个Python对象。对于像数组这样的对象,我们可以使用共享内存,避免复制对象。对于其他对象,这将对象从对象存储复制到工作进程的堆。如果与对象ID相对应的远程对象x_id不是与调用的worker相同的节点上ray.get(x_id),则远程对象将首先从具有该远程对象的对象库转移到需要它的对象库。

x_id  =  ray.get("example")
ray.get(x_id )  #“example”
  • 1
  • 2

如果与对象ID对应的远程对象x_id尚未创建,则该命令ray.get(x_id)将等待,直到创建远程对象。

一个非常常见的用例ray.get是获取对象ID的列表。在这种情况下,你可以调用ray.get(object_ids), 其中object_ids的对象ID的列表。

result_ids  =  [ ray.put(i) for i in range(10)] 
ray.get(result_ids)  #[0123456789]
  • 1
  • 2

Ray 中的异步计算

Ray允许任意Python函数异步执行。这是通过将Python函数指定为远程函数来完成的。

例如,一个普通的Python函数看起来像这样。

def  add1 (a , b ):
    return  a  +  b
  • 1
  • 2

一个远程函数看起来像这样。

@ray.remote 
def  add2 (a , b ):
    return  a  +  b
  • 1
  • 2
  • 3

远程功能

然而调用返回并导致Python解释器阻塞,直到计算完成,调用 立即返回一个对象ID并创建一个任务。该任务将由系统调度并异步执行(可能在不同的机器上)。当任务完成执行时,其返回值将被存储在对象存储中。

x_id  =  add2.remote(1 , 2)
ray.get(x_id )  #3
  • 1
  • 2

以下简单示例演示了如何使用异步任务来并行化计算。

import time

def  f1():
    time.sleep(1)

@ray.remote 
def f2():
    time.sleep(1)

#以下需要十秒。
[ f1() for _ in range(10)]

#以下需要一秒(假设系统至少有10个CPU)。
ray.get([ f2.remote() for _ in range(10)])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

提交任务和执行任务之间存在明显的区别。当调用远程函数时,执行该函数的任务将被提交给本地调度程序,并立即返回任务输出的对象ID。但是,在系统实际上在工作人员上安排任务之前,任务不会被执行。任务执行不是懒惰地完成的。系统将输入数据移动到任务中,一旦输入相关性可用并且有足够的资源进行计算,任务将立即执行。

提交任务时,每个参数可以通过值或对象ID传入。例如,这些行具有相同的行为。

add2.remote(1, 2)
add2.remote(1, ray.put(2))
add2.remote(ray.put(1), ray.put(2))
  • 1
  • 2
  • 3

远程函数永远不会返回实际值,它们总是返回对象ID。

当远程函数被实际执行时,它对Python对象进行操作。也就是说,如果使用任何对象ID调用远程函数,系统将从对象存储中检索相应的对象。

请注意,远程函数可以返回多个对象ID。

@ray.remote(num_return_vals=3)
def return_multiple():
    return 1, 2, 3

a_id, b_id, c_id = return_multiple.remote()
  • 1
  • 2
  • 3
  • 4
  • 5

表达任务之间的依赖关系

程序员可以通过将一个任务的对象ID输出作为参数传递给另一个任务来表达任务之间的依赖关系。例如,我们可以启动三个任务,每个任务都依赖于前一个任务。

@ray.remote
def f(x):
    return x + 1

x = f.remote(0)
y = f.remote(x)
z = f.remote(y)
ray.get(z) # 3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

上面的第二个任务将不会执行,直到第一个任务完成,第三个任务将不会执行直到第二个任务完成。在这个例子中,没有并行的机会。

编写任务的能力可以很容易地表达有趣的依赖关系。考虑下面的一个树减少的实现。

import numpy as np

@ray.remote
def generate_data():
    return np.random.normal(size=1000)

@ray.remote
def aggregate_data(x, y):
    return x + y

# Generate some random data. This launches 100 tasks that will be scheduled on
# various nodes. The resulting data will be distributed around the cluster.
data = [generate_data.remote() for _ in range(100)]

# Perform a tree reduce.
while len(data) > 1:
    data.append(aggregate_data.remote(data.pop(0), data.pop(0)))

# Fetch the result.
ray.get(data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

远程功能中的远程功能

到目前为止,我们一直只从驱动程序调用远程功能。但是工作进程也可以调用远程函数。为了说明这一点,请考虑下面的例子。

@ray.remote
def sub_experiment(i, j):
    # Run the jth sub-experiment for the ith experiment.
    return i + j

@ray.remote
def run_experiment(i):
    sub_results = []
    # Launch tasks to perform 10 sub-experiments in parallel.
    for j in range(10):
        sub_results.append(sub_experiment.remote(i, j))
    # Return the sum of the results of the sub-experiments.
    return sum(ray.get(sub_results))

results = [run_experiment.remote(i) for i in range(5)]
ray.get(results) # [45, 55, 65, 75, 85]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在run_experiment工作人员上执行远程功能时sub_experiment,会多次调用远程功能。这是一个例子,说明多个实验,每个实验在内部利用并行性,都可以并行运行。

分享:0

电话咨询

客服热线服务时间

周一至周五 9:00-21:00

周六至周日 9:00-18:00

咨询电话

021-67690939
15201841284

微信扫一扫