From 1c3aaf7189628440afa8f686947171ededcea9e9 Mon Sep 17 00:00:00 2001 From: Robert Nishihara Date: Wed, 12 Oct 2016 15:41:00 -0700 Subject: [PATCH] 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. --- doc/reusable-variables.md | 33 +++++++++--------- doc/serialization.md | 60 ++++++++++++++++++++------------ doc/using-ray-with-tensorflow.md | 21 +++++------ examples/hyperopt/README.md | 5 ++- 4 files changed, 66 insertions(+), 53 deletions(-) diff --git a/doc/reusable-variables.md b/doc/reusable-variables.md index 35a53cfa5..38a5b698c 100644 --- a/doc/reusable-variables.md +++ b/doc/reusable-variables.md @@ -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 diff --git a/doc/serialization.md b/doc/serialization.md index c55033f5a..16c76fe3c 100644 --- a/doc/serialization.md +++ b/doc/serialization.md @@ -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 diff --git a/doc/using-ray-with-tensorflow.md b/doc/using-ray-with-tensorflow.md index fbf713d52..eeb596179 100644 --- a/doc/using-ray-with-tensorflow.md +++ b/doc/using-ray-with-tensorflow.md @@ -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): diff --git a/examples/hyperopt/README.md b/examples/hyperopt/README.md index a94512f00..b46471d0e 100644 --- a/examples/hyperopt/README.md +++ b/examples/hyperopt/README.md @@ -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