@@ -122,8 +122,6 @@ def make_parameter_server_strategy(worker_replicas: int, ps_replicas: int, port:
122122
123123 resolver = tf .distribute .cluster_resolver .SimpleClusterResolver (
124124 cluster_spec = tf .train .ClusterSpec (cluster_def ),
125- task_type = "chief" ,
126- task_id = 0 ,
127125 rpc_layer = "grpc" ,
128126 )
129127 variable_partitioner = tf .distribute .experimental .partitioners .MinSizePartitioner (
@@ -177,17 +175,62 @@ def run_training(
177175 print ("PS addrs:" , ps_addrs )
178176 strategy = make_parameter_server_strategy (worker_replicas , ps_replicas , port , worker_addrs , ps_addrs )
179177
180- # DatasetCreator lets Keras coordinate dataset distribution for PS strategy
181- def dataset_fn (input_context : tf .distribute .InputContext ):
178+ # Switch to ClusterCoordinator-based custom training loop (DatasetCreator removed)
179+ def per_worker_dataset_fn (input_context : Optional [ tf .distribute .InputContext ] = None ):
182180 local_ds = tf .data .Dataset .from_tensor_slices ((X , y ))
183- local_ds = local_ds .shard (input_context .num_input_pipelines , input_context .input_pipeline_id )
181+ if input_context is not None :
182+ local_ds = local_ds .shard (input_context .num_input_pipelines , input_context .input_pipeline_id )
184183 local_ds = local_ds .shuffle (buffer_size = min (10000 , len (X ))).batch (batch_size ).repeat ()
185184 return local_ds
186185
187186 with strategy .scope ():
188187 model = build_sequential_model (input_dim , num_classes )
189- creator = tf .keras .utils .experimental .DatasetCreator (dataset_fn )
190- history = model .fit (creator , epochs = epochs , steps_per_epoch = steps_per_epoch )
188+ # Build losses/optimizer/metrics explicitly for custom loop
189+ optimizer = tf .keras .optimizers .Adam (learning_rate = 1e-3 )
190+ loss_obj = tf .keras .losses .SparseCategoricalCrossentropy ()
191+ train_acc = tf .keras .metrics .SparseCategoricalAccuracy ()
192+ train_loss = tf .keras .metrics .Mean ()
193+
194+ # Create a ClusterCoordinator to drive training from the chief/coordinator process
195+ coordinator = tf .distribute .coordinator .ClusterCoordinator (strategy )
196+ per_worker_ds = coordinator .create_per_worker_dataset (per_worker_dataset_fn )
197+ per_worker_iter = iter (per_worker_ds )
198+
199+ @tf .function
200+ def per_worker_train_step (iterator ):
201+ def step_fn (inputs ):
202+ features , labels = inputs
203+ with tf .GradientTape () as tape :
204+ logits = model (features , training = True )
205+ loss = loss_obj (labels , logits )
206+ # Add possible regularization losses
207+ loss += tf .add_n (model .losses ) if model .losses else 0.0
208+ grads = tape .gradient (loss , model .trainable_variables )
209+ optimizer .apply_gradients (zip (grads , model .trainable_variables ))
210+ train_acc .update_state (labels , logits )
211+ train_loss .update_state (loss )
212+ return loss
213+
214+ return strategy .run (step_fn , args = (next (iterator ),))
215+
216+ # Training loop
217+ for epoch in range (epochs ):
218+ print (f"Starting epoch { epoch + 1 } /{ epochs } ..." )
219+ train_acc .reset_state ()
220+ train_loss .reset_state ()
221+
222+ # Schedule one step per required step_per_epoch; wait for completion
223+ futures = []
224+ for _ in range (steps_per_epoch ):
225+ f = coordinator .schedule (per_worker_train_step , args = (per_worker_iter ,))
226+ futures .append (f )
227+ # Block until all scheduled steps finish
228+ coordinator .join ()
229+
230+ print (f"Epoch { epoch + 1 } - loss: { train_loss .result ().numpy ():.4f} - accuracy: { train_acc .result ().numpy ():.4f} " )
231+
232+ # Mimic Keras History-like output for downstream logging
233+ history = type ("_H" , (), {"history" : {"accuracy" : [train_acc .result ().numpy ()]}})()
191234 else :
192235 print ("Running single-process (no distributed strategy)." )
193236 model = build_sequential_model (input_dim , num_classes )
0 commit comments