Skip to content

Commit 6629c0f

Browse files
committed
Update
1 parent 0296c34 commit 6629c0f

File tree

1 file changed

+84
-158
lines changed

1 file changed

+84
-158
lines changed

docs/source/tutorial_sources/zero-to-forge/3_Monarch_101.py

Lines changed: 84 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,23 @@
9090
# Single Host ProcMesh
9191
# ~~~~~~~~~~~~~~~~~~~~
9292
#
93+
# **Key insight**: ProcMesh creates one process per GPU, automatically handling the process-to-hardware mapping.
94+
#
95+
96+
# This simple call:
97+
procs = this_host().spawn_procs(per_host={"gpus": 8})
98+
99+
# Creates:
100+
# Process 0 → GPU 0
101+
# Process 1 → GPU 1
102+
# Process 2 → GPU 2
103+
# Process 3 → GPU 3
104+
# Process 4 → GPU 4
105+
# Process 5 → GPU 5
106+
# Process 6 → GPU 6
107+
# Process 7 → GPU 7
108+
109+
######################################################################
93110
# .. mermaid::
94111
#
95112
# graph TD
@@ -124,10 +141,16 @@
124141
# style P6 fill:#F44336
125142
# style P7 fill:#F44336
126143

144+
######################################################################
145+
# The beauty: you don't manage individual processes or GPU assignments -
146+
# ProcMesh handles the topology for you.
147+
127148
######################################################################
128149
# Multi-Host ProcMesh
129150
# ~~~~~~~~~~~~~~~~~~~
130151
#
152+
# **Key insight**: ProcMesh seamlessly scales across multiple hosts with continuous process numbering.
153+
#
131154
# .. mermaid::
132155
#
133156
# graph TD
@@ -169,92 +192,81 @@
169192
# style PM2 fill:#4CAF50
170193
# style PM3 fill:#2196F3
171194

172-
######################################################################
173-
# Monarch Actor System Basics
174-
# ----------------------------
175-
#
176-
# This shows the underlying actor system that powers Forge services.
177-
178-
# Mock imports for documentation build
179-
try:
180-
from monarch.actor import Actor, endpoint, Future, ProcMesh, this_host, this_proc
181-
except ImportError:
182-
183-
class Actor:
184-
pass
185-
186-
def endpoint(func):
187-
return func
195+
# Same simple API works across hosts:
196+
cluster_procs = spawn_cluster_procs(
197+
hosts=["host1", "host2", "host3"],
198+
per_host={"gpus": 4}
199+
)
188200

189-
class Future:
190-
pass
201+
# Automatically creates:
202+
# Host 1: Processes 0-3 → GPUs 0-3
203+
# Host 2: Processes 4-7 → GPUs 0-3
204+
# Host 3: Processes 8-11 → GPUs 0-3
191205

192-
class ProcMesh:
193-
pass
206+
# Your code stays the same whether it's 1 host or 100 hosts
207+
actors = cluster_procs.spawn("my_actor", MyActor)
194208

195-
def this_proc():
196-
return None
209+
######################################################################
210+
# **The power**: Scale from single host to cluster without changing your
211+
# actor code - ProcMesh handles all the complexity.
197212

198-
def this_host():
199-
return None
213+
# This shows the underlying actor system that powers Forge services
214+
# NOTE: This is for educational purposes - use ForgeActor and .as_service() in real Forge apps!
200215

216+
from monarch.actor import Actor, endpoint, this_proc, Future
217+
from monarch.actor import ProcMesh, this_host
218+
import asyncio
201219

202220
# STEP 1: Define a basic actor
203221
class Counter(Actor):
204-
"""Basic counter actor example."""
205-
206222
def __init__(self, initial_value: int):
207223
self.value = initial_value
208224

209225
@endpoint
210226
def increment(self) -> None:
211-
"""Increment the counter."""
212227
self.value += 1
213228

214229
@endpoint
215230
def get_value(self) -> int:
216-
"""Get current counter value."""
217231
return self.value
218232

233+
# STEP 2: Single actor in local process
234+
counter: Counter = this_proc().spawn("counter", Counter, initial_value=0)
219235

220-
async def basic_actor_example():
221-
"""Example of using Monarch actors."""
222-
# STEP 2: Single actor in local process
223-
counter = this_proc().spawn("counter", Counter, initial_value=0)
224-
225-
# STEP 3: Send messages
226-
fut = counter.get_value.call_one()
227-
value = await fut
228-
print(f"Counter value: {value}") # 0
236+
# STEP 3: Send messages
237+
fut: Future[int] = counter.get_value.call_one()
238+
value = await fut
239+
print(f"Counter value: {value}") # 0
229240

241+
# STEP 4: Multiple actors across processes
242+
procs: ProcMesh = this_host().spawn_procs(per_host={"gpus": 8})
243+
counters: Counter = procs.spawn("counters", Counter, 0)
230244

231-
async def distributed_actors_example():
232-
"""Example of actors across multiple processes."""
233-
# STEP 4: Multiple actors across processes
234-
procs = this_host().spawn_procs(per_host={"gpus": 8})
235-
counters = procs.spawn("counters", Counter, 0)
245+
# STEP 5: Broadcast to all actors
246+
await counters.increment.call()
236247

237-
# STEP 5: Broadcast to all actors
238-
await counters.increment.call()
248+
# STEP 6: Different message patterns
249+
# call_one() - single actor
250+
value = await counters.get_value.call_one()
251+
print(f"One counter: {value}") # Output: One counter: 1
239252

240-
# STEP 6: Different message patterns
241-
# call_one() - single actor
242-
value = await counters.get_value.call_one()
243-
print(f"One counter: {value}")
253+
# choose() - random single actor (actors only, not services)
254+
value = await counters.get_value.choose()
255+
print(f"Random counter: {value}") # Output: Random counter: 1
244256

245-
# choose() - random single actor (actors only, not services)
246-
value = await counters.get_value.choose()
247-
print(f"Random counter: {value}")
257+
# call() - all actors, collect results
258+
values = await counters.get_value.call()
259+
print(f"All counters: {values}") # Output: All counters: [1, 1, 1, 1, 1, 1, 1, 1]
248260

249-
# call() - all actors, collect results
250-
values = await counters.get_value.call()
251-
print(f"All counters: {values}")
261+
# broadcast() - fire and forget
262+
await counters.increment.broadcast() # No return value - just sends to all actors
252263

253-
# broadcast() - fire and forget
254-
await counters.increment.broadcast()
264+
# Cleanup
265+
await procs.stop()
255266

256-
# Cleanup
257-
await procs.stop()
267+
######################################################################
268+
# Remember: This raw Monarch code is for understanding how Forge works internally.
269+
# In your Forge applications, use ForgeActor, .as_service(), .as_actor() instead!
258270

259271

260272
######################################################################
@@ -264,93 +276,25 @@ async def distributed_actors_example():
264276
# **ActorMesh** is created when you spawn actors across a ProcMesh.
265277
# Each process in the ProcMesh gets one instance of your actor.
266278
#
267-
# .. mermaid::
268-
#
269-
# graph TD
270-
# subgraph Creation["Actor Creation Process"]
271-
# Code["mesh.spawn('policy', PolicyActor, model='Qwen/Qwen3-7B')"]
272-
#
273-
# subgraph ProcMesh["ProcMesh (4 processes)"]
274-
# P0["Process 0<br/>GPU 0"]
275-
# P1["Process 1<br/>GPU 1"]
276-
# P2["Process 2<br/>GPU 2"]
277-
# P3["Process 3<br/>GPU 3"]
278-
# end
279-
#
280-
# subgraph ActorMesh["ActorMesh PolicyActor"]
281-
# A0["PolicyActor Instance #0: model=Qwen/Qwen3-7B"]
282-
# A1["PolicyActor Instance #1: model=Qwen/Qwen3-7B"]
283-
# A2["PolicyActor Instance #2: model=Qwen/Qwen3-7B"]
284-
# A3["PolicyActor Instance #3: model=Qwen/Qwen3-7B"]
285-
# end
286-
#
287-
# Code --> ProcMesh
288-
# P0 --> A0
289-
# P1 --> A1
290-
# P2 --> A2
291-
# P3 --> A3
292-
# end
293-
#
294-
# style A0 fill:#4CAF50
295-
# style A1 fill:#4CAF50
296-
# style A2 fill:#4CAF50
297-
# style A3 fill:#4CAF50
279+
# - **One actor instance per process**: `mesh.spawn("policy", PolicyActor)` creates one PolicyActor in each process
280+
# - **Same constructor arguments**: All instances get the same initialization parameters
281+
# - **Independent state**: Each actor instance maintains its own state and memory
282+
# - **Message routing**: You can send messages to one actor or all actors using different methods
283+
284+
# Simple example:
285+
procs = spawn_procs(per_host={"gpus": 4}) # 4 processes
286+
policy_actors = procs.spawn("policy", PolicyActor, model="Qwen/Qwen3-7B")
287+
288+
# Now you have 4 PolicyActor instances, one per GPU
289+
# All initialized with the same model parameter
298290

299-
######################################################################
300-
# Message Routing Through ActorMesh
301-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
302-
#
303-
# .. mermaid::
304-
#
305-
# graph TD
306-
# subgraph MessageFlow["Message Flow Patterns"]
307-
# Client["await policy_actors.generate.METHOD(prompt)"]
308-
#
309-
# subgraph Methods["Different Adverbs Route Differently"]
310-
# Choose["choose(): Routes to ONE actor, Load balanced"]
311-
# Call["call(): Routes to ALL actors, Collects results"]
312-
# Broadcast["broadcast(): Routes to ALL actors, Fire and forget"]
313-
# Stream["stream(): Routes to ALL actors, Iterator of results"]
314-
# end
315-
#
316-
# subgraph ActorInstances["PolicyActor Instances"]
317-
# A0["Actor 0: GPU 0, generates response"]
318-
# A1["Actor 1: GPU 1, generates response"]
319-
# A2["Actor 2: GPU 2, generates response"]
320-
# A3["Actor 3: GPU 3, generates response"]
321-
# end
322-
#
323-
# Client --> Choose
324-
# Client --> Call
325-
# Client --> Broadcast
326-
# Client --> Stream
327-
#
328-
# Choose -.->|"Load balanced"| A1
329-
# Call --> A0
330-
# Call --> A1
331-
# Call --> A2
332-
# Call --> A3
333-
# Broadcast --> A0
334-
# Broadcast --> A1
335-
# Broadcast --> A2
336-
# Broadcast --> A3
337-
# Stream --> A0
338-
# Stream --> A1
339-
# Stream --> A2
340-
# Stream --> A3
341-
# end
342-
#
343-
# style Choose fill:#4CAF50
344-
# style Call fill:#FF9800
345-
# style Broadcast fill:#E91E63
346-
# style Stream fill:#9C27B0
347291

348292
######################################################################
349293
# How Forge Services Use Monarch
350294
# -------------------------------
351295
#
352-
# Now the key insight: **Forge services are ServiceActors that manage
353-
# ActorMeshes of your ForgeActor replicas**.
296+
# Now the key insight: **Forge services are ``ServiceActors`` that manage
297+
# ``ActorMeshes`` of your ``ForgeActor`` replicas**.
354298
#
355299
# The Service Creation Process
356300
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -445,7 +389,7 @@ async def distributed_actors_example():
445389
# -----------------------------------------
446390
#
447391
# In real RL systems, you have multiple services that can share or use
448-
# separate ProcMeshes:
392+
# separate ``ProcMeshes``:
449393
#
450394
# .. mermaid::
451395
#
@@ -515,24 +459,6 @@ async def distributed_actors_example():
515459
# * **Scale effectively**: Where to add resources for maximum impact?
516460

517461

518-
def demonstrate_architecture_benefits():
519-
"""Example showing why the architecture matters."""
520-
# Process Isolation: Failures don't cascade
521-
# If one PolicyActor crashes, others continue serving
522-
523-
# Location Transparency: Same API whether local or remote
524-
# await policy.generate.route(prompt) # Works same everywhere
525-
526-
# Structured Distribution: ProcMesh maps to hardware
527-
# per_host={"gpus": 8} creates 8 processes, 1 per GPU
528-
529-
# Message Passing: No locks needed
530-
# Each actor processes messages sequentially, naturally thread-safe
531-
532-
# Service Abstraction: Simple interface, powerful backend
533-
# await service.method.route() hides all distribution complexity
534-
pass
535-
536462

537463
######################################################################
538464
# Conclusion

0 commit comments

Comments
 (0)