Update documentation (#445)

* Update documentation for serialization.

* Update documentation for reusable variables.

* Update documentation for using Ray with TensorFlow. This change is to allow code blocks to be copied and pasted into a Python interpreter.

* Fix documentation for hyperparameter optimization example.
This commit is contained in:
Robert Nishihara
2016-10-12 15:41:00 -07:00
committed by Philipp Moritz
parent c6ec36865d
commit 1c3aaf7189
4 changed files with 66 additions and 53 deletions
+17 -16
View File
@@ -2,28 +2,29 @@
This document explains how to create and use reusable variables in Ray.
A reusable variable is a per-worker variable which is (1) created when the worker starts, and (2) is reinitialized before a task reuses it. Thus, while a task can modify a reusable variable, the variable is reinitialized before the next task uses it. Reusable variables obviates the need for serialization/deserialization and, like Ray objects, avoid side effects.
Reusable variables are Python objects that are created once on each worker and
are reused by all subsequent tasks that run on that worker. There are two
primary reasons for doing this.
can be used by all subsequent tasks that run on that worker. Reusable variables
will be reinitialized between tasks. There are several primary reasons for doing
this.
1. You want to use an object that is not serializable, and so the code that
creates the object must run on each worker.
2. Creating the object is slow (like a TensorFlow graph), and so you want to
create it only once.
1. Reusable variables are created once on each worker and are not shipped
between machines, so they do not need to be serialized or deserialized (however,
the code that creates the reusable variable does need to be pickled).
2. Objects that are slow to construct (like a TensorFlow graph) only need to be
constructed once on each worker.
3. By reinitializing between tasks that use them, they help avoid side effects.
To elaborate on the first point, certain Python objects cannot be easily shipped
between processes or machines. Certain objects simply are not meaningful on
other machines (such as a filehandle). For others, their classes may be largely
defined in C, and so their internals may be relatively opaque to Python.
In these situations, if we need the object on all of the workers, then each
worker needs to run the code that creates the object. We accomplish this using
reusable variables. These variables are created once on each worker and are
reused by every task that runs on that worker.
To elaborate on the first point, standard Python serialization libraries like
pickle fail on some objects. With these kinds of objects, it may be easier to
ship the code that creates the object to each worker and to run the code on each
worker than it would be to create the object on the driver and ship the object
to each worker.
## Creating a Reusable Variable
To give an example, consider a gym environment, which is essentially provides a
To give an example, consider a gym environment, which essentially provides a
Python wrapper for an Atari simulator.
```python
+37 -23
View File
@@ -1,7 +1,7 @@
# Serialization in the Object Store
This document describes what Python objects Ray can and cannot serialize into
the object store.
the object store. Once an object is placed in the object store, it is immutable.
There are a number of situations in which Ray will place objects in the object
store.
@@ -16,8 +16,8 @@ A normal Python object may have pointers all over the place, so to place an
object in the object store or send it between processes, it must first be
converted to a contiguous string of bytes. This process is known as
serialization. The process of turning the string of bytes back into a Python
object is known as deserialization. The process of serialization and
deserialization is often a bottleneck in distributed computing.
object is known as deserialization. The processes of serialization and
deserialization are often bottlenecks in distributed computing.
Pickle is one example of a library for serialization and deserialization in
Python.
@@ -34,19 +34,24 @@ large variety of Python objects. However, for numerical workloads, pickling and
unpickling can be inefficient. For example, when unpickling a list of numpy
arrays, pickle will create completely new arrays in memory. In Ray, when we
deserialize a list of numpy arrays from the object store, we will create a list
of numpy arrays in Python, but each numpy array will internally just wrap a
pointer to the object store's memory. This allows for minimal deserialization.
of numpy array objects in Python, but each numpy array object is essentially
just a pointer to the relevant location in the object store's memory. There are
some advantages to this form of serialization.
- Deserialization can be very fast.
- Memory is shared between processes so worker processes can all read the same
data without having to copy it.
## What Objects Does Ray Handle
However, Ray is not currently capable of serializing arbitrary Python objects.
The set of Python objects that Ray can serialize is the smallest set S with the
following properties.
The set of Python objects that Ray can serialize includes the following.
1. S contains primitive types: ints, floats, longs, bools, strings, unicode,
numpy arrays.
2. S contains objects whose classes can be registered with `ray.register_class`.
3. S contains any list, dictionary, or tuple whose elements belong in S.
1. Primitive types: ints, floats, longs, bools, strings, unicode, and numpy
arrays.
2. Any list, dictionary, or tuple whose elements can be serialized by Ray.
3. Objects whose classes can be registered with `ray.register_class`. This point
is described below.
## Registering Custom Classes
@@ -60,9 +65,13 @@ class Foo(object):
self.b = b
```
Simply calling `ray.put(Foo(1, 2))` will fail with a message like `Ray does not know
how to serialize the object <__main__.Foo object at 0x1077d7c50>.` This can be
addressed by calling `ray.register_class(Foo)`.
Simply calling `ray.put(Foo(1, 2))` will fail with a message like
```
Ray does not know how to serialize the object <__main__.Foo object at 0x1077d7c50>.
```
This can be addressed by calling `ray.register_class(Foo)`.
```python
import ray
@@ -75,8 +84,8 @@ class Foo(object):
self.a = a
self.b = b
# Calling ray.register_class(Foo) used to ship the class definition to all of
# the workers so that workers know how to construct new Foo objects.
# Calling ray.register_class(Foo) ships the class definition to all of the
# workers so that workers know how to construct new Foo objects.
ray.register_class(Foo)
# Create a Foo object, place it in the object store, and retrieve it.
@@ -111,21 +120,26 @@ efficient data structures like arrays.
**Note:** Another setting where the naive replacement of an object with its
`__dict__` attribute fails is where an object recursively contains itself (or
multiple objects recursively contain each other). For example, the code below
currently fails.
multiple objects recursively contain each other). For example, consider the code
below.
```python
l = []
l.append(l)
# Running ray.put(l) will crash due to an infinite loop.
```
It will throw an exception with a message like the following.
```
This object exceeds the maximum recursion depth. It may contain itself recursively.
```
# Last Resort Workaround
If you find cases where Ray does the wrong thing, please let us know so we can
fix it. In the meantime, you can do your own custom serialization and
deserialization (for example by calling pickle by hand). Or by writing your own
custom serializer and deserializer.
If you find cases where Ray doesn't work or does the wrong thing, please let us
know so we can fix it. In the meantime, you can do your own custom serialization
and deserialization (for example by calling pickle by hand). Or by writing your
own custom serializer and deserializer.
```python
import pickle
+8 -13
View File
@@ -47,13 +47,13 @@ def get_and_set_weights_methods():
for var in tf.trainable_variables():
assignment_placeholders.append(tf.placeholder(var.value().dtype, var.get_shape().as_list()))
assignment_nodes.append(var.assign(assignment_placeholders[-1]))
# Define a function for getting the network weights.
def get_weights():
return [v.eval(session=sess) for v in tf.trainable_variables()]
# Define a function for setting the network weights.
def set_weights(new_weights):
sess.run(assignment_nodes, feed_dict={p: w for p, w in zip(assignment_placeholders, new_weights)})
# Return the methods.
return get_weights, set_weights
get_weights, set_weights = get_and_set_weights_methods()
@@ -102,21 +102,20 @@ NUM_ITERS = 201
def net_vars_initializer():
# Seed TensorFlow to make the script deterministic.
tf.set_random_seed(0)
# Define the inputs.
x_data = tf.placeholder(tf.float32, shape=[BATCH_SIZE])
y_data = tf.placeholder(tf.float32, shape=[BATCH_SIZE])
# Define the weights and computation.
w = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
y = w * x_data + b
# Define the loss.
loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)
# Define the weight initializer and session.
init = tf.initialize_all_variables()
sess = tf.Session()
# Additional code for setting and getting the weights.
def get_and_set_weights_methods():
assignment_placeholders = []
@@ -124,17 +123,13 @@ def net_vars_initializer():
for var in tf.trainable_variables():
assignment_placeholders.append(tf.placeholder(var.value().dtype, var.get_shape().as_list()))
assignment_nodes.append(var.assign(assignment_placeholders[-1]))
def get_weights():
return [v.eval(session=sess) for v in tf.trainable_variables()]
def set_weights(new_weights):
sess.run(assignment_nodes, feed_dict={p: w for p, w in zip(assignment_placeholders, new_weights)})
return get_weights, set_weights
get_weights, set_weights = get_and_set_weights_methods()
# Return all of the data needed to use the network.
return get_weights, set_weights, sess, train, loss, x_data, y_data, init
def net_vars_reinitializer(net_vars):
+4 -1
View File
@@ -140,7 +140,7 @@ remaining_ids = []
for _ in range(100):
params = generate_random_params()
accuracy_id = train_cnn_and_compute_accuracy.remote(randparams, train_images, train_labels, validation_images, validation_labels)
result_ids.append(accuracy_id)
remaining_ids.append(accuracy_id)
# Process the tasks one at a time.
while len(remaining_ids) > 0:
@@ -151,6 +151,9 @@ while len(remaining_ids) > 0:
print "Accuracy {}".format(accuracy)
```
Note that the above example does not associate the accuracy with the parameters
that produced that accuracy, but this is done in the actual script.
## Additional notes
**Early Stopping:** Sometimes when running an optimization, it is clear early on