ray分布式框架的介绍

使用ray,你需要解决以下两个问题:
1.ray执行异步任务时是怎样实现平行的。(ray集群中每个节点共享本节点本地存储,节点中worker并行运行,集群间worker的并行)
2.ray是怎样使用对象ID来表示不可变对象的远程对象的。(任务执行前,对象值已存入存储对象中,任务执行是通过对象ID调用存储对象的)

综述

Ray是一个分布式执行引擎。同样的代码可以再一台机器上实现高效的多处理,也可以在集群是用于大型的计算。

当我们使用Ray时,涉及到多个进程。

  • 多worker进程执行多个任务并将结果存储在对象存储中,每个worker都是一个独立的进程。
  • 每个节点上的对象存储都将不可变的对象存储存储在共享内存中,允许worker以少量的复制和并行化有效的共享同一节点上的存储对象。
  • 每一节点上的本地调度将任务分配给同一节点上的worker(一个节点上的本地调度把任分配给本节点的worker)
  • 一个driver是用户控制的python进程。例如,如果用户正在运行脚本或者使用python shell,那么driver就是运行脚本或者shell的python进程。driver和worker很相似,他们都可以提交任务给本地调度并从对象存储中获取对象,但是不同之处是本地调度不会将任务分配给driver执行。
  • Redis服务器维护系统的大部分状态。例如,它跟踪哪些对象位于哪些机器上,以及任务规范(但不包括数据)。另外可以对有问题的目标进行直接的质问(就是提示错误)。

运行Ray

启动python并运行一下命令:

import ray
ray.init()

ray启动了。(其中ray.init()方法可以加参数,具体请参考Ray相关的API

不可变的远程(remote)对象

在Ray中,我们可以在对象上创建和计算。我们将这些对象称为远程(remote)对象,并使用对象ID来引用它们。remote对象是被存储在对象存储中的,在集群中每个节点都有一个存储对象。在集群设置中,我们可能实际上不知道每个存储对象的位置。
对象ID本质上是一个唯一的ID,可以被用作引用远程(remote)对象。如果您熟悉future(期货),它和对象ID是很相似的。
我们规定远程对象是不可变的。也就是说,它们的值在创建之后不能更改。这允许在多个对象存储中复制远程对象,而不需要同步副本。

Put和Get

ray.getray.put是用作python对象和对象ID之间的转换,下边是一个例子。

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

ray.put(x)命令的运行是通过worker进程或者driver进程(dervier进程是正在运行的脚本)。它把一个python对象复制到本地对象存储中。一旦对象的被存入存储对象后,他的值就不能被改变了。
此外,ray.put(x) 返回的是一个对象ID,它本质上是一个ID,可用来引用新创建的远程(remote)对象。如果我们保存一个对象ID在一个变量(x_id=ray.put(x))中,然后我们可以将x_id传递给远程函数,这些远程函数将对相应的远程对象进行操作。
命令ray.get(x_id)接受一个对象ID,并从相应的远程对象创建一个Python对象。对于像数组这样的对象,我们可以使用内存的共享从而避免复制对象。对于其他对象,它将对象从对象存储中复制到worker进程的堆。如果与对象ID x_id对应的远程(remote)对象与调用ray.get(x_id)的worker不在同一个节点上,那么远程(remote)对象将首先从拥有它的对象存储区转移到需要它的对象存储区。

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

如果与对象ID x_id对应的远程对象尚未创建,则命令ray.get(x_id)将等待创建远程对象。
ray的一个非常常见的用例ray.get是获取对象id列表。在本例中,您可以调用ray.get(object_id),其中object_id是对象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]
ray.get(result_ids[5]) 	# 5
ray.get(result_ids[1]) 	# 1

Ray的异步计算

Ray支持异步执行任意Python函数。这是通过将Python函数指定为远程函数来实现的。

例如,一个普通的Python函数是这样的。

扫描二维码关注公众号,回复: 5754160 查看本文章
def add1(a, b):
    return a + b

远程函数是这样的。

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

远程函数(remote functions)

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

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)

# 这个操作需要10秒.
[f1() for _ in range(10)]

# 下面的操作只需要一秒钟(假设系统至少有10个cpu)
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()

任务之间的依赖关系

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

上面的第二个任务在第一个任务完成之前不会执行,第三个任务在第二个任务完成之前不会执行。在这个例子中,没有体现并行。
组合的任务更能体现任务之间的依赖关系。考虑以下树缩减的实现。

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

# 生成一些随机数据。这将启动100个任务,这些任务将在各个节点上调度。
# 结果数据将分布在集群的各个节点中。此时date的ID内存中有1000*100个数据
data = [generate_data.remote() for _ in range(100)]

# 执行树缩减。
while len(data) > 1:
    data.append(aggregate_data.remote(data.pop(0), data.pop(0)))

#获取结果 1000个数据
ray.get(data)

远程函数中的远程函数

到目前为止,我们只从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]

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

此篇主要参考Ray官网,如有错误,请阅读者提出指正,谢谢!
原英文链接:https://ray.readthedocs.io/en/latest/tutorial.html
ray API 中文:https://blog.csdn.net/weixin_43255962/article/details/88850456

猜你喜欢

转载自blog.csdn.net/weixin_43255962/article/details/88689665
今日推荐