Rate this Page

Monarch Actor API: Ping Pong#

This example demonstrates the basics of Monarch’s Actor/endpoint API, which provides a generic interface for distributed computing. We’ll cover:

  • Creating and spawning actors in process meshes

  • Calling endpoints on actors

  • Actor-to-actor communication with a ping-pong example

Hello World#

Actors are spawned in Process meshes via the monarch.actor API. For those familiar with distributed systems, it can be helpful to think of each Actor as a server with endpoints that can be called.

from monarch.actor import Actor, current_rank, endpoint, this_host

NUM_ACTORS = 4


class ToyActor(Actor):
    def __init__(self):
        self.rank = current_rank().rank

    @endpoint
    def hello_world(self, msg):
        print(f"Identity: {self.rank}, {msg=}")

Note: Meshes can be also be created on different nodes, but we’re ignoring that in this example

local_proc_mesh = this_host().spawn_procs(per_host={"gpus": NUM_ACTORS})
# This spawns 4 instances of 'ToyActor'
toy_actor = local_proc_mesh.spawn("toy_actor", ToyActor)

Once actors are spawned, we can call all of them simultaneously with Actor.endpoint.call

toy_actor.hello_world.call("hey there, from script!!").get()

We can also specify a single actor using the ‘slice’ API

futures = []
for idx in range(NUM_ACTORS):
    actor_instance = toy_actor.slice(gpus=idx)
    futures.append(
        actor_instance.hello_world.call_one(f"Here's an arbitrary unique value: {idx}")
    )

# Wait for all futures to complete
for fut in futures:
    fut.get()

Ping Pong#

Not only is it possible to call endpoints from a ‘main’ function, but actors have the useful property of being able to communicate with one another.

class ExampleActor(Actor):
    def __init__(self, actor_name):
        self.actor_name = actor_name

    @endpoint
    def init(self, other_actor):
        self.other_actor = other_actor
        self.other_actor_pair = other_actor.slice(**current_rank())
        self.identity = current_rank().rank

    @endpoint
    def send(self, msg):
        self.other_actor_pair.recv.call(
            f"Sender ({self.actor_name}:{self.identity}) {msg=}"
        ).get()

    @endpoint
    def recv(self, msg):
        print(f"Pong!, Receiver ({self.actor_name}:{self.identity}) received msg {msg}")

Spawn two different Actors in different meshes, with two instances each

local_mesh_0 = this_host().spawn_procs(per_host={"gpus": 2})
actor_0 = local_mesh_0.spawn(
    "actor_0",
    ExampleActor,
    "actor_0",  # this arg is passed to ExampleActor.__init__
)

local_mesh_1 = this_host().spawn_procs(per_host={"gpus": 2})
actor_1 = local_mesh_1.spawn(
    "actor_1",
    ExampleActor,
    "actor_1",  # this arg is passed to ExampleActor.__init__
)

Initialize each actor with references to each other

actor_0.init.call(actor_1).get()
actor_1.init.call(actor_0).get()

Send messages between actors

# Actor 0 sends to Actor 1
actor_0.send.call("Ping").get()

# Actor 1 sends to Actor 0
actor_1.send.call("Ping").get()

print("Example completed successfully!")

Total running time of the script: (0 minutes 0.000 seconds)

Gallery generated by Sphinx-Gallery