-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/SK-707 | Flower client example (#537)
* add flower client example * clarify readme * exclude dir floating imports * gh wf * new flower adapter * formatting * formatting * formatting... * Updated readme, added Dockerfile, added init file * Updated readme * point at stable * Language fixes * move adapter to fedn utils * add adapter in fedn utils * readme fix * Example updated to run natively in the venv * fix readme * python3.9 -> python3 in venv * Updated dependcies * add results for flower compat * Deps * change to fit new clientapp * Revert venv changes * Update run.sh * Code checks * Updated README, removed build.sh, instructions in readme instead * Polish readme * Fix * update gitignore * update gitignore * updater adapter * update adapter * linting * linting * linting * get_parameters and assertion added * linting * linting * readme typo fix --------- Co-authored-by: Andreas Hellander <[email protected]> Co-authored-by: Andreas Hellander <[email protected]>
- Loading branch information
1 parent
cc63b15
commit 1f72449
Showing
16 changed files
with
530 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,4 +5,5 @@ | |
}, | ||
"python.linting.enabled": true, | ||
"python.linting.flake8Enabled": true, | ||
"esbonio.sphinx.confDir": "", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
data | ||
seed.npz | ||
*.tgz | ||
*.tar.gz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
data | ||
*.npz | ||
*.tgz | ||
*.tar.gz | ||
.flower-client | ||
client.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
FROM ghcr.io/scaleoutsystems/fedn/fedn:0.8.0 | ||
|
||
COPY requirements.txt /app/config/requirements.txt | ||
|
||
# Install requirements | ||
RUN python -m venv /venv \ | ||
&& /venv/bin/pip install --upgrade pip \ | ||
&& /venv/bin/pip install --no-cache-dir -r /app/config/requirements.txt \ | ||
# Clean up | ||
&& rm -r /app/config/requirements.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
Using Flower ClientApps in FEDn | ||
============================ | ||
|
||
This example demonstrates how to run a Flower 'ClientApp' on FEDn. | ||
|
||
The FEDn compute package 'client/entrypoint' | ||
uses a built-in Flower compatibiltiy adapter for convenient wrapping of the Flower client. | ||
See `flwr_client.py` and `flwr_task.py` for the Flower client code (which is adapted from | ||
https://github.com/adap/flower/tree/main/examples/app-pytorch). | ||
|
||
|
||
Running the example | ||
------------------- | ||
|
||
See `https://fedn.readthedocs.io/en/stable/quickstart.html` for a general introduction to FEDn. | ||
This example follows the same structure as the pytorch quickstart example. | ||
|
||
Build a virtual environment (note that you might need to install the 'venv' package): | ||
|
||
.. code-block:: | ||
bin/init_venv.sh | ||
Activate the virtual environment: | ||
|
||
.. code-block:: | ||
source .flower-client/bin/activate | ||
Make the compute package (to be uploaded to FEDn): | ||
|
||
.. code-block:: | ||
tar -czvf package.tgz client | ||
Create the seed model (to be uploaded to FEDn): | ||
.. code-block:: | ||
python client/entrypoint init_seed | ||
Next, you will upload the compute package and seed model to | ||
a FEDn network. Here you have two main options: using FEDn Studio | ||
(recommended for new users), or a pseudo-local deployment | ||
on your own machine. | ||
|
||
If you are using FEDn Studio (recommended): | ||
----------------------------------------------------- | ||
|
||
Follow instructions here to register for Studio and start a project: https://fedn.readthedocs.io/en/stable/studio.html. | ||
|
||
In your Studio project: | ||
|
||
- From the "Sessions" menu, upload the compute package and seed model. | ||
- Register a client and obtain the corresponding 'client.yaml'. | ||
|
||
On your local machine / client (in the same virtual environment), start the FEDn client: | ||
|
||
.. code-block:: | ||
CLIENT_NUMBER=0 FEDN_AUTH_SCHEME=Bearer fedn run client -in client.yaml --force-ssl --secure=True | ||
Or, if you prefer to use Docker, build an image (this might take a long time): | ||
|
||
.. code-block:: | ||
docker build -t flower-client . | ||
Then start the client using Docker: | ||
|
||
.. code-block:: | ||
docker run \ | ||
-v $PWD/client.yaml:/app/client.yaml \ | ||
-e CLIENT_NUMBER=0 \ | ||
-e FEDN_AUTH_SCHEME=Bearer \ | ||
flower-client run client -in client.yaml --secure=True --force-ssl | ||
If you are running FEDn in pseudo-local mode: | ||
------------------------------------------------------------------ | ||
|
||
Deploy a FEDn network on local host (see `https://fedn.readthedocs.io/en/stable/quickstart.html`). | ||
|
||
Use the FEDn API Client to initalize FEDn with the compute package and seed model: | ||
|
||
.. code-block:: | ||
python init_fedn.py | ||
Create a file 'client.yaml' with the following content: | ||
|
||
.. code-block:: | ||
network_id: fedn-network | ||
discover_host: api-server | ||
discover_port: 8092 | ||
name: myclient | ||
Then start the client (using Docker) | ||
|
||
.. code-block:: | ||
docker run \ | ||
-v $PWD/client.yaml:/app/client.yaml \ | ||
--network=fedn_default \ | ||
-e CLIENT_NUMBER=0 \ | ||
flower-client run client -in client.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#!/bin/bash | ||
set -e | ||
|
||
# Init venv | ||
python3 -m venv .flower-client | ||
|
||
# Pip deps | ||
.flower-client/bin/pip install --upgrade pip | ||
.flower-client/bin/pip install -e ../../fedn | ||
.flower-client/bin/pip install -r requirements.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
#!/usr/bin/env python | ||
import os | ||
|
||
import fire | ||
from flwr_client import app | ||
|
||
from fedn.utils.flowercompat.client_app_adapter import FlwrClientAppAdapter | ||
from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics | ||
|
||
HELPER_MODULE = "numpyhelper" | ||
helper = get_helper(HELPER_MODULE) | ||
|
||
flwr_adapter = FlwrClientAppAdapter(app) | ||
|
||
|
||
def _get_node_id(): | ||
"""Get client number from environment variable.""" | ||
|
||
number = os.environ.get("CLIENT_NUMBER", "0") | ||
return int(number) | ||
|
||
|
||
def save_parameters(out_path, parameters_np): | ||
"""Save model paramters to file. | ||
:param model: The model to serialize. | ||
:type model: torch.nn.Module | ||
:param out_path: The path to save to. | ||
:type out_path: str | ||
""" | ||
helper.save(parameters_np, out_path) | ||
|
||
|
||
def init_seed(out_path="seed.npz"): | ||
"""Initialize seed model and save it to file. | ||
:param out_path: The path to save the seed model to. | ||
:type out_path: str | ||
""" | ||
# This calls get_parameters in the flower client which needs to be implemented. | ||
parameters_np = flwr_adapter.init_parameters(partition_id=_get_node_id()) | ||
save_parameters(out_path, parameters_np) | ||
|
||
|
||
def train(in_model_path, out_model_path): | ||
"""Complete a model update. | ||
Load model paramters from in_model_path (managed by the FEDn client), | ||
perform a model update through the flower client, and write updated paramters | ||
to out_model_path (picked up by the FEDn client). | ||
:param in_model_path: The path to the input model. | ||
:type in_model_path: str | ||
:param out_model_path: The path to save the output model to. | ||
:type out_model_path: str | ||
""" | ||
parameters_np = helper.load(in_model_path) | ||
|
||
# Train on flower client | ||
params, num_examples = flwr_adapter.train( | ||
parameters=parameters_np, partition_id=_get_node_id() | ||
) | ||
|
||
# Metadata needed for aggregation server side | ||
metadata = { | ||
# num_examples are mandatory | ||
"num_examples": num_examples, | ||
} | ||
|
||
# Save JSON metadata file (mandatory) | ||
save_metadata(metadata, out_model_path) | ||
|
||
# Save model update (mandatory) | ||
save_parameters(out_model_path, params) | ||
|
||
|
||
def validate(in_model_path, out_json_path, data_path=None): | ||
"""Validate model on the clients test dataset. | ||
:param in_model_path: The path to the input model. | ||
:type in_model_path: str | ||
:param out_json_path: The path to save the output JSON to. | ||
:type out_json_path: str | ||
:param data_path: The path to the data file. | ||
:type data_path: str | ||
""" | ||
parameters_np = helper.load(in_model_path) | ||
|
||
loss, accuracy = flwr_adapter.evaluate(parameters_np, partition_id=_get_node_id()) | ||
|
||
# JSON schema | ||
report = { | ||
"test_loss": loss, | ||
"test_accuracy": accuracy, | ||
} | ||
print(f"Loss: {loss}, accuracy: {accuracy}") | ||
# Save JSON | ||
save_metrics(report, out_json_path) | ||
|
||
|
||
if __name__ == "__main__": | ||
fire.Fire( | ||
{ | ||
"init_seed": init_seed, | ||
"train": train, | ||
"validate": validate, | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
entry_points: | ||
train: | ||
command: python entrypoint train $ENTRYPOINT_OPTS | ||
validate: | ||
command: python entrypoint validate $ENTRYPOINT_OPTS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
"""Flower client code using the ClientApp abstraction. | ||
Code adapted from https://github.com/adap/flower/tree/main/examples/app-pytorch. | ||
""" | ||
|
||
from flwr.client import ClientApp, NumPyClient | ||
from flwr_task import (DEVICE, Net, get_weights, load_data, set_weights, test, | ||
train) | ||
|
||
|
||
# Define FlowerClient and client_fn | ||
class FlowerClient(NumPyClient): | ||
def __init__(self, cid) -> None: | ||
super().__init__() | ||
print(f"STARTED CLIENT WITH CID {cid}") | ||
self.net = Net().to(DEVICE) | ||
self.trainloader, self.testloader = load_data( | ||
partition_id=int(cid), num_clients=10 | ||
) | ||
|
||
def get_parameters(self, config): | ||
return [val.cpu().numpy() for _, val in self.net.state_dict().items()] | ||
|
||
def fit(self, parameters, config): | ||
set_weights(self.net, parameters) | ||
train(self.net, self.trainloader, epochs=3) | ||
return get_weights(self.net), len(self.trainloader.dataset), {} | ||
|
||
def evaluate(self, parameters, config): | ||
set_weights(self.net, parameters) | ||
loss, accuracy = test(self.net, self.testloader) | ||
return loss, len(self.testloader.dataset), {"accuracy": accuracy} | ||
|
||
|
||
def client_fn(cid: str): | ||
"""Create and return an instance of Flower `Client`.""" | ||
return FlowerClient(cid).to_client() | ||
|
||
|
||
# Flower ClientApp | ||
app = ClientApp( | ||
client_fn=client_fn, | ||
) |
Oops, something went wrong.