Clients

Customizing clients using inheritance

The common practice is to customize the client using inheritance for important features that change internal states within a client. To customize the client using inheritance, subclass the simple.Client class (or edge.Client for cross-silo federated learning) in plato.clients, and override the following methods:

configure()

def configure(self) -> None

Override this method to implement additional tasks for initializing and configuring the client. Make sure that super().configure() is called first.

process_server_response()

def process_server_response(self, server_response) -> None

Override this method to conduct additional client-specific processing on the server response.

Example:

def process_server_response(self, server_response):
    if "current_global_round" in server_response:
        self.server.current_global_round = server_response["current_global_round"]

inbound_received()

def inbound_received(self, inbound_processor)

Override this method to complete additional tasks before the inbound processors start to process the data received from the server.

inbound_processor the pipeline of inbound processors. The list of inbound processor instances can be accessed through its attribute ‘processors’, as in the following example.

Example:

def inbound_received(self, inbound_processor):
    # insert a customized processor to the list of inbound processors
    customized_processor = DummyProcessor(
            client_id=client.client_id,
            current_round=client.current_round,
            name="DummyProcessor",
        )

    inbound_processor.processors.insert(0, customized_processor) 

inbound_processed()

def inbound_processed(self, processed_inbound_payload)

Override this method to conduct customized operations to generate a client’s response to the server when inbound data from the server has been processed.

processed_inbound_payload the inbound payload after being processed by inbound processors, e.g., model weights before loaded to the trainer.

Returns: the report and the outbound payload.

Example:

async def inbound_processed(self, processed_inbound_payload: Any) -> (SimpleNamespace, Any):
    report, outbound_payload = await self.customized_train(processed_inbound_payload)
    return report, outbound_payload

outbound_ready()

def outbound_ready(self, report, outbound_processor)

Override this method to complete additional tasks before the outbound processors start to process the data to be sent to the server.

report the metadata sent back to the server, e.g., training time, accuracy, etc.

outbound_processor the pipeline of outbound processors. The list of inbound processor instances can be accessed through its attribute ‘processors’, as in the following example.

Example:

def outbound_ready(self, report, outbound_processor):
    # customize the report 
    loss = self.get_loss()
    report.valuation = self.calc_valuation(report.num_samples, loss)
    
    # remove the first processor from the list of outbound processors
    outbound_processor.processors.pop() 

Customizing clients using callbacks

For infrastructure changes, such as logging and recording metrics, we tend to customize the client using callbacks instead. The advantage of using callbacks is that one can pass a list of multiple callbacks to the client when it is initialized, and they will be called in their order in the provided list. This helps when it is necessary to group features into different callback classes.

Within the implementation of these callback methods, one can access additional information about the local training by using the client instance.

To use callbacks, subclass the ClientCallback class in plato.callbacks.client, and override the following methods, then pass it to the client when it is initialized, or call client.add_callbacks after initialization. Examples can be found in examples/callbacks.

on_inbound_received()

def on_inbound_received(self, client, inbound_processor)

Override this method to complete additional tasks before the inbound processors start to process the data received from the server.

inbound_processor the pipeline of inbound processors. The list of inbound processor instances can be accessed through its attribute ‘processors’.

on_inbound_processed()

def on_inbound_processed(self, client, data)

Override this method to complete additional tasks when the inbound data has been processed by inbound processors.

data the inbound data after being processed by inbound processors, e.g., model weights before loaded to the trainer.

Example:

def on_inbound_processed(self, client, data):
    # print the layer names of the model weights before further operations
    for name, weights in data:
        print(name)

on_outbound_ready()

def on_outbound_ready(self, client, outbound_processor)

Override this method to complete additional tasks before the outbound processors start to process the data to be sent to the server.

outbound_processor the pipeline of outbound processors. The list of inbound processor instances can be accessed through its attribute ‘processors’.