Use Built-in Mods¶
Note
This tutorial covers preview features. The functionality and interfaces may change in future versions.
In this tutorial, we will learn how to utilize built-in mods to augment the behavior of
a ClientApp
. Mods (sometimes also called Modifiers) allow us to perform operations
before and after a task is processed in the ClientApp
.
What are Mods?¶
A Mod is a callable that wraps around a ClientApp
. It can manipulate or inspect the
incoming Message
and the resulting outgoing Message
. The signature for a Mod
is as follows:
ClientAppCallable = Callable[[Message, Context], Message]
Mod = Callable[[Message, Context, ClientAppCallable], Message]
A typical mod function might look something like this:
from flwr.client.typing import ClientAppCallable
from flwr.common import Context, Message
def example_mod(msg: Message, ctx: Context, call_next: ClientAppCallable) -> Message:
# Do something with incoming Message (or Context)
# before passing it to the next layer in the chain.
# This could be another Mod or, if this is the last Mod, the ClientApp itself.
msg = call_next(msg, ctx)
# Do something with outgoing Message (or Context)
# before returning
return msg
Using Mods¶
Mods can be registered in two ways: Application-wide mods and Function-specific mods.
Application-wide mods: These mods apply to all functions within the
ClientApp
.Function-specific mods: These mods apply only to a specific function (e.g, the function decorated by
@app.train()
)
1. Registering Application-wide Mods¶
To use application-wide mods in your ClientApp
, follow these steps:
Import the required mods¶
import flwr as fl
from flwr.client.mod import example_mod_1, example_mod_2
Create the ClientApp
with application-wide mods¶
Create your ClientApp
and pass the mods as a list to the mods
argument. The
order in which you provide the mods matters:
app = fl.client.ClientApp(
client_fn=client_fn, # Not needed if using decorators
mods=[
example_mod_1, # Application-wide Mod 1
example_mod_2, # Application-wide Mod 2
],
)
If you define functions to handle messages using decorators instead of client_fn
,
e.g., @app.train()
, you do not need to pass the client_fn
argument.
2. Registering Function-specific Mods¶
Instead of applying mods to the entire ClientApp
, you can specify them for a
particular function:
import flwr as fl
from flwr.client.mod import example_mod_3, example_mod_4
app = fl.client.ClientApp()
@app.train(mods=[example_mod_3, example_mod_4])
def train(msg, ctx):
# Training logic here
return reply_msg
@app.evaluate()
def evaluate(msg, ctx):
# Evaluation logic here
return reply_msg
In this case, example_mod_3
and example_mod_4
are only applied to the train
function.
Order of Execution¶
When the ClientApp
runs, the mods execute in the following order:
Application-wide mods (executed first, in the order they are provided)
Function-specific mods (executed after application-wide mods, in the order they are provided)
ClientApp (core function that handles the incoming
Message
and returns the outgoingMessage
)Function-specific mods (on the way back, in reverse order)
Application-wide mods (on the way back, in reverse order)
Each mod has a chance to inspect and modify the incoming Message
before passing it
to the next mod, and likewise with the outgoing Message
before returning it up the
stack.
Example Execution Flow¶
Assuming the following registration:
app = fl.client.ClientApp(mods=[example_mod_1, example_mod_2])
@app.train(mods=[example_mod_3, example_mod_4])
def train(msg, ctx):
return Message(fl.common.RecordDict(), reply_to=msg)
@app.evaluate()
def evaluate(msg, ctx):
return Message(fl.common.RecordDict(), reply_to=msg)
The execution order for an incoming train message is as follows:
example_mod_1
(before handling)example_mod_2
(before handling)example_mod_3
(before handling)example_mod_4
(before handling)train
(handling message)example_mod_4
(after handling)example_mod_3
(after handling)example_mod_2
(after handling)example_mod_1
(after handling)
The execution order for an incoming evaluate message is as follows:
example_mod_1
(before handling)example_mod_2
(before handling)evaluate
(handling message)example_mod_2
(after handling)example_mod_1
(after handling)
Conclusion¶
By following this guide, you have learned how to effectively use mods to enhance your
ClientApp
’s functionality. Remember that the order of mods is crucial and affects
how the input and output are processed.
Enjoy building a more robust and flexible ClientApp
with mods!