Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/SK-707 | Add config support to flower adapter #570

Merged
merged 11 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions examples/flower-client/Dockerfile

This file was deleted.

44 changes: 24 additions & 20 deletions examples/flower-client/README.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Using Flower ClientApps in FEDn
============================
-------------------------------

This example demonstrates how to run a Flower 'ClientApp' on FEDn.

Expand All @@ -15,28 +15,32 @@ 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):
Install fedn:

.. code-block::

bin/init_venv.sh
pip install fedn

Activate the virtual environment:
Clone this repository, then locate into this directory:

.. code-block::

source .flower-client/bin/activate
git clone https://github.com/scaleoutsystems/fedn.git
cd fedn/examples/mnist-pytorch

Make the compute package (to be uploaded to FEDn):
Create the compute package (compress the 'client' folder):

.. code-block::

tar -czvf package.tgz client
fedn package create --path client

This should create a file 'package.tgz' in the project folder.

Next, generate a seed model (the first model in the global model trail):

Create the seed model (to be uploaded to FEDn):
.. code-block::

python client/entrypoint init_seed
fedn run build --path client

Next, you will upload the compute package and seed model to
a FEDn network. Here you have two main options: using FEDn Studio
Expand All @@ -53,28 +57,26 @@ 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

On your local machine / client, start the FEDn client:

Or, if you prefer to use Docker, build an image (this might take a long time):

.. code-block::

docker build -t flower-client .
export FEDN_AUTH_SCHEME=Bearer
export FEDN_PACKAGE_EXTRACT_DIR=package
CLIENT_NUMBER=0 fedn run client -in client.yaml --secure=True --force-ssl

Then start the client using Docker:

Or, if you prefer to use Docker (this might take a long time):

.. 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
-e FEDN_PACKAGE_EXTRACT_DIR=package \
ghcr.io/scaleoutsystems/fedn/fedn:master run client -in client.yaml --secure=True --force-ssl


If you are running FEDn in pseudo-local mode:
Expand Down Expand Up @@ -105,4 +107,6 @@ Then start the client (using Docker)
-v $PWD/client.yaml:/app/client.yaml \
--network=fedn_default \
-e CLIENT_NUMBER=0 \
flower-client run client -in client.yaml
-e FEDN_AUTH_SCHEME=Bearer \
-e FEDN_PACKAGE_EXTRACT_DIR=package \
ghcr.io/scaleoutsystems/fedn/fedn:master run client -in client.yaml
10 changes: 0 additions & 10 deletions examples/flower-client/bin/init_venv.sh

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env python
import os

import fire
Expand Down Expand Up @@ -38,7 +37,7 @@ def init_seed(out_path="seed.npz"):
: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())
parameters_np = flwr_adapter.init_parameters(partition_id=_get_node_id(), config={})
save_parameters(out_path, parameters_np)


Expand All @@ -58,7 +57,7 @@ def train(in_model_path, out_model_path):

# Train on flower client
params, num_examples = flwr_adapter.train(
parameters=parameters_np, partition_id=_get_node_id()
parameters=parameters_np, partition_id=_get_node_id(), config={}
)

# Metadata needed for aggregation server side
Expand Down Expand Up @@ -86,7 +85,7 @@ def validate(in_model_path, out_json_path, data_path=None):
"""
parameters_np = helper.load(in_model_path)

loss, accuracy = flwr_adapter.evaluate(parameters_np, partition_id=_get_node_id())
loss, accuracy = flwr_adapter.evaluate(parameters_np, partition_id=_get_node_id(), config={})

# JSON schema
report = {
Expand Down
7 changes: 5 additions & 2 deletions examples/flower-client/client/fedn.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
python_env: python_env.yaml
entry_points:
build:
command: python entrypoint.py init_seed
train:
command: python entrypoint train $ENTRYPOINT_OPTS
command: python entrypoint.py train
validate:
command: python entrypoint validate $ENTRYPOINT_OPTS
command: python entrypoint.py validate
12 changes: 12 additions & 0 deletions examples/flower-client/client/python_env.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: flower-client
build_dependencies:
- pip
- setuptools
- wheel==0.37.1
dependencies:
- torch==2.2.1
- torchvision==0.17.1
- fire==0.3.1
- fedn==0.8.0
- flwr==1.8.0
- flwr-datasets[vision]==0.1.0
5 changes: 0 additions & 5 deletions examples/flower-client/requirements.txt

This file was deleted.

19 changes: 10 additions & 9 deletions fedn/fedn/utils/flowercompat/client_app_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ class FlwrClientAppAdapter:
def __init__(self, app: ClientApp) -> None:
self.app = app

def init_parameters(self, partition_id: int):
def init_parameters(self, partition_id: int, config: dict = {}):
# Construct a get_parameters message for the ClientApp
message, context = self._construct_message(
MessageTypeLegacy.GET_PARAMETERS, [], partition_id
MessageTypeLegacy.GET_PARAMETERS, [], partition_id, config
)
# Call client app with train message
client_return_message = self.app(message, context)
Expand All @@ -33,21 +33,21 @@ def init_parameters(self, partition_id: int):
client has implemented a get_parameters() function.")
return parameters

def train(self, parameters: NDArrays, partition_id: int):
def train(self, parameters: NDArrays, partition_id: int, config: dict = {}):
# Construct a train message for the ClientApp with given parameters
message, context = self._construct_message(
MessageType.TRAIN, parameters, partition_id
MessageType.TRAIN, parameters, partition_id, config
)
# Call client app with train message
client_return_message = self.app(message, context)
# Parse return message
params, num_examples = self._parse_train_message(client_return_message)
return params, num_examples

def evaluate(self, parameters: NDArrays, partition_id: int):
def evaluate(self, parameters: NDArrays, partition_id: int, config: dict = {}):
# Construct an evaluate message for the ClientApp with given parameters
message, context = self._construct_message(
MessageType.EVALUATE, parameters, partition_id
MessageType.EVALUATE, parameters, partition_id, config
)
# Call client app with evaluate message
client_return_message = self.app(message, context)
Expand Down Expand Up @@ -76,16 +76,17 @@ def _construct_message(
message_type: MessageType,
parameters: NDArrays,
partition_id: int,
config: dict,
) -> Tuple[Message, Context]:
parameters = ndarrays_to_parameters(parameters)
if message_type == MessageType.TRAIN:
fit_ins: FitIns = FitIns(parameters=parameters, config={})
fit_ins: FitIns = FitIns(parameters=parameters, config=config)
recordset = fitins_to_recordset(fitins=fit_ins, keep_input=False)
if message_type == MessageType.EVALUATE:
ev_ins: EvaluateIns = EvaluateIns(parameters=parameters, config={})
ev_ins: EvaluateIns = EvaluateIns(parameters=parameters, config=config)
recordset = evaluateins_to_recordset(evaluateins=ev_ins, keep_input=False)
if message_type == MessageTypeLegacy.GET_PARAMETERS:
get_parameters_ins: GetParametersIns = GetParametersIns({})
get_parameters_ins: GetParametersIns = GetParametersIns(config=config)
recordset = getparametersins_to_recordset(getparameters_ins=get_parameters_ins)

metadata = Metadata(
Expand Down
Loading