1. Actors
Ray中的远程函数被认为是功能性强和副作用低的框架。 仅限于远程函数的情况下,可以为我们提供分布式函数编程,这对于许多使用情况非常有用,但在实践中会受到一些限制。
Ray通过actor扩展了数据流模型。 一个actor本质上是一个有状态的worker(或service)。 当一个新的actor被实例化时,一个新的worker被创建,并且该actor的方法被安排在该特定的worker上,并且可以访问和改变该worker的状态。
假设我们已经开始了Ray。
import ray
ray.init()
2. 定义和创建一个actor
考虑下面的简单例子。装饰器 ray.remote表明类Counter实例化后为actor。
@ray.remote
class Counter(object):
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
return self.value
为了真正创建一个actor,我们通过调用Counter.remote()来实例化该类。
a1 = Counter.remote()
a2 = Counter.remote()
当一个actor被实例化,下面的情况发生
3. 使用actor
我们可以通过调用actor的方法来调度它的任务。
a1.increment.remote() # ray.get returns 1
a2.increment.remote() # ray.get returns 1
当a1.increment.remote()被调用,下面的情况发生。
然后,我们可以调用ray.get根据对象ID来获取实际值。
同样,对a2.increment.remote()的调用将产生一个任务,该任务被调度到第二个Counter actor上。 由于这两个任务在不同的actor上运行,它们可以并行执行(请注意,只有该actor上的方法才被调度分配到actor worker上,常规的远程函数不会)。
另一方面,在同一个Counter actor上调用的方法按照它们被调用的顺序依次执行。 因此它们可以相互共享状态,如下所示。
# Create ten Counter actors.
counters = [Counter.remote() for _ in range(10)]
# Increment each Counter once and get the results. These tasks all happen in parallel.
results = ray.get([c.increment.remote() for c in counters])
print(results) # prints [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
# Increment the first Counter five times. These tasks are executed serially and share state.
results = ray.get([counters[0].increment.remote() for _ in range(5)])
print(results) # prints [2, 3, 4, 5, 6]
4. 一个更加有趣的actor示例
一个常见的模式是使用actor来封装由外部库或服务管理的可变状态。
Gym为许多模拟环境提供了一个接口,用于测试和训练强化学习的agent。 这些模拟器是有状态的,使用这些模拟器的任务肯定会改变它们的状态。 我们可以使用actor来封装这些模拟器的状态。
import gym
@ray.remote
class GymEnvironment(object):
def __init__(self, name):
self.env = gym.make(name)
self.env.reset()
def step(self, action):
return self.env.step(action)
def reset(self):
self.env.reset()
我们然后如下在该actor节点上示例化一个actor并调度分配一个任务。
pong = GymEnvironment.remote("Pong-v0")
pong.step.remote(0) # Take action 0 in the simulator.
5. 在actor上使用GPU
一个常见的用例是actor包含一个神经网络。 例如,假设我们已经导入了Tensorflow并创建了一个构建神经网络的方法。
import tensorflow as tf
def construct_network():
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x, W) + b)
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
return x, y_, train_step, accuracy
我们然后可以如下为神经网络创建一个actor。
import os
# Define an actor that runs on GPUs. If there are no GPUs, then simply use
# ray.remote without any arguments and no parentheses.
@ray.remote(num_gpus=1)
class NeuralNetOnGPU(object):
def __init__(self):
# Set an environment variable to tell TensorFlow which GPUs to use. Note
# that this must be done before the call to tf.Session.
os.environ["CUDA_VISIBLE_DEVICES"] = ",".join([str(i) for i in ray.get_gpu_ids()])
with tf.Graph().as_default():
with tf.device("/gpu:0"):
self.x, self.y_, self.train_step, self.accuracy = construct_network()
# Allow this to run on CPUs if there aren't any GPUs.
config = tf.ConfigProto(allow_soft_placement=True)
self.sess = tf.Session(config=config)
# Initialize the network.
init = tf.global_variables_initializer()
self.sess.run(init)
为了表明一个actor需要用到GPU,我们将num_gpus = 1传递给ray.remote。 请注意,为了实现这一点,Ray必须初始化时指定使用GPU,例如,通过ray.init(num_gpus = 2)。 否则,当你尝试使用NeuralNetOnGPU.remote()实例化GPU版本时,会引发异常,说明系统中没有足够的GPU。
当actor创建时,它将有权通过ray.get_gpu_ids()得到可以使用的GPU的ID的列表。 这是一个整数列表,如[]或[1]或[2,5,6]。 由于我们传入了ray.remote(num_gpus = 1),因此此列表将具有一个长度。
我们可以将这一切放在一起,如下所示。
import os
import ray
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
ray.init(num_gpus=8)
def construct_network():
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x, W) + b)
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
return x, y_, train_step, accuracy
@ray.remote(num_gpus=1)
class NeuralNetOnGPU(object):
def __init__(self, mnist_data):
self.mnist = mnist_data
# Set an environment variable to tell TensorFlow which GPUs to use. Note
# that this must be done before the call to tf.Session.
os.environ["CUDA_VISIBLE_DEVICES"] = ",".join([str(i) for i in ray.get_gpu_ids()])
with tf.Graph().as_default():
with tf.device("/gpu:0"):
self.x, self.y_, self.train_step, self.accuracy = construct_network()
# Allow this to run on CPUs if there aren't any GPUs.
config = tf.ConfigProto(allow_soft_placement=True)
self.sess = tf.Session(config=config)
# Initialize the network.
init = tf.global_variables_initializer()
self.sess.run(init)
def train(self, num_steps):
for _ in range(num_steps):
batch_xs, batch_ys = self.mnist.train.next_batch(100)
self.sess.run(self.train_step, feed_dict={self.x: batch_xs, self.y_: batch_ys})
def get_accuracy(self):
return self.sess.run(self.accuracy, feed_dict={self.x: self.mnist.test.images,
self.y_: self.mnist.test.labels})
# Load the MNIST dataset and tell Ray how to serialize the custom classes.
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
# Create the actor.
nn = NeuralNetOnGPU.remote(mnist)
# Run a few steps of training and print the accuracy.
nn.train.remote(100)
accuracy = ray.get(nn.get_accuracy.remote())
print("Accuracy is {}.".format(accuracy))
6. 围绕actor句柄传递
Actor句柄可以传递到其他任务。 为了用一个简单的例子来说明这一点,考虑一个简单的actor定义。 此功能目前是实验性的,并受以下所述的限制。
@ray.remote
class Counter(object):
def __init__(self):
self.counter = 0
def inc(self):
self.counter += 1
def get_counter(self):
return self.counter
我们可以定义使用actor句柄的远程函数(或者actor方法)。f(counter)中的counter为一个actor的句柄。
@ray.remote
def f(counter):
while True:
counter.inc.remote()
如果我们实例化一个actor,我们可以将这个句柄传递给各种任务。
counter = Counter.remote()
# Start some tasks that use the actor.
[f.remote(counter) for _ in range(4)]
# Print the counter value.
for _ in range(10):
time.sleep(0.01)
print(ray.get(counter.get_counter.remote()))
7. 当前actor的限制
我们正在努力解决以下问题。