Starting Ray
ray.init() starts a Ray instance on the current machine: a scheduler, an object store in shared memory, and a pool of worker processes. Your notebook or script is the driver, the process that submits work. The argument num_cpus=4 tells Ray to schedule at most four CPU tasks at a time. These are logical CPUs: Ray uses the number for scheduling and does not check it against the hardware, so the same code behaves the same in Colab, where the machine has two cores, and on a large workstation.
memory is the RAM that Ray may assign to tasks, and object_store_memory is the size of the object store, 10 GB here. The node: entries name the machine, and the accelerator type shows that this page was run on a machine with an RTX PRO 6000 GPU. In Colab without a GPU runtime, the GPU entries are missing.
Tasks
Decorating a function with@ray.remote turns it into a task. Calling f.remote(x) does not run f. It sends the call to the scheduler, which runs it in one of the worker processes, and returns at once with an ObjectRef, a handle to a result that may not exist yet. ray.get waits for the result and returns it.
The function below sleeps for one second, standing in for a step that takes time, such as loading a file or evaluating a model on one batch. Eight calls in a loop take eight seconds. Eight remote calls run four at a time, so they finish in about two.
ray.get on a list waits for all the results. When the tasks take different times, ray.wait returns as soon as some of them are done, so the driver can process results in the order they finish.
Objects
Arguments and results of tasks live in Ray’s object store, shared memory that every worker on a machine can read.ray.put places a value there and returns a reference to it. Passing the reference to many tasks, instead of the value, stores the data once: each worker reads the same copy, and a NumPy array is not even copied into the worker’s memory. Passing a large array directly to every call would serialize it again for each one.
References can also be passed from one task to another. A task that receives a reference as an argument waits until that object exists, so a chain of calls becomes a small dependency graph that Ray runs as soon as each input is ready.
Actors
A task has no memory: each call starts from its arguments. An actor is a class decorated with@ray.remote. Creating it starts a dedicated worker process that holds the object, and method calls run in that process one at a time, in the order they arrive. Actors hold state that many tasks update, such as a model’s parameters, a counter of finished work, or a connection to a database.
The actor below keeps a running mean. Eight tasks each compute a partial sum over a slice of the data and send it to the same actor.
Resources and GPUs
Every task and actor asks for resources, one CPU by default.@ray.remote(num_gpus=1) asks for a GPU as well. Ray starts the task only on a machine with a free GPU and sets CUDA_VISIBLE_DEVICES so that the task sees only the GPU assigned to it. A fraction such as num_gpus=0.5 lets two tasks share one GPU. Ray does not limit the memory each one uses, so the fractions must match what the tasks actually need. A task that asks for a resource the cluster does not have waits forever, which is why the cell checks first.
CUDA_VISIBLE_DEVICES to it, so code in the task can use cuda:0 without knowing which physical GPU it was given.
Training with Ray Train
Ray Train runs a PyTorch training loop on several workers. You write the loop as a function, andTorchTrainer starts the workers, sets up distributed data parallel training between them, and collects the metrics they report. prepare_model wraps the model for data parallelism and moves it to the worker’s device, and ray.train.report reports metrics to Ray Train. To print the loss of every epoch here, the first worker also sends it to an actor, the same pattern as RunningMean above. ScalingConfig sets the number of workers and whether each one gets a GPU. The same function runs with one worker in Colab and with eight on a cluster.
The example fits a linear model to synthetic data with two CPU workers. Each worker generates the same data set and trains on its own half of the rows, and the gradients are averaged across the workers at every step.
use_gpu=True. Ray then gives each worker one GPU. prepare_model moves the model to that GPU, and get_device returns the same device, so the loop already moves the data there. That is the only change.
Your own Ray cluster
A Ray cluster is a head node and any number of worker nodes. The head runs the scheduler state (the global control store, GCS) and the dashboard. Every node, the head included, runs a raylet that starts worker processes and holds that node’s part of the object store. The machines must reach each other over the network, and all of them must have the same versions of Python and Ray. On the machine that will be the head, start Ray from a terminal:192.168.1.20:6379. On each other machine, join the cluster:
ray status on any node lists the nodes and their resources. The dashboard, at http://192.168.1.20:8265, shows the nodes, the running tasks and actors, and their logs. Port 6379 must be open between the machines, and port 8265 from wherever you open the dashboard.
A program connects to the cluster in one of two ways. A script started on one of the cluster’s nodes calls ray.init(address="auto"), which finds the running cluster instead of starting a new one, and every .remote call is then scheduled across all the nodes. A program on any other machine, such as your laptop, submits a job through the dashboard address:
python train.py on the cluster, and streams its output back. Inside train.py, ray.init() connects to the cluster the job runs on. ray stop on a node shuts Ray down on that node.
A two-node cluster on one machine
ray.cluster_utils.Cluster starts a head and extra worker nodes as separate processes on one machine. Ray uses it for its own tests, and it is a convenient way to see multi-node scheduling without a second computer. The cell starts a head with two CPUs and a worker node with two more, and runs eight tasks that report the node they ran on. The SPREAD scheduling strategy asks Ray to distribute the tasks over the nodes instead of filling one node first.
SPREAD asks Ray to distribute the tasks but does not guarantee an even split: in this run six of the eight tasks ran on one node and two on the other.
The same cluster accepts jobs. The cell below does what ray job submit does from a terminal, with the Python client: it submits a short script, waits for it to finish and prints its output.
Key references
The Ray documentation covers tasks, actors and objects in more depth, and the Ray Train guide covers distributed PyTorch training.Found an error on this page? Report an issue.

