1.Ray: 分布式执行框架 A Distributed Execution Framework for AI Applications 介绍

ray

Tutorial

要使用Ray,您需要了解以下内容:

  • Ray如何异步执行任务以实现并行性。
  • Ray如何使用对象ID来表示不可变的远程对象。

Overview

Ray是一个基于Python的分布式执行引擎。可以在单台机器上运行相同的代码以实现高效的多处理,并且可将其用于大型计算的群集。使用Ray时,涉及几个过程。

  • 多个 worker 进程执行任务并将结果存储在对象库(objective stores)中。每个worker都是一个独立的过程。
  • 每个节点的一个 对象存储(object store) 将不可变对象存储在共享内存中,并允许worker在最小复制和反序列化的情况下有效地共享同一节点上的对象。
  • 每个节点的一个 本地调度程序(local scheduler) 将任务分配给同一节点上的worker。
  • 全局调度程序(global scheduler) 从本地调度程序接收任务并将其分配给其他本地调度程序。
  • driver是用户控制的Python进程。例如,如果用户正在运行脚本或使用Python shell,那么driver是运行脚本或shell的Python进程。driver与worker类似,可以将任务提交到本地调度程序并从对象存储区获取对象,但不同之处在于本地调度程序不会将任务分配给要执行的driver。
  • Redis服务器维护系统的大部分状态。例如,它会跟踪哪些对象在哪些机器上以及任务规范(而不是数据)上。它也可以直接用于调试目的查询。

Immutable remote objects

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

对象ID本质上是一个唯一的ID,可用于引用远程对象。如果您熟悉期货,我们的对象ID在概念上相似。

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

Put and Get

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

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

命令ray.put(x)将由worker process或driver process运行(driver process是运行脚本的进程)。它需要一个Python对象并将其复制到本地对象存储区(这里的本地方式表示在同一个节点上)。一旦对象被存储在对象存储中,它的值就不能被改变。

另外,ray.put(x)返回一个对象ID,它本质上是一个可以用来引用新创建的远程对象的ID。如果我们用x_id = ray.put(x)将对象ID保存到变量中,那么我们可以将x_id传递给远程函数,并且这些远程函数将对相应的远程对象进行操作。

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

x_id = ray.put("example")
ray.get(x_id)  # "example"

如果尚未创建与对象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)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Asynchronous Computation in Ray

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

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

def add1(a, b):
    return a + b

远程函数看起来像这样:

@ray.remote
def add2(a, b):
    return a + b

remote function

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

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

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

import time

def f1():
    time.sleep(1)

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

# The following takes ten seconds.
[f1() for _ in range(10)]

# The following takes one second (assuming the system has at least ten CPUs).
ray.get([f2.remote() for _ in range(10)])

提交任务执行任务存在明显的区别。当调用远程函数时,执行该函数的任务将被提交给本地调度程序,并立即返回任务输出的对象ID。然而,在系统实际调度worker上的任务之前,将不执行任务。任务执行不是懒惰地完成的。系统将输入数据移动到任务中,只要其输入依赖项可用并且有足够的资源用于计算,任务就会执行。

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

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

远程函数永远不会返回实际值,它们总是返回对象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()

Expressing dependencies between tasks(表达任务之间的依赖关系)

程序员可以通过将一个任务的对象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

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

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

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)

Remote Functions Within Remote Functions(远程功能中的远程功能)

到目前为止,我们一直只从driver调用远程功能。但worker也可以调用远程函数。为了说明这一点,请考虑以下示例。

@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]

当在一个worker上执行远程函数run_experiment时,它会多次调用远程函数sub_experiment。这是一个例子,说明了多个实验(每个实验在内部利用并行性)都可以并行运行。

你可能感兴趣的:(机器学习,深度学习,tensorflow,pytorch,python)