1- import requests
21import base64
32import pickle
4- from prototype .model_tools_v2 import ToyModel , WeightSlice
53from typing import List
64
5+ import numpy as np
6+ import requests
7+
8+ from prototype .model_tools import ToyModel , WeightSlice
9+
710
811class Controller :
912 """
1013 Model partitioning controller with safe serialization.
11-
14+
1215 Replaces pickle-based transport with binary format for security.
1316 """
14-
17+
1518 def __init__ (self , workers ):
1619 """
1720 Initialize controller with worker URLs.
18-
21+
1922 Args:
2023 workers: List of worker URLs (e.g., ["http://127.0.0.1:8001", "http://127.0.0.1:8002"])
2124 """
2225 self .workers = workers
2326 # Connection pooling for performance
2427 self .session = requests .Session ()
25-
26- def partition_model (self , model : ToyModel , num_slices : int = 2 ) -> List [WeightSlice ]:
28+
29+ def partition_model (
30+ self , model : ToyModel , num_slices : int = 2
31+ ) -> List [WeightSlice ]:
2732 """
2833 Partition model into balanced slices.
29-
34+
3035 Algorithm: Balanced partitioning ensures even distribution of layers.
31-
36+
3237 Args:
3338 model: ToyModel instance to partition
3439 num_slices: Number of slices (e.g., 2 for bipartition)
35-
40+
3641 Returns:
3742 List of WeightSlice objects in execution order
3843 """
3944 L = len (model .weights )
4045 # Balanced partitioning using ceiling division
4146 slice_size = (L + num_slices - 1 ) // num_slices
42-
47+
4348 slices = []
4449 for i in range (num_slices ):
4550 start = i * slice_size
4651 end = min (L , start + slice_size )
47-
52+
4853 if start >= L :
4954 break
50-
55+
5156 sub = model .slice (start , end )
5257 slices .append (sub )
53-
58+
5459 return slices
55-
56- def preload_slices (self , slices : List [WeightSlice ], encrypt : bool = False ) -> List [tuple ]:
60+
61+ def preload_slices (
62+ self , slices : List [WeightSlice ], encrypt : bool = False
63+ ) -> List [tuple ]:
5764 """
5865 Preload model slices to workers.
59-
66+
6067 Args:
6168 slices: List of WeightSlice objects in execution order
6269 encrypt: Whether to encrypt weights during transport
63-
70+
6471 Returns:
6572 List of (slice_id, worker_url) tuples for distributed execution
6673 """
6774 assigned = []
68-
75+
6976 for i , slice_obj in enumerate (slices ):
7077 # Round-robin assignment to workers
7178 w = self .workers [i % len (self .workers )]
72-
79+
7380 # Serialize weights safely (no pickle)
7481 blob = slice_obj .to_bytes ()
75-
82+
7683 manifest = {
7784 "start" : slice_obj .start_layer ,
7885 "end" : slice_obj .end_layer ,
79- "version" : slice_obj .version
86+ "version" : slice_obj .version ,
8087 }
81-
88+
8289 payload = {
8390 "slice_id" : f"slice_{ slice_obj .start_layer } _{ slice_obj .end_layer } " ,
8491 "manifest" : manifest ,
85- "weights_b64" : base64 .b64encode (blob ).decode (' ascii' ),
86- "version" : slice_obj .version
92+ "weights_b64" : base64 .b64encode (blob ).decode (" ascii" ),
93+ "version" : slice_obj .version ,
8794 }
88-
95+
8996 # Retry with exponential backoff for transient failures
9097 max_attempts = 3
9198 backoff_base = 0.1
92-
99+
93100 for attempt in range (1 , max_attempts + 1 ):
94101 try :
95102 r = self .session .post (f"{ w } /preload" , json = payload , timeout = 10 )
@@ -100,56 +107,55 @@ def preload_slices(self, slices: List[WeightSlice], encrypt: bool = False) -> Li
100107 raise
101108 sleep_t = backoff_base * (2 ** (attempt - 1 ))
102109 import time
110+
103111 time .sleep (sleep_t )
104-
105- assigned .append ((payload [' slice_id' ], w ))
106-
112+
113+ assigned .append ((payload [" slice_id" ], w ))
114+
107115 return assigned
108-
109- def run_distributed (self , assigned : List [tuple ], x : np .ndarray ,
110- encrypt : bool = False ) -> np .ndarray :
116+
117+ def run_distributed (
118+ self , assigned : List [tuple ], x : np .ndarray , encrypt : bool = False
119+ ) -> np .ndarray :
111120 """
112121 Run distributed inference across workers.
113-
122+
114123 Args:
115124 assigned: List of (slice_id, worker_url) tuples in execution order
116125 x: Input tensor as numpy array
117126 encrypt: Whether to encrypt activations during transport
118-
127+
119128 Returns:
120129 Output tensor after passing through all slices
121130 """
122- from prototype .model_tools_v2 import ToyModel
123-
131+ from prototype .model_tools import ToyModel
132+
124133 current = x
125-
134+
126135 for slice_id , w in assigned :
127136 try :
128137 # Prepare input for this slice
129138 if encrypt :
130139 raise NotImplementedError ("Encryption not yet implemented" )
131140 else :
132- b64_input = base64 .b64encode (current .tobytes ()).decode (' ascii' )
133-
141+ b64_input = base64 .b64encode (current .tobytes ()).decode (" ascii" )
142+
134143 payload = {
135144 "slice_id" : slice_id ,
136145 "input_b64" : b64_input ,
137- "version" : "v1.0"
146+ "version" : "v1.0" ,
138147 }
139-
148+
140149 # Execute on worker
141150 r = self .session .post (f"{ w } /execute" , json = payload , timeout = 30 )
142151 r .raise_for_status ()
143-
152+
144153 # Get output
145- out_b64 = r .json ()['output_b64' ]
146- current = np .frombuffer (
147- base64 .b64decode (out_b64 ),
148- dtype = np .float32
149- )
150-
154+ out_b64 = r .json ()["output_b64" ]
155+ current = np .frombuffer (base64 .b64decode (out_b64 ), dtype = np .float32 )
156+
151157 except Exception as e :
152158 print (f"Error executing slice { slice_id } : { e } " )
153159 raise
154-
160+
155161 return current
0 commit comments