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 
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 
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 
203221class  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