Skip to content

Commit 2ba4f7d

Browse files
committed
update basic client with helper methods
1 parent 407858f commit 2ba4f7d

4 files changed

Lines changed: 74 additions & 39 deletions

File tree

fl4health/clients/basic_client.py

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -559,25 +559,20 @@ def update_metric_manager(
559559
"""
560560
metric_manager.update(preds, target)
561561

562-
def train_step(self, input: TorchInputType, target: TorchTargetType) -> tuple[TrainingLosses, TorchPredType]:
563-
"""
564-
Given a single batch of input and target data, generate predictions, compute loss, update parameters and
565-
optionally update metrics if they exist. (i.e. backprop on a single batch of data).
566-
Assumes ``self.model`` is in train mode already.
562+
def _train_step(
563+
self, model: torch.nn.Module, optimizer: Optimizer, input: TorchInputType, target: TorchTargetType
564+
) -> tuple[TrainingLosses, TorchPredType]:
565+
"""Helper train step.
567566
568-
Args:
569-
input (TorchInputType): The input to be fed into the model.
570-
target (TorchTargetType): The target corresponding to the input.
571-
572-
Returns:
573-
tuple[TrainingLosses, TorchPredType]: The losses object from the train step along with
574-
a dictionary of any predictions produced by the model.
567+
This interface allows for injection of model and optimizer params, which
568+
are useful for personalized FL methods.
575569
"""
570+
576571
# Clear gradients from optimizer if they exist
577-
self.optimizers["global"].zero_grad()
572+
optimizer.zero_grad()
578573

579574
# Call user defined methods to get predictions and compute loss
580-
preds, features = self.predict(input)
575+
preds, features = self._predict(model, input)
581576
target = self.transform_target(target)
582577
losses = self.compute_training_loss(preds, features, target)
583578

@@ -588,6 +583,22 @@ def train_step(self, input: TorchInputType, target: TorchTargetType) -> tuple[Tr
588583

589584
return losses, preds
590585

586+
def train_step(self, input: TorchInputType, target: TorchTargetType) -> tuple[TrainingLosses, TorchPredType]:
587+
"""
588+
Given a single batch of input and target data, generate predictions, compute loss, update parameters and
589+
optionally update metrics if they exist. (i.e. backprop on a single batch of data).
590+
Assumes ``self.model`` is in train mode already.
591+
592+
Args:
593+
input (TorchInputType): The input to be fed into the model.
594+
target (TorchTargetType): The target corresponding to the input.
595+
596+
Returns:
597+
tuple[TrainingLosses, TorchPredType]: The losses object from the train step along with
598+
a dictionary of any predictions produced by the model.
599+
"""
600+
return self._train_step(self.model, self.optimizers["global"], input, target)
601+
591602
def val_step(self, input: TorchInputType, target: TorchTargetType) -> tuple[EvaluationLosses, TorchPredType]:
592603
"""
593604
Given input and target, compute loss, update loss and metrics. Assumes ``self.model`` is in eval mode already.
@@ -975,34 +986,19 @@ def get_parameter_exchanger(self, config: Config) -> ParameterExchanger:
975986
"""
976987
return FullParameterExchanger()
977988

978-
def predict(self, input: TorchInputType) -> tuple[TorchPredType, TorchFeatureType]:
979-
"""
980-
Computes the prediction(s), and potentially features, of the model(s) given the input.
981-
982-
Args:
983-
input (TorchInputType): Inputs to be fed into the model. If input is of type ``dict[str, torch.Tensor]``,
984-
it is assumed that the keys of input match the names of the keyword arguments of
985-
``self.model.forward().``
986-
987-
Returns:
988-
tuple[TorchPredType, TorchFeatureType]: A tuple in which the first element contains a dictionary of
989-
predictions indexed by name and the second element contains intermediate activations indexed by name. By
990-
passing features, we can compute losses such as the contrastive loss in MOON. All predictions included in
991-
dictionary will by default be used to compute metrics separately.
989+
def _predict(self, model: torch.nn.Module, input: TorchInputType) -> tuple[TorchPredType, TorchFeatureType]:
990+
"""Helper predict method.
992991
993-
Raises:
994-
TypeError: Occurs when something other than a tensor or dict of tensors is passed in to the model's
995-
forward method.
996-
ValueError: Occurs when something other than a tensor or dict of tensors is returned by the model
997-
forward.
992+
Unlike, predict(), this interface allows for injecting the model param.
998993
"""
994+
999995
if isinstance(input, torch.Tensor):
1000-
output = self.model(input)
996+
output = model(input)
1001997
elif isinstance(input, dict):
1002998
# If input is a dictionary, then we unpack it before computing the forward pass.
1003999
# Note that this assumes the keys of the input match (exactly) the keyword args
10041000
# of self.model.forward().
1005-
output = self.model(**input)
1001+
output = model(**input)
10061002
else:
10071003
raise TypeError("'input' must be of type torch.Tensor or dict[str, torch.Tensor].")
10081004

@@ -1018,6 +1014,29 @@ def predict(self, input: TorchInputType) -> tuple[TorchPredType, TorchFeatureTyp
10181014
else:
10191015
raise ValueError("Model forward did not return a tensor, dictionary of tensors, or tuple of tensors")
10201016

1017+
def predict(self, input: TorchInputType) -> tuple[TorchPredType, TorchFeatureType]:
1018+
"""
1019+
Computes the prediction(s), and potentially features, of the model(s) given the input.
1020+
1021+
Args:
1022+
input (TorchInputType): Inputs to be fed into the model. If input is of type ``dict[str, torch.Tensor]``,
1023+
it is assumed that the keys of input match the names of the keyword arguments of
1024+
``self.model.forward().``
1025+
1026+
Returns:
1027+
tuple[TorchPredType, TorchFeatureType]: A tuple in which the first element contains a dictionary of
1028+
predictions indexed by name and the second element contains intermediate activations indexed by name. By
1029+
passing features, we can compute losses such as the contrastive loss in MOON. All predictions included in
1030+
dictionary will by default be used to compute metrics separately.
1031+
1032+
Raises:
1033+
TypeError: Occurs when something other than a tensor or dict of tensors is passed in to the model's
1034+
forward method.
1035+
ValueError: Occurs when something other than a tensor or dict of tensors is returned by the model
1036+
forward.
1037+
"""
1038+
return self._predict(self.model, input)
1039+
10211040
def compute_loss_and_additional_losses(
10221041
self, preds: TorchPredType, features: TorchFeatureType, target: TorchTargetType
10231042
) -> tuple[torch.Tensor, dict[str, torch.Tensor] | None]:
@@ -1280,6 +1299,15 @@ def update_before_epoch(self, epoch: int) -> None:
12801299
"""
12811300
pass
12821301

1302+
def _transform_gradients(self, model: torch.nn.Module, losses: TrainingLosses) -> None:
1303+
"""
1304+
Helper transform gradients method.
1305+
1306+
Unlike transform_gradients(), this helper's interface allows for injecting
1307+
model as a param.
1308+
"""
1309+
pass
1310+
12831311
def transform_gradients(self, losses: TrainingLosses) -> None:
12841312
"""
12851313
Hook function for model training only called after backwards pass but before optimizer step. Useful for
@@ -1288,7 +1316,7 @@ def transform_gradients(self, losses: TrainingLosses) -> None:
12881316
Args:
12891317
losses (TrainingLosses): The losses object from the train step
12901318
"""
1291-
pass
1319+
return self._transform_gradients(self.model, losses)
12921320

12931321
def _save_client_state(self) -> None:
12941322
"""

fl4health/clients/nnunet_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def __init__(
207207
log(INFO, "Disabling model optimizations and JIT compilation. This may impact runtime performance.")
208208

209209
def _train_step(
210-
self, model: nn.Module, optimizer: ..., input: TorchInputType, target: TorchTargetType
210+
self, model: nn.Module, optimizer: Optimizer, input: TorchInputType, target: TorchTargetType
211211
) -> tuple[TrainingLosses, TorchPredType]:
212212
# If the device type is not cuda, we don't use mixed precision training and therefore can use parent method.
213213
if self.device.type != "cuda":
@@ -956,4 +956,4 @@ def transform_gradients(self, losses: TrainingLosses) -> None:
956956
Args:
957957
losses (TrainingLosses): Not used for this transformation.
958958
"""
959-
self._transform_gradients(self.model, self.max_grad_norm)
959+
self._transform_gradients(self.model, losses)

fl4health/mixins/core_protocols.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ def initialize_all_model_weights(self, parameters: NDArrays, config: Config) ->
7777
def update_before_train(self, current_server_round: int) -> None:
7878
pass # pragma: no cover
7979

80+
def _train_step(
81+
self, model: nn.Module, optimizer: Optimizer, input: TorchInputType, target: TorchTargetType
82+
) -> tuple[TrainingLosses, TorchPredType]:
83+
pass # pragma: no cover
84+
8085
def predict(self, input: TorchInputType) -> tuple[TorchPredType, TorchFeatureType]:
8186
pass # pragma: no cover
8287

fl4health/mixins/personalized/ditto.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,9 @@ def train_step(
373373
"""
374374

375375
# global
376-
global_losses, global_preds = self._train_step(self.global_model, self.optimizers["global"], input, target)
376+
global_losses, global_preds = self._train_step(
377+
self.safe_global_model(), self.optimizers["global"], input, target
378+
)
377379
# local
378380
local_losses, local_preds = self._train_step(self.model, self.optimizers["local"], input, target)
379381

0 commit comments

Comments
 (0)