From 411f30c1251e1f2fc49d6075d991283cf363a2c7 Mon Sep 17 00:00:00 2001 From: Richard Liaw Date: Wed, 28 Aug 2019 17:54:15 -0700 Subject: [PATCH] [docs] Second push of changes (#5391) --- README.rst | 182 ++++++++-- ci/jenkins_tests/run_multi_node_tests.sh | 14 +- doc/examples/doc_code/tf_example.py | 94 +++++ doc/examples/doc_code/torch_example.py | 180 +++++++++ doc/source/actors.rst | 27 +- doc/source/advanced.rst | 160 +++----- doc/source/async_api.rst | 9 - doc/source/configure.rst | 4 +- doc/source/deploy-on-kubernetes.rst | 2 +- doc/source/distributed_training.rst | 116 ++++-- doc/source/example-resnet.rst | 3 +- doc/source/fault-tolerance.rst | 2 +- doc/source/images/tune-wide.png | Bin 0 -> 45254 bytes doc/source/index.rst | 70 ++-- doc/source/inspect.rst | 25 +- doc/source/install-source.rst | 179 --------- doc/source/installation.rst | 152 +++++++- doc/source/object-store.rst | 162 +++++++++ doc/source/tune-usage.rst | 11 +- doc/source/tune.rst | 8 +- doc/source/user-profiling.rst | 341 +----------------- doc/source/using-ray-with-pytorch.rst | 96 +++++ doc/source/using-ray-with-tensorflow.rst | 304 +++------------- doc/source/walkthrough.rst | 48 +-- .../sgd/examples/train_example.py | 78 ++-- .../experimental/sgd/examples/tune_example.py | 101 ++++++ python/ray/tune/README.rst | 2 +- 27 files changed, 1299 insertions(+), 1071 deletions(-) create mode 100644 doc/examples/doc_code/tf_example.py create mode 100644 doc/examples/doc_code/torch_example.py create mode 100644 doc/source/images/tune-wide.png delete mode 100644 doc/source/install-source.rst create mode 100644 doc/source/object-store.rst create mode 100644 doc/source/using-ray-with-pytorch.rst create mode 100644 python/ray/experimental/sgd/examples/tune_example.py diff --git a/README.rst b/README.rst index f93dbf04c..acd94fccc 100644 --- a/README.rst +++ b/README.rst @@ -11,49 +11,169 @@ | + **Ray is a fast and simple framework for building and running distributed applications.** +Ray is packaged with the following libraries for accelerating machine learning workloads: -Ray is easy to install: ``pip install ray`` +- `Tune`_: Scalable Hyperparameter Tuning +- `RLlib`_: Scalable Reinforcement Learning +- `Distributed Training `__ -Example Use +Install Ray with: ``pip install ray``. For nightly wheels, see the `Installation page `__. + +Quick Start ----------- -+------------------------------------------------+----------------------------------------------------+ -| **Basic Python** | **Distributed with Ray** | -+------------------------------------------------+----------------------------------------------------+ -|.. code-block:: python |.. code-block:: python | -| | | -| # Execute f serially. | # Execute f in parallel. | -| | | -| | @ray.remote | -| def f(): | def f(): | -| time.sleep(1) | time.sleep(1) | -| return 1 | return 1 | -| | | -| | | -| | ray.init() | -| results = [f() for i in range(4)] | results = ray.get([f.remote() for i in range(4)]) | -+------------------------------------------------+----------------------------------------------------+ +Execute Python functions in parallel. + +.. code-block:: python + + import ray + ray.init() + + @ray.remote + def f(x): + return x * x + + futures = [f.remote(i) for i in range(4)] + print(ray.get(futures)) + +To use Ray's actor model: + +.. code-block:: python -Ray comes with libraries that accelerate deep learning and reinforcement learning development: + import ray + ray.init() -- `Tune`_: Hyperparameter Optimization Framework -- `RLlib`_: Scalable Reinforcement Learning -- `Distributed Training `__ + @ray.remote + class Counter(): + def __init__(self): + self.n = 0 -.. _`Tune`: http://ray.readthedocs.io/en/latest/tune.html -.. _`RLlib`: http://ray.readthedocs.io/en/latest/rllib.html + def increment(self): + self.n += 1 -Installation ------------- + def read(self): + return self.n -Ray can be installed on Linux and Mac with ``pip install ray``. + counters = [Counter.remote() for i in range(4)] + [c.increment.remote() for c in counters] + futures = [c.read.remote() for c in counters] + print(ray.get(futures)) -To build Ray from source or to install the nightly versions, see the `installation documentation`_. -.. _`installation documentation`: http://ray.readthedocs.io/en/latest/installation.html +Ray programs can run on a single machine, and can also seamlessly scale to large clusters. To execute the above Ray script in the cloud, just download `this configuration file `__, and run: + +``ray submit [CLUSTER.YAML] example.py --start`` + +Read more about `launching clusters `_. + +Tune Quick Start +---------------- + +.. image:: https://github.com/ray-project/ray/raw/master/doc/source/images/tune-wide.png + +`Tune`_ is a library for hyperparameter tuning at any scale. + +- Launch a multi-node distributed hyperparameter sweep in less than 10 lines of code. +- Supports any deep learning framework, including PyTorch, TensorFlow, and Keras. +- Visualize results with `TensorBoard `__. +- Choose among scalable SOTA algorithms such as `Population Based Training (PBT)`_, `Vizier's Median Stopping Rule`_, `HyperBand/ASHA`_. +- Tune integrates with many optimization libraries such as `Facebook Ax `_, `HyperOpt `_, and `Bayesian Optimization `_ and enables you to scale them transparently. + +To run this example, you will need to install the following: + +.. code-block:: bash + + $ pip install ray torch torchvision filelock + + +This example runs a parallel grid search to train a Convolutional Neural Network using PyTorch. + +.. code-block:: python + + + import torch.optim as optim + from ray import tune + from ray.tune.examples.mnist_pytorch import ( + get_data_loaders, ConvNet, train, test) + + + def train_mnist(config): + train_loader, test_loader = get_data_loaders() + model = ConvNet() + optimizer = optim.SGD(model.parameters(), lr=config["lr"]) + for i in range(10): + train(model, optimizer, train_loader) + acc = test(model, test_loader) + tune.track.log(mean_accuracy=acc) + + + analysis = tune.run( + train_mnist, config={"lr": tune.grid_search([0.001, 0.01, 0.1])}) + + print("Best config: ", analysis.get_best_config(metric="mean_accuracy")) + + # Get a dataframe for analyzing trial results. + df = analysis.dataframe() + +If TensorBoard is installed, automatically visualize all trial results: + +.. code-block:: bash + + tensorboard --logdir ~/ray_results + +.. _`Tune`: https://ray.readthedocs.io/en/latest/tune.html +.. _`Population Based Training (PBT)`: https://ray.readthedocs.io/en/latest/tune-schedulers.html#population-based-training-pbt +.. _`Vizier's Median Stopping Rule`: https://ray.readthedocs.io/en/latest/tune-schedulers.html#median-stopping-rule +.. _`HyperBand/ASHA`: https://ray.readthedocs.io/en/latest/tune-schedulers.html#asynchronous-hyperband + +RLlib Quick Start +----------------- + +`RLlib`_ is an open-source library for reinforcement learning built on top of Ray that offers both high scalability and a unified API for a variety of applications. + +.. code-block:: bash + + pip install tensorflow # or tensorflow-gpu + pip install ray[rllib] # also recommended: ray[debug] + +.. code-block:: python + + import gym + from gym.spaces import Discrete, Box + from ray import tune + + class SimpleCorridor(gym.Env): + def __init__(self, config): + self.end_pos = config["corridor_length"] + self.cur_pos = 0 + self.action_space = Discrete(2) + self.observation_space = Box(0.0, self.end_pos, shape=(1, )) + + def reset(self): + self.cur_pos = 0 + return [self.cur_pos] + + def step(self, action): + if action == 0 and self.cur_pos > 0: + self.cur_pos -= 1 + elif action == 1: + self.cur_pos += 1 + done = self.cur_pos >= self.end_pos + return [self.cur_pos], 1 if done else 0, done, {} + + tune.run( + "PPO", + config={ + "env": SimpleCorridor, + "num_workers": 4, + "env_config": {"corridor_length": 5}}) + +.. _`RLlib`: https://ray.readthedocs.io/en/latest/rllib.html + More Information ---------------- @@ -63,12 +183,16 @@ More Information - `Blog`_ - `Ray paper`_ - `Ray HotOS paper`_ +- `RLlib paper`_ +- `Tune paper`_ .. _`Documentation`: http://ray.readthedocs.io/en/latest/index.html .. _`Tutorial`: https://github.com/ray-project/tutorial .. _`Blog`: https://ray-project.github.io/ .. _`Ray paper`: https://arxiv.org/abs/1712.05889 .. _`Ray HotOS paper`: https://arxiv.org/abs/1703.03924 +.. _`RLlib paper`: https://arxiv.org/abs/1712.09381 +.. _`Tune paper`: https://arxiv.org/abs/1807.05118 Getting Involved ---------------- diff --git a/ci/jenkins_tests/run_multi_node_tests.sh b/ci/jenkins_tests/run_multi_node_tests.sh index 70209656f..aab70e310 100755 --- a/ci/jenkins_tests/run_multi_node_tests.sh +++ b/ci/jenkins_tests/run_multi_node_tests.sh @@ -40,4 +40,16 @@ $SUPPRESS_OUTPUT docker run --rm --shm-size=${SHM_SIZE} --memory=${MEMORY_SIZE} python /ray/python/ray/experimental/sgd/examples/train_example.py --num-replicas=2 $SUPPRESS_OUTPUT docker run --rm --shm-size=${SHM_SIZE} --memory=${MEMORY_SIZE} $DOCKER_SHA \ - python /ray/python/ray/experimental/sgd/examples/train_example.py --tune + python /ray/python/ray/experimental/sgd/examples/tune_example.py + +$SUPPRESS_OUTPUT docker run --rm --shm-size=${SHM_SIZE} --memory=${MEMORY_SIZE} $DOCKER_SHA \ + python /ray/python/ray/experimental/sgd/examples/tune_example.py + +$SUPPRESS_OUTPUT docker run --rm --shm-size=${SHM_SIZE} --memory=${MEMORY_SIZE} $DOCKER_SHA \ + python /ray/python/ray/experimental/sgd/examples/tune_example.py --num-replicas=2 + +$SUPPRESS_OUTPUT docker run --rm --shm-size=${SHM_SIZE} --memory=${MEMORY_SIZE} $DOCKER_SHA \ + python /ray/doc/examples/doc_code/torch_example.py + +$SUPPRESS_OUTPUT docker run --rm --shm-size=${SHM_SIZE} --memory=${MEMORY_SIZE} $DOCKER_SHA \ + python /ray/doc/examples/doc_code/tf_example.py diff --git a/doc/examples/doc_code/tf_example.py b/doc/examples/doc_code/tf_example.py new file mode 100644 index 000000000..505bc543b --- /dev/null +++ b/doc/examples/doc_code/tf_example.py @@ -0,0 +1,94 @@ +# flake8: noqa +""" +This file holds code for the TF best-practices guide in the documentation. + +It ignores yapf because yapf doesn't allow comments right after code blocks, +but we put comments right after code blocks to prevent large white spaces +in the documentation. +""" + +# yapf: disable +# __tf_model_start__ +import tensorflow as tf +from tensorflow.keras import layers + + +def create_keras_model(): + model = tf.keras.Sequential() + # Adds a densely-connected layer with 64 units to the model: + model.add(layers.Dense(64, activation="relu", input_shape=(32, ))) + # Add another: + model.add(layers.Dense(64, activation="relu")) + # Add a softmax layer with 10 output units: + model.add(layers.Dense(10, activation="softmax")) + + model.compile( + optimizer=tf.train.RMSPropOptimizer(0.01), + loss=tf.keras.losses.categorical_crossentropy, + metrics=[tf.keras.metrics.categorical_accuracy]) + return model +# __tf_model_end__ +# yapf: enable + +# yapf: disable +# __ray_start__ +import ray +import numpy as np + +ray.init() + +def random_one_hot_labels(shape): + n, n_class = shape + classes = np.random.randint(0, n_class, n) + labels = np.zeros((n, n_class)) + labels[np.arange(n), classes] = 1 + return labels + + +# Use GPU wth +# @ray.remote(num_gpus=1) +@ray.remote +class Network(): + def __init__(self): + self.model = create_keras_model() + self.dataset = np.random.random((1000, 32)) + self.labels = random_one_hot_labels((1000, 10)) + + def train(self): + history = self.model.fit(self.dataset, self.labels, verbose=False) + return history.history + + def get_weights(self): + return self.model.get_weights() + + def set_weights(self, weights): + # Note that for simplicity this does not handle the optimizer state. + self.model.set_weights(weights) +# __ray_end__ +# yapf: enable + +# yapf: disable +# __actor_start__ +NetworkActor = Network.remote() +result_object_id = NetworkActor.train.remote() +ray.get(result_object_id) +# __actor_end__ +# yapf: enable + +# yapf: disable +# __weight_average_start__ +NetworkActor2 = Network.remote() +NetworkActor2.train.remote() +weights = ray.get( + [NetworkActor.get_weights.remote(), + NetworkActor2.get_weights.remote()]) + +averaged_weights = [(layer1 + layer2) / 2 + for layer1, layer2 in zip(weights[0], weights[1])] + +weight_id = ray.put(averaged_weights) +[ + actor.set_weights.remote(weight_id) + for actor in [NetworkActor, NetworkActor2] +] +ray.get([actor.train.remote() for actor in [NetworkActor, NetworkActor2]]) diff --git a/doc/examples/doc_code/torch_example.py b/doc/examples/doc_code/torch_example.py new file mode 100644 index 000000000..b89f6b5ae --- /dev/null +++ b/doc/examples/doc_code/torch_example.py @@ -0,0 +1,180 @@ +# flake8: noqa +""" +This file holds code for the Torch best-practices guide in the documentation. + +It ignores yapf because yapf doesn't allow comments right after code blocks, +but we put comments right after code blocks to prevent large white spaces +in the documentation. +""" +# yapf: disable +# __torch_model_start__ +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class Model(nn.Module): + def __init__(self): + super(Model, self).__init__() + self.conv1 = nn.Conv2d(1, 20, 5, 1) + self.conv2 = nn.Conv2d(20, 50, 5, 1) + self.fc1 = nn.Linear(4 * 4 * 50, 500) + self.fc2 = nn.Linear(500, 10) + + def forward(self, x): + x = F.relu(self.conv1(x)) + x = F.max_pool2d(x, 2, 2) + x = F.relu(self.conv2(x)) + x = F.max_pool2d(x, 2, 2) + x = x.view(-1, 4 * 4 * 50) + x = F.relu(self.fc1(x)) + x = self.fc2(x) + return F.log_softmax(x, dim=1) +# __torch_model_end__ +# yapf: enable + +# yapf: disable +# __torch_helper_start__ +from filelock import FileLock +from torchvision import datasets, transforms + + +def train(model, device, train_loader, optimizer): + model.train() + for batch_idx, (data, target) in enumerate(train_loader): + # This break is for speeding up the tutorial. + if batch_idx * len(data) > 1024: + return + data, target = data.to(device), target.to(device) + optimizer.zero_grad() + output = model(data) + loss = F.nll_loss(output, target) + loss.backward() + optimizer.step() + + +def test(model, device, test_loader): + model.eval() + test_loss = 0 + correct = 0 + with torch.no_grad(): + for data, target in test_loader: + data, target = data.to(device), target.to(device) + output = model(data) + + # sum up batch loss + test_loss += F.nll_loss( + output, target, reduction="sum").item() + pred = output.argmax( + dim=1, + keepdim=True) + correct += pred.eq(target.view_as(pred)).sum().item() + + test_loss /= len(test_loader.dataset) + return { + "loss": test_loss, + "accuracy": 100. * correct / len(test_loader.dataset) + } + + +def dataset_creators(use_cuda): + kwargs = {"num_workers": 1, "pin_memory": True} if use_cuda else {} + with FileLock("./data.lock"): + train_loader = torch.utils.data.DataLoader( + datasets.MNIST( + "./data", + train=True, + download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307, ), (0.3081, )) + ])), + 128, + shuffle=True, + **kwargs) + test_loader = torch.utils.data.DataLoader( + datasets.MNIST( + "./data", + train=False, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307, ), (0.3081, )) + ])), + 128, + shuffle=True, + **kwargs) + + return train_loader, test_loader +# __torch_helper_end__ +# yapf: enable + +# yapf: disable +# __torch_net_start__ +import torch.optim as optim + + +class Network(): + def __init__(self, lr=0.01, momentum=0.5): + use_cuda = torch.cuda.is_available() + self.device = device = torch.device("cuda" if use_cuda else "cpu") + self.train_loader, self.test_loader = dataset_creators(use_cuda) + + self.model = Model().to(device) + self.optimizer = optim.SGD( + self.model.parameters(), lr=lr, momentum=momentum) + + def train(self): + train(self.model, self.device, self.train_loader, self.optimizer) + return test(self.model, self.device, self.test_loader) + + def get_weights(self): + return self.model.state_dict() + + def set_weights(self, weights): + self.model.load_state_dict(weights) + + def save(self): + torch.save(self.model.state_dict(), "mnist_cnn.pt") + + +net = Network() +net.train() +# __torch_net_end__ +# yapf: enable + +# yapf: disable +# __torch_ray_start__ +import ray +ray.init() + +RemoteNetwork = ray.remote(Network) +# Use the below instead of `ray.remote(network)` to leverage the GPU. +# RemoteNetwork = ray.remote(num_gpus=1)(Network) +# __torch_ray_end__ +# yapf: enable + +# yapf: disable +# __torch_actor_start__ +NetworkActor = RemoteNetwork.remote() +NetworkActor2 = RemoteNetwork.remote() + +ray.get([NetworkActor.train.remote(), NetworkActor2.train.remote()]) +# __torch_actor_end__ +# yapf: enable + +# yapf: disable +# __weight_average_start__ +weights = ray.get( + [NetworkActor.get_weights.remote(), + NetworkActor2.get_weights.remote()]) + +from collections import OrderedDict +averaged_weights = OrderedDict( + [(k, (weights[0][k] + weights[1][k]) / 2) for k in weights[0]]) + +weight_id = ray.put(averaged_weights) +[ + actor.set_weights.remote(weight_id) + for actor in [NetworkActor, NetworkActor2] +] +ray.get([actor.train.remote() for actor in [NetworkActor, NetworkActor2]]) diff --git a/doc/source/actors.rst b/doc/source/actors.rst index 497fd17fc..ba4993308 100644 --- a/doc/source/actors.rst +++ b/doc/source/actors.rst @@ -42,6 +42,9 @@ When the above actor is instantiated, the following events happen. 2. A ``Counter`` object is created on that worker and the ``Counter`` constructor is run. +Actor Methods +------------- + Any method of the actor can return multiple object IDs with the ``ray.method`` decorator: .. code-block:: python @@ -59,6 +62,7 @@ Any method of the actor can return multiple object IDs with the ``ray.method`` d assert ray.get(obj_id1) == 1 assert ray.get(obj_id2) == 2 + Resources with Actors --------------------- @@ -95,11 +99,28 @@ have these resources (see `configuration instructions lifetime, but every time it executes a method, it will need to acquire 1 CPU resource. + +.. code-block:: python + + @ray.remote(resources={'Resource2': 1}) + class GPUActor(object): + pass + + If you need to instantiate many copies of the same actor with varying resource requirements, you can do so as follows. .. code-block:: python + @ray.remote(num_cpus=4) + class Counter(object): + def __init__(self): + self.value = 0 + + def increment(self): + self.value += 1 + return self.value + a1 = Counter._remote(num_cpus=1, resources={"Custom1": 1}) a2 = Counter._remote(num_cpus=2, resources={"Custom2": 1}) a3 = Counter._remote(num_cpus=3, resources={"Custom3": 1}) @@ -107,12 +128,6 @@ requirements, you can do so as follows. Note that to create these actors successfully, Ray will need to be started with sufficient CPU resources and the relevant custom resources. -.. code-block:: python - - @ray.remote(resources={'Resource2': 1}) - class GPUActor(object): - pass - Terminating Actors ------------------ diff --git a/doc/source/advanced.rst b/doc/source/advanced.rst index 80010d7f5..9612bc04e 100644 --- a/doc/source/advanced.rst +++ b/doc/source/advanced.rst @@ -3,6 +3,55 @@ Advanced Usage This page will cover some more advanced examples of using Ray's flexible programming model. +Dynamic Remote Parameters +------------------------- + +You can dynamically adjust resource requirements or return values of ``ray.remote`` during execution with ``._remote``. + +For example, here we instantiate many copies of the same actor with varying resource requirements. Note that to create these actors successfully, Ray will need to be started with sufficient CPU resources and the relevant custom resources: + +.. code-block:: python + + @ray.remote(num_cpus=4) + class Counter(object): + def __init__(self): + self.value = 0 + + def increment(self): + self.value += 1 + return self.value + + a1 = Counter._remote(num_cpus=1, resources={"Custom1": 1}) + a2 = Counter._remote(num_cpus=2, resources={"Custom2": 1}) + a3 = Counter._remote(num_cpus=3, resources={"Custom3": 1}) + +You can specify different resource requirements for tasks (but not for actor methods): + +.. code-block:: python + + @ray.remote + def g(): + return ray.get_gpu_ids() + + object_gpu_ids = g.remote() + assert ray.get(object_gpu_ids) == [0] + + dynamic_object_gpu_ids = g._remote(args=[], num_cpus=1, num_gpus=1) + assert ray.get(dynamic_object_gpu_ids) == [0] + +And vary the number of return values for tasks (and actor methods too): + +.. code-block:: python + + @ray.remote + def f(n): + return list(range(n)) + + id1, id2 = f._remote(args=[2], num_return_vals=2) + assert ray.get(id1) == 0 + assert ray.get(id2) == 1 + + Nested Remote Functions ----------------------- @@ -94,114 +143,3 @@ Notes * Several limitations come from Cython's own `unsupported `_ Python features. * We currently do not support compiling and distributing Cython code to ``ray`` clusters. In other words, Cython developers are responsible for compiling and distributing any Cython code to their cluster (much as would be the case for users who need Python packages like ``scipy``). * For most simple use cases, developers need not worry about Python 2 or 3, but users who do need to care can have a look at the ``language_level`` Cython compiler directive (see `here `_). - -Serialization -------------- - -There are a number of situations in which Ray will place objects in the object -store. Once an object is placed in the object store, it is immutable. Situations include: - -1. The return values of a remote function. -2. The value ``x`` in a call to ``ray.put(x)``. -3. Arguments to remote functions (except for simple arguments like ints or - floats). - -A Python object may have an arbitrary number of pointers with arbitrarily deep -nesting. To place an object in the object store or send it between processes, -it must first be converted to a contiguous string of bytes. Serialization and deserialization can often be a bottleneck. - -Pickle is standard Python serialization library. However, for numerical workloads, pickling and unpickling can be inefficient. For example, if multiple processes want to access a Python list of numpy arrays, each process must unpickle the list and create its own new copies of the arrays. This can lead to high memory overheads, even when all processes are read-only and could easily share memory. - -In Ray, we optimize for numpy arrays by using the `Apache Arrow`_ data format. -When we deserialize a list of numpy arrays from the object store, we still -create a Python list of numpy array objects. However, rather than copy each -numpy array, each numpy array object holds a pointer to the relevant array held -in shared 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. - -.. _`Apache Arrow`: https://arrow.apache.org/ - -What Objects Does Ray Handle -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ray does not currently support serialization of arbitrary Python objects. The -set of Python objects that Ray can serialize using Arrow includes the following. - -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. - -For a more general object, Ray will first attempt to serialize the object by -unpacking the object as a dictionary of its fields. This behavior is not -correct in all cases. If Ray cannot serialize the object as a dictionary of its -fields, Ray will fall back to using pickle. However, using pickle will likely -be inefficient. - -Notes and limitations -~~~~~~~~~~~~~~~~~~~~~ - -- We currently handle certain patterns incorrectly, according to Python - semantics. For example, a list that contains two copies of the same list will - be serialized as if the two lists were distinct. - - .. code-block:: python - - l1 = [0] - l2 = [l1, l1] - l3 = ray.get(ray.put(l2)) - - l2[0] is l2[1] # True. - l3[0] is l3[1] # False. - -- For reasons similar to the above example, we also do not currently handle - objects that recursively contain themselves (this may be common in graph-like - data structures). - - .. code-block:: python - - l = [] - l.append(l) - - # Try to put this list that recursively contains itself in the object store. - ray.put(l) - - This will throw an exception with a message like the following. - - .. code-block:: bash - - This object exceeds the maximum recursion depth. It may contain itself recursively. - -- Whenever possible, use numpy arrays for maximum performance. - -Last Resort Workaround -~~~~~~~~~~~~~~~~~~~~~~ - -If you find cases where Ray serialization doesn't work or does something -unexpected, please `let us know`_ so we can fix it. In the meantime, you may -have to resort to writing custom serialization and deserialization code (e.g., -calling pickle by hand). - -.. _`let us know`: https://github.com/ray-project/ray/issues - -.. code-block:: python - - import pickle - - @ray.remote - def f(complicated_object): - # Deserialize the object manually. - obj = pickle.loads(complicated_object) - return "Successfully passed {} into f.".format(obj) - - # Define a complicated object. - l = [] - l.append(l) - - # Manually serialize the object and pass it in as a string. - ray.get(f.remote(pickle.dumps(l))) # prints 'Successfully passed [[...]] into f.' - -**Note:** If you have trouble with pickle, you may have better luck with -cloudpickle. diff --git a/doc/source/async_api.rst b/doc/source/async_api.rst index 95867745f..76b8b68ff 100644 --- a/doc/source/async_api.rst +++ b/doc/source/async_api.rst @@ -5,15 +5,6 @@ Since Python 3.5, it is possible to write concurrent code using the ``async/awai This document describes Ray's support for asyncio, which enables integration with popular async frameworks (e.g., aiohttp, aioredis, etc.) for high performance web and prediction serving. -Starting Ray ------------- - -You must initialize Ray first. - -Please refer to `Starting Ray`_ for instructions. - -.. _`Starting Ray`: http://ray.readthedocs.io/en/latest/tutorial.html#starting-ray - Converting Ray objects into asyncio futures ------------------------------------------- diff --git a/doc/source/configure.rst b/doc/source/configure.rst index 72ceeb4a5..d3a3337f2 100644 --- a/doc/source/configure.rst +++ b/doc/source/configure.rst @@ -5,6 +5,9 @@ This page discusses the various way to configure Ray, both from the Python API and from the command line. Take a look at the ``ray.init`` `documentation `__ for a complete overview of the configurations. +.. important:: For the multi-node setting, you must first run `ray start` on the command line before ``ray.init`` in Python. On a single machine, you can run ``ray.init()`` without `ray start`. + + Cluster Resources ----------------- @@ -143,7 +146,6 @@ Plasma is a high-performance shared memory object store originally developed in Ray and now being developed in `Apache Arrow`_. See the `relevant documentation`_. - On Linux, it is possible to increase the write throughput of the Plasma object store by using huge pages. You first need to create a file system and activate huge pages as follows. diff --git a/doc/source/deploy-on-kubernetes.rst b/doc/source/deploy-on-kubernetes.rst index 4f5f43ea4..6c5713509 100644 --- a/doc/source/deploy-on-kubernetes.rst +++ b/doc/source/deploy-on-kubernetes.rst @@ -79,7 +79,7 @@ following. One of the pods will download and run `this example script`_. -.. _`this example script`: https://github.com/ray-project/ray/blob/master/doc/kubernetes/example.py +.. _`this example script`: https://github.com/ray-project/ray/tree/master/doc/kubernetes/example.py The script prints its output. To view the output, first find the pod name by running ``kubectl get all``. You'll see output like the following. diff --git a/doc/source/distributed_training.rst b/doc/source/distributed_training.rst index 61f3442eb..13c2e4581 100644 --- a/doc/source/distributed_training.rst +++ b/doc/source/distributed_training.rst @@ -1,48 +1,106 @@ Distributed Training (Experimental) =================================== +Ray's ``PyTorchTrainer`` simplifies distributed model training for PyTorch. The ``PyTorchTrainer`` is a wrapper around ``torch.distributed.launch`` with a Python API to easily incorporate distributed training into a larger Python application, as opposed to needing to execute training outside of Python. -Ray includes abstractions for distributed model training that integrate with -deep learning frameworks, such as PyTorch. +---------- -Ray Train is built on top of the Ray task and actor abstractions to provide -seamless integration into existing Ray applications. +**With Ray**: -PyTorch Interface ------------------ - -To use Ray Train with PyTorch, pass model and data creator functions to the -``ray.experimental.sgd.pytorch.PyTorchTrainer`` class. -To drive the distributed training, ``trainer.train()`` can be called -repeatedly. +Wrap your training with this: .. code-block:: python - model_creator = lambda config: YourPyTorchModel() - data_creator = lambda config: YourTrainingSet(), YourValidationSet() - - trainer = PyTorchTrainer( + ray.init(args.address) + trainer1 = PyTorchTrainer( model_creator, data_creator, - optimizer_creator=utils.sgd_mse_optimizer, - config={"lr": 1e-4}, - num_replicas=2, - resources_per_replica=Resources(num_gpus=1), - batch_size=16, - backend="auto") + optimizer_creator, + num_replicas= * , + use_gpu=True, + batch_size=512, + backend="gloo") - for i in range(NUM_EPOCHS): - trainer.train() + trainer1.train() -Under the hood, Ray Train will create *replicas* of your model -(controlled by ``num_replicas``) which are each managed by a worker. -Multiple devices (e.g. GPUs) can be managed by each replica (controlled by ``resources_per_replica``), -which allows training of lage models across multiple GPUs. -The ``PyTorchTrainer`` class coordinates the distributed computation and training to improve the model. -The full documentation for ``PyTorchTrainer`` is as follows: +Then, start a Ray cluster `via autoscaler `_ or `manually `_. + +.. code-block:: bash + + ray up CLUSTER.yaml + python train.py --address="localhost:" + + +---------- + +**Before, with Pytorch**: + +In your training program, insert the following: + +.. code-block:: + + torch.distributed.init_process_group(backend='YOUR BACKEND', + init_method='env://') + + model = torch.nn.parallel.DistributedDataParallel(model, + device_ids=[arg.local_rank], + output_device=arg.local_rank) + +Then, separately, on each machine: + +.. code-block:: + + # Node 1: *(IP: 192.168.1.1, and has a free port: 1234)* + $ python -m torch.distributed.launch --nproc_per_node=NUM_GPUS_YOU_HAVE + --nnodes=4 --node_rank=0 --master_addr="192.168.1.1" + --master_port=1234 YOUR_TRAINING_SCRIPT.py (--arg1 --arg2 --arg3 + and all other arguments of your training script) + # Node 2: + $ python -m torch.distributed.launch --nproc_per_node=NUM_GPUS_YOU_HAVE + --nnodes=4 --node_rank=1 --master_addr="192.168.1.1" + --master_port=1234 YOUR_TRAINING_SCRIPT.py (--arg1 --arg2 --arg3 + and all other arguments of your training script) + # Node 3: + $ python -m torch.distributed.launch --nproc_per_node=NUM_GPUS_YOU_HAVE + --nnodes=4 --node_rank=2 --master_addr="192.168.1.1" + --master_port=1234 YOUR_TRAINING_SCRIPT.py (--arg1 --arg2 --arg3 + and all other arguments of your training script) + # Node 4: + $ python -m torch.distributed.launch --nproc_per_node=NUM_GPUS_YOU_HAVE + --nnodes=4 --node_rank=3 --master_addr="192.168.1.1" + --master_port=1234 YOUR_TRAINING_SCRIPT.py (--arg1 --arg2 --arg3 + and all other arguments of your training script) + + +PyTorchTrainer Example +---------------------- + +Below is an example of using Ray's PyTorchTrainer. Under the hood, ``PytorchTrainer`` will create *replicas* of your model (controlled by ``num_replicas``) which are each managed by a worker. + +.. literalinclude:: ../../python/ray/experimental/sgd/examples/train_example.py + :language: python + :start-after: __torch_train_example__ + + +Hyperparameter Optimization on Distributed Pytorch +-------------------------------------------------- + +``PyTorchTrainer`` naturally integrates with Tune via the ``PyTorchTrainable`` interface. The same arguments to ``PyTorchTrainer`` should be passed into the ``tune.run(config=...)`` as shown below. + +.. literalinclude:: ../../python/ray/experimental/sgd/examples/tune_example.py + :language: python + :start-after: __torch_tune_example__ + + +Package Reference +----------------- .. autoclass:: ray.experimental.sgd.pytorch.PyTorchTrainer :members: .. automethod:: __init__ + + +.. autoclass:: ray.experimental.sgd.pytorch.PyTorchTrainable + :members: diff --git a/doc/source/example-resnet.rst b/doc/source/example-resnet.rst index 403772472..4615b26a1 100644 --- a/doc/source/example-resnet.rst +++ b/doc/source/example-resnet.rst @@ -1,7 +1,7 @@ ResNet ====== -This code adapts the `TensorFlow ResNet example`_ to do data parallel training +This code uses ResNet to do data parallel training across multiple GPUs using Ray. View the `code for this example`_. To run the example, you will need to install `TensorFlow`_ (at @@ -99,6 +99,5 @@ object store. mean_weights = {k: sum([weights[k] for weights in all_weights]) / num_gpus for k in all_weights[0]} weight_id = ray.put(mean_weights) -.. _`TensorFlow ResNet example`: https://github.com/tensorflow/models/tree/master/resnet .. _`TensorFlow`: https://www.tensorflow.org/install/ .. _`code for this example`: https://github.com/ray-project/ray/tree/master/doc/examples/resnet diff --git a/doc/source/fault-tolerance.rst b/doc/source/fault-tolerance.rst index 6c388c9f8..f27c5d596 100644 --- a/doc/source/fault-tolerance.rst +++ b/doc/source/fault-tolerance.rst @@ -37,7 +37,7 @@ Process Failures ~~~~~~~~~~~~~~~~ 1. Ray does not recover from the failure of any of the following processes: - a Redis server and the monitor process. + any of the Redis servers and the monitor process. 2. If a driver fails, that driver will not be restarted and the job will not complete. diff --git a/doc/source/images/tune-wide.png b/doc/source/images/tune-wide.png new file mode 100644 index 0000000000000000000000000000000000000000..bb26a3074bad2e0e0ec37165f0e27f1c54411f9c GIT binary patch literal 45254 zcmeEt^;?u%7q;hkJlLop9Y=uyK}t#(LP_ZcY3c45V1PjdVMxiLhVGQkp*tmp?(Sx2 z_#WQ(JEQOSH+=Jp*X6ZYd+)VZ-0NQJfy>E=J-SbF|JJQrkHkL+E8Mz;gLmuJAN6o_+E%ot_}DXu<9OxYRX?&Ai2-3OY-$U6R& zs{Fc(JHU4T|Mwpa)W7SzSYj6Zf`iqEXMbqN%TDX@oVWZYf;I*lSNtK2b;oA~2`W1j zmopP~|LErJq*Xg+y}|y)C$IdrMGf4zCk{ET3}SmODF=T;>A6Y??MEgnAd8xI%-IjI zYl)H=Mt1sE*_*kl_3)ryzrjJvD=oXk2%Xo3@J+FZKd=j_@X1_ZqOpHWk1a87JtP6G zQ|2Vx)((hs@n(67UBn{VkW|U0?!FUY33?wjYUJ7HKRx{33yxjj$vOZ5m7Ps%gt8Qd zL~I_-!W){N-)~t_BEc?Ud9%M-GiQl&no8H;+dD)5)`Xy2|OcnOZfdroSbbD5mk?lt5s4;!y1~Jvl+=+GNTc} zWC94msJp-9#V!ErJfiU3@?^lqUwz9!-PCq; zUfmM8L(D7XcKq>@>^qlP@6xmqG4^aMf~3c<2;8Sy7)P2fY8u3lWf#*q^{!6sviH5` z?SJ2u7-uX`r03juX&pjF!Xsrd+$NMefmf}S<#tFDapTqh{RITb+Vp0Y-9}j}Tj*Ku zvJ`65sjTIa~c}KFr36}|ZQ_sPt4Y%b(%x+_siW(hU zRX7IbP98}2Y(1m^l@ZS>7U^khs#m@&0R7LEQ&g9$Fkjsdy12@?oTE-03AtEZRobh< zdWnBG&_dh8?vnNW6dgVEau8rvbtZFk>C6uvob@oJar`b%>eH;&0VPPsFL!EqY*ts# zMH0$thCzEhf0gm_jq5eUQa%m{w(pE!a+1km8Xj>x-T5TRRqL5QbPjnqB_n+4)%v8mAn%Hzu%|1 zRW$t4QE0K>x~eliHj0#A$^zY2ZP4*8Gf-Fev^0|w`;4CqWWZCrlTL}p_H2@1;FzW3 zJaF&Wm&`n82BpcrAJg`zmM`%@)+0+f22A8(ot%aw0`I7gUxmTl4c@46|E{8t)Dl{) zw(dvpg28FE4~1N|KfMy?4P#TxXq*d;SEE^_H`D69;?O^8Q!nm)j4wd7EX`(xT^%K`}8C za8t=9KMaNJWe1>=v~jFR&!^(GYCm;{a=&NtY{mJfjmDZB*?WGh;x;l;82IX^M2cO> z^70G?H;1%#zJ|J*0P9W+p@fB_pnW$vx%4~e-+dct-rB2jiK37!Bp$M}}dGsNN|Z-MCV0_S%URCO=Dn7;s=L86FjFV^Z!p_xX2 z<%f1H``=&rX|z;0*ny`dNm+ooG|T&DbF=k-(hKhP@z|)3h5c@iAUbNu`JeEsSX72loF;G`J`Hn;+fNU(FwKux;j-7?g`UhE3Y^C=vh=lD=A_|43W_$EFw{~#V)P)ZEl{l{^2I+;3Js3p0XAPZD+nLL9P>ZtWn9BEL~OnwfcMKa3~REOC=ObWT%< zlKcJ0UZRUVB%GHDPJzQRp$@deJDc>8qr$GPcP35$$Erd@?dlrVk%fOm@=ZB{mcQZR zb!m*>+u(jNj%-tG{fYby5j?$QY6_ECvj<Qs0wz8rCadB~6 zdWGL~@PQ70{$NmMjgaShQT4SlL@&ofn`R=Iy23|v;P-n)%zm$)35;>1nd`zZg?ho2 zW4L__?F)>orYWyc9wqd@zn3szMyJx}^N%^UmQI!YwuQqWvIqRyBIZubRn8~ITxs*z zvwA+gSLI|q0AL(8^Rh@Ih+bOy<3`tQw3v@fhLi#1C3fK_;baBvkuf`#>5P`+cpgP~ z6<#c6k50RkueFC^_Hi{H)@eMiy~<`K^tZG!K3m&cz+7YPTWC zQuIFs*6a9T*L~M@Vh6Zaq?dJrAVv>4hx7LPdjr>$-<+SqgEuTkNmtOx1sS4#Q}DBl z_k`u4cQ#)Bxy3u6VgD-()Yr{PpY>N&Ew(&eU)>WVXKnlx3^MxiGd*{TcAFyaJlI3y zt~}@4!>hxPV!qz&DfZ$LJont&I+ozjADkzmMwT*U*rT3=O{ZFC=fKZt6v#Q$ zGpRf{CsVGW@Pp$Vwqf+Sab3cV5y#_oXm9;}X40u4F^K$F9BALMi5|g&*~?+@1;git zekoDlecgl{9@jxSk6pNKQ*(QWU~Ji3DAR#(7YuX$COUUAVxy9+mANAFNEu2f-YAR* zgvF*$35})T=U^tSIt_&4F9*Kzn#Um|G@Cb`8N7ifc#dn}V*l;9jjMv`7ve{$SK@8# z=T=P@Iw}@=%;Je3)1tpdq3AExOxq6SCDI1G!hVzFoqSPm7<0mX{D=}1^rR{PSt-J8 zoO^`^uR>_G1KW2vV3xw&VNXEK6cA+)v77!2kKG>BHjTX+$&y!h*Wup~Nxck?WJ zHRuEvTp%utR!yIkwgGh~eGfVHnhXF&wI3p#)a-YaI%9sET&TzKV->wCi_iB^Jze}e zz0~Ah1`pmfh-1NaMfx1SU)t3r{6jx76IQ!E`;x#iK&eWnogxy+*j<6$_*T7u+~IEL z8|g+~cw=)OaD3V;hp0@2EUC*c_5_cKD8mm#B*g%Q0wA~F)YrN{DCORYPzr^b-LEj` z346y|v75rVjc@=!&r>R|?R04?`DOVW*ou2&lYMsH6fv|&jX9Xmh`=fIFF)m2uy4{J z(~{LBA-H!&py1DP>{V`^Tt68D*Y!8c1gVGxy>W}moIJ7if$vOb2F#>QYAX_GNt*l| zlaBiE1hg)eIlQrW<}|jP8N2O~t!wAPx$rAVdtOh(9!fiM*bcr#hon5B0GTO{EE5<~ zl$=3k#Pi*hI;I&HKF;)i?#pJk|DFn#pE&{`OZ@~=?3s*@*CfXZ=+}|15nOB48)oxH zV%aIVkH|n*uS;I#*!d4YE+%x7pCn6JGz#S$RqHp#9AW6#BTObuOCy8yx;0OysEi{N zQ<9aGD}Djq`WcrnkCJ?lhgB+%S-@OV^CC^9$eKp(RoJmYmQuP$1NVbA=8BjDWtGT5nU6YA_LG;(bU`NZ_V^uND-XZImg+LC#gY_^Q!0in~)>mzB#276R zq%mCuH{I<2!}p?>p-jYw{j@jb=a6QMZN^!mUH4>;b-A;XxM=V;5Lhm5!I4nJyoF^)p^C#y2)4l!3qF(GLPxE@2s7? zp>O{Cn%~LkU*&dY-SYs;4C>hXpg_?-SSY5$0X6xaTf@yi* zzAx6iCY5gHhu$C*Z)rNgKmeH0+LYP#f-I5WjuU+;y7@J7@)!JY8)%L_sq!WU1BVNv zxr+yTTD3XDRp&vZyi(^O`-1-fgVA z&wl&&nzv@dXuOiYa2leCr4K6*&o-s-t8%Ggbp&b@+MeuW%lY2Nkp)?=@poUpk1%=+ z$l_yOcXVKkuUmEvO7SsERyfUkR{kr;CQx7U3rWw{Fs_?yhyfL!e#U*|x@SY9Fp6RB zm}G}1W_8NcHz8dQ&lE#@Qxq+`^ZBb<1XgWshmC0g1zW2$R%`XN;$bB=VLWD=LV!23 zhIupTk1UVix<4WSbuP`b$=2n{)a3{JT1|EdeJB+u6x0Xna!l%=oE4iR!ycRJ*h*rr zxGO$DH)5?I7L52uD()YLPU%>k8Gfy{?>jk4_wl%9x})`%y$syP8bm@_k@az%D3>1wM~iG`ta|)b?|Sj8aE#1TD724kXVJ zpe&Rq7TB9oEH1V0I5}0NjCYXGusc}pBAFz>3UgC%7^CGf#a9GgFwYxgyS+T}${dIg z_g{2=`?kzcJeFZ&F3`Rbp>z(=9ew_;l{XQ2ybk0aY_Y{(Kwirq5$!+5_{8UpZd5?FpvksV!~>eL%K*aSaMb+(qt4ZeY_13?1fIB z$uKn=PyQl}+@9UHH}q$>W!oXKxAm*4mJB`=HOGo9LX_P~qk$J{50P9^UW@Vv%kZfPu^{2Pz0VsXL&ip8s+Xje8#RqBS>uUH|cb)}JC+ zNp6$E+dGjlUM_{}nXG?5#)A>WcvY^gr$%T=E`YxX?YLEM*?iIWY2YuQ=PX130K?^Y z$Ye?Bje(ubRYO<@X?`0X-JkzTo23HTT{3cXNa~~e7amfA=+o137M3F=qUgNnpfndA zbu_UrFG*o5SDoDhGBNM^FKRNUhSLnXYjSX9MlgX*CauJeu)kmmcl^%kfpGnuNCR=Wb^|3V<*}zb$uvh`WrrJX7L_@ZK=j(sDOaUF{|TKx+D{3KNx=f`Gx8AShe((!Qg`4BPfdIW}R?RE>PpU<+BRk;Tx z=jFg4KortExUpX^5!~a(v7E+~pal8H_a7=n+vQ--w=4g1s&FDlzS}uDh)8#q#Wwt> z(dJ`@nw9ZocQ*vT+(X(hD=gQe`G-IE<#0m2 zzm~f}xH^ysQ`poK+qRv3q~jXo!Ma^F_LQH7hN5xuN^A$i0M~d(3K9)?GM?IEYpuZP z=$0@;>qWQVGUwFPbH32$KuPhBWunQRn2%n^7ssc#hva@wpH?na>SATFsCz?tO9or} zq~*la9tiD74^cB!Whj%Mh~G`?IKL2ePc+%EJBaPUneT&;u3slhu&wDFW-~%$$Vk4)s@#>E>yd7XM~|{XJ-hY3#~_2>l4$IgR3C?=f#l2zY!m_~6QF(VGhh$oKK?iN$lSZqkDot0JL`Qk{nz zBM<29C$P8vq~!HviM=C7#x%LDj@>zm2&`rjM%YJ~O{L5qzN1VZ0m5iQVz%Cf+GLNk z*VF2h*o5Z_-7t`uGh?OB02Uc*>&wV{0B_H03;gZg{nO(|u^^{`2SPsZ_0_^;7X9F_ zoT^59FWd{BB4eIq-chH`H#NkIn=p?xJORU63LIGGhC1CT$P*zIH!yi(2&0YR0 z`->*+_EyfY`w=q_J*B#N>7ZxcYZLkFRuiRHKWo(}>0GrtOiPaBCbIwe`sh@ zX2@bob06h68^+fb8povBYuS?b+ZlgmJq{Y6OP^E_3u3p3B4QOZ361>r+-TG6;Dtwd z;1fejEh`WEP0h}+&v;+rRn2L(+7;bDjk!l%ALRKwV>;39EF1`qTEbvc_v}{{hx1Vg zNy{O=J8e?KF$mOMtDUJiwJmh%^g=lL`}dk}ZI**Fmv5H_ieoI}uJ2w^-@~8bi-_e8 zcR1@;MVTfDGS!7}?MRNdJ+s{l*NpHbDZmws)#NunU zI)SA(7rB6{5iPIp#iXB+PdthJTD0=T zk;@?O3|f~tDqQtWzAm9qT~e&y@?-r*8fVBA6F;#4Ej91?7Y%Ig@x=YIjp^@?8GS|X zeqy{UpV4^~>{kS%r1qN3rWAY441Df*68>_jIiIFvgQV*@mVu_(KS87_Dt)cRN5^Z zgVfBZq#z0n3m_kD6DEmk^Sw8ih#@w#^rB-pKusB3ci_je0vr#==DLan(6d?_9R#dc z5)?{Lfwxl^LQ`3pBoL~O@fgmZclb~8ellUerk7X z5_=qF=Gi$Y4TaH9s$=~i+%K8-=@c67kH^tJO!N0lERlc)zR@P~m9RofaF)^B95-6i zt#~uwe3=JyP66U5tY}<5)@5>DKQ~t5D7DjcM59M# z%U6xhSwYSSC6iAB)PYW~R7|)3;|gSaU}4)`gY`~^IqpCpQB|~H<$m|#JlM4ZqQN;( zaLw?kFP&A#1l;BmSUy8K^k$lF#1uO0wfYWmUZU5He`$G?Uk^q`33?WW6p&Mp53 zdk?!QQrcbBkMzf+e{OBeswA(;`#TK7IwBXcSF~(uY&gRK zPGLTzAkWP29~U1e%s8vlDy{r#(mzscGd}R+HLYxfmXhXsoZo;qk4D)v+hL-t@5!POeZqO;xOY`1V);dN?P4js)pvmkRjs>dj(rhV|l;8n9GCu$j z6%bhX?(#UlOe@c`EF&)eRk-~9;(osBaQe<&z$I?dLr@Vx%ps%IIMgXn0Iq(2uM+mb ziqE*9`?ocs^_Ll;j+E~RF1EP z%;@`iIjiww%w1-c)<=oEnhY&*oCUIg2s!Xhe&&y?>p;+q3w~fUl%s8!qltrlPae1O zNrG+HujoM8zx1~JCk>;ovRGiqC+qo=#$*k!YE8|G@`7Sc&XeQ-!4Os5jk&+NA~f_P z1LL2E0{Bf0s3s1z{7NS(!yU-iuU&`zHX4kP1GF{J;Fw7VtjS$WH1idZjF(8y#YBk% zTun+FJUKuszRF@HJ;5#zRkMx$ernV+WlPOkdp+u36_bbbf>)L#>9v3`$?pkOj}q?@ z7Drn6@!IT$7v)K%v(L3+(a5YUo{Qgo?`R*3Qa{szhqYX+cHuwM@?{lsFp$fkjkwtJ zWY>2#GCnCQ3c)gJ_^#!95DM#~&k5n5le11;)Y1l~*(`gvXLKM*;!s#s#{6nbH?W(V&H&27CWxy(}d#1nNJow{DzUE2rkx6(27* zuPanYjXSD=LSd^Jv4!*pOo#bnB_Geg$^dc;-bu@7@J2r98he9VyRy%xj<*V=H8YjO z)JQ#svh>C^rh4+sh2zjgZ7`$aLsqQnl$qM%O8dJL0N3 z=7D_gMkk7d$)lXs5=;g&Y+sy3=s4Ou{ZBB7Srtntb9x5MH1M74D%c!$ZWTh~P(`lP zG<=!uS-&>9e&IE5Z`s;=dwsM+_67}1BHt>Qgd&Vg*>E2YTt7x%2^bpk<*idXWL#oW z;F3UdQg(yASyjn$`$TM&)-dy9Z6WF=?^=dw$HO)id7bGEcLG;^rtIwRoa6tpP?#%p zW%Hr*WH)%dT^{hMC>9Nks6xwl=Vu?iC9dlX-i%Rd`JKyvFj3{0)}j}ROj_4v)lyp1 z3gb*I0)j1&I{9kk!NlXQ?v%_QDU)_BU$?>aKAlE7W-;Im0o6GL zC_AO?!Bif~qsomqMdtdRd*tnfiq;F4#bvW zTc7;I04}Z6u7)_|M&!@rb^WF+<^sx&x}o%)U39a|Ffi6}!@gvh&p#**c6crro-}?P zCzy0H*}#SBAVwS>nLD-8K?Co<9@^m!cRkFy%9`wc&%Oy7#E?C;SwTFXqb( z-1BMetBaW~N6u_Tij5q*zr}>GMJm)Xd8V9kavzZ z^yhS1CS`#-Dne15ieYTZwU`o8+5y zrLZC%LH7+g!WR~u3R|#XiL^J4dmR=(c7)gKPtEfw$=F%x7GkZeyq6f$W-@NZgUrfo z>8-~4HkP8eUv$uJTJyAm_+#Q-uElS+4r3vV+e@$S5e%KY71HG3ym#cRf`0ND1rBSm zqFG8}Qdt<7Xcj&wgIxLOm`s+e*a?!mv%97m4@?AY1=q{2%gIw}4x8>=lwE$B83`lM z>YBdP7%;s_=YxYlz4+$eSGkqoSq?$^nB%QvO)#NHK`Z_}gT=`N{TFr>)?>``n@oDaUooFnLU+1J zYhr|l1v>ZbZbC5=m$7K;5=U28S5EyJb`Q6|gx@>pTaLHdNOw2u{kI1MGDHhWpKbFi zsgvCIKA9X7NJ$6yq;?c-s7Ea7%vE9~;i3es5uuQ2qDy^Jgp7H_ii+o zy{v<#w(MoTm<*PmtM|PZ(OI(Fs|E!E7Z>m{?#B>GhjL6FKJ&|LMawl_S8DnxU)b<& z%|_23q!>Dkj1EEkz^NDzXq4!al0B?bY8eKSeW12|Pp^WFbMFRE+Oc?K`Rr^BudUsQ z;C_L<4OLnfE>WKN#Ze@W#25Jtpv+UL><~g&PJ~NH$^s@F|2g}tZX*3ir<}p*cu{0z zpz7ffV=fw~c9;a$gjx=gfwEV=&VOTqUUJu%THb}z(Iv{cduNY5cR)vp_9?cAF*g+N*xpX4V{Myh@7 z%}=f|T=VjKPBP&?z7;paxv`+k@<~u`Xi|CPINr9Gh$6k>;5jXp_xdF4SJY=Q+}z`q z8boG#BA?-x)xv{*54OxNxD&yoCFt8%voxl&dCgbav$-@U(DRXCc^o#`LU_be6)BMc zbZUGM920L*mZMX7i4!c>L$4$S`7jAm>SPWa8Quz6>~rB*-SOLq;C?+b4MU`5r?g72 ztUHwaq#w!hv!Yyk1x%8ZN+#59+f|-yj%LLMt`xveY&+8F5I{RQKIYhlIqPS0t&*UK zyR%wprM~!OZ+=YV40P#={YYW4@zGOg-`h!&XX$h&od7Mcl=^GOck{YR$i{A=7iKL1 zdY2{&LO6coCu-_it@)(|`-Y{N7%0E_i%u7jrr*AY(_5L2`ykL`*V4+$%0&j7C1!6YR+IizfY{sT zf{a3ePL}Ue!Pt`=b%cw96kxg55ZmCL?}S>sv4JTBkk>c#o~u=^O<| z!`)*kS%iUZMS4*DKkp&)9{?WU;v=HeIIcZ{wRZouAuxC1+ErXP>du=smV6W)gz&syBhV?=GK#FP6C8)a56=DRS9sMPrHMT1Udp`qsTrS}C#3J6EVF_v85sL)AdpbW;PLp%2*G`GLQU_){WjE0 zl<7`Od994skC7}^S7>{bm`?jeS!`@RJA~otl4uCPe)n+#aI3&DE#Xz-6%C)p6v_Ze zS=bmg7#v%VEe-2`-ruS2838d?<^&Z*rh{1xPxZByuUIQLW!TO3H!X_8RusE4m>%!& zxrnc>clZn8xdz;&J1as-W(B=RlOolh`TLH}pCc@S9)p*{Jyz9)uU5k~ zf@7DK;P>N*{*48mf2>mYa5B`Ct`vcs%#!e@-~f&mgxFVyadzdiqB%Xz3X`v{TsX*i zm4$$Dw<3KsCW=&l`CE+@dPc*of1q%v>iJQy$D&zjq)cN?Mzsmo=*rlvc!OyUq}6K_ zI?ZFp2b$rbIyW4=nTuW=`gh-3S3-hW?60aeN;JbNcH?=>N`$NN5PVEk`qfG`;UlT@ zls=Q1^8Ea$#BC85nc$moEC{8nop#Bq{bfLW0jXN$HEU!^iC~Ms7m){Gc@$7fZLVog z8fIP>Ck-;sXjU>Vhx(VVmyf1?v8@c&{s?;d8f(56=gldSk?QE}oS0ElkpTx}Jq&cH0MXk0u>?Z876og-pO4z4NH`q=bxPJs=nYajE1`TDNsu{L2)aa*yv>LRto)(&NBx^X1hn6311k^?b)_J!V;L zGWDO9A3cF$rBbOaaXsOF;Fd@pq|SV7FeURK*n|0R>4%+Br7th$`-@+RG5O3OBV*3m z1u1>)T>stw*0q=OQzaD3!K|V5WPlzp*p||UFtN5msW`7lfwM0|W@uh?)aR1h-1~YP=>Qc$z{=4xkC0*~aX1ch#n-005NWJgWX? zFp|TDWKE{)*Nl0y45vpmL1MkBS0xJ{=7*Ih^qbmx!w5?NH&&tCU4u|Wn*BK0KF%(6 zz<*R-^}PIIdc$Rf@>1zG?O$3*AWQw+*53=;tg+%O3wb3y?f#Bf28$nN$@i!$J~Qbf zzJi=O8E&rMdKYOYoy~OKZS&$K5#;REH0z5_BjdWO{m#-086TOnpve+ikAs08`6T_7 zj)ZdZRmFY&FCt-eK|*;`y=ra~T9{-PoxXs0s@scAuu3gq9CgQ1G=YWtIhrP`L$+T?P5bFTZe5X0}77_dah|d=8m8EVN08Bdjc1_s+gFE(IDh=n$$$lyss& z1modLcg81Z5pZ4?Y5K>g+}sT1L2E+xz3T9(jd1`G13%9g7vp-08dkMa(!b7u$iRhjU|40U}R6TgXAco#MK9;3?Et1M3z{y`+TMMT@g7`naUXss*&6Ivj3AC4QvMMqr;G2p7NP3?> zExZ>3Haw>_DH&$dX*tJv$tgR(;(%u`!kM~2DcsQ_5ahA#4)DEmw_8F4@dS1pb(ce$ zVk~y7Gv8i4y^I&snK|s7z_hUwu?eJJe$Q&qhs8J<>eLky762W}-S(o4>df)U_dqRk z^;XG!M6XmuW?qBgIF$3Y`!qmJAugLzXGs+ae0cK5o)({aS&n}s+=-o8y{#aF?UT4E zIpDgfKR2?d&V?h@GXXmLRM38KGDMqI5QQK0M zb4@~E<~KV?045+39R{$Jd6pPL0L6TiI^l4ruQ*jjJVrW=<BndN*; zJ_-BRaXvW+%e*UXoDl0XUoft_>HE1Qo@`s2;VswDE0ObNb{trIXxXEjQGU^>SsSP9 zcwN}sdN3mDR%fHNIH04VDZuK{Q0m(d_9cO1V?j*`6_LVH?1ro54}3EFYY6GLC zyIw^avwBYin1U)L7+*AW@h*p5D^FG{eD!O3^6Xi>`%|*B*@>HC(Tbl)z;v$Fm;7LO z>TbgBM(M?N)9q=uL1|DnJLE|^K^i5~)mA@58mQ-A#$fhdTTRVu$ouc`=|h7bKP-xO zKN{_{EcAy^N}hSNg`B8Mq_NBi|9hBv9C3rLo|}SY2*lVuf&)_s04ujiJ*hg(dnLl1 zasHsCqc4l+;VZC4lvb5Td48MsCKHYfgOBB8#z#)z^PlNCwtQy@<#K}ASktBW)Z=`b zh*+K)guDNBvHji!C795>+@9bOPjxNJ&RqOpZm_XMbpyYQfJ|i#+Ne&ivYX4?>Qg(a z_NGU8^C^Z5c5uk;N%LS=oIHNN%KhxN< z&WL0Orp8=`QW0$^K$%4|C}Ru2C%0(35!PB&tajb`DIIx-b1!MJ48k<&i*S%gB@@cg zlym{0^ChW?Atc-qCV0zy=&8vv%+c1MV=|A1(5nauJmZys7#x{b8n4UYn_PybtBX33K9U4CqQu= zA}@cuZeR7U!@X0mzW=3`zMI!g7*F6FUK=Ktdq`N?a6tGe=YZK&l|9`IA&s8zai#{g&S-n}Onj?JRG zckhR0dpZjB5385vzy4e9<^3;o^z9^0RR8#?=b2|I(Xjooevxo5NkP&YkwCY;;@Eqh z=xgxifco%ctGipq`c#SyWl+>GEro{=($G^;u^Ow;s``gvr^ZmEB~aEj{9jK3R51HF zR0ls0!NoWwq+C(fC6Rl!as_|h8?IUP3AQp;Y8Ie$$cy25h`;G+yCB+OuVWce3x?UA ze$=R=;=$Ml|NXtTXph^?El7X6RQxO&DU_Z{$0T{yNE_@_(f;_Y4n^t0HmN?u$(@C- zRKI3BKIqnJ5P~y3Ss>TVpAP>Jm>uYp}KnzbPIJ4&tLvvSe^Jn~a%{A}I91`u zoNax4KP?~Ra;h_IM65YCpKOl6K`CM-+SrulEq?1U^QiuIJj{-h`P`@DTl7|wu^;|z zl$g8A(Dd!rau79ZladqLj;!qE+T~hxte9DvdEmrzV- zfjBWEWjv4LK~opP`VkY|r}&nBY>x~0^jU{h^|(eTL9IL}8#zJGP`aIPP8+#Pl-;_c z-Zth)y6V41MAxV9#6Qx}sh_&3 zJ3ZJKZdIF8!@K_OEp*Riz@Ms&?Q83no{xXG4}CS5KPtp^VL{FGk-j7F(Pi;uLenv0 z`<|9u2_Jo}AQ39L(dlZw2p)Y(*ct~uUNCWL_f5gV>RY#V@%8ilX%%~NAc_&%<)du( z&J7bknl;IlzR25OE@kaLMP9dP`?Mj2U%CXL3|99RxUQ<*ERWhN8|R}n` zrp3x^{yOyr<+~6%TG&^$u5Ig1N5|#9HQzn`!v^ybyyzBU(Pf^nWiTBdmHXCz53iJc z%p@#KE! zwVYT;JaOTCEDzdnV5S^%7{Oz_I!I+&2)>3eIP$TPO^Y91!*hg1KW)%Jr23u`(BLe&I8E> z!n{nIYom!*fr=T{W||d~{H3RzTjhmVm61(3R zY+Z~?*DlZ6#lzmgaxm#Uh4Nz~0wRQd{X?Q1fv`ORywIrQ^faeO39rnqrR@7;6HJZb zC^CvOKDKdYM6KAL_-@oJbZ8&QzK*Wm^;#J3QE1xl-1zpWhs$^*!SwRhFTw^0Ypkso z)GjFtdf7OPf&9|SZ!tUX3i(S!JTMy{mcJ3*7ICi9Hhy@(4EJ)eg3Woq_x47J!Z8Wa z>!h}NBqMAy%@Q{4ZLJ0WG;ptejCZf^e-KG=N>0Cg&ch!cF?^b^Cl1je;b7TrI*EO; z;Jf8_E>z3yM0l`06kZMZ=_1w8fVzOfbB-W6*)S>AaoT9p!n%*hVjYXQ_&P@qi_>8* zYuWrWrqTum?u}e^JC(uNKvJ5%!gDV^$r!$Byc}%ta_iP7PrdQ`IQ=V}(Eb7MKqc=5 z=1UH_@Q5)s2d&Y{-ePkqL&9=g!>H?H=1q6!n0wxhFhcw|DC_YkE~UX@91?R>=DOhB z#c(pL?Q=@7?ef`($z|ga`D22ps^!Z>{n$ZFmWu1d1YQ`OlW$vm$4TQYk~f%#BswPs z)$;+Zs$x5z`b;jCgp}n+{z9R&6qkLirH5vqPPsCz@I6;3W_5g-y80WLEGAy(Fwp#G z;mT62C>rzdaL}3F^?cPDm~-%He+)AgeQ< z^?D}l$&E*Hjq)j@npublZXJpMp?rNf{9CY|igNaAO%>C1)UH=V75Wbbcb`c)ypbAd z(IA}QKHK(7-EV8PvHFg0s-_d-J0FtF@EN#ycs?SA;*ySSLG34*j8;pOj8a8;*vy=G zIHU}Z(}IoDIKHn8BwB`PS$%@p)Ma%gk$-z!DX29;_E}aq>C@?%Yx#51bq>qRgXwcS zEi@Er*H;rpQ+e@51YaKs5yR>5LA4R-$85jRd#o6&dlF_X=M+UNu5y#o2pPA zX5%c@x94|fnRD`MPnV7Qqn|b^quH7e;ZcD5-O-ioT0njY%8ND)DHTN)!|L)ad&krW z)VOF#jowgZ`Qab)8ZZON^cMLW`o9BH5u#SAnmk8!ii%OV=^0KnV49_Ia9C)cTWZyL zH_=wWUmw<`!`kxOU-_QtuGvptozk;AW9Q{<-prB6y$noqvEwN;`Edaz_sU8CxK@zw^^{Y~(nf$%Vm$CnY;ZQ}8G)WU8sP z0WU22m|xa#8X5W$YOJtd3Z%^ZppglvV}O(B!LC|z@?I5mpTl|V)Sm3|viaU)YU!b= z>gIM<2;AOg?pcv_WHXHlCYt}gL(PSO2lndje zSp%A+EYfYuvW<1(%o1yj$O8BB87&ey4-K}K|I^4=VpDAVv0oPtQPV`9e%@)UW-ukU zuxSh;Hh4qX{lRdWs5;DLkM(WJ9+xp!=++i@^P`~v?R|#+G2zvrbNOXu?3q?ERu~Z8XCJ>QBQwbwXunqX~fHUiBQhH@5l7` zjbjfw$d`F=xEy8=BcoCYq zh~V#|Ui#HA^5H^r)D7;CjIU6uyO8tiZ9x~4BW77r8u;o1VvxNL<44_Ltt$d!n2(Q0 zb_BXY$a#y5d5VmUYq_lm*-S3-^RJ~on89FY+YsL4JjSprzp>glTOQ)rhnu)oHn9-5ud2d(-zr&YNE;)=^FZeVGaRWu4OGWX z!-_g{KuhQmqhT!*QZMvx$S#;>d1b!bqcWWSJ6DV3nnWLy)9wiKZ4pd&o1IU!L(k+O zx$&^o`U$@y3DuY{8}!!Sjpw(TE^<6|<0B-ZS43W}9guQKXivvEtT!=sJjymN`_My> z7ycFuz~JE@<2$WS7u(K+uhk!7o9j#jnmF#4l;_~Q zWU!LR$`zO+&F*kBptIu}l%Z{XlTXGY0n?khXqnJ0F(HbXaF)BYm8(}QF;ehw zz_~=BxUzjrK7@rO0p(PA(TK7-Z-o8HpF1XVhFzDHgy}IcMyS_MB(w=a7eXUwT-07x zUYHXyzhRzxE&eM-t*T0C%q8agg98>bYTHw(Das9KY5>GmbSz(@yBgyO-cP3?)*^IL zFNY-@Q$WX*$?kZz&7!{OUb!!KZ$U^%JV;=(KU?-1{QcXYJp$>h0%emie8v4kONlAS z>kyL!@0N+p6^e;;LnKPtAK^Yax%KloUv(U*Vqh~De>~0E=~v=v@C-7vv4(?RoHY(= zyNGE!lWMzmSwqW~F~n0sIagx!A+NTN-U?D7PnFmfn8tAPn1jdVkH?Lx*8$|J$>o-w zX_ebMT?HiS4G^i(ur$ZCDw~RNUkI-8rcAuSKQb!iJl`!A;}8~G?$l%avnEAtHH`-E zo%=b1#IEMEzCq*iCJ&I81d(ss@(P@$YjPS~&bL7;hnL22Z1#O)7NZ_^XG(*)4Q}rn zW?ck301OlRH?6T2MVtsd-9O??xRF0Nrbs4*tK3e9XN!$tt|^J0E(8qM{UH)*eZ_5G z_(0DLHw*QG)l>ldqczr3pwEnj%=JPIzQTPDap&qH(?qn%IqtB?2=(KIcOh+Z&qlxw z?!uOB-g%@^q&OKYQh!`Ekj&b`D0QP?#X`ZYt)vMA$``w92%QlRlBx=+BY$*6xU%eIeS2oCKZTzWp<&&hO({Mo^FMoK}j=6;NsCoROrF`)5y3`QT}x4H{DW-Kn;}M z*C);9&@Rp9)GNsb>5^S=Ud~WECrI793`*y|LoQp4VH#!ckR#)iw+=4xyDGo+ysKaXHDVRw3PY9lpT}wiPX8bFzA`GRE^OP!7EqLKX_W2` zr5k}6I;5qg8x#Ztq@_#BfgwhQZlxRP27#eu0BPxO`#jeBuJ8Z%=W{JwFf7j5`;P0r z?l}ACx@8=ko9kYg<3hLOlvrgMsVkKRW{k_5>~mhrT>Z5pD=6gd^ghjcnWnAwm{ z|mk_nVf{49C(&2O>j{N~nv)yh46DwDtt}Gb^S$+PlpD;^SSpcR4-^L%pU=`kl@uNFyXlJPmOS7^! z1D(%^5w(?O{=Vkjk^w#Oozh7xsBu|<7cna5#ffCN+g=&4*TKDaLuB$`A?;RM_f$T2 zy@<=b(@~P za~5@8hGRkp{lnGh!}aq9^kIga8dW5$qwr*VVk1%7xH2q!M%q<(!q&Vpqflo1SrOJY zL+N14{ho0a`hHDecQs@p+Hf22RBR>g@u`+gOxKJLC+2mt21vt$n6vWAz|;D86`mno zX{(&dD4(ox7$CknqL6##b$l%4OKEZsS?t5-Ms_Aw$ya7cGMwW?2=2$4G*P5rL#41h zgr&Ar6ymVc;;Gwe{}6Lw2%6Y`CoF4wXZe-vv1yB?{K9Ei;j1A(i-$i78%;}h^a<5e zG7RFzJp?2Is&!5vBO=;odz_FF&;uUDtb);(u0ie;qE_$%B; zp%yQpa)VrG`V?E>PJq|`K@MteuMc%&?ktic(biaHW-4=>*Gyze=ykX-wkvzLD8qqT zJ=)1MgQ+%RTw&?}9qceME|P;b?IaFCp}#3=nQ=enGBt`Dw>|tYhx!582i*v zT#z_7Yngy=&)chS8}x*~JtWHfHph>1>6^^>$q^40 z*qblmh%~V){?3SB$b=b%8fNJ<3`FsZ z3@PV(n(8Ihyxe%~!t}U7s$*fN+7~k{U|KxX-^Lrme^_ARvHG$pYuWSuxdj}x#P&m` zjzxEhJ^L4{q$!X-8=}`#Sbq)y`NGl8o@J5PEhM@we7Ns@l%L26A5dxE=|y{mgNc z^eQ)PjMIV>i`56?683I=q(@Nw^w-v~9);FcLi{2F{nHrs%;Vm|x-gnZJcHEeBriK@ z`QE2{_7M;ndUA}gR0l50;&h-DEyD443uBC*L`oeX7Ik)!$2L2tG=wr z$ym4nb@Q1=&I?Sjk~t1Z^tsb#l|x& z(}ptbT;55HIQq47<2tnSr-_h*LWp3HZ~7p|Gd;BxQWMu1AD6udL$>nHJB$+sXiG^q zwg+Ca311}7W*Gi9)k{s&#h$h63wN?3hPo?1yX-b3#0fcfg5tDWo>p5UQ^8KdV}DIp-J(;+akotW44gdTCj^lJI z>${JED?Z~={k-i`thdEg4a`;M|<6cd}OHWcVCG1ktXFlCHGEJE$nw5jZ$1cS&jCy~? znYmZ{^kHh7>zr70xX)}r+t%^EK@`el#X4ithLoE%1YKa-Dr#~#({5!6?rc#OJ~WX% zj)?6PH9y?tUM#FwGi@x;LffZUdc0GbVXRe(O(^2$MRN=;-M8U-8cBTdWyGPz3-h{) zEJiESpC=@*V@Wgmq#touJ8LTV)65XLy@hXQ<5}E0SkF}{B7#qcWWul5eTEs2XBykA zU=x=nu3tkeyuS2tsJP|E$OvecHNYa^x%1W|erK$bQ95%Vc1O(O1&@n%^v;+37neGw->;R`19U;tlI{ z;tdj?RYk?VJeccRXP;|YWS<-6iL_shY9FiUn^)nRY0r39nINXdl6En$H*-oA8+$~< z%{dKRfrZY`ElmB%Ji*Be6WxxvIgk!IV~(A45HLMHCpg&oU_;LuTtO7O%)e~MLAf?{ ze>$ssOS%2v3jxb|fUdbyY2~vAlZ{fnp{5fv004C%7vfwjwd?CnB6DG94J0-w2XkGE z*!FtDT5^%892L$F{ZJc3iF&c|Nv|HZZOQWuU(xvVqtKOS>t2%EslZK5*aB(;X9)EW z%}m$Bm6jV1J&?wRB`0rY+|Z)K%PZgZ$sF-cM*Pu+u41ugtt+wPXZ;j6B0m^6XiAnW zj9-m1!IO-Dj;lHL#J_d)@RzWV(F@^3v#~YLY8UCdgGBS*DqHJ*newCT?ZF$Mb zq}g3vtl^BbON22y{SktXCY|2Z4vX9lt$^{qIgYpWYR=oYnM01e(P^z{Fo#SZBR4hT z!b8`frh0Dmem;JFd;9e!jrNBo5AK(0qFoE;X31-PY|sZt7yFC)m1a@C)?H?_Z=V@Y z0^Z4&;fV|x-hb;rzen6IrORVEZAu+W_rdfn#wI#RjVsi69*>zWMJQev9KR)JbR-V- zAq#1bboOyqlGzR2&`(e!OG;r7H6>csCi`<>B@oSvfBZ!hRDI{CO42OGf}X&PF;=bS zbdhSl;6e}%2xMA4I+N+zhOZul_ssygP3H{y?X zfy;cn^pU-%F)1voQjpC%m)`!|QFP|6`XlJcta(2r`Y@CFN~@{Fvg4GI{viUMR2!I6=XGUDR??SRuk>+9>qbjqzp zZd|)|ZOg*KqVYxj-qRnZxvcgzueg*Hy%g>+H{fE#kT3tZwy;N;1MLNlv`hbFOq@X# z>5~B8u9Y3TP7$k+J%}1i90v_N(KHJja}JgiF;mv1i{Ke^GJ(JL_X4K)=+Q?z35jN| z>({Sa@RRis@2p?;@i`OW<>g%y6&0oC;wpV0nsNUwK0f{j*A1POYNr+D@HcPBT)upK zsj985&1Qmq?t~S4cKIw|b*Px@mJ=$x1amDm+R{1Bo}9&@KD(-NL4uswkw`oLwpSZ9 zO~Lc?keI5BY)1XaF8JBO=Y1o??p#b7!9H3~SA%ihuxf{=ctaX|uj|=pBdGsc=|@~< zO3HV#nwkl!s;Wmk;GeXvZVCrGdsRVkv9FSh%!(IS>Q+%r!h!`$ z84LQ{+l{rEY9N}w=s8QftSqwm>92C`rEj#W)`{O6lLegUvokY4t*)-Jmzwt;%c-mH z!b?iFp_rIqyOWcX?O3;OV^5Bcf7syU;0SSZbi|RClDZY(@BiJvz<}h|ty^#g<!Pc8s1{j)$RwqARhY#wu&2Bx>X!7|U(3(C^?d8|b zDHOz5Uab_jjZnpB)y!wixNnLxrUoBeq|_IR#48Jd9~jDyu-#QtVtTc+ypM^3WigvV z?cixHneepR4)C;<$~;$tZnX^?5%{D>K`2DV>U%R0e4ge#al-Uph51YFdFPw#+kUxl zc-I}Gq={0C(kn96H8scm$H&KfEG#VcBqSv1!$U(t(!M?|6B9?TLqa;Q-?$M>ba4KL zAH&B4O+ zuVDPK(UPi5uC_>wgcPLwCnfE;{@dEsCUJ$s$2XxyDvl!JP>hHtBmcS6KGK6Q$|#|ncOgN$lToE}_LBx=-_B1hAcxe%o)8kZ)uTA|tcuJP|(Waatt znlZt=&~=2898Fn%8{On^68>bG#i*rQ`rM;q<=WjC+1g#J3fGm$;5hR2;*G2MZy@=F z8nwF>kd*2X_T4`6wOaSMzZ~IFqi0leOQWC-L{rm(EmuAzCB?9;ygV%=``gNRcMzxI zCeu#ANxMt>T+WPx;B0R8LpyRGPU=|rEk>C2^0}m(o{(wos6OcF)`GEz?~OqbA^|dX zhu@;ZMW(j<8RuQqTFHo^HWTLwjPuh8_#souaXYf38;~ShA*kJ4pFvX`{5iEiW$f9Q zvcmM#n!HMkrjUN?veM34oW0wKXl2KoEoFcHs$1L#?U{nNnv!voQ8r|#>YSd>XlksW(QJLqB*;(X>Wsl%w4LmD4t9M?)WsASXq zwg^%IuN}ITQy#JtnJep6e@W5-s(VaZmixojn_iTP&j_>#%- z@MV(&WW(MXe{6B-tu}p&M&3=q#)-s+58baC%7V)xoj>NRcRe5vzUUkPxfm6*)H!vGz!tWSNuhLC;5zUw)&6qzLXrY2ZtQO5QvNH z{QP{9h^D#bI}h;^B+k}8e~8!>f;p!vecpH$>Xc)BdJR%^p$4C{X3*a-RDvFz%gRlo zW)AgGcV2K24Im^&8sCm&v>XL`Hx|#eRkIbaI-=$3SX%Q~{c81?1sX!{-EUA3w31G=M!m{pp+dS)*}Q9O^k#JljD$1oE14vhQSytHQs z?M=M$!>QrZb~MoW57EL0(r}$eJ5)#ZIadC>gD$(W^Q3FBn0$kSL@`e$7Q&sI)yztA zzHcTsKq^wp9BF*4))Jg^BOkf?H^-%YaWKpEF;hUUbTOt@AsMmNE@>K&_QhqqS?D4) zk#*4X*p*kK)|^F#Fg2EiO}RV_q*aUmv{4h2&m8u4W@cZ{>`3p}s^OLsosW%m{@J=; z(Tg$Nl!;gM^4@7{`7VSHb+y;#!UPVfXY2pOV+si(dQt7fZtZ|2SmWs%yElAq6->w; zl1wi$6+ zBVBk7a&UE^2AJp^rsbhE(ZZE#BL=i{MLWrt)IMt*b+Qlo7dcv% zArm)A#$R<2#Ky-MSGsbSwAybx(>oxC-X~T_`@(;&W2sVSFd?Xh?-~J#qv@Q9hHtx~ z^ROM!{P@Z`vd2x)XZINdlQjih%L?7lRo_@F*AXuI;{!9fv}O-!?>%fTS?8QGFguaG zK_=9wGGZ^*cf_XNb$=&#q}1a9CRtP2hgL>iu2)L#p9;RJG#Cn+;*)4JhT2kU^_yN=)Y>!&xy7 zlUW_Dc9If~2eu;hR=C{TmWLs5N4i@vc~ybd47aLLd+kZnSFn8#0+Ko{{KVP3ePmXg|{Qyg6Pv*y&aMB0eaV zAm){SM3C0w_O&?w2rlbuQ1NX8nu0wYnppm#=(x$kXy}Q>fXh6Ov(ksR#XB)-IGPWko0Pd- z=o|=e44DP_d>r0_%`S4*p*V`2&C)8*zOOrV2`#&o60a{N&Ll7o=QQUhMfUQZf2Ht$ zTY5!RC`bf1mi&#{%%Ks)JeMr*bYMxE!(04j^Qag}#pJ{5#f>5b`{#O_-Q}9~K_&Qh zX{eJkS&GV~F?mmm5hlB^!r^4oA@Or%Ob_oEn$CPFjrl=wXjNs@p-jxXK=_X8vKPXN zSIq1`l^4@Toz-{(Eyts;GU%(ysc6(MzQ3 zOz4-iABlZrzjxfkKDc6UnD423usENIe1AJ9SHg_vlZ08TvV>4y@ky#^qnYy91P{GF zAhtE4Ew75Vc{7>x<&^22lSDeDi8b#*Nl{exp^?#iERhL(%rvd7>Wvj;nqhG)8z;MJ zyz?1uoPz1_6qRH`m65A4r;GWF_A!*{F;%UA4sRpXykeB<3KL;HUk#*YRO4oao>>k% z=#a3eLheiH=lGg8*{t`_z)KktBhcBeCNtK$ZG`2(D zR!%si@Kv}1GA61WYJOx5sop(=#HrkXF10^_Hh_q=uctAzZcu-tJoFR1z%g+?N4U1f zK;TLFT0;xNb@N)~aoTMKS&n08AEHjp zjN*H%LpT;O^`iP#Lrj}nU-^lbY3*M}zU5;vq_x+1&#XK#D7ACFqP6SQjNC*Pp{h%C z&Ic)o66M1esM6Q)9UV!zX}P|zJV!4azk5Frh$Sm@qHwcYEn0D*Lc`#Gw3LDg##(Hc ztSYb_-AADha@Hr7nJ@vl3c^$4al*{E5{prxf?wYtVWj}962pv6 z4`;=I!4qnw{up|zc!1|67U@yLA^Z;IQS7PbQh}m?Hxx@pFiRo9I@kGHxTGB45bkQsmU4)Os-hSPKMXqRL||>XRjO6Q{#5F(in1^ZXd%34zt+rtK`qkF&DZ$t1bHDI;)C}s zCE0H%brb0~SJd4Zur#*Az+S`vNgB;Qtcgc32fuYO^nwUYu+kN{rq7A%ASNg3{PART z3MVJ-Dq%o33U}2u3LR+#@?GP%@jLOheF=qL$EE4Dz*G=ePli9#X?DK_k$JFy?eep= ztV=b%|2$-VQJh3bS&&3ZS+K+4ajk2bSq?gf-P}K1{^RVg68SRZ*RDi5R-U zkPWXhgXBJv2FfVrgg0qR#|d@iaS9Ow-$2Hz(&E){H*=vG`gm)DYm431MZLBF4YhRR!-ykagBEAqkvf67 z>dgw3FN)(>4h=_e8OCbFEWlj3ndzkzH}X=1%IgOMP}BPPsb8HS1mY!3z|&GRAE`ixNE+j6<`?z9DvB=;Yt21Gb;XaxlcLJ< zYGZ9wr&eQCQ`V029u;!`LbVsZFN}Uvps=onp0so&2Mtz|NV{kb&D^d)8sHx=`r-MU zjms`e&w7mq{Q8rBf$;YaKfYPT{Z|}+KKsn@m#}_({7&UB*Z=Xsql~{gh(A7fA!_v3 zOObwj;?wlUgn#^e>G;*ZOa1rzfB)ON)hzkvC-B|>U&#MciG2LX5GS930Ez#*sw#I! zPtRjiZwjBv><6P}6tqA?DIzj5gN&S9M1Y!Fd;rve@iV}goe*z*dHF7sMDQJ@VmO?{ zX}&4&@p#H_wSG(ZVHYZ;H8nMlM$k4l%me*J1> zN*+8hKK?3tpOk=@SRmo_@KUh;7+ObJUOw4CS67Hk=FgPikGRNy0pcR-g9q*!ly`*_ zGBPe4q@{y2+}*3qk z+c7OQ)lo!P*s-vvsIn)ShiUTTN6KVxRzX@?+T^-9&zSZhx=6(S{+MCG)>Nf^)%*9$ zrQ(_2w4-JBup(w-L&)a*O56Db1U$Y~&iM+(7V@xod3o7AdGdriq1$Zi3&zd~mg`2z zdPrfxB2djrN5%}jpKHqLc|~&aq%9OW;<{5JAt4b!W&PydQ9U(s)K{}VW8a(Qm|7`FzkU05s8OsZQ1$NJQ($$wm|EeN{N9kmLnN~_$BOlZ z6d&NhtE;NCqQ)SXEOP?`TQ%+kJB^QzSG9o$Q?7>$jE-txNmA@TUG5L!1r}`H;aaNC zu+@sgnxNaWxXszIt1Xwlc=^VS8z)j>1jOFEy?iDeUhOGppWP26*N1CP$G(mf7|JUu zo|+!*EFGgK5GbNPn)WSg&>%SjzK`$e>AAAfla$S`@SGCS-biv%GdM6{)X~2sgsL8i zUh(MZ>S9bPoXSNVdBt25e>Oy#f-|x_=}vA~O#hzH5w>AvWwi&Q0yDGo+vfotQ&YA6 zXEL8;wy|Tt^~JPxbQ~#$osM9EK|u{Pco--9_g9a+eX}w%jnaU=%YmHBg^leeMnkiB1`aFNeEzUnCXDmkJ^vI}SXlG~{9d*4C*)H@rBIzwzEopLcvdVDsY3X=3JSJm} z+v9wtv%dn@+}vCU0AfNk&pHtfP8Ra%C;tniSoeoNr#?_zzH;TtI%reTk`NQy`rZW~ zrf;^DyZ7LHTNU{G=-61l=ac@W41lEs9QLzy*eAPH(Iyr8N=izzCr70x`YI}sBDDbn zBO_-o$w{79PhSTH=e=`8PIkwl6b4%{-vKX%b1MGlpMO}lx3^C}e*9?J(9l4`Yd~yl{krT(wajrzeycec8+&7G#nQ-#W2CQ7d3AHMp>K4Q_ig)P zHF)_@6|;WLurM>T4KT5u*Kgju!JeQp^s{X))~_`%1VYk6&MMW{)n#?K+EA@u-1SJ~ zjvZ@NVqq!z$Khc|F!_n^<5`9WI|h!?b@mB5+S(hgE-q(-b8~YpAii0l&3GofcIQVX z#l}yrAg@U7l={~@=aYIEM$olW4h;?>Lkc?!RHnXt>o>k}3XGD6gp_n8>Z~mNwD2~~ zR_=S#v^z|!tiHDjetYV>jJ$#Z#Ng-8RdU7@l94-{FVK%(oZV=@v9EV-Sy55J;O*`G z{$Ryn9W>`GEa*!v_TPcjdmIBdVkD+&A5{ z$iceyKIH{sz*j7R4<$WcG)DThN#GK%xS3*vx! zh_jG(!jFzF73eEMQQ!Fk0|R@F`&nBH4lW`@NC7r|>9ub-l8!5Mv9PvY*V+-S%Rujz zn(EK`4)(p?=;yMX@CuH}uo&wkiHkaX8=jt?PE}G;;xtahuR-#QgM7U5J}V1p)%J#% zZyWn9HY*cT0WJjv#T6?cN(rq4X1W6`7F%0e0U;aOxr^iqzlR~7Aqd86Ew9SLS97$p(G)Zjd`V|(Je#{*$<|yxnmcj zfLv;m(dw{Vg#>t%OMp?Y7$qLYoavSe)7_iKonC8?k%lRq=dG8v|G7=j*+ za%mg{$H~Q^w>Y$1eu?Q4_RXLWBKq?A;$nLWVyDsw!gBxT;IaZ&R0=W z^N>@!l!#I^aqL1LbXG&ry9BHWDH{MTR7N9>^z^2u-03dt(e?@DBx32r!gw^FK+qKSR?G_e zLh&68=9W~Sima_Y8N7x`uu4ZqciWhaCk+3$dw=6IN_=fZ9Tb9nKPY6?K}AL;c>X!R z7zn569omJ3g*H<#MDdNgv3Mf$>&{ZQSjg+wcliYc>64O^(=E23Vf#DJ*aEs{F#)*Y zdw6-Zx3#yg`|giuA{JnGf9dbz97qL!Xi(G}e2D~L9mK?=AIX6M1c1BH92l(q4nGmV zaVa!nqQB&d;DPl-DeL`&(sA?t^cf%!$4`BIc7$H^vq85>+wchq8~3>D(}{yT%zKl0 zB&KI(Zi5(s+u8qO7hOWOYF)aLo(q_@t?8LQ40IT)k9y?%1E1C@*T2>C~v^F+X?XS>Rx9^|JutX zzkh!0#5nQ$H>1SwrdSvgMp!~O0h%3YOGqsGgLv$rt2?zTOo52;wnN0nYasLUEjf?$ zkOc+SNEpllSy}0Z1O(6#a92lJ+1J!Np7IC#`%11(P6_}`m3g_j6-P%#W&m$Ccr2*m z>l5fL7V1;?lHb&{5{x>OHa9gbka?ub-Me@10R4Q!vML8PTBK|ECM4u2wQl8yy@w8u zjQCOHHFF~)P+%%WvopDn<~~?OVgHLLJ&BVA+ETGY;kK}ndIOOQo4?ZL%O&p>e?2uR5URG&zu2%%q zvGxP1vPq2)xv}GYeUah!QIqA?GW2wG;eb++c%a8iqDMwX zXjIY!d3MkfrJ>_(kaH$Mw|I)GP^cae4*Xn1qa$u@ADep5|chTNqa_rxz93CIb%b;+lr_ z;pDpO*R?f$E?(Z^j81B*7&)D&cQGQGXMh>KXCM-dxNxw*L# zq(Nj%yWPO>a2?O*O*43ACKUr>-cv}ZmIyh6>k$8Y zQs?8vU!HR3bUt&3xi}1P&Q<_v)TE>-2*3epgPTjjLPHl=4eH#35QwcC4Ppe@MF6BVZZa+1YVqKqP7H=*Tmq(XT^zpY&;^0(Pd4RJKn{ zN-D9qts7l_eA!p~VFN0gHZ*16t z395Z?054gcNqdCm^`>K^{?L=1ZZ<=jslDief_0la!MLWjwrPMdj!LG>oID~Pjk3)I zEYG_0WL_MJEhBmhp?{#*{rdH5OVjW|%B^P2>s=GCQ7fPH!MK(+Jw3fK2xx5L;^IC2 z0Rh3q2KC3eGW8m`I5@L=yStZ>%{{Q-xtAHmHP2+6#x(SW_E|Cde_wy z6xInr+IPQW{o&l;;Epax*E$jtiPk~gpkG`L>VbLkF+0%GyDj_kEKdAmjYG9xg zfEtI~ii(35{8wy1mgqLYC2VVBgVRxya?Ee2?ARKLk9ZXrNF2Qqlb@OC;6jNuM3TD! zcqootOqZ)*VyC2hihT0GJ4r<1`MV9VgExHu@~JQhS-HWnlDg)`#xV+`Rx6jZVU1B6 z)v~i!8h{v1?uzC9#z9Y2D3m9W!$kU3XlUrS$EN1y2lXI<%|4cag4i(WhW)krbcmlU zqBbRE%InNy8sx=wVAHGDU!z=x%$_}S535sVs@B`QE+Zqe1?(r^cCyU!rWXO!&!D)h zv1F?6jy1?WdM4)60P~UrZhnxrrP;pe1_J!LFa;Y62*q#ED+v^pJ|`oZhV}9X1-G>M zf`L_2>|by<&tgaGwfg&+Sc}D+)7io2%FyY;0;$#i#Atm=PSh+IdddNtcX!sm$IqlBg(>SlFk$Z z)ZBaq!w{(!`SN$}MP*sJR=VXZRVAI%7a7*}KF7oHR~`GvmUn&Y`4>s^ z{r=R*Xg+&sX-UaxrSI&qpWg)ox6XTuBJI*;|9o}WvAn!|eD%!c87Eq^R`a#MdF*#) zj#5yrVM;Cf&o*9AJqIZ@E8+nJev_17#IX6XXj4=T^?&~@MF}q zQVI$M=$X4vRX=84T2Fr*^!24NJCV2S-ra;c5$pGnbMKW>`nJ$$XM6$z(Wl%bk#KLO zKANm}KV1!tin{vxnRrF|ei2+N$n3c@upUiXoDh14H=?v{M^CR8=9~pgZKRI?kAn7T z{kOD_mePrNSWS|)G>T-$NL`0Bsc}$~h?S+JdWTdI1dmYVS$tLFPM0fD<@Pw(_ zihQ>hS~b%`TxuMbx^~T!zI!)NIG`Jfm*sV;FAv-BHqv{xmw~FKeuwtY6V-jwOb5m`m!zbmB11%rzA_4Nh;0Rl zXYPZLXxQJf9rJhr#6$!w({_~?6`;l>j#=>RCoK0;DnhOsWmVjxXB@;v`4&%su~ok(4t{Q1 z^@qN8O|Ndvf@I3eS#W0EYF2*GhIb1)P`q<(2xc)|%Cyp(a_He8O1kb^YY64~jI%P- z4242fUUOi;)be+cnbKxawa#mUh|01uXE41x2NyWcmJS!Boo#Lg?#eeP^3#fjUa_l! z*Vs==C(0(~y@xRJ6oPzPr(mQ80voE&-|0eFELq8UFpVk_-}exj2J0skrszMj5;ajP zmVG0gEkwYoM>N~ubK)@p$#$gMn67qeq>Y%DGe8{nGtR{yZUAmOg&WecvP$vGHXi-5 z4O&G=3RaI~V?!n=HixZFY!yJm1k*M@^sm&~FZU#knRSgjySf@s&Z?IVJcF>b46g47 zUw-*Wts@$J&p{nE>{a`|=NU)2of~5xabuVyb&-gO2ppmOmT3WG7K&gO zx9~M>DDLf_n7X*QI8;ne?qzp;uFcL~?0U{$_xUONDYW(Zp{$%-XS4NK@!6fUpA|}e zsaGLgl>Y)qQ;Q;aI#+aRco+}lENrOdW%#~c4b7ckldwd#5lx0Z5V&8hIxL!<<&|Uf z0khSEK!)!{wu{LH`l3xX;wbe+@h}DcCD9*wUO8;R{N_O@E%hkZw{8+!V z@5!f_%D>IXxVr^%03Tlv$U}zKPK&S(AfB!Z14tzDhNqVoo9Rzl{zCmP-SyD!jHUyp z7~@ata{;EMmWxPKe4pLg+L~W(J?0B7a)|10#;s>r*!RSSJ9`Z+0_|u!P_VpbynumP zB%7v&25Wp~y8Lb|!OE;5n@(9rlg)o7atQ_??Uzj^TE=vuszGd`(;bMoTb>;Zd54bwM34B0(c!LemxrQ3Ilg_9$SnQJ6AIQX;?(G zO_FAJ>8(hOsEv%@TiM!}4Xvb)mX$c=~5%SnV~HS=}v z2ktPPPq%=T^@II7f8F;iKR8JOjKU-TqaOlfFHQl z@5Ol=7%>wRIPjC%!|~u}#j3KfAq%GJIY%B-ZS4t+YC*Md{3PIb^T8CJt5c9Ko%a^w zR-6SdvZTY4dzanTzkc0(`tx|e7mulb0Voc=Ir8|}Gk9!$rbsQ(Jr6`m-Xj}fy2EO~ z-2_Ye>#e2*hm1?e0Eg6`11553#Lba9y5sQF_rioxWNt@`@~-=ny?7PQ-)sKcKD4Q$ zzc+wO0~{~-UPm0>=1h6F$_b>!gb%9wOSEjt`d7SEbK~qlx0NReIJ@$Gnsc}>9UexJ zhDn2?qhrLBO$Ch4c5?5E`Onfc(bd27NUvM#y1|z{u(~0brX2o`p5@s6;&^}kLOuU% z22|GB2gc-EsukjNYe}$z>KFEz^6FwMK~l~}j}vml7#Lz%FmgOqT+Ev8aO*#TV}YRZ z2MzWQ4-Ij`r7hN+YiO)`(uUT&JwXr;y~YzMywec~9z(!Yq95{N+}N50*Jx__M>bb2 z12ppBWIR@H8AXPlai1&iB{2Tq7p?$w2UrSYSgYfc+&Ir#|4;7>^Uw6lo(J19`3Ex9&cs2~dSa@yH&s9IY`#Uaqu!yL~t|J?m5)NRK!@qws4Dr`5JvA_cC zCR2;q75mp4)UYo9C5<8A22(S3heJRT&GY;QAl28)_eo`(y>(*AIWQfo0;O0B9p?vV_)xHFKO(>LQ4j?Zq zujZwtjZGaVPDmaOeb>}bXtIY9z?`F=pAY>0&qY@P@?_oB-3`5ky9APNikT{hY~Q>? zryucEDto}uVb)VWcgpO%;oZA;JZZ`qN+Y*>Lh+f3ZLF=;=@j3#2Ts`dW>!?h|7!Jv zgYEtTD6cOsZ#1at>(}cP<3LQ#(XJ=Bu?#8|xA%QF$4eF$0LRmnR1tYU$f9qE5!`$m zGW`*VR{>CJ^ZXGR0P78auba~UXVowM`P)Gj7Awp6p7p6Orw(J1n+^ zCxq^fGHbJ0p<%t|Uin2u;p`62Gx&mp$Rge;!_>_-nF+c{J53!Kf1AhMKjxu|$xNV7 zZh#Pjx19o7XCZ}nNb%Jt6$F9YXj#*EcM)hrtG~a0!g6nlOd|iymye!;Uh*y+|Eg}$ zs2jaXdR=_d&nR&H^`8)ycMXKE5!YJ%IQ9X6#sd=*0v>sX92Aj;jD4h~d!F?={XwT2 zpuPytuXqVOq&aIKZ*0T0fYXgWc8y$B?A4D^74YxlqrVKJ{9RU7*5rokedt3|Ay60F zUd)`8N$GXXA9$7;2i!_yZ6ZpA6+~6>^4ofhaU`ST*m5U@iLEZKW11QA^ z3JGDh2&{?3EpL&hDa-tt0KvlhTd1Js3U8kOz-W}!YAk?ZBatVdfV`)Qb=CXf};wQX7gFN=^@^AHD z`g^4~A(BR_ssjp)GvYf>?Cdt9f?x+abfy3gu{OK?}f4x zVU4W-a*9JAdLADkogLZmtqJoubGrRGe<~mo;Ib?P3N-+LJE!bwi>hjCg)(fv=z;_r zl6si&WUV?K0Oz;qws6v0KOew<@$&Cgzic(GHUMBnKp!6$CsN~exJh*z_srL?Va9GQ zTZ0Ht(F>BWR$r*L_7UyW$4ZbYTC|*=L5oQCkG%V5d`~w1c)?$hx%|=I(7FL>xpEbO)Y?NN`9DgLrs&c|UG2pMCrG?N=dy)<(0dVgj&5R8DQc14970AE@1|u?9W{ z(7j0#lpFs0>qifPX1;mWTVq`hU^D``y0DJEzEYw)#!cK?xaE(6Lb7EdHCPmxnVG$v z!AJ~PJxO?*_m72k{NC<^K;SIEaIHar0iHLuE52uvjspOsGRNU_l= zn$rC8ueaifd2aw+c+&th`4_FpQaYT`ay1ZZQ?F{&CI5E!HE3-z6)SL;p2t? zGXVY!Z@A*pr(bT8Y?5T@2x8|T!dedVq5a=C0Wib!A0V1?lH;5F-WnxoQ;RHZ3|k-R z|5gMj>Xw=Btquf3YhHLl5M%%2CIn$iUcwarTY%5(?F|?IwOl7a!2nHx*Lxk_fi2t! z{#Or3Dn&3Q(~EueGEjwQz39esepKnR3GNCH;q)Js0Vh&EJKA5Ox$Y073BpGC_?QHs zTbJQZbSsD!s~(W?0g?^};{4uM?2G)yxFudOWzOu8IqU2pTT zg};3qTm4C3KVAa1oDBv#LIFq0CRt1cz`eP;s#R+{RZ)=l{S%cUc)hm2%}rd3zSN}q zVXeT4rNlP=gWvxw@}RH+MkC{4_fgErChaFA%M;Q)EekqFF`akL<>ht zo^g}hCl>QPJ3{csQ3&cWZ@{!n4fqY0RiLVI#Mr+^2dMqey@TpOu5Y7X z{HLc+mb9D&)7W~`MN)pzjYkInWZHK7#V(pbVBP>109@%4pnoFxV*I~WV639jp9}Kg z(qTdS37d)pP^!QA6QnMgVgpA-Pgn1fWe>Qk0BCjKY5Re4;hnJzwfTRkfX{B)-O|Kl zQL%QWxU1w@iIxGV_WZ(%OUN5WS&kr?n32v_jbksVs`~zMyu_p)a{A>i{SVCMeHD@= zDynN}m~nTdC;*TXWtdmz;`Z+-+(7|6J|BUYds;aanZK8cxRUwgHM>L9zYY()LQ-x| zHMrAs64vGml7O)mUuD1nFRtSMoI0aq0)}J(IyYP>(E^T9>Fn)yQyZ-ExoY=eUvEVG zXYO>0g~i3LS1-TxVH{b@1Rm%zRbeZX5&BD!_&|~B+SCrV2eM_vvImQf2EibaMRz=l zVfJ53z&knG2a19MA`!avf7-k9f2h~?f1cB+b1H?DrG%0tVJwBjP!W|SS;p4LUMiC8 zW~Po)4B@_5BmR^NU~RHFLY~ z>%Ok{^?tvv&*U|Do%#94+iGBa`Jav402?_4PudNDp2@7E@&|PU#1t^{?{Qjo9ddBY*ZY1yZfn>aEwCG>gc;CIf1QWu zs4gkF!LPxK?X_EdDf-Xhmc!wmLc&AW#C0E7;Osr!CPS8)K;k-_`~D2;66is0giQ5? zTLGNkU)_&Am%s_B?k7VnJX-Ky+xz&UMpz1TXr>96GDlT<{Ha{av71Y(PuBmZmr_X@ zHcr6b4LcMDlXnkZ(|W*ER#8zIo1ENhH7qQ&c4KWb9AN-`WPVu|Rp2mCP*jBeAn17Q z*JI_=J*a0-y_?dcvaCi@s;a8uk}3f5YJr+^@NAlDRcxcIPAdR_M{?}S6-QV18@+j`Unr)RAhIrS| za2mj}8Mp(;569Q{IEw?J*EukN^VgF`1OLn?)@49k7(!Z_vs0N3e8L}E+i0CT_-U!< z5P#l8f2M;cu2zq3aQXeCV!OTT*+g5$oj46rw3tUTF0U>FzG(x9g|q$r{e*{0)-vDs zNBPL%!<8o+52SPEWp;T&}D>7DqyP0_`)c@t! z&8g&yM%#$ynd#~GXmY5%s`&DzPTcg`E$w|>b2>$F_9HkuQ*lGW1EB5l*2gl8zTVG{?! zsLa11;q5U=NVxd;J35hjZ88j<*36l+XMHm*{?>&ri+&}CSRzXw3;Ehr+v}cMe|r6{ zZ52}hv{L3w3V2e?fQ7Lm4RR)d9MV0H$pg8Faa2HumFroD#LsjdyYY1R43;wx05_Hg zEyJa&S67M_RrUO(u&c$V}W z0a(xDWr>#YwbSr$fm(``wvVgv#vE~9g_MRS_~A1(q%38XF>aA{jKAM+GVz?UogS3f zLvWZ*-s`-tkiu(|V2ccm&szwLiiV3C(l(b(E%haZkR2aB_&|Pqqql>1WG&k3Gy=ie zm7bcG)*@m!`RvRxrWq*vGaW^gV6IaB>sP>a$jU< z7LuMB%7knIt(N=db2rHW(K#{82Ed{QvP~Bdy<*DSf0eGNjhAp!Xo)`^=pV7<^<_HR zg&;E%UD@zLb#-;|yGLfI z+O6jtv2;m(=&!Fnl|v0V6^L9u1^o-dcAZ_Yl6Y-0`!-b)$iP~k(#0O|01WzF_^HB~ z#1P-L%l?Rt4mL-gMOZH=+r+#PyUIL;H$~IWPaJ)&)w;tICzovg)Cm|Q-S=bM9=jpB zE*QRNT%4Vo!64rI`8p*s?nszx76T2yEx!823)-8}_2A0e>`sIwy--(CQ@gP5%con; z6{9H<1KCz1r4P?3JGi>8Y~+-;u--1P*Y2y!8W18ae!GBu(o!?hf1@b%NX_d(@J#0m+(KEN5hBo~&11FP(xfUUDEgdvDzo zjR6JYTG?xra8AEp+0^Ere%uCjCpSQ3Z}jwe{s7X9c6#14f>ZZUZ2F>M1FQ$C(zVFt zolof#YKMOXUBPdWuCK3;m5WfRFjy5?ymwYpUEa^n?;gb1LrFq~RhQp+PEaBqIelV( za=!1QH38sOd#r}0Hash&8`MAS!}c*IYG!|BMhXT5#u-7aBtXsxmu;H1kJGZthEPcd zYr_+eKGY%hYb??x-H8ThQ}%mE7z8{z!jUv+?+>SUQBT?mj6fqK$Kw4H0sGWWnzWrY z+LKgIPNhF*0a*k!DlgbRe{>&ix3*97QwY$czt(}>4|$^Q9zFtYS78TUhul?do!7qC z78aEsQ?YgHR$Y@^8Z_oCX@Nyl65_cSzv`f&VJ3DC4HIH1rx*{clQ)Xn3^U)jR$ zr3{&VGM2lJ)@eQth5=vjagv`F6da5_l{-U4vt?2mZkm&PpNT4Z$o(S5C-8W@v%mwn zUM_QHGv2+!n7#AVMIhK2Z$2+zfQ(PdkR9ZXwIk9Bxh-7h`}+h^B7!|S`b_um;RArD zRFPLG;V&W!gmw%gUIJuihB6H2zlE>Ix8xS!MZMeqwv=cxZe!2W^SdVOx9v0vOJuot zBu=S7H#jyrT8Zs^VlR<%x$w%6DH}?`*7|!=L14|dr+~=z7@)<6iyHYd`**A@7+6+< zyx*CCV#o5>`MIKxR6FiW=mqsI1hfJ7D$I~*4(zH`N`hU zB8MwQU{7_`96`l?TrOHL_zX|t$x)u0Kx+zAD`=(#>0IsBIL}nc z`u+eYA<@@0%?A*zjUzxq&?2{Oil!XtTlRDNCEfjYzrWGW?J=@s&`Uv2c>(@e+zd04 zz3Qs?bNuxF(kR@GS_?3{=+;z@L(~jTJH-MGDBOLu90iJQvW3$fb7;`?9Dl>}Q(VLQ zK7M?1NKK7uOYWi@>^wBIsr0-}QB;VYbhU|5_Ms&k^A28UP2}pYU+o|4zK3Z&J3Rh| z5fU1TE3B;4Qv#~Iv#Tq#x_`#T19sTr0m7Hh7&c zL#qV%f>V1uXap4As^Eza7dpGU7cR6N3&<}h*o>d=-kDU-7H2P?2CmYs0Ih-^tbBD* zZxqsVlB^NUoEXFcc^QS#fPLA%=UctYoZA6u3#ITB5qq@{Qa|gWs3Pym^jtG& z$_gw>5CJ%JHK34k{VzZ5nrJ7wQPsY1F1IzML9@2M4K+*T;{pV}3zOWo8Ec92Xk!J0+JJ87Kb}_zN6_ai{%jns_KDc^NuFM#C5$t0lRl~baWVu z-l&qCyFk-xH(qAgFfWN3oEY8 zBBmt05huSiW3Gv+1G+nQC7i{Wg=d?QIAKv$Uwc`zOSJ@o7>?keueYMaI2GlVh1w2& z6Q!C(>!R3|AIc{MtzH>xY1R`W4V~Ut4SIb=GoRe0(UNguOnc z;W4!y;U6YuWkh=M-%j&Zv=ub|*&6%b@JiMchA3b)8BPwWfd+)1wwB$>x%`m*-D1>p zbL~d)eC9sdlt>Vkh-#!AT+SQK6OJ=nJVG$<-CkmHc~ zZY~a*${UKL8c%tP8fcWayFyXkm~38isKvCf*=ME{fRY^5u2Y0lDHKxcy3#lqf+!@B zW}y-I@?)a^+J+i~kK&`CGn^{%ZlV#)59otR#}^-5+!Ouko0THD$Xx&=rS*aCk`Itq zYq=wlD5YHo+JQryb5z|`9ogpRoe{p%iVPQz=7D19sCN*yrkUm1q-Ufvg6!6FgA~{e zmghk5U&)D_s!~igHvwjMa@q;yadn_`?567@MGI+>f80z4N3`s+G(GF?z_dV)`GJxS z(C+*IUtH9X96fmwrE69zNmoa1{VD*U@WP}lxK2wY`mFsga?v7+IW{fMFa$ONe4Z4{ zd$@dtCSA;Ph4|{??~ETK2`=;5A4&7~P7P`TMNs2cCet={)2t>#Myxkbp)OTJR4H16 z!xdnv;LuQE2@E$A@@|SLf;!-(wR(p8-YC|SfQ}e1o2HHC1>hMn2#6UHJQA;cNLv*A zLZLEdnrln>{q`?n3|HW;mzY3g6apSgzb9k3WTVMIND1AI282~#DTzH_(SNL{eQf)l zggTTdiB|$4I|Cj`oti<;Hmkg#Xy7c}h*-jNXHqp3VKR^&6D&5fZDMxXp)_WAV^H`6 ztzGlOZ+JcMa-2NL`{Pt(WVI#tmMm1#m>Nm`De3tDJOy# z9y6GHPWkyG$j;9zD&}3J*B?z~d{^GWV5Y%dN03%{u#j=+L3UL1_6L+X6OQaozyZQg zQok#v&f{-on$=cPK=nXN8Afp1p;Pxfuw%?zF=a8dFR)$$X;1{ik9^jJ(Ei7Qj&2m7 zB?}Vw7|`^GOf1PF{5gNkmR?XR5x@#*1J1E8-YYVQF{a!Vl1>6pn9Z#eQ)GFGC~gl& z?9Dmzxad#)U_aAq*Cuy>Y1l@Z&4~Mmv7Fl$Xi@6Nk;O`#=9eximbh|j$koGi`FLJ zTM1ATwUf1^ z0dz@qU>tI)!3awneOy?(Xu%`aX%o}q zUoMbLKPskVbx+Ol*{8^9m_A$gwBFhzs_0VzW5u{|aJU&+D5eAhPd)_}*Qp@=?B1(7 zy?oJNPF&limG}lP{U@`xY-piWG$GBs02XK4kwXa{^S8kXgA_4bR#v86qjw+pZ4AsU zVygj2bOKz}+!QNMr+N6*S#H|*M?+bl!r}-HeL4yQ#s{nt0n!uuQ8!FKp2w}4E*Ge<;2ta1# zUDN4B=C)P3k-PW}9>s4*Hv4om?g>vu7j4>67VJu?Flw4tVa~)8>j*ciT#bZd-WaK= ztLOAWY##+H!g5D}E#FfBTzng)zkV#6Ai^=Y_4o9I2Se-z1I$y-1Sdp|EPtWi8(c>V zsJlzEpq&VLNTUimgt15gUbi-!?eLaPh_*$ev9qIXx$tvLI9B&^)d1VA(!c-3l?;3m z<|-q{kdRhkva9|^OA?lwJKosXh}Z4E*>2E=TQV`iSa2VY!0p1ek#YuD z`(|L)a@r>$7@G}hgB2lUkKTC1%Ix%`T2SV$`|Jz4Hb7gY9Gj{fWC?t@(_nKEbYX*S zmk!%FI_i~CCU-!{C%6PEGkIl9O5m#xatk($UgQ+_9KBPC*wzi!T$7$X-bpL@SQY-t zXQWExvz5>i*?0nmNQfiH)bRP#Fw34&x1G58D;&e5_{o}OQG#eW4A49>VX(6_+)sGS zj(6@0NB%x?2kg8;p8xrkKK^~7D4lB`Zwic-X5bPOZAwSb{o&1@g;B zlVk@n%?~Vj6g#@dcc-Adhmk?a>(p5>;W^@LEra z_ivQz42yrg^%4;4ZPtub!-;@ZJEr<<%Q5!TKd7V^FZ9(TeELRa5(2JV>FtJ@UZo`_ zK1Vq+_iJ(qDlVtj_wa5=ar9Z+p0nQM)B$-OhTyu(35{kdVSU^{?<1@@5%ZXzXCqup zaB1|LqHbk=_Qg>}MGG_ddHINmSbFork me on GitHub -*Ray is a fast and simple framework for building and running distributed applications.* +.. image:: https://github.com/ray-project/ray/raw/master/doc/source/images/ray_header_logo.png -Ray comes with libraries that accelerate deep learning and reinforcement learning development: +**Ray is a fast and simple framework for building and running distributed applications.** -- `Tune`_: Scalable Hyperparameter Search +Ray is packaged with the following libraries for accelerating machine learning workloads: + +- `Tune`_: Scalable Hyperparameter Tuning - `RLlib`_: Scalable Reinforcement Learning - `Distributed Training `__ @@ -25,8 +27,11 @@ View the `codebase on GitHub`_. Quick Start ----------- +Execute Python functions in parallel. + .. code-block:: python + import ray ray.init() @ray.remote @@ -40,6 +45,7 @@ To use Ray's actor model: .. code-block:: python + import ray ray.init() @ray.remote @@ -58,17 +64,18 @@ To use Ray's actor model: futures = [c.read.remote() for c in counters] print(ray.get(futures)) +Visit the `Walkthrough `_ page a more comprehensive overview of Ray features. Ray programs can run on a single machine, and can also seamlessly scale to large clusters. To execute the above Ray script in the cloud, just download `this configuration file `__, and run: ``ray submit [CLUSTER.YAML] example.py --start`` -See more details in the `Cluster Launch page `_. +Read more about `launching clusters `_. Tune Quick Start ---------------- -`Tune`_ is a scalable framework for hyperparameter search built on top of Ray with a focus on deep learning and deep reinforcement learning. +`Tune`_ is a library for hyperparameter tuning at any scale. With Tune, you can launch a multi-node distributed hyperparameter sweep in less than 10 lines of code. Tune supports any deep learning framework, including PyTorch, TensorFlow, and Keras. .. note:: @@ -138,28 +145,48 @@ RLlib Quick Start .. _`RLlib`: rllib.html -Contact -------- -The following are good places to discuss Ray. -1. `ray-dev@googlegroups.com`_: For discussions about development or any general - questions. -2. `StackOverflow`_: For questions about how to use Ray. -3. `GitHub Issues`_: For bug reports and feature requests. +More Information +---------------- + +- `Tutorial`_ +- `Blog`_ +- `Ray paper`_ +- `Ray HotOS paper`_ +- `RLlib paper`_ +- `Tune paper`_ + +.. _`Tutorial`: https://github.com/ray-project/tutorial +.. _`Blog`: https://ray-project.github.io/ +.. _`Ray paper`: https://arxiv.org/abs/1712.05889 +.. _`Ray HotOS paper`: https://arxiv.org/abs/1703.03924 +.. _`RLlib paper`: https://arxiv.org/abs/1712.09381 +.. _`Tune paper`: https://arxiv.org/abs/1807.05118 + +Getting Involved +---------------- + +- `ray-dev@googlegroups.com`_: For discussions about development or any general + questions. +- `StackOverflow`_: For questions about how to use Ray. +- `GitHub Issues`_: For reporting bugs and feature requests. +- `Pull Requests`_: For submitting code contributions. .. _`ray-dev@googlegroups.com`: https://groups.google.com/forum/#!forum/ray-dev .. _`GitHub Issues`: https://github.com/ray-project/ray/issues .. _`StackOverflow`: https://stackoverflow.com/questions/tagged/ray +.. _`Pull Requests`: https://github.com/ray-project/ray/pulls + .. toctree:: - :maxdepth: 1 + :maxdepth: -1 :caption: Installation installation.rst .. toctree:: - :maxdepth: 1 + :maxdepth: -1 :caption: Using Ray walkthrough.rst @@ -167,6 +194,7 @@ The following are good places to discuss Ray. using-ray-with-gpus.rst user-profiling.rst inspect.rst + object-store.rst configure.rst memory-management.rst advanced.rst @@ -174,7 +202,7 @@ The following are good places to discuss Ray. package-ref.rst .. toctree:: - :maxdepth: 1 + :maxdepth: -1 :caption: Cluster Setup autoscaling.rst @@ -183,7 +211,7 @@ The following are good places to discuss Ray. deploying-on-slurm.rst .. toctree:: - :maxdepth: 1 + :maxdepth: -1 :caption: Tune tune.rst @@ -198,7 +226,7 @@ The following are good places to discuss Ray. tune-contrib.rst .. toctree:: - :maxdepth: 1 + :maxdepth: -1 :caption: RLlib rllib.rst @@ -214,7 +242,7 @@ The following are good places to discuss Ray. rllib-package-ref.rst .. toctree:: - :maxdepth: 1 + :maxdepth: -1 :caption: Experimental distributed_training.rst @@ -224,7 +252,7 @@ The following are good places to discuss Ray. async_api.rst .. toctree:: - :maxdepth: 1 + :maxdepth: -1 :caption: Examples example-rl-pong.rst @@ -235,12 +263,12 @@ The following are good places to discuss Ray. example-lbfgs.rst example-streaming.rst using-ray-with-tensorflow.rst + using-ray-with-pytorch.rst .. toctree:: - :maxdepth: 1 + :maxdepth: -1 :caption: Development and Internals - install-source.rst development.rst profiling.rst internals-overview.rst diff --git a/doc/source/inspect.rst b/doc/source/inspect.rst index 469ef7bac..d9f7a5c0e 100644 --- a/doc/source/inspect.rst +++ b/doc/source/inspect.rst @@ -27,7 +27,7 @@ For context, when using Ray, several processes are involved. that it can submit tasks to its raylet and get objects from the object store, but it is different in that the raylet will not assign tasks to the driver to be executed. -- A **Redis server** maintains much of the system's state. For example, it keeps +- Multiple **Redis servers** maintain much of the system's state. For example, it keeps track of which objects live on which machines and of the task specifications (but not data). It can also be queried directly for debugging purposes. @@ -42,9 +42,13 @@ To get information about the current nodes in your cluster, you can use ``ray.no .. code-block:: python - >>> import ray - >>> ray.init() - >>> ray.nodes() + import ray + + ray.init() + + print(ray.nodes()) + + """ [{'ClientID': 'a9e430719685f3862ed7ba411259d4138f8afb1e', 'IsInsertion': True, 'NodeManagerAddress': '192.168.19.108', @@ -54,6 +58,7 @@ To get information about the current nodes in your cluster, you can use ``ray.no 'RayletSocketName': '/tmp/ray/session_2019-07-28_17-03-53_955034_24883/sockets/raylet', 'Resources': {'CPU': 4.0}, 'alive': True}] + """ The above information includes: @@ -73,15 +78,5 @@ To get information about the current total resource capacity of your cluster, yo To get information about the current available resource capacity of your cluster, you can use ``ray.available_resources()``. -.. autofunction:: ray.cluster_resources +.. autofunction:: ray.available_resources :noindex: - - -Object Information ------------------- - -To get information about the current objects that have been placed in the Ray object store across the cluster, you can use ``ray.objects()``. - -.. autofunction:: ray.objects - :noindex: - diff --git a/doc/source/install-source.rst b/doc/source/install-source.rst deleted file mode 100644 index 94b075de4..000000000 --- a/doc/source/install-source.rst +++ /dev/null @@ -1,179 +0,0 @@ -Installing Ray from Source -========================== - -If you want to use the latest version of Ray, you can build it from source. -Below, we have instructions for building from source for both Linux and MacOS. - -Dependencies -~~~~~~~~~~~~ - -To build Ray, first install the following dependencies. We recommend using -`Anaconda`_. - -.. _`Anaconda`: https://www.continuum.io/downloads - -For Ubuntu, run the following commands: - -.. code-block:: bash - - sudo apt-get update - sudo apt-get install -y build-essential curl unzip psmisc - - # If you are not using Anaconda, you need the following. - sudo apt-get install python-dev # For Python 2. - sudo apt-get install python3-dev # For Python 3. - - pip install cython==0.29.0 - -For MacOS, run the following commands: - -.. code-block:: bash - - brew update - brew install wget - - pip install cython==0.29.0 - - -If you are using Anaconda, you may also need to run the following. - -.. code-block:: bash - - conda install libgcc - - -Install Ray -~~~~~~~~~~~ - -Ray can be built from the repository as follows. - -.. code-block:: bash - - git clone https://github.com/ray-project/ray.git - - # Install Bazel. - ray/ci/travis/install-bazel.sh - - cd ray/python - pip install -e . --verbose # Add --user if you see a permission denied error. - -Alternatively, Ray can be built from the repository without cloning using pip. - -.. code-block:: bash - - pip install git+https://github.com/ray-project/ray.git#subdirectory=python - -Cleaning the source tree -~~~~~~~~~~~~~~~~~~~~~~~~ - -The source tree can be cleaned by running - -.. code-block:: bash - - git clean -f -f -x -d - -in the ``ray/`` directory. Warning: this command will delete all untracked files -and directories and will reset the repository to its checked out state. -For a shallower working directory cleanup, you may want to try: - -.. code-block:: bash - - rm -rf ./build - -under ``ray/``. Incremental builds should work as follows: - -.. code-block:: bash - - pushd ./build && make && popd - -under ``ray/``. - -Docker Source Images --------------------- - -Run the script to create Docker images. - -.. code-block:: bash - - cd ray - ./build-docker.sh - -This script creates several Docker images: - -- The ``ray-project/deploy`` image is a self-contained copy of code and binaries - suitable for end users. -- The ``ray-project/examples`` adds additional libraries for running examples. -- The ``ray-project/base-deps`` image builds from Ubuntu Xenial and includes - Anaconda and other basic dependencies and can serve as a starting point for - developers. - -Review images by listing them: - -.. code-block:: bash - - docker images - -Output should look something like the following: - -.. code-block:: bash - - REPOSITORY TAG IMAGE ID CREATED SIZE - ray-project/examples latest 7584bde65894 4 days ago 3.257 GB - ray-project/deploy latest 970966166c71 4 days ago 2.899 GB - ray-project/base-deps latest f45d66963151 4 days ago 2.649 GB - ubuntu xenial f49eec89601e 3 weeks ago 129.5 MB - - -Launch Ray in Docker -~~~~~~~~~~~~~~~~~~~~ - -Start out by launching the deployment container. - -.. code-block:: bash - - docker run --shm-size= -t -i ray-project/deploy - -Replace ```` with a limit appropriate for your system, for example -``512M`` or ``2G``. The ``-t`` and ``-i`` options here are required to support -interactive use of the container. - -**Note:** Ray requires a **large** amount of shared memory because each object -store keeps all of its objects in shared memory, so the amount of shared memory -will limit the size of the object store. - -You should now see a prompt that looks something like: - -.. code-block:: bash - - root@ebc78f68d100:/ray# - -Test if the installation succeeded -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To test if the installation was successful, try running some tests. This assumes -that you've cloned the git repository. - -.. code-block:: bash - - python -m pytest -v python/ray/tests/test_mini.py - - -Troubleshooting installing Arrow --------------------------------- - -Some candidate possibilities. - -You have a different version of Flatbuffers installed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Arrow pulls and builds its own copy of Flatbuffers, but if you already have -Flatbuffers installed, Arrow may find the wrong version. If a directory like -``/usr/local/include/flatbuffers`` shows up in the output, this may be the -problem. To solve it, get rid of the old version of flatbuffers. - -There is some problem with Boost -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If a message like ``Unable to find the requested Boost libraries`` appears when -installing Arrow, there may be a problem with Boost. This can happen if you -installed Boost using MacPorts. This is sometimes solved by using Brew instead. diff --git a/doc/source/installation.rst b/doc/source/installation.rst index 07f7ff8e8..2e01289dc 100644 --- a/doc/source/installation.rst +++ b/doc/source/installation.rst @@ -13,7 +13,7 @@ You can install the latest stable version of Ray as follows. pip install -U ray # also recommended: ray[debug] -Trying snapshots from master +Latest Snapshots (Nightlies) ---------------------------- Here are links to the latest wheels (which are built for each commit on the @@ -42,3 +42,153 @@ master branch). To install these wheels, run the following command: .. _`MacOS Python 3.6`: https://s3-us-west-2.amazonaws.com/ray-wheels/latest/ray-0.8.0.dev3-cp36-cp36m-macosx_10_6_intel.whl .. _`MacOS Python 3.5`: https://s3-us-west-2.amazonaws.com/ray-wheels/latest/ray-0.8.0.dev3-cp35-cp35m-macosx_10_6_intel.whl .. _`MacOS Python 2.7`: https://s3-us-west-2.amazonaws.com/ray-wheels/latest/ray-0.8.0.dev3-cp27-cp27m-macosx_10_6_intel.whl + +Building Ray from Source +------------------------ + +Installing from ``pip`` should be sufficient for most Ray users. + +However, should you need to build from source, follow instructions below for both Linux and MacOS. + +Dependencies +~~~~~~~~~~~~ + +To build Ray, first install the following dependencies. We recommend using +`Anaconda`_. + +.. _`Anaconda`: https://www.continuum.io/downloads + +For Ubuntu, run the following commands: + +.. code-block:: bash + + sudo apt-get update + sudo apt-get install -y build-essential curl unzip psmisc + + # If you are not using Anaconda, you need the following. + sudo apt-get install python-dev # For Python 2. + sudo apt-get install python3-dev # For Python 3. + + pip install cython==0.29.0 + +For MacOS, run the following commands: + +.. code-block:: bash + + brew update + brew install wget + + pip install cython==0.29.0 + + +If you are using Anaconda, you may also need to run the following. + +.. code-block:: bash + + conda install libgcc + + +Install Ray +~~~~~~~~~~~ + +Ray can be built from the repository as follows. + +.. code-block:: bash + + git clone https://github.com/ray-project/ray.git + + # Install Bazel. + ray/ci/travis/install-bazel.sh + + cd ray/python + pip install -e . --verbose # Add --user if you see a permission denied error. + +Docker Source Images +-------------------- + +Run the script to create Docker images. + +.. code-block:: bash + + cd ray + ./build-docker.sh + +This script creates several Docker images: + +- The ``ray-project/deploy`` image is a self-contained copy of code and binaries + suitable for end users. +- The ``ray-project/examples`` adds additional libraries for running examples. +- The ``ray-project/base-deps`` image builds from Ubuntu Xenial and includes + Anaconda and other basic dependencies and can serve as a starting point for + developers. + +Review images by listing them: + +.. code-block:: bash + + docker images + +Output should look something like the following: + +.. code-block:: bash + + REPOSITORY TAG IMAGE ID CREATED SIZE + ray-project/examples latest 7584bde65894 4 days ago 3.257 GB + ray-project/deploy latest 970966166c71 4 days ago 2.899 GB + ray-project/base-deps latest f45d66963151 4 days ago 2.649 GB + ubuntu xenial f49eec89601e 3 weeks ago 129.5 MB + + +Launch Ray in Docker +~~~~~~~~~~~~~~~~~~~~ + +Start out by launching the deployment container. + +.. code-block:: bash + + docker run --shm-size= -t -i ray-project/deploy + +Replace ```` with a limit appropriate for your system, for example +``512M`` or ``2G``. The ``-t`` and ``-i`` options here are required to support +interactive use of the container. + +**Note:** Ray requires a **large** amount of shared memory because each object +store keeps all of its objects in shared memory, so the amount of shared memory +will limit the size of the object store. + +You should now see a prompt that looks something like: + +.. code-block:: bash + + root@ebc78f68d100:/ray# + +Test if the installation succeeded +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To test if the installation was successful, try running some tests. This assumes +that you've cloned the git repository. + +.. code-block:: bash + + python -m pytest -v python/ray/tests/test_mini.py + + +Troubleshooting installing Arrow +-------------------------------- + +Some candidate possibilities. + +You have a different version of Flatbuffers installed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Arrow pulls and builds its own copy of Flatbuffers, but if you already have +Flatbuffers installed, Arrow may find the wrong version. If a directory like +``/usr/local/include/flatbuffers`` shows up in the output, this may be the +problem. To solve it, get rid of the old version of flatbuffers. + +There is some problem with Boost +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a message like ``Unable to find the requested Boost libraries`` appears when +installing Arrow, there may be a problem with Boost. This can happen if you +installed Boost using MacPorts. This is sometimes solved by using Brew instead. diff --git a/doc/source/object-store.rst b/doc/source/object-store.rst new file mode 100644 index 000000000..0cf80750b --- /dev/null +++ b/doc/source/object-store.rst @@ -0,0 +1,162 @@ +Object Store and Serialization +============================== + +Since Ray processes do not share memory space, data transferred between workers and nodes will need to **serialized** and **deserialized**. Ray uses the `Plasma object store `_ to efficiently transfer objects across different processes and different nodes. + + +Object Store (Plasma) +--------------------- + +Plasma is an in-memory object store that is being developed as part of `Apache Arrow`_. Ray uses Plasma to efficiently transfer objects across different processes and different nodes. All objects in Plasma object store are **immutable** and held in shared memory. This is so that they can be accessed efficiently by many workers on the same node. + +Ray will place objects in the object store in the following situations: + +1. Calling ``ray.put`` + +.. code:: python + + y = 2 + # This places `2` into the object store. + object_id = ray.put(y) + +2. The return values of a remote function. + +.. code:: python + + @ray.remote + def remote_function(): + return 1 + + # This places `1` into the object store. + object_id = remote_function.remote() + + +3. Arguments to remote functions (except for simple arguments like ints or floats). + +.. code:: python + + @ray.remote + def remote_function(y): + # Note that inside the remote function, the actual argument is provided. + return len(y) + + argument = np.random.rand(100, 100) + # This implicitly places `argument` into the object store. + remote_function.remote(argument) + +Each node has its own object store. When data is put into the object store, it does not get automatically broadcasted to other nodes. Data remains local to the writer until requested by another task or actor on another node. + + +.. tip:: In certain cases, it may be necessary to either write `your own serialization protocol `_ or use Actors to hold objects and transfer object state (i.e., weight matrices) among Ray workers. + +Advanced: Huge Pages +~~~~~~~~~~~~~~~~~~~~ + +On Linux, it is possible to increase the write throughput of the Plasma object store by using huge pages. See the `Configuration page `_ for information on how to use huge pages in Ray. + + +.. _`Apache Arrow`: https://arrow.apache.org/ + + +Serialization Overview +---------------------- + +Objects that are serialized for transfer among Ray processes go through three stages: + +**1. Serialize using pyarrow**: Below is the set of Python objects that Ray can serialize using ``pyarrow``: + +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. + +**2. ``__dict__`` serialization**: If a direct usage of PyArrow is not possible, Ray will recursively extract the object’s ``__dict__`` and serialize that using pyarrow. This behavior is not correct in all cases. + +**3. Cloudpickle**: Ray falls back to ``cloudpickle`` as a final attempt for serialization. This may be slow. + +Custom Serialization +~~~~~~~~~~~~~~~~~~~~ + +If none of these options work, we recommend registering a custom serializer. + +.. autofunction:: ray.register_custom_serializer + :noindex: + +Below is an example of using ``ray.register_custom_serializer``: + +.. code-block:: python + + import ray + + ray.init() + + class Foo(object): + def __init__(self, value): + self.value = value + + def custom_serializer(obj): + return obj.value + + def custom_deserializer(value): + object = Foo() + object.value = value + return object + + ray.register_custom_serializer( + Foo, serializer=custom_serializer, deserializer=custom_deserializer) + + object_id = ray.put(Foo(100)) + assert ray.get(object_id).value == 100 + + +If you find cases where Ray serialization doesn't work or does something unexpected, please `let us know`_ so we can fix it. + +.. _`let us know`: https://github.com/ray-project/ray/issues + + +Serialization: Numpy Arrays +--------------------------- + +Ray optimizes for numpy arrays by using the `Apache Arrow`_ data format. +The numpy array is stored as a read-only object, and all Ray workers on the same node can read the numpy array in the object store without copying (zero-copy reads). Each numpy array object in the worker process holds a pointer to the relevant array held in shared memory. Any writes to the read-only object will result in a copy into the local process 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. + +Serialization notes and limitations +----------------------------------- + +- Ray currently handles certain patterns incorrectly, according to Python + semantics. For example, a list that contains two copies of the same list will + be serialized as if the two lists were distinct. + + .. code-block:: python + + l1 = [0] + l2 = [l1, l1] + l3 = ray.get(ray.put(l2)) + + assert l2[0] is l2[1] + assert not l3[0] is l3[1] + +- For reasons similar to the above example, we also do not currently handle + objects that recursively contain themselves (this may be common in graph-like + data structures). + + .. code-block:: python + + l = [] + l.append(l) + + # Try to put this list that recursively contains itself in the object store. + ray.put(l) + + This will throw an exception with a message like the following. + + .. code-block:: bash + + This object exceeds the maximum recursion depth. It may contain itself recursively. + +- Whenever possible, use numpy arrays for maximum performance. diff --git a/doc/source/tune-usage.rst b/doc/source/tune-usage.rst index f6953d162..54fc37a70 100644 --- a/doc/source/tune-usage.rst +++ b/doc/source/tune-usage.rst @@ -140,12 +140,9 @@ You may want to get a summary of multiple experiments that point to the same ``l See the `full documentation `_ for the ``Analysis`` object. -Training Features ------------------ Tune Search Space (Default) -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - +--------------------------- You can use ``tune.grid_search`` to specify an axis of a grid search. By default, Tune also supports sampling parameters from user-specified lambda functions, which can be used independently or in combination with grid search. @@ -177,7 +174,7 @@ The following shows grid search over two nested parameters combined with random For more information on variant generation, see `basic_variant.py `__. Custom Trial Names -~~~~~~~~~~~~~~~~~~ +------------------ To specify custom trial names, you can pass use the ``trial_name_creator`` argument to `tune.run`. This takes a function with the following signature, and @@ -205,7 +202,7 @@ be sure to wrap it with `tune.function`: An example can be found in `logging_example.py `__. Sampling Multiple Times -~~~~~~~~~~~~~~~~~~~~~~~ +----------------------- By default, each random variable and grid search point is sampled once. To take multiple random samples, add ``num_samples: N`` to the experiment config. If `grid_search` is provided as an argument, the grid will be repeated `num_samples` of times. @@ -230,7 +227,7 @@ E.g. in the above, ``num_samples=10`` repeats the 3x3 grid search 10 times, for Using GPUs (Resource Allocation) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------------- Tune will allocate the specified GPU and CPU ``resources_per_trial`` to each individual trial (defaulting to 1 CPU per trial). Under the hood, Tune runs each trial as a Ray actor, using Ray's resource handling to allocate resources and place actors. A trial will not be scheduled unless at least that amount of resources is available in the cluster, preventing the cluster from being overloaded. diff --git a/doc/source/tune.rst b/doc/source/tune.rst index f559797b1..8f3e8303a 100644 --- a/doc/source/tune.rst +++ b/doc/source/tune.rst @@ -1,17 +1,17 @@ -Tune: Scalable Hyperparameter Search +Tune: Scalable Hyperparameter Tuning ==================================== .. image:: images/tune.png :scale: 30% :align: center -Tune is a scalable framework for hyperparameter search and model training with a focus on deep learning and deep reinforcement learning. +Tune is a library for hyperparameter tuning at any scale. - * Scale to running on a large distributed cluster without changing your code. - * Launch a multi-node Tune experiment in less than 10 lines of code. + * Launch a multi-node distributed hyperparameter sweep in less than 10 lines of code. * Supports any deep learning framework, including PyTorch, TensorFlow, and Keras. * Visualize results with `TensorBoard `__. * Choose among scalable SOTA algorithms such as `Population Based Training (PBT)`_, `Vizier's Median Stopping Rule`_, `HyperBand/ASHA`_. + * Tune integrates with many optimization libraries such as `Facebook Ax `_, `HyperOpt `_, and `Bayesian Optimization `_ and enables you to scale them transparently. .. _`Population Based Training (PBT)`: tune-schedulers.html#population-based-training-pbt .. _`Vizier's Median Stopping Rule`: tune-schedulers.html#median-stopping-rule diff --git a/doc/source/user-profiling.rst b/doc/source/user-profiling.rst index 87696e5d7..ce9d9c19f 100644 --- a/doc/source/user-profiling.rst +++ b/doc/source/user-profiling.rst @@ -21,344 +21,11 @@ Then open `chrome://tracing`_ in the Chrome web browser, and load .. _`chrome://tracing`: chrome://tracing +Observing Ray Work +------------------ -A Basic Example to Profile --------------------------- - -Let's try to profile a simple example, and compare how different ways to -write a simple loop can affect performance. - -As a proxy for a computationally intensive and possibly slower function, -let's define our remote function to just sleep for 0.5 seconds: - -.. code-block:: python - - import ray - import time - - # Our time-consuming remote function - @ray.remote - def func(): - time.sleep(0.5) - -In our example setup, we wish to call our remote function ``func()`` five -times, and store the result of each call into a list. To compare the -performance of different ways of looping our calls to our remote function, -we can define each loop version as a separate function on the driver script. - -For the first version **ex1**, each iteration of the loop calls the remote -function, then calls ``ray.get`` in an attempt to store the current result -into the list, as follows: - -.. code-block:: python - - # This loop is suboptimal in Ray, and should only be used for the sake of this example - def ex1(): - list1 = [] - for i in range(5): - list1.append(ray.get(func.remote())) - -For the second version **ex2**, each iteration of the loop calls the remote -function, and stores it into the list **without** calling ``ray.get`` each time. -``ray.get`` is used after the loop has finished, in preparation for processing -``func()``'s results: - -.. code-block:: python - - # This loop is more proper in Ray - def ex2(): - list2 = [] - for i in range(5): - list2.append(func.remote()) - ray.get(list2) - -Finally, for an example that's not so parallelizable, let's create a -third version **ex3** where the driver has to call a local -function in between each call to the remote function ``func()``: - -.. code-block:: python - - # A local function executed on the driver, not on Ray - def other_func(): - time.sleep(0.3) - - def ex3(): - list3 = [] - for i in range(5): - other_func() - list3.append(func.remote()) - ray.get(list3) - - -Timing Performance Using Python's Timestamps --------------------------------------------- - -One way to sanity-check the performance of the three loops is simply to -time how long it takes to complete each loop version. We can do this using -python's built-in ``time`` `module`_. - -.. _`module`: https://docs.python.org/3/library/time.html - -The ``time`` module contains a useful ``time()`` function that returns the -current timestamp in unix time whenever it's called. We can create a generic -function wrapper to call ``time()`` right before and right after each loop -function to print out how long each loop takes overall: - -.. code-block:: python - - # This is a generic wrapper for any driver function you want to time - def time_this(f): - def timed_wrapper(*args, **kw): - start_time = time.time() - result = f(*args, **kw) - end_time = time.time() - - # Time taken = end_time - start_time - print('| func:%r args:[%r, %r] took: %2.4f seconds |' % \ - (f.__name__, args, kw, end_time - start_time)) - return result - return timed_wrapper - -To always print out how long the loop takes to run each time the loop -function ``ex1()`` is called, we can evoke our ``time_this`` wrapper with -a function decorator. This can similarly be done to functions ``ex2()`` -and ``ex3()``: - -.. code-block:: python - - @time_this # Added decorator - def ex1(): - list1 = [] - for i in range(5): - list1.append(ray.get(func.remote())) - - def main(): - ray.init() - ex1() - ex2() - ex3() - - if __name__ == "__main__": - main() - -Then, running the three timed loops should yield output similar to this: - -.. code-block:: bash - - | func:'ex1' args:[(), {}] took: 2.5083 seconds | - | func:'ex2' args:[(), {}] took: 1.0032 seconds | - | func:'ex3' args:[(), {}] took: 2.0039 seconds | - -Let's interpret these results. - -Here, ``ex1()`` took substantially more time than ``ex2()``, where -their only difference is that ``ex1()`` calls ``ray.get`` on the remote -function before adding it to the list, while ``ex2()`` waits to fetch the -entire list with ``ray.get`` at once. - -.. code-block:: python - - @ray.remote - def func(): # A single call takes 0.5 seconds - time.sleep(0.5) - - def ex1(): # Took Ray 2.5 seconds - list1 = [] - for i in range(5): - list1.append(ray.get(func.remote())) - - def ex2(): # Took Ray 1 second - list2 = [] - for i in range(5): - list2.append(func.remote()) - ray.get(list2) - -Notice how ``ex1()`` took 2.5 seconds, exactly five times 0.5 seconds, or -the time it would take to wait for our remote function five times in a row. - -By calling ``ray.get`` after each call to the remote function, ``ex1()`` -removes all ability to parallelize work, by forcing the driver to wait for -each ``func()``'s result in succession. We are not taking advantage of Ray -parallelization here! - -Meanwhile, ``ex2()`` takes about 1 second, much faster than it would normally -take to call ``func()`` five times iteratively. Ray is running each call to -``func()`` in parallel, saving us time. - -``ex1()`` is actually a common user mistake in Ray. ``ray.get`` is not -necessary to do before adding the result of ``func()`` to the list. Instead, -the driver should send out all parallelizable calls to the remote function -to Ray before waiting to receive their results with ``ray.get``. ``ex1()``'s -suboptimal behavior can be noticed just using this simple timing test. - -Realistically, however, many applications are not as highly parallelizable -as ``ex2()``, and the application includes sections where the code must run in -serial. ``ex3()`` is such an example, where the local function ``other_func()`` -must run first before each call to ``func()`` can be submitted to Ray. - -.. code-block:: python - - # A local function that must run in serial - def other_func(): - time.sleep(0.3) - - def ex3(): # Took Ray 2 seconds, vs. ex1 taking 2.5 seconds - list3 = [] - for i in range(5): - other_func() - list2.append(func.remote()) - ray.get(list3) - -What results is that while ``ex3()`` still gained 0.5 seconds of speedup -compared to the completely serialized ``ex1()`` version, this speedup is -still nowhere near the ideal speedup of ``ex2()``. - -The dramatic speedup of ``ex2()`` is possible because ``ex2()`` is -theoretically completely parallelizable: if we were given 5 CPUs, all 5 calls -to ``func()`` can be run in parallel. What is happening with ``ex3()``, -however, is that each parallelized call to ``func()`` is staggered by a wait -of 0.3 seconds for the local ``other_func()`` to finish. - -``ex3()`` is thus a manifestation of `Amdahls Law`_: the fastest theoretically -possible execution time from parallelizing an application is limited to be -no better than the time it takes to run all serial parts in serial. - -.. _`Amdahls Law`: https://en.wikipedia.org/wiki/Amdahl%27s_law - -Due to Amdahl's Law, ``ex3()`` must take at least 1.5 -seconds -- the time it takes for 5 serial calls to ``other_func()`` to finish! -After an additional 0.5 seconds to execute func and get the result, the -computation is done. - - -Profiling Using An External Profiler (Line Profiler) ----------------------------------------------------- - -One way to profile the performance of our code using Ray is to use a third-party -profiler such as `Line_profiler`_. Line_profiler is a useful line-by-line -profiler for pure Python applications that formats its output side-by-side with -the profiled code itself. - -Alternatively, another third-party profiler (not covered in this documentation) -that you could use is `Pyflame`_, which can generate profiling graphs. - -.. _`Line_profiler`: https://github.com/rkern/line_profiler -.. _`Pyflame`: https://github.com/uber/pyflame - -First install ``line_profiler`` with pip: - -.. code-block:: bash - - pip install line_profiler - -``line_profiler`` requires each section of driver code that you want to profile as -its own independent function. Conveniently, we have already done so by defining -each loop version as its own function. To tell ``line_profiler`` which functions -to profile, just add the ``@profile`` decorator to ``ex1()``, ``ex2()`` and -``ex3()``. Note that you do not need to import ``line_profiler`` into your Ray -application: - -.. code-block:: python - - @profile # Added decorator - def ex1(): - list1 = [] - for i in range(5): - list1.append(ray.get(func.remote())) - - def main(): - ray.init() - ex1() - ex2() - ex3() - - if __name__ == "__main__": - main() - -Then, when we want to execute our Python script from the command line, instead -of ``python your_script_here.py``, we use the following shell command to run the -script with ``line_profiler`` enabled: - -.. code-block:: bash - - kernprof -l your_script_here.py - -This command runs your script and prints only your script's output as usual. -``Line_profiler`` instead outputs its profiling results to a corresponding -binary file called ``your_script_here.py.lprof``. - -To read ``line_profiler``'s results to terminal, use this shell command: - -.. code-block:: bash - - python -m line_profiler your_script_here.py.lprof - -In our loop example, this command outputs results for ``ex1()`` as follows. -Note that execution time is given in units of 1e-06 seconds: - -.. code-block:: bash - - Timer unit: 1e-06 s - - Total time: 2.50883 s - File: your_script_here.py - Function: ex1 at line 28 - - Line # Hits Time Per Hit % Time Line Contents - ============================================================== - 29 @profile - 30 def ex1(): - 31 1 3.0 3.0 0.0 list1 = [] - 32 6 18.0 3.0 0.0 for i in range(5): - 33 5 2508805.0 501761.0 100.0 list1.append(ray.get(func.remote())) - - -Notice that each hit to ``list1.append(ray.get(func.remote()))`` at line 33 -takes the full 0.5 seconds waiting for ``func()`` to finish. Meanwhile, in -``ex2()`` below, each call of ``func.remote()`` at line 40 only takes 0.127 ms, -and the majority of the time (about 1 second) is spent on waiting for ``ray.get()`` -at the end: - - -.. code-block:: bash - - Total time: 1.00357 s - File: your_script_here.py - Function: ex2 at line 35 - - Line # Hits Time Per Hit % Time Line Contents - ============================================================== - 36 @profile - 37 def ex2(): - 38 1 2.0 2.0 0.0 list2 = [] - 39 6 13.0 2.2 0.0 for i in range(5): - 40 5 637.0 127.4 0.1 list2.append(func.remote()) - 41 1 1002919.0 1002919.0 99.9 ray.get(list2) - - -And finally, ``line_profiler``'s output for ``ex3()``. Each call to -``func.remote()`` at line 50 still take magnitudes faster than 0.5 seconds, -showing that Ray is successfully parallelizing the remote calls. However, each -call to the local function ``other_func()`` takes the full 0.3 seconds, -totalling up to the guaranteed minimum application execution time of 1.5 -seconds: - -.. code-block:: bash - - Total time: 2.00446 s - File: basic_kernprof.py - Function: ex3 at line 44 - - Line # Hits Time Per Hit % Time Line Contents - ============================================================== - 44 @profile - 45 #@time_this - 46 def ex3(): - 47 1 2.0 2.0 0.0 list3 = [] - 48 6 13.0 2.2 0.0 for i in range(5): - 49 5 1501934.0 300386.8 74.9 other_func() - 50 5 917.0 183.4 0.0 list3.append(func.remote()) - 51 1 501589.0 501589.0 25.0 ray.get(list3) +You can run ``ray stack`` to dump the stack traces of all Ray workers on +the current node. This requires ``py-spy`` to be installed. See the `Troubleshooting page `_ for more details. Profiling Using Python's CProfile diff --git a/doc/source/using-ray-with-pytorch.rst b/doc/source/using-ray-with-pytorch.rst new file mode 100644 index 000000000..d3cd017ae --- /dev/null +++ b/doc/source/using-ray-with-pytorch.rst @@ -0,0 +1,96 @@ +Best Practices: Ray with PyTorch +================================ + +This document describes best practices for using Ray with PyTorch. Feel free to contribute if you think this document is missing anything. + +Downloading Data +---------------- + +It is very common for multiple Ray actors running PyTorch to have code that downloads the dataset for training and testing. + +.. code-block:: python + + # This is running inside a Ray actor + # ... + torch.utils.data.DataLoader( + datasets.MNIST( + "../data", train=True, download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])), + 128, shuffle=True, **kwargs) + # ... + +This may cause different processes to simultaneously download the data and cause data corruption. One easy workaround for this is to use ``Filelock``: + +.. code-block:: python + + from filelock import FileLock + + with FileLock("./data.lock"): + torch.utils.data.DataLoader( + datasets.MNIST( + "./data", train=True, download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])), + 128, shuffle=True, **kwargs) + + +Use Actors for Parallel Models +------------------------------ + +One common use case for using Ray with PyTorch is to parallelize the training of multiple models. + +.. tip:: + Avoid sending the PyTorch model directly. Send ``model.state_dict()``, as + PyTorch tensors are natively supported by the Plasma Object Store. + + +Suppose we have a simple network definition (this one is modified from the +PyTorch documentation). + +.. literalinclude:: ../examples/doc_code/torch_example.py + :language: python + :start-after: __torch_model_start__ + :end-before: __torch_model_end__ + +Along with these helper training functions: + +.. literalinclude:: ../examples/doc_code/torch_example.py + :language: python + :start-after: __torch_helper_start__ + :end-before: __torch_helper_end + +Let's now define a class that captures the training process. + +.. literalinclude:: ../examples/doc_code/torch_example.py + :language: python + :start-after: __torch_net_start__ + :end-before: __torch_net_end + + +To train multiple models, you can convert the above class into a Ray Actor class. + +.. literalinclude:: ../examples/doc_code/torch_example.py + :language: python + :start-after: __torch_ray_start__ + :end-before: __torch_ray_end__ + + +Then, we can instantiate multiple copies of the Model, each running on different processes. If GPU is enabled, each copy runs on a different GPU. See the `GPU guide `_ for more information. + +.. literalinclude:: ../examples/doc_code/torch_example.py + :language: python + :start-after: __torch_actor_start__ + :end-before: __torch_actor_end__ + + +We can then use ``set_weights`` and ``get_weights`` to move the weights of the neural network around. The below example averages the weights of the two networks and sends them back to update the original actors. + + +.. literalinclude:: ../examples/doc_code/torch_example.py + :language: python + :start-after: __weight_average_start__ diff --git a/doc/source/using-ray-with-tensorflow.rst b/doc/source/using-ray-with-tensorflow.rst index e8f5cc7d1..39acabe49 100644 --- a/doc/source/using-ray-with-tensorflow.rst +++ b/doc/source/using-ray-with-tensorflow.rst @@ -1,31 +1,57 @@ -Using Ray with TensorFlow -========================= +Best Practices: Ray with Tensorflow +=================================== -This document describes best practices for using Ray with TensorFlow. - -To see more involved examples using TensorFlow, take a look at -`A3C`_, `ResNet`_, and `LBFGS`_. - -.. _`A3C`: http://ray.readthedocs.io/en/latest/example-a3c.html -.. _`ResNet`: http://ray.readthedocs.io/en/latest/example-resnet.html -.. _`LBFGS`: http://ray.readthedocs.io/en/latest/example-lbfgs.html +This document describes best practices for using Ray with TensorFlow. Feel free to contribute if you think this document is missing anything. +Use Actors for Parallel Models +------------------------------ If you are training a deep network in the distributed setting, you may need to ship your deep network between processes (or machines). However, shipping the model is not always straightforward. -A straightforward attempt to pickle a TensorFlow graph gives mixed -results. Some examples fail, and some succeed (but produce very large strings). -The results are similar with other pickling libraries as well. +.. tip:: + Avoid sending the Tensorflow model directly. A straightforward attempt to pickle a TensorFlow graph gives mixed results. Furthermore, creating a TensorFlow graph can take tens of seconds, and so serializing a graph and recreating it in another process will be inefficient. -Furthermore, creating a TensorFlow graph can take tens of seconds, and so -serializing a graph and recreating it in another process will be inefficient. -The better solution is to replicate the same TensorFlow graph on each worker once +It is recommended to replicate the same TensorFlow graph on each worker once at the beginning and then to ship only the weights between the workers. Suppose we have a simple network definition (this one is modified from the TensorFlow documentation). +.. literalinclude:: ../examples/doc_code/tf_example.py + :language: python + :start-after: __tf_model_start__ + :end-before: __tf_model_end__ + +It is strongly recommended you create actors to handle this. To do this, first initialize +ray and define an Actor class: + +.. literalinclude:: ../examples/doc_code/tf_example.py + :language: python + :start-after: __ray_start__ + :end-before: __ray_end__ + +Then, we can instantiate this actor and train it on the separate process: + +.. literalinclude:: ../examples/doc_code/tf_example.py + :language: python + :start-after: __actor_start__ + :end-before: __actor_end__ + + +We can then use ``set_weights`` and ``get_weights`` to move the weights of the neural network +around. This allows us to manipulate weights between different models running in parallel without shipping the actual TensorFlow graphs, which are much more complex Python objects. + + +.. literalinclude:: ../examples/doc_code/tf_example.py + :language: python + :start-after: __weight_average_start__ + + +Lower-level TF Utilities +------------------------ +Given a low-level TF definition: + .. code-block:: python import tensorflow as tf @@ -62,6 +88,7 @@ network as follows. .. code-block:: python + sess = tf.Session() # First initialize the weights. sess.run(init) # Get the weights @@ -78,251 +105,16 @@ unmanageably large over time. w.assign(np.zeros(1)) # This adds a node to the graph every time you call it. b.assign(np.zeros(1)) # This adds a node to the graph every time you call it. -Complete Example for Weight Averaging -------------------------------------- - -Putting this all together, we would first embed the graph in an actor. Within -the actor, we would use the ``get_weights`` and ``set_weights`` methods of the -``TensorFlowVariables`` class. We would then use those methods to ship the weights -(as a dictionary of variable names mapping to numpy arrays) between the -processes without shipping the actual TensorFlow graphs, which are much more -complex Python objects. - -.. code-block:: python - - import tensorflow as tf - import numpy as np - import ray - import ray.experimental.tf_utils - - ray.init() - - BATCH_SIZE = 100 - NUM_BATCHES = 1 - NUM_ITERS = 201 - - class Network(object): - def __init__(self, x, y): - # Seed TensorFlow to make the script deterministic. - tf.set_random_seed(0) - # Define the inputs. - self.x_data = tf.constant(x, dtype=tf.float32) - self.y_data = tf.constant(y, dtype=tf.float32) - # Define the weights and computation. - w = tf.Variable(tf.random_uniform([1], -1.0, 1.0)) - b = tf.Variable(tf.zeros([1])) - y = w * self.x_data + b - # Define the loss. - self.loss = tf.reduce_mean(tf.square(y - self.y_data)) - optimizer = tf.train.GradientDescentOptimizer(0.5) - self.grads = optimizer.compute_gradients(self.loss) - self.train = optimizer.apply_gradients(self.grads) - # Define the weight initializer and session. - init = tf.global_variables_initializer() - self.sess = tf.Session() - # Additional code for setting and getting the weights - self.variables = ray.experimental.tf_utils.TensorFlowVariables(self.loss, self.sess) - # Return all of the data needed to use the network. - self.sess.run(init) - - # Define a remote function that trains the network for one step and returns the - # new weights. - def step(self, weights): - # Set the weights in the network. - self.variables.set_weights(weights) - # Do one step of training. - self.sess.run(self.train) - # Return the new weights. - return self.variables.get_weights() - - def get_weights(self): - return self.variables.get_weights() - - # Define a remote function for generating fake data. - @ray.remote(num_return_vals=2) - def generate_fake_x_y_data(num_data, seed=0): - # Seed numpy to make the script deterministic. - np.random.seed(seed) - x = np.random.rand(num_data) - y = x * 0.1 + 0.3 - return x, y - - # Generate some training data. - batch_ids = [generate_fake_x_y_data.remote(BATCH_SIZE, seed=i) for i in range(NUM_BATCHES)] - x_ids = [x_id for x_id, y_id in batch_ids] - y_ids = [y_id for x_id, y_id in batch_ids] - # Generate some test data. - x_test, y_test = ray.get(generate_fake_x_y_data.remote(BATCH_SIZE, seed=NUM_BATCHES)) - - # Create actors to store the networks. - remote_network = ray.remote(Network) - actor_list = [remote_network.remote(x_ids[i], y_ids[i]) for i in range(NUM_BATCHES)] - - # Get initial weights of some actor. - weights = ray.get(actor_list[0].get_weights.remote()) - - # Do some steps of training. - for iteration in range(NUM_ITERS): - # Put the weights in the object store. This is optional. We could instead pass - # the variable weights directly into step.remote, in which case it would be - # placed in the object store under the hood. However, in that case multiple - # copies of the weights would be put in the object store, so this approach is - # more efficient. - weights_id = ray.put(weights) - # Call the remote function multiple times in parallel. - new_weights_ids = [actor.step.remote(weights_id) for actor in actor_list] - # Get all of the weights. - new_weights_list = ray.get(new_weights_ids) - # Add up all the different weights. Each element of new_weights_list is a dict - # of weights, and we want to add up these dicts component wise using the keys - # of the first dict. - weights = {variable: sum(weight_dict[variable] for weight_dict in new_weights_list) / NUM_BATCHES for variable in new_weights_list[0]} - # Print the current weights. They should converge to roughly to the values 0.1 - # and 0.3 used in generate_fake_x_y_data. - if iteration % 20 == 0: - print("Iteration {}: weights are {}".format(iteration, weights)) - -How to Train in Parallel using Ray and Gradients ------------------------------------------------- - -In some cases, you may want to do data-parallel training on your network. We use the network -above to illustrate how to do this in Ray. The only differences are in the remote function -``step`` and the driver code. - -In the function ``step``, we run the grad operation rather than the train operation to get the gradients. -Since Tensorflow pairs the gradients with the variables in a tuple, we extract the gradients to avoid -needless computation. - -Extracting numerical gradients -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Code like the following can be used in a remote function to compute numerical gradients. - -.. code-block:: python - - x_values = [1] * 100 - y_values = [2] * 100 - numerical_grads = sess.run([grad[0] for grad in grads], feed_dict={x_data: x_values, y_data: y_values}) - -Using the returned gradients to train the network -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By pairing the symbolic gradients with the numerical gradients in a feed_dict, we can update the network. - -.. code-block:: python - - # We can feed the gradient values in using the associated symbolic gradient - # operation defined in tensorflow. - feed_dict = {grad[0]: numerical_grad for (grad, numerical_grad) in zip(grads, numerical_grads)} - sess.run(train, feed_dict=feed_dict) - -You can then run ``variables.get_weights()`` to see the updated weights of the network. - -For reference, the full code is below: - -.. code-block:: python - - import tensorflow as tf - import numpy as np - import ray - - ray.init() - - BATCH_SIZE = 100 - NUM_BATCHES = 1 - NUM_ITERS = 201 - - class Network(object): - def __init__(self, x, y): - # Seed TensorFlow to make the script deterministic. - tf.set_random_seed(0) - # Define the inputs. - x_data = tf.constant(x, dtype=tf.float32) - y_data = tf.constant(y, dtype=tf.float32) - # 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. - self.loss = tf.reduce_mean(tf.square(y - y_data)) - optimizer = tf.train.GradientDescentOptimizer(0.5) - self.grads = optimizer.compute_gradients(self.loss) - self.train = optimizer.apply_gradients(self.grads) - # Define the weight initializer and session. - init = tf.global_variables_initializer() - self.sess = tf.Session() - # Additional code for setting and getting the weights - self.variables = ray.experimental.tf_utils.TensorFlowVariables(self.loss, self.sess) - # Return all of the data needed to use the network. - self.sess.run(init) - - # Define a remote function that trains the network for one step and returns the - # new weights. - def step(self, weights): - # Set the weights in the network. - self.variables.set_weights(weights) - # Do one step of training. We only need the actual gradients so we filter over the list. - actual_grads = self.sess.run([grad[0] for grad in self.grads]) - return actual_grads - - def get_weights(self): - return self.variables.get_weights() - - # Define a remote function for generating fake data. - @ray.remote(num_return_vals=2) - def generate_fake_x_y_data(num_data, seed=0): - # Seed numpy to make the script deterministic. - np.random.seed(seed) - x = np.random.rand(num_data) - y = x * 0.1 + 0.3 - return x, y - - # Generate some training data. - batch_ids = [generate_fake_x_y_data.remote(BATCH_SIZE, seed=i) for i in range(NUM_BATCHES)] - x_ids = [x_id for x_id, y_id in batch_ids] - y_ids = [y_id for x_id, y_id in batch_ids] - # Generate some test data. - x_test, y_test = ray.get(generate_fake_x_y_data.remote(BATCH_SIZE, seed=NUM_BATCHES)) - - # Create actors to store the networks. - remote_network = ray.remote(Network) - actor_list = [remote_network.remote(x_ids[i], y_ids[i]) for i in range(NUM_BATCHES)] - local_network = Network(x_test, y_test) - - # Get initial weights of local network. - weights = local_network.get_weights() - - # Do some steps of training. - for iteration in range(NUM_ITERS): - # Put the weights in the object store. This is optional. We could instead pass - # the variable weights directly into step.remote, in which case it would be - # placed in the object store under the hood. However, in that case multiple - # copies of the weights would be put in the object store, so this approach is - # more efficient. - weights_id = ray.put(weights) - # Call the remote function multiple times in parallel. - gradients_ids = [actor.step.remote(weights_id) for actor in actor_list] - # Get all of the weights. - gradients_list = ray.get(gradients_ids) - - # Take the mean of the different gradients. Each element of gradients_list is a list - # of gradients, and we want to take the mean of each one. - mean_grads = [sum([gradients[i] for gradients in gradients_list]) / len(gradients_list) for i in range(len(gradients_list[0]))] - - feed_dict = {grad[0]: mean_grad for (grad, mean_grad) in zip(local_network.grads, mean_grads)} - local_network.sess.run(local_network.train, feed_dict=feed_dict) - weights = local_network.get_weights() - - # Print the current weights. They should converge to roughly to the values 0.1 - # and 0.3 used in generate_fake_x_y_data. - if iteration % 20 == 0: - print("Iteration {}: weights are {}".format(iteration, weights)) .. autoclass:: ray.experimental.tf_utils.TensorFlowVariables :members: +.. note:: This may not work with `tf.Keras`. + + + Troubleshooting ---------------- +~~~~~~~~~~~~~~~ Note that ``TensorFlowVariables`` uses variable names to determine what variables to set when calling ``set_weights``. One common issue arises when two diff --git a/doc/source/walkthrough.rst b/doc/source/walkthrough.rst index 879c356c7..a41f73174 100644 --- a/doc/source/walkthrough.rst +++ b/doc/source/walkthrough.rst @@ -47,14 +47,12 @@ This causes a few things changes in behavior: .. code:: python - >>> regular_function() - 1 + assert regular_function() == 1 - >>> remote_function.remote() - ObjectID(1c80d6937802cd7786ad25e50caf2f023c95e350) + object_id = remote_function.remote() - >>> ray.get(remote_function.remote()) - 1 + # The value of the original `regular_function` + assert ray.get(object_id) == 1 3. **Parallelism:** Invocations of ``regular_function`` happen **serially**, for example @@ -76,17 +74,20 @@ This causes a few things changes in behavior: See the `ray.remote package reference `__ page for specific documentation on how to use ``ray.remote``. -**Object IDs** can also be passed into remote functions. When the function actually gets executed, **the argument will be a retrieved as a regular Python object**. +**Object IDs** can also be passed into remote functions. When the function actually gets executed, **the argument will be a retrieved as a regular Python object**. For example, take this function: .. code:: python - >>> y1_id = f.remote(x1_id) - >>> ray.get(y1_id) - 1 + @ray.remote + def remote_chain_function(value): + return value + 1 - >>> y2_id = f.remote(x2_id) - >>> ray.get(y2_id) - [1, 2, 3] + + y1_id = remote_function.remote() + assert ray.get(y1_id) == 1 + + chained_id = remote_chain_function.remote(y1_id) + assert ray.get(chained_id) == 2 Note the following behaviors: @@ -171,12 +172,8 @@ Object IDs can be created in multiple ways. .. code-block:: python - >>> y = 1 - >>> y_id = ray.put(y) - >>> print(y_id) - ObjectID(0369a14bc595e08cfbd508dfaa162cb7feffffff) - -Here is the docstring for ``ray.put``: + y = 1 + object_id = ray.put(y) .. autofunction:: ray.put :noindex: @@ -198,14 +195,9 @@ shared memory and avoid copying the object. .. code-block:: python - >>> y = 1 - >>> obj_id = ray.put(y) - >>> print(obj_id) - ObjectID(0369a14bc595e08cfbd508dfaa162cb7feffffff) - >>> ray.get(obj_id) - 1 - -Here is the docstring for ``ray.get``: + y = 1 + obj_id = ray.put(y) + assert ray.get(obj_id) == 1 .. autofunction:: ray.get :noindex: @@ -219,8 +211,6 @@ works as follows. ready_ids, remaining_ids = ray.wait(object_ids, num_returns=1, timeout=None) -Here is the docstring for ``ray.wait``: - .. autofunction:: ray.wait :noindex: diff --git a/python/ray/experimental/sgd/examples/train_example.py b/python/ray/experimental/sgd/examples/train_example.py index dcd790667..40c9ff132 100644 --- a/python/ray/experimental/sgd/examples/train_example.py +++ b/python/ray/experimental/sgd/examples/train_example.py @@ -1,14 +1,55 @@ +""" +This file holds code for a Training guide for PytorchSGD in the documentation. + +It ignores yapf because yapf doesn't allow comments right after code blocks, +but we put comments right after code blocks to prevent large white spaces +in the documentation. +""" + +# yapf: disable +# __torch_train_example__ from __future__ import absolute_import from __future__ import division from __future__ import print_function import argparse -from ray import tune -from ray.experimental.sgd.pytorch.pytorch_trainer import (PyTorchTrainer, - PyTorchTrainable) +import numpy as np +import torch +import torch.nn as nn -from ray.experimental.sgd.tests.pytorch_utils import ( - model_creator, optimizer_creator, data_creator) +from ray.experimental.sgd.pytorch.pytorch_trainer import PyTorchTrainer + + +class LinearDataset(torch.utils.data.Dataset): + """y = a * x + b""" + + def __init__(self, a, b, size=1000): + x = np.random.random(size).astype(np.float32) * 10 + x = np.arange(0, 10, 10 / size, dtype=np.float32) + self.x = torch.from_numpy(x) + self.y = torch.from_numpy(a * x + b) + + def __getitem__(self, index): + return self.x[index, None], self.y[index, None] + + def __len__(self): + return len(self.x) + + +def model_creator(config): + return nn.Linear(1, 1) + + +def optimizer_creator(model, config): + """Returns criterion, optimizer""" + criterion = nn.MSELoss() + optimizer = torch.optim.SGD(model.parameters(), lr=1e-4) + return criterion, optimizer + + +def data_creator(config): + """Returns training set, validation set""" + return LinearDataset(2, 5), LinearDataset(2, 5, size=400) def train_example(num_replicas=1, use_gpu=False): @@ -25,27 +66,6 @@ def train_example(num_replicas=1, use_gpu=False): print("success!") -def tune_example(num_replicas=1, use_gpu=False): - config = { - "model_creator": tune.function(model_creator), - "data_creator": tune.function(data_creator), - "optimizer_creator": tune.function(optimizer_creator), - "num_replicas": num_replicas, - "use_gpu": use_gpu, - "batch_size": 512, - "backend": "gloo" - } - - analysis = tune.run( - PyTorchTrainable, - num_samples=12, - config=config, - stop={"training_iteration": 2}, - verbose=1) - - return analysis.get_best_config(metric="validation_loss", mode="min") - - if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( @@ -72,8 +92,4 @@ if __name__ == "__main__": import ray ray.init(redis_address=args.redis_address) - - if args.tune: - tune_example(num_replicas=args.num_replicas, use_gpu=args.use_gpu) - else: - train_example(num_replicas=args.num_replicas, use_gpu=args.use_gpu) + train_example(num_replicas=args.num_replicas, use_gpu=args.use_gpu) diff --git a/python/ray/experimental/sgd/examples/tune_example.py b/python/ray/experimental/sgd/examples/tune_example.py new file mode 100644 index 000000000..5c81c6d39 --- /dev/null +++ b/python/ray/experimental/sgd/examples/tune_example.py @@ -0,0 +1,101 @@ +# yapf: disable +""" +This file holds code for a Distributed Pytorch + Tune page in the docs. + +It ignores yapf because yapf doesn't allow comments right after code blocks, +but we put comments right after code blocks to prevent large white spaces +in the documentation. +""" + +# __torch_tune_example__ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import torch +import torch.nn as nn + +import ray +from ray import tune +from ray.experimental.sgd.pytorch.pytorch_trainer import PyTorchTrainable + + +class LinearDataset(torch.utils.data.Dataset): + """y = a * x + b""" + + def __init__(self, a, b, size=1000): + x = np.random.random(size).astype(np.float32) * 10 + x = np.arange(0, 10, 10 / size, dtype=np.float32) + self.x = torch.from_numpy(x) + self.y = torch.from_numpy(a * x + b) + + def __getitem__(self, index): + return self.x[index, None], self.y[index, None] + + def __len__(self): + return len(self.x) + + +def model_creator(config): + return nn.Linear(1, 1) + + +def optimizer_creator(model, config): + """Returns criterion, optimizer""" + criterion = nn.MSELoss() + optimizer = torch.optim.SGD(model.parameters(), lr=config.get("lr", 1e-4)) + return criterion, optimizer + + +def data_creator(config): + """Returns training set, validation set""" + return LinearDataset(2, 5), LinearDataset(2, 5, size=400) + + +def tune_example(num_replicas=1, use_gpu=False): + config = { + "model_creator": tune.function(model_creator), + "data_creator": tune.function(data_creator), + "optimizer_creator": tune.function(optimizer_creator), + "num_replicas": num_replicas, + "use_gpu": use_gpu, + "batch_size": 512, + "backend": "gloo" + } + + analysis = tune.run( + PyTorchTrainable, + num_samples=12, + config=config, + stop={"training_iteration": 2}, + verbose=1) + + return analysis.get_best_config(metric="validation_loss", mode="min") + + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument( + "--redis-address", + type=str, + help="the address to use for Redis") + parser.add_argument( + "--num-replicas", + "-n", + type=int, + default=1, + help="Sets number of replicas for training.") + parser.add_argument( + "--use-gpu", + action="store_true", + default=False, + help="Enables GPU training") + parser.add_argument( + "--tune", action="store_true", default=False, help="Tune training") + + args, _ = parser.parse_known_args() + + ray.init(redis_address=args.redis_address) + tune_example(num_replicas=args.num_replicas, use_gpu=args.use_gpu) diff --git a/python/ray/tune/README.rst b/python/ray/tune/README.rst index 9a09675bc..94dc5b5b7 100644 --- a/python/ray/tune/README.rst +++ b/python/ray/tune/README.rst @@ -1,4 +1,4 @@ -Tune: Scalable Hyperparameter Search +Tune: Scalable Hyperparameter Tuning ==================================== Tune is a scalable framework for hyperparameter search with a focus on deep learning and deep reinforcement learning.