Client#

class fl_sim.nodes.Client(client_id: int, device: device, model: Module, dataset: FedDataset, config: ClientConfig)[source]#

Bases: Node

The class to simulate the client node.

The client node is responsible for training the local models, and communicating with the server node.

Parameters:
accept_parameters() → None[source]#

Accepts parameters from received_messages.

The named "model_state" mapping (the recommended exchange format, see ModelState) takes precedence over the flat positional "parameters" list.

Need to be implemented in the child class to specify part in received_messages if the key is not “parameters”/”model_state”.

Some algorithms (e.g. IFCA, FedPD) do not put the parameters under the "parameters" key of the received messages (or communicate only with a subset of clients in a round); for those, the parameters are accepted in the client’s update(), so a missing key is not an error.

communicate(target: Server) → None[source]#

Upload the local model parameters and metrics to the target server.

This is the default (typed-API) implementation; subclasses override it for algorithm-specific payloads (deltas, gradients, control variates, etc.). The typed-API way to send additional data is building a ClientToServer and calling Server.receive_one() on the target; the legacy way (overriding communicate and appending to target._received_messages directly) keeps working.

Parameters:

target (Server) – The server to upload to.

Return type:

None

evaluate(part: str) → Dict[str, float][source]#

Evaluate the model on the given part of the dataset.

Parameters:

part (str) – The part of the dataset to evaluate on, can be either “train” or “val”.

Returns:

The metrics of the evaluation.

Return type:

Dict[str, float]

extra_repr_keys() → List[str][source]#

Extra keys for __repr__() and __str__().

get_all_data() → Tuple[Tensor, Tensor][source]#

Get all the data on the client.

This method is a helper function for fast access to the data on the client, including both training and validation data; both features and labels.

property is_convergent: bool#

Whether the training process is convergent.

receive(message: ServerToClient) → None[source]#

Receive a typed message broadcast from the server.

The message is stored in self._latest_message (for typed-API consumers) and merged into the legacy self._received_messages dict (for legacy consumers, e.g. accept_parameters()).

Parameters:

message (ServerToClient) – The typed message broadcast from the server.

Return type:

None

sample_data() → Tuple[Tensor, Tensor][source]#

Sample data for training.

solve_inner() → None[source]#

Main part of inner loop solver.

Basic example:

self.model.train()
epoch_losses = []
for epoch in range(self.config.num_epochs):
    batch_losses = []
    for batch_idx, (data, target) in enumerate(self.train_loader):
        data, target = data.to(self.device), target.to(self.device)
        self.optimizer.zero_grad()
        output = self.model(data)
        loss = self.criterion(output, target)
        loss.backward()
        self.optimizer.step()
        batch_losses.append(loss.item())
    epoch_losses.append(sum(batch_losses) / len(batch_losses))
self.lr_scheduler.step()
abstract train() → None[source]#

Main part of inner loop solver.

Basic example:

self.model.train()
epoch_losses = []
for epoch in range(self.config.num_epochs):
    batch_losses = []
    for batch_idx, (data, target) in enumerate(self.train_loader):
        data, target = data.to(self.device), target.to(self.device)
        self.optimizer.zero_grad()
        output = self.model(data)
        loss = self.criterion(output, target)
        loss.backward()
        self.optimizer.step()
        batch_losses.append(loss.item())
    epoch_losses.append(sum(batch_losses) / len(batch_losses))
self.lr_scheduler.step()