diff --git a/examples/flower-client/Dockerfile b/examples/flower-client/Dockerfile deleted file mode 100644 index a8a191403..000000000 --- a/examples/flower-client/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -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 \ No newline at end of file diff --git a/examples/flower-client/README.rst b/examples/flower-client/README.rst index 7b9e30754..2093caf3f 100644 --- a/examples/flower-client/README.rst +++ b/examples/flower-client/README.rst @@ -1,5 +1,5 @@ Using Flower ClientApps in FEDn -============================ +------------------------------- This example demonstrates how to run a Flower 'ClientApp' on FEDn. @@ -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/flower-client -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 @@ -53,20 +57,17 @@ 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:: @@ -74,7 +75,8 @@ Then start the client using Docker: -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: @@ -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 diff --git a/examples/flower-client/bin/init_venv.sh b/examples/flower-client/bin/init_venv.sh deleted file mode 100755 index 5a0c16d15..000000000 --- a/examples/flower-client/bin/init_venv.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/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 diff --git a/examples/flower-client/client/entrypoint b/examples/flower-client/client/entrypoint.py similarity index 95% rename from examples/flower-client/client/entrypoint rename to examples/flower-client/client/entrypoint.py index 79dad9eb4..24f20773e 100755 --- a/examples/flower-client/client/entrypoint +++ b/examples/flower-client/client/entrypoint.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python import os import fire @@ -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) @@ -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 @@ -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 = { diff --git a/examples/flower-client/client/fedn.yaml b/examples/flower-client/client/fedn.yaml index e5d3b2166..9fb33f18f 100644 --- a/examples/flower-client/client/fedn.yaml +++ b/examples/flower-client/client/fedn.yaml @@ -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 \ No newline at end of file + command: python entrypoint.py validate \ No newline at end of file diff --git a/examples/flower-client/client/flwr_client.py b/examples/flower-client/client/flwr_client.py index b3920d940..297df3ca7 100644 --- a/examples/flower-client/client/flwr_client.py +++ b/examples/flower-client/client/flwr_client.py @@ -11,7 +11,6 @@ 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 diff --git a/examples/flower-client/client/python_env.yaml b/examples/flower-client/client/python_env.yaml new file mode 100644 index 000000000..984a2e96d --- /dev/null +++ b/examples/flower-client/client/python_env.yaml @@ -0,0 +1,11 @@ +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[flower]==0.9.0 + - flwr-datasets[vision]==0.1.0 \ No newline at end of file diff --git a/examples/flower-client/requirements.txt b/examples/flower-client/requirements.txt deleted file mode 100644 index a363d216e..000000000 --- a/examples/flower-client/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -fire~=0.6.0 -flwr==1.8.0 -flwr-datasets[vision]==0.0.2 -torch==2.2.1 -torchvision==0.17.1 \ No newline at end of file diff --git a/fedn/fedn/utils/flowercompat/client_app_adapter.py b/fedn/fedn/utils/flowercompat/client_app_adapter.py index 09ce879d8..610e5ca9f 100644 --- a/fedn/fedn/utils/flowercompat/client_app_adapter.py +++ b/fedn/fedn/utils/flowercompat/client_app_adapter.py @@ -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) @@ -33,10 +33,10 @@ 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) @@ -44,10 +44,10 @@ def train(self, parameters: NDArrays, partition_id: int): 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) @@ -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( diff --git a/fedn/setup.py b/fedn/setup.py index 6c274a8f7..30831c6fc 100644 --- a/fedn/setup.py +++ b/fedn/setup.py @@ -28,6 +28,9 @@ "plotly", "virtualenv", ], + extras_require={ + 'flower': ["flwr==1.8.0"] + }, license='Apache 2.0', zip_safe=False, entry_points={