Runhouse lets you serverlessly dispatch and execute regular Python on your existing cloud infrastructure. You can quickly develop, test, and iterate on your ML programs, from your local IDE while executing on powerful remote compute. Then, identically execute your code in production simply by scheduling the dispatch while keeping the underlying program code exactly the same.
This tutorial demonstrates how to
Connect to an existing remote IP, fresh cloud VM, or fresh Kubernetes pod in Python as a Runhouse cluster
Send a locally defined function onto the remote compute and call it as a service
The Runhouse base package can be installed with:
!pip install runhouse
To use Runhouse to launch on-demand clusters, please instead run the following command.
!pip install "runhouse[sky]"
import runhouse as rh
First, let’s define the function that we want to be run on our remote compute. This is just a regular Python function; no decorators, wrappers, or configs are necessary.
def get_platform(a = 0): import platform return platform.platform()
In Runhouse, a “cluster” is a unit of compute, somewhere you can send
code, data, or requests to execute. We define a Runhouse cluster using
the rh.cluster
factory function.
This requires having access to an existing VM (via SSH), a cloud provider account to launch elastic compute, or a Kubernetes cluster (~/.kube/config). If you do not have access to a cluster, you can try the local version of this tutorial, which sets up and deploys the Python function to a local server.
To use a cluster that’s already running:
cluster = rh.cluster( name="rh-cluster", host="example-cluster", # hostname or ip address, ssh_creds={"ssh_user": "ubuntu", "ssh_private_key": "~/.ssh/id_rsa"}, # credentials for ssh-ing into the cluster )
If you do not have a cluster up, but have cloud credentials (e.g. AWS,
Google Cloud, Azure) for launching clusters or a kubeconfig for an
existing Kubernetes cluster, you can set up and launch an on-demand
cluster with rh.ondemand_cluster
. This uses SkyPilot under the hood,
so run sky check
in a CLI first to make sure credentials are set up
properly.
cluster = rh.ondemand_cluster( name="rh-cluster", instance_type="CPU:2+", provider="aws", autostop_mins=60, launcher_type="local", ) cluster.up_if_not()
There are a number of options to specify the resources more finely, such
as GPUs (instance_type="A10G:4"
), cloud provider names
(instance_type="m5.xlarge"
), num_nodes=n
for multiple
instances, memory
, disk_size
, region
, image_id
,
open_ports
, spot
, and more. See the on_demand_cluster
docs.
You can also omit the provider argument to allocate from the cheapest
available source for which you have credentials.
Notice the launcher_type is local, which means that Runhouse will launch a cluster with your local credentials. In a production setting, we expect folks to generally launch with Den, and authenticate with Runhouse rather than moving keys and secrets around, or giving everyone individual permissions to launch clusters.
Simply wrap the function in rh.function
and send it to the cluster
with .to
. This deploys the function to the cluster as a service by
syncing over the code, setting up any specified Runhouse environment
(see Envs or
dependencies, environment variables, secrets, conda environments),
importing the synced code, and serving it in the Runhouse API server.
Classes, or Modules
are also supported. Remote instances of a remote
class have persisted state, enabling powerful usage patterns.
remote_get_platform = rh.function(get_platform).to(cluster)
INFO | 2024-05-16 03:20:53.066103 | Because this function is defined in a notebook, writing it out to /Users/donny/code/notebooks/docs/get_platform_fn.py to make it importable. Please make sure the function does not rely on any local variables, including imports (which should be moved inside the function body). This restriction does not apply to functions defined in normal Python files.
INFO | 2024-05-16 03:20:53.079931 | Port 32300 is already in use. Trying next port.
INFO | 2024-05-16 03:20:53.081995 | Forwarding port 32301 to port 32300 on localhost.
INFO | 2024-05-16 03:20:54.215570 | Server rh-cluster is up.
INFO | 2024-05-16 03:20:54.224806 | Copying package from file:///Users/donny/code/notebooks to: rh-cluster
INFO | 2024-05-16 03:20:55.395007 | Calling _cluster_default_env.install
INFO | 2024-05-16 03:20:55.948421 | Time to call _cluster_default_env.install: 0.55 seconds
INFO | 2024-05-16 03:20:55.960756 | Sending module get_platform of type <class 'runhouse.resources.functions.function.Function'> to rh-cluster
Deploying the function to the cluster took ~2 seconds, and the function
we defined above, get_platform
, now exists remotely on the cluster,
and can be called remotely using remote_fn
. You can call this remote
function normally from local, with remote_fn()
, and it runs on the
cluster and returns the result to our local environment.
When we run the local and remote versions of this function, you see different results based on where it executes.
print(f"Local Platform: {get_platform()}") print(f"Remote Platform: {remote_get_platform()}")
INFO | 2024-05-16 03:21:03.941205 | Calling get_platform.call
Local Platform: macOS-14.4.1-arm64-arm-64bit
INFO | 2024-05-16 03:21:04.513689 | Time to call get_platform.call: 0.57 seconds
Remote Platform: Linux-5.15.0-1049-aws-x86_64-with-glibc2.31
If you launched an on-demand cluster, you can terminate it by calling
cluster.teardown()
.
cluster.teardown()
What we just did, running a locally defined function on remote compute, is just the tip of the iceberg of what’s possible with Runhouse. With a large suite of even more abstractions and features, Runhouse lets you quickly and seamlessly integrate between local and remote environments.
We recommend you now review the extended guide on getting started with Runhouse. You can also take a look at our examples or at the API reference