Add new FlexibleClient for added flexibility and migrate pFL and mixins to use this new class#398
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #398 +/- ##
==========================================
+ Coverage 79.37% 79.85% +0.48%
==========================================
Files 157 158 +1
Lines 9496 9540 +44
==========================================
+ Hits 7537 7618 +81
+ Misses 1959 1922 -37 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
BasicClient to prepare for pFL mixins
BasicClient to prepare for pFL mixinsBasicClient for added flexibility (and to prepare for pFL mixins)
scarere
left a comment
There was a problem hiding this comment.
Overall I like how the models and optimizers are now configurable, however I wonder if there is a way to do the same thing without adding a large number of internal methods? I could very well be missing something here in how all the pieces fit together.
193098a to
803363a
Compare
BasicClient for added flexibility (and to prepare for pFL mixins)FlexibleClient for added flexibility and migrate pFL and mixins to use this
FlexibleClient for added flexibility and migrate pFL and mixins to use thisFlexibleClient for added flexibility and migrate pFL and mixins to use this new class
emersodb
left a comment
There was a problem hiding this comment.
Overall, I think this PR is pretty much ready to go. However, I wanted to point out two things:
- I had a potentially dumb idea that I laid out in a comment in
FlexibleClient - I wonder if, rather than migrating clients over to
FlexibleClient, we should create implementations usingFlexibleClientand keep those that use BasicClient as well, since we'll be supporting both paradigms, at least for the foreseeable future. We also want to make sure we're careful about moving more complex clients over, so that we understand what "Ditto-fying" of "MR-MTL-fying" actually means. I.e. does it actually function in a sensical way. Just a thought.
b6fc115 to
b3ee469
Compare
73b5abd to
2317ba2
Compare
90ca7e0 to
5f67019
Compare
eafdd5b to
c6e827d
Compare
9c7c457 to
29167c5
Compare
|
There was a problem hiding this comment.
Overall great PR, it's definitely clear that the scope of implementing these mixins was a lot more work than I had imagined it would be initially, a lot under the hood here. A lot of my comments are just straight questions for learning purposes as I'm new to Protocols and Mixins. I felt that there might be some opportunity to simplify the ditto mixin, and perhaps the protocols, however thats contigent that I understood the code properly, and there are likely some constraints I'm not seeing that necessitated certain choices in implementation. Also I was a decently confused by the structure of some of the tests, not sure if thats due to a knowledge gap or if here is a way to simplify them.
| """ | ||
| return self._val_step_with_model(self.model, input, target) | ||
|
|
||
| def predict_with_model( |
There was a problem hiding this comment.
For all the other new "with model" methods, you have redefined the original method to call the new one with the default model. Why didn't you redefine the predict method? Ie.
def predict(self, input):
return self.predict_with_model(input, self.model)There was a problem hiding this comment.
I think we might have discussed this a bit, or something related in the last PR. Mostly this was to not break the public interface that had priorly been established with BasicClient. Now that we have FlexibleClient that inherits BasicClient I'm still inclined not to overload the interface.
We couldn't add model as param to BasicClient because we can't pass a default value, and thus we would have to unfortunately introduce a breaking change.
|
|
||
| return losses, preds | ||
|
|
||
| def _apply_backwards_on_losses_and_take_step( |
There was a problem hiding this comment.
Is it necessary for this to be it's own function. It's only 3 lines of code and as far as I can tell the only place it will be called is in _train_step_with_model_and_optimizer. Do you feel there is a need here for this functionality to be separate? If not we can reduce the number of methods in this class by 1, and the number of lines by a decent amount
There was a problem hiding this comment.
Without trying to get this all to work with NNunetClient I would have to agree with you. But when trying to ditto-ify it, I learned that this client specializes this logic and I need to be able to pass the respective model, optimizers and losses.
So, this should be interpreted as us introducing flexibility that will be used in the subsequent PR when we move over NNunetClient to inherit FlexibleClient
| NOTE: Subclasses should implement this method if there is a need to specialize | ||
| the train_step logic. | ||
|
|
||
| Args: |
There was a problem hiding this comment.
The model and optmizer arguments are undefined in this docstring
| input (TorchInputType): The input to be fed into the model. | ||
| target (TorchTargetType): The target corresponding to the input. | ||
|
|
||
| Returns: |
There was a problem hiding this comment.
nit: Not sure if there is a standard here, but when the return signature for a function is a fixed length tuple containing different return values, I usually seperate each value of the tuple as separate return values with the tuple type being implicit. Ie
"""
Returns:
losses (TrainingLosses) : The losses object ...
preds (TorchPredType): dictionary of any predictions ...
"""Is there a preferred way here?
There was a problem hiding this comment.
This occurs in several other methods in this class, so I'll just let this comment represent all instances of this. I recognize it was probably just copied from BasicClient's docstrings but if my suggested method is preferable then maybe a good idea to start implementing this practice here?
There was a problem hiding this comment.
I like this suggestion in principle, but I don't think it adheres to the "Google docstring" style. It's hard to tell how they "want" you to document returns, but by default the docstrings that are generated follow the tuple typing. The style you're describing is part of the numpy standard I think though. So I'm not 100% sure.
In any case, the rest of the library doesn't break tuples out in the way you're describing at the moment.
There was a problem hiding this comment.
In this case, we should keep as is to be standard with rest of library.
| self, model: nn.Module, input: TorchInputType, target: TorchTargetType | ||
| ) -> tuple[EvaluationLosses, TorchPredType]: | ||
| """ | ||
| Helper method for val_step that allows for injection of model. |
There was a problem hiding this comment.
Docstring missing definition for model argument
| @@ -0,0 +1,286 @@ | |||
| import datetime | |||
There was a problem hiding this comment.
I'm assuming that all these tests are just copied from the tests for basic client. It seems redundant to have the same tests twice. Is there not a way to modify the basic client tests to be run a second time but with a difference MockClient? In this case using the FlexibleMockClient instead, like just parametrize the client. I'm pretty sure pytest has like a decorator or something that does this. Then the flexible client tests can be specific to the functionality that's unique to flexible client. Also now that flexible client inherits from the basic client, won't most of these tests just be covering the same code?
There was a problem hiding this comment.
We could parametrize maybe -- not going to do that here tho lol.
emersodb
left a comment
There was a problem hiding this comment.
I left a very minor comment or two. However, from my perspective, the code is ready to go.
It would be good to address Shawn's comments before merging. 🙂
| input (TorchInputType): The input to be fed into the model. | ||
| target (TorchTargetType): The target corresponding to the input. | ||
|
|
||
| Returns: |
There was a problem hiding this comment.
I like this suggestion in principle, but I don't think it adheres to the "Google docstring" style. It's hard to tell how they "want" you to document returns, but by default the docstrings that are generated follow the tuple typing. The style you're describing is part of the numpy standard I think though. So I'm not 100% sure.
In any case, the rest of the library doesn't break tuples out in the way you're describing at the moment.
| This mixin implements the Ditto algorithm from Ditto: Fair and Robust Federated Learning Through | ||
| Personalization. This mixin inherits from the `AdaptiveDriftConstrainedMixin`, and like that mixin, | ||
| this should be mixed with a `BasicClient` type in order to apply the Ditto personalization method | ||
| this should be mixed with a `FlexibleClient` type in order to apply the Ditto personalization method |
There was a problem hiding this comment.
I can answer (1) at least. It's presence as something separate makes it overridable. Which was mildly useful in implementing the FENDA + Ditto approach in the library.
|
Alright I believe I addressed all CRs at this point. @scarere comment's re: better organization of test suites are good and valid. I think we should handle them more broadly in the library. And, so as to not drag on this PR any longer, am choosing not to handle tests clean up here. |


PR Type
Feature
Short Description
This PR adds a new
FlexibleClientthat is similar toBasicClientbut introduces methods for training, predicting, and evaluating that has dependency injection of models and optimizers. I've moved our existing mixins stuff to work withFlexibleClientrather thanBasicClient.Now
BasicClientis completely untouched.In creating new clients:
FlexibleClientif they only have a single-model client; otherwise, users should subclassBasicClient.Migration plan:
BasicClienttoFlexibleClient.Added internal methods in
FlexibleClientSubclasses of
FlexibleClientwill need to implement:predict_with_model()(this is a public method; removespredict()completely)_train_step_with_model_and_optimizer()_apply_backwards_on_losses_and_take_step()— called within private train step helper_compute_preds_and_losses()— called within private train step helper_val_step_with_model()_transform_gradients_with_model()— allowing for model injection for transforming gradientsTests Added
predict,train_stepandval_stepare overridden.