diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml index 1b451975e..124e82afe 100644 --- a/.github/workflows/integration-tests.yaml +++ b/.github/workflows/integration-tests.yaml @@ -15,8 +15,8 @@ jobs: strategy: matrix: to_test: - - "mnist-keras kerashelper" - - "mnist-pytorch pytorchhelper" + - "mnist-keras numpyhelper" + - "mnist-pytorch numpyhelper" python_version: ["3.8", "3.9","3.10"] os: - ubuntu-20.04 diff --git a/docker-compose.yaml b/docker-compose.yaml index aa4550c25..0871d8620 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -119,6 +119,8 @@ services: - "/venv/bin/pip install --no-cache-dir -e /app/fedn && /venv/bin/fedn run combiner --init config/settings-combiner.yaml" ports: - 12080:12080 + depends_on: + - api-server # Client client: @@ -136,3 +138,6 @@ services: - "/venv/bin/pip install --no-cache-dir -e /app/fedn && /venv/bin/fedn run client --init config/settings-client.yaml" deploy: replicas: 0 + depends_on: + - api-server + - combiner diff --git a/docs/fedn.utils.rst b/docs/fedn.utils.rst index 7fcc67d44..303e8cb93 100644 --- a/docs/fedn.utils.rst +++ b/docs/fedn.utils.rst @@ -33,10 +33,10 @@ fedn.utils.dispatcher module :undoc-members: :show-inheritance: -fedn.utils.helpers module +fedn.utils.helpers.helpers module ------------------------- -.. automodule:: fedn.utils.helpers +.. automodule:: fedn.utils.helpers.helpers :members: :undoc-members: :show-inheritance: diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 355d86919..f48d06017 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -73,7 +73,7 @@ A *entrypoint.py* example can look like this: import fire import torch - from fedn.utils.helpers import get_helper, save_metadata, save_metrics + from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics HELPER_MODULE = 'pytorchhelper' NUM_CLASSES = 10 @@ -298,7 +298,7 @@ For validations it is a requirement that the output is saved in a valid json for python entrypoint.py validate in_model_path out_json_path -In the code example we use the helper function :py:meth:`fedn.utils.helpers.save_metrics` to save the validation scores as a json file. +In the code example we use the helper function :py:meth:`fedn.utils.helpers.helpers.save_metrics` to save the validation scores as a json file. The Dahboard in the FEDn UI will plot any scalar metric in this json file, but you can include any type in the file assuming that it is valid json. These values can then be obtained (by an athorized user) from the MongoDB database or using the :py:mod:`fedn.network.api.client`. diff --git a/examples/mnist-keras/client/entrypoint b/examples/mnist-keras/client/entrypoint index 5b1d76c01..f0149fad6 100755 --- a/examples/mnist-keras/client/entrypoint +++ b/examples/mnist-keras/client/entrypoint @@ -7,9 +7,11 @@ import fire import numpy as np import tensorflow as tf -from fedn.utils.helpers import get_helper, save_metadata, save_metrics +from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics + +HELPER_MODULE = 'numpyhelper' +helper = get_helper(HELPER_MODULE) -HELPER_MODULE = 'kerashelper' NUM_CLASSES = 10 @@ -22,7 +24,16 @@ def _get_data_path(): return f"/var/data/clients/{number}/mnist.npz" -def _compile_model(img_rows=28, img_cols=28): +def compile_model(img_rows=28, img_cols=28): + """ Compile the TF model. + + param: img_rows: The number of rows in the image + type: img_rows: int + param: img_cols: The number of rows in the image + type: img_cols: int + return: The compiled model + type: keras.model.Sequential + """ # Set input shape input_shape = (img_rows, img_cols, 1) @@ -36,10 +47,11 @@ def _compile_model(img_rows=28, img_cols=28): model.compile(loss=tf.keras.losses.categorical_crossentropy, optimizer=tf.keras.optimizers.Adam(), metrics=['accuracy']) + return model -def _load_data(data_path, is_train=True): +def load_data(data_path, is_train=True): # Load data if data_path is None: data = np.load(_get_data_path()) @@ -63,46 +75,77 @@ def _load_data(data_path, is_train=True): def init_seed(out_path='seed.npz'): - weights = _compile_model().get_weights() - helper = get_helper(HELPER_MODULE) + """ Initialize seed model and save it to file. + + :param out_path: The path to save the seed model to. + :type out_path: str + """ + weights = compile_model().get_weights() helper.save(weights, out_path) def train(in_model_path, out_model_path, data_path=None, batch_size=32, epochs=1): + """ Complete a model update. + + Load model paramters from in_model_path (managed by the FEDn client), + perform a model update, 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 + :param data_path: The path to the data file. + :type data_path: str + :param batch_size: The batch size to use. + :type batch_size: int + :param epochs: The number of epochs to train. + :type epochs: int + """ # Load data - x_train, y_train = _load_data(data_path) + x_train, y_train = load_data(data_path) # Load model - model = _compile_model() - helper = get_helper(HELPER_MODULE) + model = compile_model() weights = helper.load(in_model_path) model.set_weights(weights) # Train model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs) - # Save - weights = model.get_weights() - helper.save(weights, out_model_path) - # Metadata needed for aggregation server side metadata = { + # num_examples are mandatory 'num_examples': len(x_train), 'batch_size': batch_size, 'epochs': epochs, } - # Save JSON metadata file + # Save JSON metadata file (mandatory) save_metadata(metadata, out_model_path) + # Save model update (mandatory) + weights = model.get_weights() + helper.save(weights, out_model_path) + def validate(in_model_path, out_json_path, data_path=None): + """ Validate model. + + :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 + """ + # Load data - x_train, y_train = _load_data(data_path) - x_test, y_test = _load_data(data_path, is_train=False) + x_train, y_train = load_data(data_path) + x_test, y_test = load_data(data_path, is_train=False) # Load model - model = _compile_model() + model = compile_model() helper = get_helper(HELPER_MODULE) weights = helper.load(in_model_path) model.set_weights(weights) @@ -127,10 +170,10 @@ def validate(in_model_path, out_json_path, data_path=None): def infer(in_model_path, out_json_path, data_path=None): # Using test data for inference but another dataset could be loaded - x_test, _ = _load_data(data_path, is_train=False) + x_test, _ = load_data(data_path, is_train=False) # Load model - model = _compile_model() + model = compile_model() helper = get_helper(HELPER_MODULE) weights = helper.load(in_model_path) model.set_weights(weights) diff --git a/examples/mnist-pytorch/.gitignore b/examples/mnist-pytorch/.gitignore index a9f01054b..84f374386 100644 --- a/examples/mnist-pytorch/.gitignore +++ b/examples/mnist-pytorch/.gitignore @@ -2,5 +2,6 @@ data *.npz *.tgz *.tar.gz +*.ipynb .mnist-pytorch client.yaml \ No newline at end of file diff --git a/examples/mnist-pytorch/API_Example.ipynb b/examples/mnist-pytorch/API_Example.ipynb new file mode 100644 index 000000000..ed126e536 --- /dev/null +++ b/examples/mnist-pytorch/API_Example.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "622f7047", + "metadata": {}, + "source": [ + "## FEDn API Example\n", + "\n", + "This notebook provides an example of how to use the FEDn API to organize experiments and to analyze validation results. We will here run one training session using FedAvg and one session using FedAdam and compare the results.\n", + "\n", + "When you start this tutorial you should have a deployed FEDn Network up and running, and you should have created the compute package and the initial model, see the README for instructions." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "743dfe47", + "metadata": {}, + "outputs": [], + "source": [ + "from fedn import APIClient\n", + "from fedn.dashboard.plots import Plot\n", + "import uuid\n", + "import json\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import collections" + ] + }, + { + "cell_type": "markdown", + "id": "1046a4e5", + "metadata": {}, + "source": [ + "We make a client connection to the FEDn API service. Here we assume that FEDn is deployed locally in pseudo-distributed mode with default ports." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1061722d", + "metadata": {}, + "outputs": [], + "source": [ + "DISCOVER_HOST = '127.0.0.1'\n", + "DISCOVER_PORT = 8092\n", + "client = APIClient(DISCOVER_HOST, DISCOVER_PORT)" + ] + }, + { + "cell_type": "markdown", + "id": "07f69f5f", + "metadata": {}, + "source": [ + "Initialize FEDn with the compute package and seed model. Note that these files needs to be created separately by follwing instructions in the README." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "5107f6f9", + "metadata": {}, + "outputs": [], + "source": [ + "client.set_package('package.tgz', 'numpyhelper')\n", + "client.set_initial_model('seed.npz')\n", + "seed_model = client.get_initial_model()" + ] + }, + { + "cell_type": "markdown", + "id": "4e26c50b", + "metadata": {}, + "source": [ + "Next we start a training session using FedAvg:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f0380d35", + "metadata": {}, + "outputs": [], + "source": [ + "session_config_fedavg = {\n", + " \"helper\": \"numpyhelper\",\n", + " \"session_id\": \"experiment_fedavg\",\n", + " \"aggregator\": \"fedavg\",\n", + " \"model_id\": seed_model['model_id'],\n", + " \"rounds\": 10\n", + " }\n", + "\n", + "result_fedavg = client.start_session(**session_config_fedavg)" + ] + }, + { + "cell_type": "markdown", + "id": "4eeea5aa", + "metadata": {}, + "source": [ + "Wait until the session finished. Then run a session using FedAdam:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "4f70d7d9", + "metadata": {}, + "outputs": [], + "source": [ + "session_config_fedopt = {\n", + " \"helper\": \"numpyhelper\",\n", + " \"session_id\": \"experiment_fedopt\",\n", + " \"aggregator\": \"fedopt\",\n", + " \"model_id\": seed_model['model_id'],\n", + " \"rounds\": 10\n", + " }\n", + "\n", + "result_fedopt = client.start_session(**session_config_fedopt)" + ] + }, + { + "cell_type": "markdown", + "id": "29552af9", + "metadata": {}, + "source": [ + "Next, we retrive all model validations from all clients, extract the training accuracy metric, and compute its mean value accross all clients" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "11fd17ef", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "models = client.list_models(session_id = \"experiment_fedavg\")\n", + "\n", + "validations = []\n", + "acc = collections.OrderedDict()\n", + "for model in models[\"result\"]:\n", + " model_id = model[\"model\"]\n", + " validations = client.list_validations(modelId=model_id)\n", + "\n", + " for _ , validation in validations.items(): \n", + " metrics = json.loads(validation['data'])\n", + " try:\n", + " acc[model_id].append(metrics['training_accuracy'])\n", + " except KeyError: \n", + " acc[model_id] = [metrics['training_accuracy']]\n", + " \n", + "mean_acc_fedavg = []\n", + "for model, data in acc.items():\n", + " mean_acc_fedavg.append(np.mean(data))\n", + "mean_acc_fedavg.reverse()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "900eb0a7", + "metadata": {}, + "outputs": [], + "source": [ + "models = client.list_models(session_id = \"experiment_fedopt\")\n", + "\n", + "validations = []\n", + "acc = collections.OrderedDict()\n", + "for model in models[\"result\"]:\n", + " model_id = model[\"model\"]\n", + " validations = client.list_validations(modelId=model_id)\n", + " for _ , validation in validations.items(): \n", + " metrics = json.loads(validation['data'])\n", + " try:\n", + " acc[model_id].append(metrics['training_accuracy'])\n", + " except KeyError: \n", + " acc[model_id] = [metrics['training_accuracy']]\n", + " \n", + "mean_acc_fedopt = []\n", + "for model, data in acc.items():\n", + " mean_acc_fedopt.append(np.mean(data))\n", + "mean_acc_fedopt.reverse()" + ] + }, + { + "cell_type": "markdown", + "id": "40db4542", + "metadata": {}, + "source": [ + "Finally, plot the resulting accuracy" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "d064aaf9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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F1+Ml5cXXl5efPvtt8ycORMvLy8cDofZJdYqkZGRxMbGltvWtm1bkpOTTapIHnvsMcaOHcutt95Khw4duP3223n44YeZOnWq2aXVehEREQCkpaWV256Wllb2mjspjFRjhmHwwAMPsHDhQr7++muaNm1qdkm1Wt++ffn9999Zv3592dK1a1eGDRvG+vXrsdlsZpdYq/Tq1euUru7btm2jSZMmJlUk+fn5WK3lP3ZsNhtOp9OkiuS4pk2bEhERQWJiYtm27Oxsfv75Z3r27On29nWbphobNWoU8+fPZ9GiRQQFBZXd1wsJCcHPz8/k6mqfoKCgU57XCQgIoF69enqOxwQPP/wwl156Kc888wy33HILq1ev5rXXXuO1114zu7Raa9CgQTz99NM0btyYdu3asW7dOqZPn87f/vY3s0urFXJzc9mxY0fZ17t372b9+vWEhobSuHFjRo8ezZQpU2jZsiVNmzZlwoQJREVFlfW4cStDqi3gtMsbb7xhdmlyTO/evY2HHnrI7DJqrc8++8xo3769YbfbjTZt2hivvfaa2SXVatnZ2cZDDz1kNG7c2PD19TWaNWtmjB8/3igqKjK7tFphxYoVp/3MGDFihGEYhuF0Oo0JEyYY4eHhht1uN/r27WskJSVVSW0aZ0RERERMpWdGRERExFQKIyIiImIqhRERERExlcKIiIiImEphREREREylMCIiIiKmUhgRERERUymMiIiIiKkURkRERMRUCiMiIiJiKoURERERMZXCiIiIiJjq/wHq35Vjnbzm4wAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "x = range(1,len(mean_acc_fedavg)+1)\n", + "plt.plot(x, mean_acc_fedavg, x, mean_acc_fedopt)\n", + "plt.legend(['FedAvg', 'FedAdam'])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/mnist-pytorch/client/entrypoint b/examples/mnist-pytorch/client/entrypoint index 8d7953b59..47099aaa1 100755 --- a/examples/mnist-pytorch/client/entrypoint +++ b/examples/mnist-pytorch/client/entrypoint @@ -7,9 +7,11 @@ import docker import fire import torch -from fedn.utils.helpers import get_helper, save_metadata, save_metrics +from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics + +HELPER_MODULE = 'numpyhelper' +helper = get_helper(HELPER_MODULE) -HELPER_MODULE = 'pytorchhelper' NUM_CLASSES = 10 @@ -24,7 +26,7 @@ def _get_data_path(): return f"/var/data/clients/{number}/mnist.pt" -def _compile_model(): +def compile_model(): """ Compile the pytorch model. :return: The compiled model. @@ -44,12 +46,11 @@ def _compile_model(): x = torch.nn.functional.log_softmax(self.fc3(x), dim=1) return x - # Return model return Net() -def _load_data(data_path, is_train=True): - """ Load data from disk. +def load_data(data_path, is_train=True): + """ Load data from disk. :param data_path: Path to data file. :type data_path: str @@ -76,54 +77,52 @@ def _load_data(data_path, is_train=True): return X, y -def _save_model(model, out_path): - """ Save model to disk. +def save_parameters(model, out_path): + """ Save model paramters to file. - :param model: The model to save. + :param model: The model to serialize. :type model: torch.nn.Module :param out_path: The path to save to. :type out_path: str """ - weights = model.state_dict() - weights_np = collections.OrderedDict() - for w in weights: - weights_np[w] = weights[w].cpu().detach().numpy() - helper = get_helper(HELPER_MODULE) - helper.save(weights, out_path) + parameters_np = [val.cpu().numpy() for _, val in model.state_dict().items()] + helper.save(parameters_np, out_path) -def _load_model(model_path): - """ Load model from disk. +def load_parameters(model_path): + """ Load model parameters from file and populate model. param model_path: The path to load from. :type model_path: str :return: The loaded model. :rtype: torch.nn.Module """ - helper = get_helper(HELPER_MODULE) - weights_np = helper.load(model_path) - weights = collections.OrderedDict() - for w in weights_np: - weights[w] = torch.tensor(weights_np[w]) - model = _compile_model() - model.load_state_dict(weights) - model.eval() + model = compile_model() + parameters_np = helper.load(model_path) + + params_dict = zip(model.state_dict().keys(), parameters_np) + state_dict = collections.OrderedDict({key: torch.tensor(x) for key, x in params_dict}) + model.load_state_dict(state_dict, strict=True) return model def init_seed(out_path='seed.npz'): - """ Initialize seed model. + """ Initialize seed model and save it to file. :param out_path: The path to save the seed model to. :type out_path: str """ # Init and save - model = _compile_model() - _save_model(model, out_path) + model = compile_model() + save_parameters(model, out_path) def train(in_model_path, out_model_path, data_path=None, batch_size=32, epochs=1, lr=0.01): - """ Train model. + """ Complete a model update. + + Load model paramters from in_model_path (managed by the FEDn client), + perform a model update, 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 @@ -139,10 +138,10 @@ def train(in_model_path, out_model_path, data_path=None, batch_size=32, epochs=1 :type lr: float """ # Load data - x_train, y_train = _load_data(data_path) + x_train, y_train = load_data(data_path) - # Load model - model = _load_model(in_model_path) + # Load parmeters and initialize model + model = load_parameters(in_model_path) # Train optimizer = torch.optim.SGD(model.parameters(), lr=lr) @@ -166,17 +165,18 @@ def train(in_model_path, out_model_path, data_path=None, batch_size=32, epochs=1 # Metadata needed for aggregation server side metadata = { + # num_examples are mandatory 'num_examples': len(x_train), 'batch_size': batch_size, 'epochs': epochs, 'lr': lr } - # Save JSON metadata file + # Save JSON metadata file (mandatory) save_metadata(metadata, out_model_path) - # Save model update - _save_model(model, out_model_path) + # Save model update (mandatory) + save_parameters(model, out_model_path) def validate(in_model_path, out_json_path, data_path=None): @@ -190,11 +190,12 @@ def validate(in_model_path, out_json_path, data_path=None): :type data_path: str """ # Load data - x_train, y_train = _load_data(data_path) - x_test, y_test = _load_data(data_path, is_train=False) + x_train, y_train = load_data(data_path) + x_test, y_test = load_data(data_path, is_train=False) # Load model - model = _load_model(in_model_path) + model = load_parameters(in_model_path) + model.eval() # Evaluate criterion = torch.nn.NLLLoss() @@ -225,5 +226,4 @@ if __name__ == '__main__': 'init_seed': init_seed, 'train': train, 'validate': validate, - # '_get_data_path': _get_data_path, # for testing }) diff --git a/fedn/cli/run_cmd.py b/fedn/cli/run_cmd.py index 45c10dee1..917017512 100644 --- a/fedn/cli/run_cmd.py +++ b/fedn/cli/run_cmd.py @@ -251,9 +251,8 @@ def dashboard_cmd(ctx, host, port, secret_key, local_package, name, init): @click.option('-c', '--max_clients', required=False, default=30, help='The maximal number of client connections allowed.') @click.option('-in', '--init', required=False, default=None, help='Path to configuration file to (re)init combiner.') -@click.option('-a', '--aggregator', required=False, default='fedavg', help='Filename of the aggregator module to use.') @click.pass_context -def combiner_cmd(ctx, discoverhost, discoverport, token, name, host, port, fqdn, secure, verify, max_clients, init, aggregator): +def combiner_cmd(ctx, discoverhost, discoverport, token, name, host, port, fqdn, secure, verify, max_clients, init): """ :param ctx: @@ -269,7 +268,7 @@ def combiner_cmd(ctx, discoverhost, discoverport, token, name, host, port, fqdn, """ config = {'discover_host': discoverhost, 'discover_port': discoverport, 'token': token, 'host': host, 'port': port, 'fqdn': fqdn, 'name': name, 'secure': secure, 'verify': verify, 'max_clients': max_clients, - 'init': init, 'aggregator': aggregator} + 'init': init} if config['init']: apply_config(config) diff --git a/fedn/fedn/network/api/client.py b/fedn/fedn/network/api/client.py index 85fcc6009..11851f7b0 100644 --- a/fedn/fedn/network/api/client.py +++ b/fedn/fedn/network/api/client.py @@ -119,12 +119,16 @@ def get_round(self, round_id): response = requests.get(self._get_url(f'get_round?round_id={round_id}'), verify=self.verify) return response.json() - def start_session(self, session_id=None, round_timeout=180, rounds=5, round_buffer_size=-1, delete_models=True, + def start_session(self, session_id=None, aggregator='fedavg', model_id=None, round_timeout=180, rounds=5, round_buffer_size=-1, delete_models=True, validate=True, helper='kerashelper', min_clients=1, requested_clients=8): """ Start a new session. :param session_id: The session id to start. :type session_id: str + :param aggregator: The aggregator plugin to use. + :type aggregator: str + :param model_id: The id of the initial model. + :type model_id: str :param round_timeout: The round timeout to use in seconds. :type round_timeout: int :param rounds: The number of rounds to perform. @@ -146,6 +150,8 @@ def start_session(self, session_id=None, round_timeout=180, rounds=5, round_buff """ response = requests.post(self._get_url('start_session'), json={ 'session_id': session_id, + 'aggregator': aggregator, + 'model_id': model_id, 'round_timeout': round_timeout, 'rounds': rounds, 'round_buffer_size': round_buffer_size, diff --git a/fedn/fedn/network/api/interface.py b/fedn/fedn/network/api/interface.py index 4370c0428..d37778aef 100644 --- a/fedn/fedn/network/api/interface.py +++ b/fedn/fedn/network/api/interface.py @@ -1004,6 +1004,8 @@ def list_combiners_data(self, combiners): def start_session( self, session_id, + aggregator='fedavg', + model_id=None, rounds=5, round_timeout=180, round_buffer_size=-1, @@ -1017,6 +1019,10 @@ def start_session( :param session_id: The session id to start. :type session_id: str + :param aggregator: The aggregator plugin to use. + :type aggregator: str + :param initial_model: The initial model for the session. + :type initial_model: str :param rounds: The number of rounds to perform. :type rounds: int :param round_timeout: The round timeout to use in seconds. @@ -1083,11 +1089,13 @@ def start_session( validate = False # Get lastest model as initial model for session - model_id = self.statestore.get_latest_model() + if not model_id: + model_id = self.statestore.get_latest_model() # Setup session config session_config = { "session_id": session_id if session_id else str(uuid.uuid4()), + "aggregator": aggregator, "round_timeout": round_timeout, "buffer_size": round_buffer_size, "model_id": model_id, diff --git a/fedn/fedn/network/clients/client.py b/fedn/fedn/network/clients/client.py index 3b8689bb1..7b19fd88b 100644 --- a/fedn/fedn/network/clients/client.py +++ b/fedn/fedn/network/clients/client.py @@ -27,7 +27,7 @@ from fedn.network.clients.package import PackageRuntime from fedn.network.clients.state import ClientState, ClientStateToString from fedn.utils.dispatcher import Dispatcher -from fedn.utils.helpers import get_helper +from fedn.utils.helpers.helpers import get_helper CHUNK_SIZE = 1024 * 1024 VALID_NAME_REGEX = '^[a-zA-Z0-9_-]*$' diff --git a/fedn/fedn/network/combiner/aggregators/aggregator.py b/fedn/fedn/network/combiner/aggregators/aggregator.py deleted file mode 100644 index 4b024efa7..000000000 --- a/fedn/fedn/network/combiner/aggregators/aggregator.py +++ /dev/null @@ -1,119 +0,0 @@ -import json -import queue -from abc import ABC, abstractmethod - -import fedn.network.grpc.fedn_pb2 as fedn - - -class Aggregator(ABC): - """ Abstract class defining an aggregator. """ - - @abstractmethod - def __init__(self, id, storage, server, modelservice, control): - """ Initialize the aggregator. - - :param id: A reference to id of :class: `fedn.network.combiner.Combiner` - :type id: str - :param storage: Model repository for :class: `fedn.network.combiner.Combiner` - :type storage: class: `fedn.common.storage.s3.s3repo.S3ModelRepository` - :param server: A handle to the Combiner class :class: `fedn.network.combiner.Combiner` - :type server: class: `fedn.network.combiner.Combiner` - :param modelservice: A handle to the model service :class: `fedn.network.combiner.modelservice.ModelService` - :type modelservice: class: `fedn.network.combiner.modelservice.ModelService` - :param control: A handle to the :class: `fedn.network.combiner.round.RoundController` - :type control: class: `fedn.network.combiner.round.RoundController` - """ - self.name = self.__class__.__name__ - self.storage = storage - self.id = id - self.server = server - self.modelservice = modelservice - self.control = control - self.model_updates = queue.Queue() - - @abstractmethod - def combine_models(self, nr_expected_models=None, nr_required_models=1, helper=None, timeout=180): - """Routine for combining model updates. Implemented in subclass. - - :param nr_expected_models: Number of expected models. If None, wait for all models. - :type nr_expected_models: int - :param nr_required_models: Number of required models to combine. - :type nr_required_models: int - :param helper: A helper object. - :type helper: :class: `fedn.utils.plugins.helperbase.HelperBase` - :param timeout: Timeout in seconds to wait for models to be combined. - :type timeout: int - :return: A combined model. - """ - pass - - def on_model_update(self, model_update): - """Callback when a new client model update is recieved. - Performs (optional) pre-processing and then puts the update id - on the aggregation queue. Override in subclass as needed. - - :param model_update: A ModelUpdate message. - :type model_id: str - """ - try: - self.server.report_status("AGGREGATOR({}): callback received model update {}".format(self.name, model_update.model_update_id), - log_level=fedn.Status.INFO) - - # Validate the update and metadata - valid_update = self._validate_model_update(model_update) - if valid_update: - # Push the model update to the processing queue - self.model_updates.put(model_update) - else: - self.server.report_status("AGGREGATOR({}): Invalid model update, skipping.".format(self.name)) - except Exception as e: - self.server.report_status("AGGREGATOR({}): Failed to receive candidate model! {}".format(self.name, e), - log_level=fedn.Status.WARNING) - pass - - def on_model_validation(self, model_validation): - """ Callback when a new client model validation is recieved. - Performs (optional) pre-processing and then writes the validation - to the database. Override in subclass as needed. - - :param validation: Dict containing validation data sent by client. - Must be valid JSON. - :type validation: dict - """ - - # self.report_validation(validation) - self.server.report_status("AGGREGATOR({}): callback processed validation {}".format(self.name, model_validation.model_id), - log_level=fedn.Status.INFO) - - def _validate_model_update(self, model_update): - """ Validate the model update. - - :param model_update: A ModelUpdate message. - :type model_update: object - :return: True if the model update is valid, False otherwise. - :rtype: bool - """ - # TODO: Validate the metadata to check that it contains all variables assumed by the aggregator. - data = json.loads(model_update.meta)['training_metadata'] - if 'num_examples' not in data.keys(): - self.server.report_status("AGGREGATOR({}): Model validation failed, num_examples missing in metadata.".format(self.name)) - return False - return True - - def next_model_update(self, helper): - """ Get the next model update from the queue. - - :param helper: A helper object. - :type helper: object - :return: A tuple containing the model update, metadata and model id. - :rtype: tuple - """ - model_update = self.model_updates.get(block=False) - model_id = model_update.model_update_id - model_next = self.control.load_model_update(helper, model_id) - # Get relevant metadata - data = json.loads(model_update.meta)['training_metadata'] - config = json.loads(json.loads(model_update.meta)['config']) - data['round_id'] = config['round_id'] - - return model_next, data, model_id diff --git a/fedn/fedn/network/combiner/aggregators/aggregatorbase.py b/fedn/fedn/network/combiner/aggregators/aggregatorbase.py index b74390c72..e7cd38402 100644 --- a/fedn/fedn/network/combiner/aggregators/aggregatorbase.py +++ b/fedn/fedn/network/combiner/aggregators/aggregatorbase.py @@ -19,18 +19,18 @@ class AggregatorBase(ABC): :type server: class: `fedn.network.combiner.Combiner` :param modelservice: A handle to the model service :class: `fedn.network.combiner.modelservice.ModelService` :type modelservice: class: `fedn.network.combiner.modelservice.ModelService` - :param control: A handle to the :class: `fedn.network.combiner.round.RoundController` - :type control: class: `fedn.network.combiner.round.RoundController` + :param control: A handle to the :class: `fedn.network.combiner.roundhandler.RoundHandler` + :type control: class: `fedn.network.combiner.roundhandler.RoundHandler` """ @abstractmethod - def __init__(self, storage, server, modelservice, control): + def __init__(self, storage, server, modelservice, round_handler): """ Initialize the aggregator.""" self.name = self.__class__.__name__ self.storage = storage self.server = server self.modelservice = modelservice - self.control = control + self.round_handler = round_handler self.model_updates = queue.Queue() @abstractmethod @@ -53,10 +53,12 @@ def combine_models(self, nr_expected_models=None, nr_required_models=1, helper=N def on_model_update(self, model_update): """Callback when a new client model update is recieved. - Performs (optional) pre-processing and then puts the update id - on the aggregation queue. Override in subclass as needed. - :param model_update: A ModelUpdate message. + Performs (optional) validation and pre-processing, + and then puts the update id on the aggregation queue. + Override in subclass as needed. + + :param model_update: fedn.network.grpc.fedn.proto.ModelUpdate :type model_id: str """ try: @@ -70,7 +72,7 @@ def on_model_update(self, model_update): else: logger.warning("AGGREGATOR({}): Invalid model update, skipping.".format(self.name)) except Exception as e: - logger.error("AGGREGATOR({}): Failed to receive model update! {}".format(self.name, e)) + logger.error("AGGREGATOR({}): failed to receive model update! {}".format(self.name, e)) pass def _validate_model_update(self, model_update): @@ -81,36 +83,49 @@ def _validate_model_update(self, model_update): :return: True if the model update is valid, False otherwise. :rtype: bool """ - # TODO: Validate the metadata to check that it contains all variables assumed by the aggregator. data = json.loads(model_update.meta)['training_metadata'] if 'num_examples' not in data.keys(): logger.error("AGGREGATOR({}): Model validation failed, num_examples missing in metadata.".format(self.name)) return False return True - def next_model_update(self, helper): + def next_model_update(self): """ Get the next model update from the queue. :param helper: A helper object. :type helper: object - :return: A tuple containing the model update, metadata and model id. - :rtype: tuple + :return: The model update. + :rtype: fedn.network.grpc.fedn.proto.ModelUpdate """ model_update = self.model_updates.get(block=False) + return model_update + + def load_model_update(self, model_update, helper): + """ Load the memory representation of the model update. + + Load the model update paramters and the + associate metadata into memory. + + :param model_update: The model update. + :type model_update: fedn.network.grpc.fedn.proto.ModelUpdate + :param helper: A helper object. + :type helper: fedn.utils.helpers.helperbase.Helper + :return: A tuple of (parameters, metadata) + :rtype: tuple + """ model_id = model_update.model_update_id - model_next = self.control.load_model_update(helper, model_id) + model = self.round_handler.load_model_update(helper, model_id) # Get relevant metadata data = json.loads(model_update.meta)['training_metadata'] config = json.loads(json.loads(model_update.meta)['config']) data['round_id'] = config['round_id'] - return model_next, data, model_id + return model, data def get_state(self): """ Get the state of the aggregator's queue, including the number of model updates.""" state = { 'queue_len': self.model_updates.qsize() - } return state @@ -126,8 +141,8 @@ def get_aggregator(aggregator_module_name, storage, server, modelservice, contro :type server: class: `fedn.network.combiner.Combiner` :param modelservice: A handle to the model service :class: `fedn.network.combiner.modelservice.ModelService` :type modelservice: class: `fedn.network.combiner.modelservice.ModelService` - :param control: A handle to the :class: `fedn.network.combiner.round.RoundController` - :type control: class: `fedn.network.combiner.round.RoundController` + :param control: A handle to the :class: `fedn.network.combiner.roundhandler.RoundHandler` + :type control: class: `fedn.network.combiner.roundhandler.RoundHandler` :return: An aggregator instance. :rtype: class: `fedn.combiner.aggregators.AggregatorBase` """ diff --git a/fedn/fedn/network/combiner/aggregators/fedavg.py b/fedn/fedn/network/combiner/aggregators/fedavg.py index ae479676d..e8541f326 100644 --- a/fedn/fedn/network/combiner/aggregators/fedavg.py +++ b/fedn/fedn/network/combiner/aggregators/fedavg.py @@ -14,24 +14,24 @@ class Aggregator(AggregatorBase): :type server: class: `fedn.network.combiner.Combiner` :param modelservice: A handle to the model service :class: `fedn.network.combiner.modelservice.ModelService` :type modelservice: class: `fedn.network.combiner.modelservice.ModelService` - :param control: A handle to the :class: `fedn.network.combiner.round.RoundController` - :type control: class: `fedn.network.combiner.round.RoundController` + :param control: A handle to the :class: `fedn.network.combiner.roundhandler.RoundHandler` + :type control: class: `fedn.network.combiner.roundhandler.RoundHandler` """ - def __init__(self, storage, server, modelservice, control): + def __init__(self, storage, server, modelservice, round_handler): """Constructor method""" - super().__init__(storage, server, modelservice, control) + super().__init__(storage, server, modelservice, round_handler) self.name = "fedavg" - def combine_models(self, helper=None, time_window=180, max_nr_models=100, delete_models=True): - """Aggregate model updates in the queue by computing an incremental - weighted average of parameters. + def combine_models(self, helper=None, delete_models=True): + """Aggregate all model updates in the queue by computing an incremental + weighted average of model parameters. - :param helper: An instance of :class: `fedn.utils.helpers.HelperBase`, ML framework specific helper, defaults to None - :type helper: class: `fedn.utils.helpers.HelperBase`, optional + :param helper: An instance of :class: `fedn.utils.helpers.helpers.HelperBase`, ML framework specific helper, defaults to None + :type helper: class: `fedn.utils.helpers.helpers.HelperBase`, optional :param time_window: The time window for model aggregation, defaults to 180 :type time_window: int, optional :param max_nr_models: The maximum number of updates aggregated, defaults to 100 @@ -56,9 +56,13 @@ def combine_models(self, helper=None, time_window=180, max_nr_models=100, delete while not self.model_updates.empty(): try: # Get next model from queue - model_next, metadata, model_id = self.next_model_update(helper) + model_update = self.next_model_update() + + # Load model parameters and metadata + model_next, metadata = self.load_model_update(model_update, helper) + logger.info( - "AGGREGATOR({}): Processing model update {}, metadata: {} ".format(self.name, model_id, metadata)) + "AGGREGATOR({}): Processing model update {}, metadata: {} ".format(self.name, model_update.model_update_id, metadata)) # Increment total number of examples total_examples += metadata['num_examples'] @@ -72,9 +76,9 @@ def combine_models(self, helper=None, time_window=180, max_nr_models=100, delete nr_aggregated_models += 1 # Delete model from storage if delete_models: - self.modelservice.models.delete(model_id) + self.modelservice.models.delete(model_update.model_update_id) logger.info( - "AGGREGATOR({}): Deleted model update {} from storage.".format(self.name, model_id)) + "AGGREGATOR({}): Deleted model update {} from storage.".format(self.name, model_update.model_update_id)) self.model_updates.task_done() except Exception as e: logger.error( diff --git a/fedn/fedn/network/combiner/aggregators/fedopt.py b/fedn/fedn/network/combiner/aggregators/fedopt.py new file mode 100644 index 000000000..2298190af --- /dev/null +++ b/fedn/fedn/network/combiner/aggregators/fedopt.py @@ -0,0 +1,137 @@ +import math + +from fedn.common.log_config import logger +from fedn.network.combiner.aggregators.aggregatorbase import AggregatorBase + + +class Aggregator(AggregatorBase): + """ Federated Optimization (FedOpt) aggregator. + + Implmentation following: https://arxiv.org/pdf/2003.00295.pdf + + Aggregate pseudo gradients computed by subtracting the model + update from the global model weights from the previous round. + + :param id: A reference to id of :class: `fedn.network.combiner.Combiner` + :type id: str + :param storage: Model repository for :class: `fedn.network.combiner.Combiner` + :type storage: class: `fedn.common.storage.s3.s3repo.S3ModelRepository` + :param server: A handle to the Combiner class :class: `fedn.network.combiner.Combiner` + :type server: class: `fedn.network.combiner.Combiner` + :param modelservice: A handle to the model service :class: `fedn.network.combiner.modelservice.ModelService` + :type modelservice: class: `fedn.network.combiner.modelservice.ModelService` + :param control: A handle to the :class: `fedn.network.combiner.roundhandler.RoundHandler` + :type control: class: `fedn.network.combiner.roundhandler.RoundHandler` + + """ + + def __init__(self, storage, server, modelservice, round_handler): + + super().__init__(storage, server, modelservice, round_handler) + + self.name = "fedopt" + self.v = None + self.m = None + + # Server side hyperparameters. Note that these may need extensive fine tuning. + self.eta = 0.1 + self.beta1 = 0.9 + self.beta2 = 0.99 + self.tau = 1e-4 + + def combine_models(self, helper=None, delete_models=True): + """Compute pseudo gradients usigng model updates in the queue. + + :param helper: An instance of :class: `fedn.utils.helpers.helpers.HelperBase`, ML framework specific helper, defaults to None + :type helper: class: `fedn.utils.helpers.helpers.HelperBase`, optional + :param time_window: The time window for model aggregation, defaults to 180 + :type time_window: int, optional + :param max_nr_models: The maximum number of updates aggregated, defaults to 100 + :type max_nr_models: int, optional + :param delete_models: Delete models from storage after aggregation, defaults to True + :type delete_models: bool, optional + :return: The global model and metadata + :rtype: tuple + """ + + data = {} + data['time_model_load'] = 0.0 + data['time_model_aggregation'] = 0.0 + + model = None + nr_aggregated_models = 0 + total_examples = 0 + + logger.info( + "AGGREGATOR({}): Aggregating model updates... ".format(self.name)) + + while not self.model_updates.empty(): + try: + # Get next model from queue + model_update = self.next_model_update() + + # Load model paratmeters and metadata + model_next, metadata = self.load_model_update(model_update, helper) + + logger.info( + "AGGREGATOR({}): Processing model update {}, metadata: {} ".format(self.name, model_update.model_update_id, metadata)) + print("***** ", model_update, flush=True) + + # Increment total number of examples + total_examples += metadata['num_examples'] + + if nr_aggregated_models == 0: + model_old = self.round_handler.load_model_update(helper, model_update.model_id) + pseudo_gradient = helper.subtract(model_next, model_old) + else: + pseudo_gradient_next = helper.subtract(model_next, model_old) + pseudo_gradient = helper.increment_average( + pseudo_gradient, pseudo_gradient_next, metadata['num_examples'], total_examples) + + print("NORM PSEUDOGRADIENT: ", helper.norm(pseudo_gradient), flush=True) + + nr_aggregated_models += 1 + # Delete model from storage + if delete_models: + self.modelservice.models.delete(model_update.model_update_id) + logger.info( + "AGGREGATOR({}): Deleted model update {} from storage.".format(self.name, model_update.model_update_id)) + self.model_updates.task_done() + except Exception as e: + logger.error( + "AGGREGATOR({}): Error encoutered while processing model update {}, skipping this update.".format(self.name, e)) + self.model_updates.task_done() + + model = self.serveropt_adam(helper, pseudo_gradient, model_old) + + data['nr_aggregated_models'] = nr_aggregated_models + + logger.info("AGGREGATOR({}): Aggregation completed, aggregated {} models.".format(self.name, nr_aggregated_models)) + return model, data + + def serveropt_adam(self, helper, pseudo_gradient, model_old): + """ Server side optimization, FedAdam. + + :param helper: instance of helper class. + :type helper: Helper + :param pseudo_gradient: The pseudo gradient. + :type pseudo_gradient: As defined by helper. + :return: new model weights. + :rtype: as defined by helper. + """ + + if not self.v: + self.v = helper.ones(pseudo_gradient, math.pow(self.tau, 2)) + + if not self.m: + self.m = helper.multiply(pseudo_gradient, [(1.0-self.beta1)]*len(pseudo_gradient)) + else: + self.m = helper.add(self.m, pseudo_gradient, self.beta1, (1.0-self.beta1)) + + p = helper.power(pseudo_gradient, 2) + self.v = helper.add(self.v, p, self.beta2, (1.0-self.beta2)) + sv = helper.add(helper.sqrt(self.v), helper.ones(self.v, self.tau)) + t = helper.divide(self.m, sv) + + model = helper.add(model_old, t, 1.0, self.eta) + return model diff --git a/fedn/fedn/network/combiner/combiner.py b/fedn/fedn/network/combiner/combiner.py index b977e4d9b..b4ea1bc1c 100644 --- a/fedn/fedn/network/combiner/combiner.py +++ b/fedn/fedn/network/combiner/combiner.py @@ -16,7 +16,7 @@ set_log_stream) from fedn.network.combiner.connect import ConnectorCombiner, Status from fedn.network.combiner.modelservice import ModelService -from fedn.network.combiner.round import RoundController +from fedn.network.combiner.roundhandler import RoundHandler from fedn.network.grpc.server import Server from fedn.network.storage.s3.repository import Repository from fedn.network.storage.statestore.mongostatestore import MongoStateStore @@ -134,10 +134,10 @@ def __init__(self, config): self.server = Server(self, self.modelservice, grpc_config) # Set up round controller - self.control = RoundController(config['aggregator'], self.repository, self, self.modelservice) + self.round_handler = RoundHandler(self.repository, self, self.modelservice) # Start thread for round controller - threading.Thread(target=self.control.run, daemon=True).start() + threading.Thread(target=self.round_handler.run, daemon=True).start() # Start thread for client status updates: TODO: Should be configurable threading.Thread(target=self._deamon_thread_client_status, daemon=True).start() @@ -385,7 +385,7 @@ def _flush_model_update_queue(self): :return: True if successful, else False """ - q = self.control.aggregator.model_updates + q = self.round_handler.aggregator.model_updates try: with q.mutex: q.queue.clear() @@ -418,7 +418,7 @@ def Start(self, control: fedn.ControlRequest, context): logger.debug("grpc.Combiner.Start: Round config {}".format(config)) - job_id = self.control.push_round_config(config) + job_id = self.round_handler.push_round_config(config) logger.info("grcp.Combiner.Start: Pushed round config (job_id): {}".format(job_id)) response = fedn.ControlResponse() @@ -428,6 +428,30 @@ def Start(self, control: fedn.ControlRequest, context): return response + def SetAggregator(self, control: fedn.ControlRequest, context): + """ Set the active aggregator. + + :param control: the control request + :type control: :class:`fedn.network.grpc.fedn_pb2.ControlRequest` + :param context: the context (unused) + :type context: :class:`grpc._server._Context` + :return: the control response + :rtype: :class:`fedn.network.grpc.fedn_pb2.ControlResponse` + """ + logger.debug("grpc.Combiner.SetAggregator: Called") + for parameter in control.parameter: + aggregator = parameter.value + + status = self.round_handler.set_aggregator(aggregator) + + response = fedn.ControlResponse() + if status: + response.message = 'Success' + else: + response.message = 'Failed' + + return response + def FlushAggregationQueue(self, control: fedn.ControlRequest, context): """ Flush the queue. @@ -699,7 +723,7 @@ def SendModelUpdate(self, request, context): :return: the response :rtype: :class:`fedn.network.grpc.fedn_pb2.Response` """ - self.control.aggregator.on_model_update(request) + self.round_handler.aggregator.on_model_update(request) response = fedn.Response() response.response = "RECEIVED ModelUpdate {} from client {}".format( diff --git a/fedn/fedn/network/combiner/interfaces.py b/fedn/fedn/network/combiner/interfaces.py index dcfffa8dd..1bb93168b 100644 --- a/fedn/fedn/network/combiner/interfaces.py +++ b/fedn/fedn/network/combiner/interfaces.py @@ -187,6 +187,30 @@ def flush_model_update_queue(self): else: raise + def set_aggregator(self, aggregator): + """ Set the active aggregator module. + + :param aggregator: The name of the aggregator module. + :type config: str + """ + + channel = Channel(self.address, self.port, + self.certificate).get_channel() + control = rpc.ControlStub(channel) + + request = fedn.ControlRequest() + p = request.parameter.add() + p.key = "aggregator" + p.value = aggregator + + try: + control.SetAggregator(request) + except grpc.RpcError as e: + if e.code() == grpc.StatusCode.UNAVAILABLE: + raise CombinerUnavailableError + else: + raise + def submit(self, config): """ Submit a compute plan to the combiner. diff --git a/fedn/fedn/network/combiner/round.py b/fedn/fedn/network/combiner/roundhandler.py similarity index 95% rename from fedn/fedn/network/combiner/round.py rename to fedn/fedn/network/combiner/roundhandler.py index 769220c52..aa944a058 100644 --- a/fedn/fedn/network/combiner/round.py +++ b/fedn/fedn/network/combiner/roundhandler.py @@ -6,18 +6,18 @@ from fedn.common.log_config import logger from fedn.network.combiner.aggregators.aggregatorbase import get_aggregator -from fedn.utils.helpers import get_helper +from fedn.utils.helpers.helpers import get_helper class ModelUpdateError(Exception): pass -class RoundController: - """ Round controller. +class RoundHandler: + """ Round handler. - The round controller recieves round configurations from the global controller - and coordinates model updates and aggregation, and model validations. + The round handler processes requests from the global controller + to produce model updates and perform model validations. :param aggregator_name: The name of the aggregator plugin module. :type aggregator_name: str @@ -29,14 +29,16 @@ class RoundController: :type modelservice: class: `fedn.network.combiner.modelservice.ModelService` """ - def __init__(self, aggregator_name, storage, server, modelservice): - """ Initialize the RoundController.""" + def __init__(self, storage, server, modelservice): + """ Initialize the RoundHandler.""" self.round_configs = queue.Queue() self.storage = storage self.server = server self.modelservice = modelservice - self.aggregator = get_aggregator(aggregator_name, self.storage, self.server, self.modelservice, self) + + def set_aggregator(self, aggregator): + self.aggregator = get_aggregator(aggregator, self.storage, self.server, self.modelservice, self) def push_round_config(self, round_config): """Add a round_config (job description) to the inbox. @@ -56,10 +58,10 @@ def push_round_config(self, round_config): return round_config['_job_id'] def load_model_update(self, helper, model_id): - """Load model update in its native format. + """Load model update with id model_id into its memory representation. - :param helper: An instance of :class: `fedn.utils.helpers.HelperBase`, ML framework specific helper, defaults to None - :type helper: class: `fedn.utils.helpers.HelperBase` + :param helper: An instance of :class: `fedn.utils.helpers.helpers.HelperBase` + :type helper: class: `fedn.utils.helpers.helpers.HelperBase` :param model_id: The ID of the model update, UUID in str format :type model_id: str """ @@ -141,7 +143,7 @@ def _training_round(self, config, clients): """ logger.info( - "ROUNDCONTROL: Initiating training round, participating clients: {}".format(clients)) + "ROUNDHANDLER: Initiating training round, participating clients: {}".format(clients)) meta = {} meta['nr_expected_updates'] = len(clients) diff --git a/fedn/fedn/network/controller/control.py b/fedn/fedn/network/controller/control.py index 615edb3b5..842760625 100644 --- a/fedn/fedn/network/controller/control.py +++ b/fedn/fedn/network/controller/control.py @@ -114,9 +114,8 @@ def session(self, config): last_round = int(self.get_latest_round_id()) - # Clear potential stragglers/old model updates at combiners for combiner in self.network.get_combiners(): - combiner.flush_model_update_queue() + combiner.set_aggregator(config['aggregator']) # Execute the rounds in this session for round in range(1, int(config["rounds"] + 1)): @@ -141,6 +140,8 @@ def session(self, config): flush=True, ) + config["model_id"] = self.statestore.get_latest_model() + # TODO: Report completion of session self._state = ReducerState.idle @@ -166,12 +167,10 @@ def round(self, session_config, round_id): round_config["rounds"] = 1 round_config["round_id"] = round_id round_config["task"] = "training" - round_config["model_id"] = self.statestore.get_latest_model() - round_config["helper_type"] = self.statestore.get_helper() self.set_round_config(round_id, round_config) - # Get combiners that are able to participate in round, given round_config + # Get combiners that are able to participate in the round, given round_config participating_combiners = self.get_participating_combiners(round_config) # Check if the policy to start the round is met diff --git a/fedn/fedn/network/controller/controlbase.py b/fedn/fedn/network/controller/controlbase.py index 90a86e3bd..2e17d0b97 100644 --- a/fedn/fedn/network/controller/controlbase.py +++ b/fedn/fedn/network/controller/controlbase.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from time import sleep -import fedn.utils.helpers +import fedn.utils.helpers.helpers from fedn.network.api.network import Network from fedn.network.combiner.interfaces import CombinerUnavailableError from fedn.network.state import ReducerState @@ -98,7 +98,7 @@ def get_helper(self): :rtype: :class:`fedn.utils.plugins.helperbase.HelperBase` """ helper_type = self.statestore.get_helper() - helper = fedn.utils.helpers.get_helper(helper_type) + helper = fedn.utils.helpers.helpers.get_helper(helper_type) if not helper: raise MisconfiguredHelper( "Unsupported helper type {}, please configure compute_package.helper !".format( diff --git a/fedn/fedn/network/grpc/fedn.proto b/fedn/fedn/network/grpc/fedn.proto index 1980889ca..14deee520 100644 --- a/fedn/fedn/network/grpc/fedn.proto +++ b/fedn/fedn/network/grpc/fedn.proto @@ -199,6 +199,7 @@ service Control { rpc Start(ControlRequest) returns (ControlResponse); rpc Stop(ControlRequest) returns (ControlResponse); rpc FlushAggregationQueue(ControlRequest) returns (ControlResponse); + rpc SetAggregator(ControlRequest) returns (ControlResponse); } service Reducer { diff --git a/fedn/fedn/network/grpc/fedn_pb2.py b/fedn/fedn/network/grpc/fedn_pb2.py index daca249d8..752bb20c3 100644 --- a/fedn/fedn/network/grpc/fedn_pb2.py +++ b/fedn/fedn/network/grpc/fedn_pb2.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! -# source: fedn.proto +# source: fedn/network/grpc/fedn.proto """Generated protocol buffer code.""" +from google.protobuf.internal import enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -13,80 +15,302 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nfedn.proto\x12\x04grpc\":\n\x08Response\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08response\x18\x02 \x01(\t\"\x8c\x02\n\x06Status\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x0e\n\x06status\x18\x02 \x01(\t\x12(\n\tlog_level\x18\x03 \x01(\x0e\x32\x15.grpc.Status.LogLevel\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x1e\n\x04type\x18\x07 \x01(\x0e\x32\x10.grpc.StatusType\x12\r\n\x05\x65xtra\x18\x08 \x01(\t\"B\n\x08LogLevel\x12\x08\n\x04INFO\x10\x00\x12\t\n\x05\x44\x45\x42UG\x10\x01\x12\x0b\n\x07WARNING\x10\x02\x12\t\n\x05\x45RROR\x10\x03\x12\t\n\x05\x41UDIT\x10\x04\"\xab\x01\n\x12ModelUpdateRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08model_id\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x0c\n\x04meta\x18\x07 \x01(\t\"\xaf\x01\n\x0bModelUpdate\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08model_id\x18\x03 \x01(\t\x12\x17\n\x0fmodel_update_id\x18\x04 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x0c\n\x04meta\x18\x07 \x01(\t\"\xc5\x01\n\x16ModelValidationRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08model_id\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x0c\n\x04meta\x18\x07 \x01(\t\x12\x14\n\x0cis_inference\x18\x08 \x01(\x08\"\xa8\x01\n\x0fModelValidation\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08model_id\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x0c\n\x04meta\x18\x07 \x01(\t\"\x89\x01\n\x0cModelRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12\n\n\x02id\x18\x04 \x01(\t\x12!\n\x06status\x18\x05 \x01(\x0e\x32\x11.grpc.ModelStatus\"]\n\rModelResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\n\n\x02id\x18\x02 \x01(\t\x12!\n\x06status\x18\x03 \x01(\x0e\x32\x11.grpc.ModelStatus\x12\x0f\n\x07message\x18\x04 \x01(\t\"U\n\x15GetGlobalModelRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\"h\n\x16GetGlobalModelResponse\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08model_id\x18\x03 \x01(\t\")\n\tHeartbeat\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\"W\n\x16\x43lientAvailableMessage\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\t\x12\x11\n\ttimestamp\x18\x03 \x01(\t\"R\n\x12ListClientsRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x07\x63hannel\x18\x02 \x01(\x0e\x32\r.grpc.Channel\"*\n\nClientList\x12\x1c\n\x06\x63lient\x18\x01 \x03(\x0b\x32\x0c.grpc.Client\"0\n\x06\x43lient\x12\x18\n\x04role\x18\x01 \x01(\x0e\x32\n.grpc.Role\x12\x0c\n\x04name\x18\x02 \x01(\t\"m\n\x0fReassignRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x0e\n\x06server\x18\x03 \x01(\t\x12\x0c\n\x04port\x18\x04 \x01(\r\"c\n\x10ReconnectRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x11\n\treconnect\x18\x03 \x01(\r\"\'\n\tParameter\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"T\n\x0e\x43ontrolRequest\x12\x1e\n\x07\x63ommand\x18\x01 \x01(\x0e\x32\r.grpc.Command\x12\"\n\tparameter\x18\x02 \x03(\x0b\x32\x0f.grpc.Parameter\"F\n\x0f\x43ontrolResponse\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\"\n\tparameter\x18\x02 \x03(\x0b\x32\x0f.grpc.Parameter\"\x13\n\x11\x43onnectionRequest\"<\n\x12\x43onnectionResponse\x12&\n\x06status\x18\x01 \x01(\x0e\x32\x16.grpc.ConnectionStatus*\x84\x01\n\nStatusType\x12\x07\n\x03LOG\x10\x00\x12\x18\n\x14MODEL_UPDATE_REQUEST\x10\x01\x12\x10\n\x0cMODEL_UPDATE\x10\x02\x12\x1c\n\x18MODEL_VALIDATION_REQUEST\x10\x03\x12\x14\n\x10MODEL_VALIDATION\x10\x04\x12\r\n\tINFERENCE\x10\x05*\x86\x01\n\x07\x43hannel\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x19\n\x15MODEL_UPDATE_REQUESTS\x10\x01\x12\x11\n\rMODEL_UPDATES\x10\x02\x12\x1d\n\x19MODEL_VALIDATION_REQUESTS\x10\x03\x12\x15\n\x11MODEL_VALIDATIONS\x10\x04\x12\n\n\x06STATUS\x10\x05*F\n\x0bModelStatus\x12\x06\n\x02OK\x10\x00\x12\x0f\n\x0bIN_PROGRESS\x10\x01\x12\x12\n\x0eIN_PROGRESS_OK\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03*8\n\x04Role\x12\n\n\x06WORKER\x10\x00\x12\x0c\n\x08\x43OMBINER\x10\x01\x12\x0b\n\x07REDUCER\x10\x02\x12\t\n\x05OTHER\x10\x03*J\n\x07\x43ommand\x12\x08\n\x04IDLE\x10\x00\x12\t\n\x05START\x10\x01\x12\t\n\x05PAUSE\x10\x02\x12\x08\n\x04STOP\x10\x03\x12\t\n\x05RESET\x10\x04\x12\n\n\x06REPORT\x10\x05*I\n\x10\x43onnectionStatus\x12\x11\n\rNOT_ACCEPTING\x10\x00\x12\r\n\tACCEPTING\x10\x01\x12\x13\n\x0fTRY_AGAIN_LATER\x10\x02\x32z\n\x0cModelService\x12\x33\n\x06Upload\x12\x12.grpc.ModelRequest\x1a\x13.grpc.ModelResponse(\x01\x12\x35\n\x08\x44ownload\x12\x12.grpc.ModelRequest\x1a\x13.grpc.ModelResponse0\x01\x32\xba\x01\n\x07\x43ontrol\x12\x34\n\x05Start\x12\x14.grpc.ControlRequest\x1a\x15.grpc.ControlResponse\x12\x33\n\x04Stop\x12\x14.grpc.ControlRequest\x1a\x15.grpc.ControlResponse\x12\x44\n\x15\x46lushAggregationQueue\x12\x14.grpc.ControlRequest\x1a\x15.grpc.ControlResponse2V\n\x07Reducer\x12K\n\x0eGetGlobalModel\x12\x1b.grpc.GetGlobalModelRequest\x1a\x1c.grpc.GetGlobalModelResponse2\xab\x03\n\tConnector\x12\x44\n\x14\x41llianceStatusStream\x12\x1c.grpc.ClientAvailableMessage\x1a\x0c.grpc.Status0\x01\x12*\n\nSendStatus\x12\x0c.grpc.Status\x1a\x0e.grpc.Response\x12?\n\x11ListActiveClients\x12\x18.grpc.ListClientsRequest\x1a\x10.grpc.ClientList\x12\x45\n\x10\x41\x63\x63\x65ptingClients\x12\x17.grpc.ConnectionRequest\x1a\x18.grpc.ConnectionResponse\x12\x30\n\rSendHeartbeat\x12\x0f.grpc.Heartbeat\x1a\x0e.grpc.Response\x12\x37\n\x0eReassignClient\x12\x15.grpc.ReassignRequest\x1a\x0e.grpc.Response\x12\x39\n\x0fReconnectClient\x12\x16.grpc.ReconnectRequest\x1a\x0e.grpc.Response2\xca\x03\n\x08\x43ombiner\x12T\n\x18ModelUpdateRequestStream\x12\x1c.grpc.ClientAvailableMessage\x1a\x18.grpc.ModelUpdateRequest0\x01\x12\x46\n\x11ModelUpdateStream\x12\x1c.grpc.ClientAvailableMessage\x1a\x11.grpc.ModelUpdate0\x01\x12\\\n\x1cModelValidationRequestStream\x12\x1c.grpc.ClientAvailableMessage\x1a\x1c.grpc.ModelValidationRequest0\x01\x12N\n\x15ModelValidationStream\x12\x1c.grpc.ClientAvailableMessage\x1a\x15.grpc.ModelValidation0\x01\x12\x34\n\x0fSendModelUpdate\x12\x11.grpc.ModelUpdate\x1a\x0e.grpc.Response\x12<\n\x13SendModelValidation\x12\x15.grpc.ModelValidation\x1a\x0e.grpc.Responseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x66\x65\x64n/network/grpc/fedn.proto\x12\x04grpc\":\n\x08Response\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08response\x18\x02 \x01(\t\"\x8c\x02\n\x06Status\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x0e\n\x06status\x18\x02 \x01(\t\x12(\n\tlog_level\x18\x03 \x01(\x0e\x32\x15.grpc.Status.LogLevel\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x1e\n\x04type\x18\x07 \x01(\x0e\x32\x10.grpc.StatusType\x12\r\n\x05\x65xtra\x18\x08 \x01(\t\"B\n\x08LogLevel\x12\x08\n\x04INFO\x10\x00\x12\t\n\x05\x44\x45\x42UG\x10\x01\x12\x0b\n\x07WARNING\x10\x02\x12\t\n\x05\x45RROR\x10\x03\x12\t\n\x05\x41UDIT\x10\x04\"\xab\x01\n\x12ModelUpdateRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08model_id\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x0c\n\x04meta\x18\x07 \x01(\t\"\xaf\x01\n\x0bModelUpdate\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08model_id\x18\x03 \x01(\t\x12\x17\n\x0fmodel_update_id\x18\x04 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x0c\n\x04meta\x18\x07 \x01(\t\"\xc5\x01\n\x16ModelValidationRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08model_id\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x0c\n\x04meta\x18\x07 \x01(\t\x12\x14\n\x0cis_inference\x18\x08 \x01(\x08\"\xa8\x01\n\x0fModelValidation\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08model_id\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x0c\n\x04meta\x18\x07 \x01(\t\"\x89\x01\n\x0cModelRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12\n\n\x02id\x18\x04 \x01(\t\x12!\n\x06status\x18\x05 \x01(\x0e\x32\x11.grpc.ModelStatus\"]\n\rModelResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\n\n\x02id\x18\x02 \x01(\t\x12!\n\x06status\x18\x03 \x01(\x0e\x32\x11.grpc.ModelStatus\x12\x0f\n\x07message\x18\x04 \x01(\t\"U\n\x15GetGlobalModelRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\"h\n\x16GetGlobalModelResponse\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x10\n\x08model_id\x18\x03 \x01(\t\")\n\tHeartbeat\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\"W\n\x16\x43lientAvailableMessage\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\t\x12\x11\n\ttimestamp\x18\x03 \x01(\t\"R\n\x12ListClientsRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x07\x63hannel\x18\x02 \x01(\x0e\x32\r.grpc.Channel\"*\n\nClientList\x12\x1c\n\x06\x63lient\x18\x01 \x03(\x0b\x32\x0c.grpc.Client\"0\n\x06\x43lient\x12\x18\n\x04role\x18\x01 \x01(\x0e\x32\n.grpc.Role\x12\x0c\n\x04name\x18\x02 \x01(\t\"m\n\x0fReassignRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x0e\n\x06server\x18\x03 \x01(\t\x12\x0c\n\x04port\x18\x04 \x01(\r\"c\n\x10ReconnectRequest\x12\x1c\n\x06sender\x18\x01 \x01(\x0b\x32\x0c.grpc.Client\x12\x1e\n\x08receiver\x18\x02 \x01(\x0b\x32\x0c.grpc.Client\x12\x11\n\treconnect\x18\x03 \x01(\r\"\'\n\tParameter\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"T\n\x0e\x43ontrolRequest\x12\x1e\n\x07\x63ommand\x18\x01 \x01(\x0e\x32\r.grpc.Command\x12\"\n\tparameter\x18\x02 \x03(\x0b\x32\x0f.grpc.Parameter\"F\n\x0f\x43ontrolResponse\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\"\n\tparameter\x18\x02 \x03(\x0b\x32\x0f.grpc.Parameter\"\x13\n\x11\x43onnectionRequest\"<\n\x12\x43onnectionResponse\x12&\n\x06status\x18\x01 \x01(\x0e\x32\x16.grpc.ConnectionStatus*\x84\x01\n\nStatusType\x12\x07\n\x03LOG\x10\x00\x12\x18\n\x14MODEL_UPDATE_REQUEST\x10\x01\x12\x10\n\x0cMODEL_UPDATE\x10\x02\x12\x1c\n\x18MODEL_VALIDATION_REQUEST\x10\x03\x12\x14\n\x10MODEL_VALIDATION\x10\x04\x12\r\n\tINFERENCE\x10\x05*\x86\x01\n\x07\x43hannel\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x19\n\x15MODEL_UPDATE_REQUESTS\x10\x01\x12\x11\n\rMODEL_UPDATES\x10\x02\x12\x1d\n\x19MODEL_VALIDATION_REQUESTS\x10\x03\x12\x15\n\x11MODEL_VALIDATIONS\x10\x04\x12\n\n\x06STATUS\x10\x05*F\n\x0bModelStatus\x12\x06\n\x02OK\x10\x00\x12\x0f\n\x0bIN_PROGRESS\x10\x01\x12\x12\n\x0eIN_PROGRESS_OK\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03*8\n\x04Role\x12\n\n\x06WORKER\x10\x00\x12\x0c\n\x08\x43OMBINER\x10\x01\x12\x0b\n\x07REDUCER\x10\x02\x12\t\n\x05OTHER\x10\x03*J\n\x07\x43ommand\x12\x08\n\x04IDLE\x10\x00\x12\t\n\x05START\x10\x01\x12\t\n\x05PAUSE\x10\x02\x12\x08\n\x04STOP\x10\x03\x12\t\n\x05RESET\x10\x04\x12\n\n\x06REPORT\x10\x05*I\n\x10\x43onnectionStatus\x12\x11\n\rNOT_ACCEPTING\x10\x00\x12\r\n\tACCEPTING\x10\x01\x12\x13\n\x0fTRY_AGAIN_LATER\x10\x02\x32z\n\x0cModelService\x12\x33\n\x06Upload\x12\x12.grpc.ModelRequest\x1a\x13.grpc.ModelResponse(\x01\x12\x35\n\x08\x44ownload\x12\x12.grpc.ModelRequest\x1a\x13.grpc.ModelResponse0\x01\x32\xf8\x01\n\x07\x43ontrol\x12\x34\n\x05Start\x12\x14.grpc.ControlRequest\x1a\x15.grpc.ControlResponse\x12\x33\n\x04Stop\x12\x14.grpc.ControlRequest\x1a\x15.grpc.ControlResponse\x12\x44\n\x15\x46lushAggregationQueue\x12\x14.grpc.ControlRequest\x1a\x15.grpc.ControlResponse\x12<\n\rSetAggregator\x12\x14.grpc.ControlRequest\x1a\x15.grpc.ControlResponse2V\n\x07Reducer\x12K\n\x0eGetGlobalModel\x12\x1b.grpc.GetGlobalModelRequest\x1a\x1c.grpc.GetGlobalModelResponse2\xab\x03\n\tConnector\x12\x44\n\x14\x41llianceStatusStream\x12\x1c.grpc.ClientAvailableMessage\x1a\x0c.grpc.Status0\x01\x12*\n\nSendStatus\x12\x0c.grpc.Status\x1a\x0e.grpc.Response\x12?\n\x11ListActiveClients\x12\x18.grpc.ListClientsRequest\x1a\x10.grpc.ClientList\x12\x45\n\x10\x41\x63\x63\x65ptingClients\x12\x17.grpc.ConnectionRequest\x1a\x18.grpc.ConnectionResponse\x12\x30\n\rSendHeartbeat\x12\x0f.grpc.Heartbeat\x1a\x0e.grpc.Response\x12\x37\n\x0eReassignClient\x12\x15.grpc.ReassignRequest\x1a\x0e.grpc.Response\x12\x39\n\x0fReconnectClient\x12\x16.grpc.ReconnectRequest\x1a\x0e.grpc.Response2\xca\x03\n\x08\x43ombiner\x12T\n\x18ModelUpdateRequestStream\x12\x1c.grpc.ClientAvailableMessage\x1a\x18.grpc.ModelUpdateRequest0\x01\x12\x46\n\x11ModelUpdateStream\x12\x1c.grpc.ClientAvailableMessage\x1a\x11.grpc.ModelUpdate0\x01\x12\\\n\x1cModelValidationRequestStream\x12\x1c.grpc.ClientAvailableMessage\x1a\x1c.grpc.ModelValidationRequest0\x01\x12N\n\x15ModelValidationStream\x12\x1c.grpc.ClientAvailableMessage\x1a\x15.grpc.ModelValidation0\x01\x12\x34\n\x0fSendModelUpdate\x12\x11.grpc.ModelUpdate\x1a\x0e.grpc.Response\x12<\n\x13SendModelValidation\x12\x15.grpc.ModelValidation\x1a\x0e.grpc.Responseb\x06proto3') -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'fedn_pb2', _globals) +_STATUSTYPE = DESCRIPTOR.enum_types_by_name['StatusType'] +StatusType = enum_type_wrapper.EnumTypeWrapper(_STATUSTYPE) +_CHANNEL = DESCRIPTOR.enum_types_by_name['Channel'] +Channel = enum_type_wrapper.EnumTypeWrapper(_CHANNEL) +_MODELSTATUS = DESCRIPTOR.enum_types_by_name['ModelStatus'] +ModelStatus = enum_type_wrapper.EnumTypeWrapper(_MODELSTATUS) +_ROLE = DESCRIPTOR.enum_types_by_name['Role'] +Role = enum_type_wrapper.EnumTypeWrapper(_ROLE) +_COMMAND = DESCRIPTOR.enum_types_by_name['Command'] +Command = enum_type_wrapper.EnumTypeWrapper(_COMMAND) +_CONNECTIONSTATUS = DESCRIPTOR.enum_types_by_name['ConnectionStatus'] +ConnectionStatus = enum_type_wrapper.EnumTypeWrapper(_CONNECTIONSTATUS) +LOG = 0 +MODEL_UPDATE_REQUEST = 1 +MODEL_UPDATE = 2 +MODEL_VALIDATION_REQUEST = 3 +MODEL_VALIDATION = 4 +INFERENCE = 5 +DEFAULT = 0 +MODEL_UPDATE_REQUESTS = 1 +MODEL_UPDATES = 2 +MODEL_VALIDATION_REQUESTS = 3 +MODEL_VALIDATIONS = 4 +STATUS = 5 +OK = 0 +IN_PROGRESS = 1 +IN_PROGRESS_OK = 2 +FAILED = 3 +WORKER = 0 +COMBINER = 1 +REDUCER = 2 +OTHER = 3 +IDLE = 0 +START = 1 +PAUSE = 2 +STOP = 3 +RESET = 4 +REPORT = 5 +NOT_ACCEPTING = 0 +ACCEPTING = 1 +TRY_AGAIN_LATER = 2 + + +_RESPONSE = DESCRIPTOR.message_types_by_name['Response'] +_STATUS = DESCRIPTOR.message_types_by_name['Status'] +_MODELUPDATEREQUEST = DESCRIPTOR.message_types_by_name['ModelUpdateRequest'] +_MODELUPDATE = DESCRIPTOR.message_types_by_name['ModelUpdate'] +_MODELVALIDATIONREQUEST = DESCRIPTOR.message_types_by_name['ModelValidationRequest'] +_MODELVALIDATION = DESCRIPTOR.message_types_by_name['ModelValidation'] +_MODELREQUEST = DESCRIPTOR.message_types_by_name['ModelRequest'] +_MODELRESPONSE = DESCRIPTOR.message_types_by_name['ModelResponse'] +_GETGLOBALMODELREQUEST = DESCRIPTOR.message_types_by_name['GetGlobalModelRequest'] +_GETGLOBALMODELRESPONSE = DESCRIPTOR.message_types_by_name['GetGlobalModelResponse'] +_HEARTBEAT = DESCRIPTOR.message_types_by_name['Heartbeat'] +_CLIENTAVAILABLEMESSAGE = DESCRIPTOR.message_types_by_name['ClientAvailableMessage'] +_LISTCLIENTSREQUEST = DESCRIPTOR.message_types_by_name['ListClientsRequest'] +_CLIENTLIST = DESCRIPTOR.message_types_by_name['ClientList'] +_CLIENT = DESCRIPTOR.message_types_by_name['Client'] +_REASSIGNREQUEST = DESCRIPTOR.message_types_by_name['ReassignRequest'] +_RECONNECTREQUEST = DESCRIPTOR.message_types_by_name['ReconnectRequest'] +_PARAMETER = DESCRIPTOR.message_types_by_name['Parameter'] +_CONTROLREQUEST = DESCRIPTOR.message_types_by_name['ControlRequest'] +_CONTROLRESPONSE = DESCRIPTOR.message_types_by_name['ControlResponse'] +_CONNECTIONREQUEST = DESCRIPTOR.message_types_by_name['ConnectionRequest'] +_CONNECTIONRESPONSE = DESCRIPTOR.message_types_by_name['ConnectionResponse'] +_STATUS_LOGLEVEL = _STATUS.enum_types_by_name['LogLevel'] +Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), { + 'DESCRIPTOR' : _RESPONSE, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.Response) + }) +_sym_db.RegisterMessage(Response) + +Status = _reflection.GeneratedProtocolMessageType('Status', (_message.Message,), { + 'DESCRIPTOR' : _STATUS, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.Status) + }) +_sym_db.RegisterMessage(Status) + +ModelUpdateRequest = _reflection.GeneratedProtocolMessageType('ModelUpdateRequest', (_message.Message,), { + 'DESCRIPTOR' : _MODELUPDATEREQUEST, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ModelUpdateRequest) + }) +_sym_db.RegisterMessage(ModelUpdateRequest) + +ModelUpdate = _reflection.GeneratedProtocolMessageType('ModelUpdate', (_message.Message,), { + 'DESCRIPTOR' : _MODELUPDATE, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ModelUpdate) + }) +_sym_db.RegisterMessage(ModelUpdate) + +ModelValidationRequest = _reflection.GeneratedProtocolMessageType('ModelValidationRequest', (_message.Message,), { + 'DESCRIPTOR' : _MODELVALIDATIONREQUEST, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ModelValidationRequest) + }) +_sym_db.RegisterMessage(ModelValidationRequest) + +ModelValidation = _reflection.GeneratedProtocolMessageType('ModelValidation', (_message.Message,), { + 'DESCRIPTOR' : _MODELVALIDATION, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ModelValidation) + }) +_sym_db.RegisterMessage(ModelValidation) + +ModelRequest = _reflection.GeneratedProtocolMessageType('ModelRequest', (_message.Message,), { + 'DESCRIPTOR' : _MODELREQUEST, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ModelRequest) + }) +_sym_db.RegisterMessage(ModelRequest) + +ModelResponse = _reflection.GeneratedProtocolMessageType('ModelResponse', (_message.Message,), { + 'DESCRIPTOR' : _MODELRESPONSE, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ModelResponse) + }) +_sym_db.RegisterMessage(ModelResponse) + +GetGlobalModelRequest = _reflection.GeneratedProtocolMessageType('GetGlobalModelRequest', (_message.Message,), { + 'DESCRIPTOR' : _GETGLOBALMODELREQUEST, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.GetGlobalModelRequest) + }) +_sym_db.RegisterMessage(GetGlobalModelRequest) + +GetGlobalModelResponse = _reflection.GeneratedProtocolMessageType('GetGlobalModelResponse', (_message.Message,), { + 'DESCRIPTOR' : _GETGLOBALMODELRESPONSE, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.GetGlobalModelResponse) + }) +_sym_db.RegisterMessage(GetGlobalModelResponse) + +Heartbeat = _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), { + 'DESCRIPTOR' : _HEARTBEAT, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.Heartbeat) + }) +_sym_db.RegisterMessage(Heartbeat) + +ClientAvailableMessage = _reflection.GeneratedProtocolMessageType('ClientAvailableMessage', (_message.Message,), { + 'DESCRIPTOR' : _CLIENTAVAILABLEMESSAGE, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ClientAvailableMessage) + }) +_sym_db.RegisterMessage(ClientAvailableMessage) + +ListClientsRequest = _reflection.GeneratedProtocolMessageType('ListClientsRequest', (_message.Message,), { + 'DESCRIPTOR' : _LISTCLIENTSREQUEST, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ListClientsRequest) + }) +_sym_db.RegisterMessage(ListClientsRequest) + +ClientList = _reflection.GeneratedProtocolMessageType('ClientList', (_message.Message,), { + 'DESCRIPTOR' : _CLIENTLIST, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ClientList) + }) +_sym_db.RegisterMessage(ClientList) + +Client = _reflection.GeneratedProtocolMessageType('Client', (_message.Message,), { + 'DESCRIPTOR' : _CLIENT, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.Client) + }) +_sym_db.RegisterMessage(Client) + +ReassignRequest = _reflection.GeneratedProtocolMessageType('ReassignRequest', (_message.Message,), { + 'DESCRIPTOR' : _REASSIGNREQUEST, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ReassignRequest) + }) +_sym_db.RegisterMessage(ReassignRequest) + +ReconnectRequest = _reflection.GeneratedProtocolMessageType('ReconnectRequest', (_message.Message,), { + 'DESCRIPTOR' : _RECONNECTREQUEST, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ReconnectRequest) + }) +_sym_db.RegisterMessage(ReconnectRequest) + +Parameter = _reflection.GeneratedProtocolMessageType('Parameter', (_message.Message,), { + 'DESCRIPTOR' : _PARAMETER, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.Parameter) + }) +_sym_db.RegisterMessage(Parameter) + +ControlRequest = _reflection.GeneratedProtocolMessageType('ControlRequest', (_message.Message,), { + 'DESCRIPTOR' : _CONTROLREQUEST, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ControlRequest) + }) +_sym_db.RegisterMessage(ControlRequest) + +ControlResponse = _reflection.GeneratedProtocolMessageType('ControlResponse', (_message.Message,), { + 'DESCRIPTOR' : _CONTROLRESPONSE, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ControlResponse) + }) +_sym_db.RegisterMessage(ControlResponse) + +ConnectionRequest = _reflection.GeneratedProtocolMessageType('ConnectionRequest', (_message.Message,), { + 'DESCRIPTOR' : _CONNECTIONREQUEST, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ConnectionRequest) + }) +_sym_db.RegisterMessage(ConnectionRequest) + +ConnectionResponse = _reflection.GeneratedProtocolMessageType('ConnectionResponse', (_message.Message,), { + 'DESCRIPTOR' : _CONNECTIONRESPONSE, + '__module__' : 'fedn.network.grpc.fedn_pb2' + # @@protoc_insertion_point(class_scope:grpc.ConnectionResponse) + }) +_sym_db.RegisterMessage(ConnectionResponse) + +_MODELSERVICE = DESCRIPTOR.services_by_name['ModelService'] +_CONTROL = DESCRIPTOR.services_by_name['Control'] +_REDUCER = DESCRIPTOR.services_by_name['Reducer'] +_CONNECTOR = DESCRIPTOR.services_by_name['Connector'] +_COMBINER = DESCRIPTOR.services_by_name['Combiner'] if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_STATUSTYPE']._serialized_start=2307 - _globals['_STATUSTYPE']._serialized_end=2439 - _globals['_CHANNEL']._serialized_start=2442 - _globals['_CHANNEL']._serialized_end=2576 - _globals['_MODELSTATUS']._serialized_start=2578 - _globals['_MODELSTATUS']._serialized_end=2648 - _globals['_ROLE']._serialized_start=2650 - _globals['_ROLE']._serialized_end=2706 - _globals['_COMMAND']._serialized_start=2708 - _globals['_COMMAND']._serialized_end=2782 - _globals['_CONNECTIONSTATUS']._serialized_start=2784 - _globals['_CONNECTIONSTATUS']._serialized_end=2857 - _globals['_RESPONSE']._serialized_start=20 - _globals['_RESPONSE']._serialized_end=78 - _globals['_STATUS']._serialized_start=81 - _globals['_STATUS']._serialized_end=349 - _globals['_STATUS_LOGLEVEL']._serialized_start=283 - _globals['_STATUS_LOGLEVEL']._serialized_end=349 - _globals['_MODELUPDATEREQUEST']._serialized_start=352 - _globals['_MODELUPDATEREQUEST']._serialized_end=523 - _globals['_MODELUPDATE']._serialized_start=526 - _globals['_MODELUPDATE']._serialized_end=701 - _globals['_MODELVALIDATIONREQUEST']._serialized_start=704 - _globals['_MODELVALIDATIONREQUEST']._serialized_end=901 - _globals['_MODELVALIDATION']._serialized_start=904 - _globals['_MODELVALIDATION']._serialized_end=1072 - _globals['_MODELREQUEST']._serialized_start=1075 - _globals['_MODELREQUEST']._serialized_end=1212 - _globals['_MODELRESPONSE']._serialized_start=1214 - _globals['_MODELRESPONSE']._serialized_end=1307 - _globals['_GETGLOBALMODELREQUEST']._serialized_start=1309 - _globals['_GETGLOBALMODELREQUEST']._serialized_end=1394 - _globals['_GETGLOBALMODELRESPONSE']._serialized_start=1396 - _globals['_GETGLOBALMODELRESPONSE']._serialized_end=1500 - _globals['_HEARTBEAT']._serialized_start=1502 - _globals['_HEARTBEAT']._serialized_end=1543 - _globals['_CLIENTAVAILABLEMESSAGE']._serialized_start=1545 - _globals['_CLIENTAVAILABLEMESSAGE']._serialized_end=1632 - _globals['_LISTCLIENTSREQUEST']._serialized_start=1634 - _globals['_LISTCLIENTSREQUEST']._serialized_end=1716 - _globals['_CLIENTLIST']._serialized_start=1718 - _globals['_CLIENTLIST']._serialized_end=1760 - _globals['_CLIENT']._serialized_start=1762 - _globals['_CLIENT']._serialized_end=1810 - _globals['_REASSIGNREQUEST']._serialized_start=1812 - _globals['_REASSIGNREQUEST']._serialized_end=1921 - _globals['_RECONNECTREQUEST']._serialized_start=1923 - _globals['_RECONNECTREQUEST']._serialized_end=2022 - _globals['_PARAMETER']._serialized_start=2024 - _globals['_PARAMETER']._serialized_end=2063 - _globals['_CONTROLREQUEST']._serialized_start=2065 - _globals['_CONTROLREQUEST']._serialized_end=2149 - _globals['_CONTROLRESPONSE']._serialized_start=2151 - _globals['_CONTROLRESPONSE']._serialized_end=2221 - _globals['_CONNECTIONREQUEST']._serialized_start=2223 - _globals['_CONNECTIONREQUEST']._serialized_end=2242 - _globals['_CONNECTIONRESPONSE']._serialized_start=2244 - _globals['_CONNECTIONRESPONSE']._serialized_end=2304 - _globals['_MODELSERVICE']._serialized_start=2859 - _globals['_MODELSERVICE']._serialized_end=2981 - _globals['_CONTROL']._serialized_start=2984 - _globals['_CONTROL']._serialized_end=3170 - _globals['_REDUCER']._serialized_start=3172 - _globals['_REDUCER']._serialized_end=3258 - _globals['_CONNECTOR']._serialized_start=3261 - _globals['_CONNECTOR']._serialized_end=3688 - _globals['_COMBINER']._serialized_start=3691 - _globals['_COMBINER']._serialized_end=4149 + _STATUSTYPE._serialized_start=2325 + _STATUSTYPE._serialized_end=2457 + _CHANNEL._serialized_start=2460 + _CHANNEL._serialized_end=2594 + _MODELSTATUS._serialized_start=2596 + _MODELSTATUS._serialized_end=2666 + _ROLE._serialized_start=2668 + _ROLE._serialized_end=2724 + _COMMAND._serialized_start=2726 + _COMMAND._serialized_end=2800 + _CONNECTIONSTATUS._serialized_start=2802 + _CONNECTIONSTATUS._serialized_end=2875 + _RESPONSE._serialized_start=38 + _RESPONSE._serialized_end=96 + _STATUS._serialized_start=99 + _STATUS._serialized_end=367 + _STATUS_LOGLEVEL._serialized_start=301 + _STATUS_LOGLEVEL._serialized_end=367 + _MODELUPDATEREQUEST._serialized_start=370 + _MODELUPDATEREQUEST._serialized_end=541 + _MODELUPDATE._serialized_start=544 + _MODELUPDATE._serialized_end=719 + _MODELVALIDATIONREQUEST._serialized_start=722 + _MODELVALIDATIONREQUEST._serialized_end=919 + _MODELVALIDATION._serialized_start=922 + _MODELVALIDATION._serialized_end=1090 + _MODELREQUEST._serialized_start=1093 + _MODELREQUEST._serialized_end=1230 + _MODELRESPONSE._serialized_start=1232 + _MODELRESPONSE._serialized_end=1325 + _GETGLOBALMODELREQUEST._serialized_start=1327 + _GETGLOBALMODELREQUEST._serialized_end=1412 + _GETGLOBALMODELRESPONSE._serialized_start=1414 + _GETGLOBALMODELRESPONSE._serialized_end=1518 + _HEARTBEAT._serialized_start=1520 + _HEARTBEAT._serialized_end=1561 + _CLIENTAVAILABLEMESSAGE._serialized_start=1563 + _CLIENTAVAILABLEMESSAGE._serialized_end=1650 + _LISTCLIENTSREQUEST._serialized_start=1652 + _LISTCLIENTSREQUEST._serialized_end=1734 + _CLIENTLIST._serialized_start=1736 + _CLIENTLIST._serialized_end=1778 + _CLIENT._serialized_start=1780 + _CLIENT._serialized_end=1828 + _REASSIGNREQUEST._serialized_start=1830 + _REASSIGNREQUEST._serialized_end=1939 + _RECONNECTREQUEST._serialized_start=1941 + _RECONNECTREQUEST._serialized_end=2040 + _PARAMETER._serialized_start=2042 + _PARAMETER._serialized_end=2081 + _CONTROLREQUEST._serialized_start=2083 + _CONTROLREQUEST._serialized_end=2167 + _CONTROLRESPONSE._serialized_start=2169 + _CONTROLRESPONSE._serialized_end=2239 + _CONNECTIONREQUEST._serialized_start=2241 + _CONNECTIONREQUEST._serialized_end=2260 + _CONNECTIONRESPONSE._serialized_start=2262 + _CONNECTIONRESPONSE._serialized_end=2322 + _MODELSERVICE._serialized_start=2877 + _MODELSERVICE._serialized_end=2999 + _CONTROL._serialized_start=3002 + _CONTROL._serialized_end=3250 + _REDUCER._serialized_start=3252 + _REDUCER._serialized_end=3338 + _CONNECTOR._serialized_start=3341 + _CONNECTOR._serialized_end=3768 + _COMBINER._serialized_start=3771 + _COMBINER._serialized_end=4229 # @@protoc_insertion_point(module_scope) diff --git a/fedn/fedn/network/grpc/fedn_pb2_grpc.py b/fedn/fedn/network/grpc/fedn_pb2_grpc.py index 6484fea30..1a03fe970 100644 --- a/fedn/fedn/network/grpc/fedn_pb2_grpc.py +++ b/fedn/fedn/network/grpc/fedn_pb2_grpc.py @@ -2,7 +2,7 @@ """Client and server classes corresponding to protobuf-defined services.""" import grpc -from . import fedn_pb2 as fedn__pb2 +from fedn.network.grpc import fedn_pb2 as fedn_dot_network_dot_grpc_dot_fedn__pb2 class ModelServiceStub(object): @@ -15,15 +15,15 @@ def __init__(self, channel): channel: A grpc.Channel. """ self.Upload = channel.stream_unary( - '/grpc.ModelService/Upload', - request_serializer=fedn__pb2.ModelRequest.SerializeToString, - response_deserializer=fedn__pb2.ModelResponse.FromString, - ) + '/grpc.ModelService/Upload', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelResponse.FromString, + ) self.Download = channel.unary_stream( - '/grpc.ModelService/Download', - request_serializer=fedn__pb2.ModelRequest.SerializeToString, - response_deserializer=fedn__pb2.ModelResponse.FromString, - ) + '/grpc.ModelService/Download', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelResponse.FromString, + ) class ModelServiceServicer(object): @@ -44,60 +44,59 @@ def Download(self, request, context): def add_ModelServiceServicer_to_server(servicer, server): rpc_method_handlers = { - 'Upload': grpc.stream_unary_rpc_method_handler( - servicer.Upload, - request_deserializer=fedn__pb2.ModelRequest.FromString, - response_serializer=fedn__pb2.ModelResponse.SerializeToString, - ), - 'Download': grpc.unary_stream_rpc_method_handler( - servicer.Download, - request_deserializer=fedn__pb2.ModelRequest.FromString, - response_serializer=fedn__pb2.ModelResponse.SerializeToString, - ), + 'Upload': grpc.stream_unary_rpc_method_handler( + servicer.Upload, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelResponse.SerializeToString, + ), + 'Download': grpc.unary_stream_rpc_method_handler( + servicer.Download, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( - 'grpc.ModelService', rpc_method_handlers) + 'grpc.ModelService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) - # This class is part of an EXPERIMENTAL API. - + # This class is part of an EXPERIMENTAL API. class ModelService(object): """Missing associated documentation comment in .proto file.""" @staticmethod def Upload(request_iterator, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.stream_unary(request_iterator, target, '/grpc.ModelService/Upload', - fedn__pb2.ModelRequest.SerializeToString, - fedn__pb2.ModelResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def Download(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_stream(request, target, '/grpc.ModelService/Download', - fedn__pb2.ModelRequest.SerializeToString, - fedn__pb2.ModelResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class ControlStub(object): @@ -110,20 +109,25 @@ def __init__(self, channel): channel: A grpc.Channel. """ self.Start = channel.unary_unary( - '/grpc.Control/Start', - request_serializer=fedn__pb2.ControlRequest.SerializeToString, - response_deserializer=fedn__pb2.ControlResponse.FromString, - ) + '/grpc.Control/Start', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.FromString, + ) self.Stop = channel.unary_unary( - '/grpc.Control/Stop', - request_serializer=fedn__pb2.ControlRequest.SerializeToString, - response_deserializer=fedn__pb2.ControlResponse.FromString, - ) + '/grpc.Control/Stop', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.FromString, + ) self.FlushAggregationQueue = channel.unary_unary( - '/grpc.Control/FlushAggregationQueue', - request_serializer=fedn__pb2.ControlRequest.SerializeToString, - response_deserializer=fedn__pb2.ControlResponse.FromString, - ) + '/grpc.Control/FlushAggregationQueue', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.FromString, + ) + self.SetAggregator = channel.unary_unary( + '/grpc.Control/SetAggregator', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.FromString, + ) class ControlServicer(object): @@ -147,85 +151,112 @@ def FlushAggregationQueue(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def SetAggregator(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_ControlServicer_to_server(servicer, server): rpc_method_handlers = { - 'Start': grpc.unary_unary_rpc_method_handler( - servicer.Start, - request_deserializer=fedn__pb2.ControlRequest.FromString, - response_serializer=fedn__pb2.ControlResponse.SerializeToString, - ), - 'Stop': grpc.unary_unary_rpc_method_handler( - servicer.Stop, - request_deserializer=fedn__pb2.ControlRequest.FromString, - response_serializer=fedn__pb2.ControlResponse.SerializeToString, - ), - 'FlushAggregationQueue': grpc.unary_unary_rpc_method_handler( - servicer.FlushAggregationQueue, - request_deserializer=fedn__pb2.ControlRequest.FromString, - response_serializer=fedn__pb2.ControlResponse.SerializeToString, - ), + 'Start': grpc.unary_unary_rpc_method_handler( + servicer.Start, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.SerializeToString, + ), + 'Stop': grpc.unary_unary_rpc_method_handler( + servicer.Stop, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.SerializeToString, + ), + 'FlushAggregationQueue': grpc.unary_unary_rpc_method_handler( + servicer.FlushAggregationQueue, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.SerializeToString, + ), + 'SetAggregator': grpc.unary_unary_rpc_method_handler( + servicer.SetAggregator, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( - 'grpc.Control', rpc_method_handlers) + 'grpc.Control', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) - # This class is part of an EXPERIMENTAL API. - + # This class is part of an EXPERIMENTAL API. class Control(object): """Missing associated documentation comment in .proto file.""" @staticmethod def Start(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Control/Start', - fedn__pb2.ControlRequest.SerializeToString, - fedn__pb2.ControlResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def Stop(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Control/Stop', - fedn__pb2.ControlRequest.SerializeToString, - fedn__pb2.ControlResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def FlushAggregationQueue(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Control/FlushAggregationQueue', - fedn__pb2.ControlRequest.SerializeToString, - fedn__pb2.ControlResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SetAggregator(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/grpc.Control/SetAggregator', + fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ControlResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class ReducerStub(object): @@ -238,10 +269,10 @@ def __init__(self, channel): channel: A grpc.Channel. """ self.GetGlobalModel = channel.unary_unary( - '/grpc.Reducer/GetGlobalModel', - request_serializer=fedn__pb2.GetGlobalModelRequest.SerializeToString, - response_deserializer=fedn__pb2.GetGlobalModelResponse.FromString, - ) + '/grpc.Reducer/GetGlobalModel', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.GetGlobalModelRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.GetGlobalModelResponse.FromString, + ) class ReducerServicer(object): @@ -256,38 +287,37 @@ def GetGlobalModel(self, request, context): def add_ReducerServicer_to_server(servicer, server): rpc_method_handlers = { - 'GetGlobalModel': grpc.unary_unary_rpc_method_handler( - servicer.GetGlobalModel, - request_deserializer=fedn__pb2.GetGlobalModelRequest.FromString, - response_serializer=fedn__pb2.GetGlobalModelResponse.SerializeToString, - ), + 'GetGlobalModel': grpc.unary_unary_rpc_method_handler( + servicer.GetGlobalModel, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.GetGlobalModelRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.GetGlobalModelResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( - 'grpc.Reducer', rpc_method_handlers) + 'grpc.Reducer', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) - # This class is part of an EXPERIMENTAL API. - + # This class is part of an EXPERIMENTAL API. class Reducer(object): """Missing associated documentation comment in .proto file.""" @staticmethod def GetGlobalModel(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Reducer/GetGlobalModel', - fedn__pb2.GetGlobalModelRequest.SerializeToString, - fedn__pb2.GetGlobalModelResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.GetGlobalModelRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.GetGlobalModelResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class ConnectorStub(object): @@ -300,40 +330,40 @@ def __init__(self, channel): channel: A grpc.Channel. """ self.AllianceStatusStream = channel.unary_stream( - '/grpc.Connector/AllianceStatusStream', - request_serializer=fedn__pb2.ClientAvailableMessage.SerializeToString, - response_deserializer=fedn__pb2.Status.FromString, - ) + '/grpc.Connector/AllianceStatusStream', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Status.FromString, + ) self.SendStatus = channel.unary_unary( - '/grpc.Connector/SendStatus', - request_serializer=fedn__pb2.Status.SerializeToString, - response_deserializer=fedn__pb2.Response.FromString, - ) + '/grpc.Connector/SendStatus', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Status.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + ) self.ListActiveClients = channel.unary_unary( - '/grpc.Connector/ListActiveClients', - request_serializer=fedn__pb2.ListClientsRequest.SerializeToString, - response_deserializer=fedn__pb2.ClientList.FromString, - ) + '/grpc.Connector/ListActiveClients', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ListClientsRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientList.FromString, + ) self.AcceptingClients = channel.unary_unary( - '/grpc.Connector/AcceptingClients', - request_serializer=fedn__pb2.ConnectionRequest.SerializeToString, - response_deserializer=fedn__pb2.ConnectionResponse.FromString, - ) + '/grpc.Connector/AcceptingClients', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ConnectionRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ConnectionResponse.FromString, + ) self.SendHeartbeat = channel.unary_unary( - '/grpc.Connector/SendHeartbeat', - request_serializer=fedn__pb2.Heartbeat.SerializeToString, - response_deserializer=fedn__pb2.Response.FromString, - ) + '/grpc.Connector/SendHeartbeat', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Heartbeat.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + ) self.ReassignClient = channel.unary_unary( - '/grpc.Connector/ReassignClient', - request_serializer=fedn__pb2.ReassignRequest.SerializeToString, - response_deserializer=fedn__pb2.Response.FromString, - ) + '/grpc.Connector/ReassignClient', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ReassignRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + ) self.ReconnectClient = channel.unary_unary( - '/grpc.Connector/ReconnectClient', - request_serializer=fedn__pb2.ReconnectRequest.SerializeToString, - response_deserializer=fedn__pb2.Response.FromString, - ) + '/grpc.Connector/ReconnectClient', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ReconnectRequest.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + ) class ConnectorServicer(object): @@ -389,170 +419,169 @@ def ReconnectClient(self, request, context): def add_ConnectorServicer_to_server(servicer, server): rpc_method_handlers = { - 'AllianceStatusStream': grpc.unary_stream_rpc_method_handler( - servicer.AllianceStatusStream, - request_deserializer=fedn__pb2.ClientAvailableMessage.FromString, - response_serializer=fedn__pb2.Status.SerializeToString, - ), - 'SendStatus': grpc.unary_unary_rpc_method_handler( - servicer.SendStatus, - request_deserializer=fedn__pb2.Status.FromString, - response_serializer=fedn__pb2.Response.SerializeToString, - ), - 'ListActiveClients': grpc.unary_unary_rpc_method_handler( - servicer.ListActiveClients, - request_deserializer=fedn__pb2.ListClientsRequest.FromString, - response_serializer=fedn__pb2.ClientList.SerializeToString, - ), - 'AcceptingClients': grpc.unary_unary_rpc_method_handler( - servicer.AcceptingClients, - request_deserializer=fedn__pb2.ConnectionRequest.FromString, - response_serializer=fedn__pb2.ConnectionResponse.SerializeToString, - ), - 'SendHeartbeat': grpc.unary_unary_rpc_method_handler( - servicer.SendHeartbeat, - request_deserializer=fedn__pb2.Heartbeat.FromString, - response_serializer=fedn__pb2.Response.SerializeToString, - ), - 'ReassignClient': grpc.unary_unary_rpc_method_handler( - servicer.ReassignClient, - request_deserializer=fedn__pb2.ReassignRequest.FromString, - response_serializer=fedn__pb2.Response.SerializeToString, - ), - 'ReconnectClient': grpc.unary_unary_rpc_method_handler( - servicer.ReconnectClient, - request_deserializer=fedn__pb2.ReconnectRequest.FromString, - response_serializer=fedn__pb2.Response.SerializeToString, - ), + 'AllianceStatusStream': grpc.unary_stream_rpc_method_handler( + servicer.AllianceStatusStream, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Status.SerializeToString, + ), + 'SendStatus': grpc.unary_unary_rpc_method_handler( + servicer.SendStatus, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Status.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.SerializeToString, + ), + 'ListActiveClients': grpc.unary_unary_rpc_method_handler( + servicer.ListActiveClients, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ListClientsRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientList.SerializeToString, + ), + 'AcceptingClients': grpc.unary_unary_rpc_method_handler( + servicer.AcceptingClients, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ConnectionRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ConnectionResponse.SerializeToString, + ), + 'SendHeartbeat': grpc.unary_unary_rpc_method_handler( + servicer.SendHeartbeat, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Heartbeat.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.SerializeToString, + ), + 'ReassignClient': grpc.unary_unary_rpc_method_handler( + servicer.ReassignClient, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ReassignRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.SerializeToString, + ), + 'ReconnectClient': grpc.unary_unary_rpc_method_handler( + servicer.ReconnectClient, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ReconnectRequest.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( - 'grpc.Connector', rpc_method_handlers) + 'grpc.Connector', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) - # This class is part of an EXPERIMENTAL API. - + # This class is part of an EXPERIMENTAL API. class Connector(object): """Missing associated documentation comment in .proto file.""" @staticmethod def AllianceStatusStream(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_stream(request, target, '/grpc.Connector/AllianceStatusStream', - fedn__pb2.ClientAvailableMessage.SerializeToString, - fedn__pb2.Status.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.Status.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def SendStatus(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Connector/SendStatus', - fedn__pb2.Status.SerializeToString, - fedn__pb2.Response.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.Status.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def ListActiveClients(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Connector/ListActiveClients', - fedn__pb2.ListClientsRequest.SerializeToString, - fedn__pb2.ClientList.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ListClientsRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientList.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def AcceptingClients(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Connector/AcceptingClients', - fedn__pb2.ConnectionRequest.SerializeToString, - fedn__pb2.ConnectionResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ConnectionRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ConnectionResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def SendHeartbeat(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Connector/SendHeartbeat', - fedn__pb2.Heartbeat.SerializeToString, - fedn__pb2.Response.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.Heartbeat.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def ReassignClient(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Connector/ReassignClient', - fedn__pb2.ReassignRequest.SerializeToString, - fedn__pb2.Response.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ReassignRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def ReconnectClient(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Connector/ReconnectClient', - fedn__pb2.ReconnectRequest.SerializeToString, - fedn__pb2.Response.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ReconnectRequest.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) class CombinerStub(object): @@ -565,35 +594,35 @@ def __init__(self, channel): channel: A grpc.Channel. """ self.ModelUpdateRequestStream = channel.unary_stream( - '/grpc.Combiner/ModelUpdateRequestStream', - request_serializer=fedn__pb2.ClientAvailableMessage.SerializeToString, - response_deserializer=fedn__pb2.ModelUpdateRequest.FromString, - ) + '/grpc.Combiner/ModelUpdateRequestStream', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelUpdateRequest.FromString, + ) self.ModelUpdateStream = channel.unary_stream( - '/grpc.Combiner/ModelUpdateStream', - request_serializer=fedn__pb2.ClientAvailableMessage.SerializeToString, - response_deserializer=fedn__pb2.ModelUpdate.FromString, - ) + '/grpc.Combiner/ModelUpdateStream', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelUpdate.FromString, + ) self.ModelValidationRequestStream = channel.unary_stream( - '/grpc.Combiner/ModelValidationRequestStream', - request_serializer=fedn__pb2.ClientAvailableMessage.SerializeToString, - response_deserializer=fedn__pb2.ModelValidationRequest.FromString, - ) + '/grpc.Combiner/ModelValidationRequestStream', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelValidationRequest.FromString, + ) self.ModelValidationStream = channel.unary_stream( - '/grpc.Combiner/ModelValidationStream', - request_serializer=fedn__pb2.ClientAvailableMessage.SerializeToString, - response_deserializer=fedn__pb2.ModelValidation.FromString, - ) + '/grpc.Combiner/ModelValidationStream', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelValidation.FromString, + ) self.SendModelUpdate = channel.unary_unary( - '/grpc.Combiner/SendModelUpdate', - request_serializer=fedn__pb2.ModelUpdate.SerializeToString, - response_deserializer=fedn__pb2.Response.FromString, - ) + '/grpc.Combiner/SendModelUpdate', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelUpdate.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + ) self.SendModelValidation = channel.unary_unary( - '/grpc.Combiner/SendModelValidation', - request_serializer=fedn__pb2.ModelValidation.SerializeToString, - response_deserializer=fedn__pb2.Response.FromString, - ) + '/grpc.Combiner/SendModelValidation', + request_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelValidation.SerializeToString, + response_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + ) class CombinerServicer(object): @@ -639,145 +668,144 @@ def SendModelValidation(self, request, context): def add_CombinerServicer_to_server(servicer, server): rpc_method_handlers = { - 'ModelUpdateRequestStream': grpc.unary_stream_rpc_method_handler( - servicer.ModelUpdateRequestStream, - request_deserializer=fedn__pb2.ClientAvailableMessage.FromString, - response_serializer=fedn__pb2.ModelUpdateRequest.SerializeToString, - ), - 'ModelUpdateStream': grpc.unary_stream_rpc_method_handler( - servicer.ModelUpdateStream, - request_deserializer=fedn__pb2.ClientAvailableMessage.FromString, - response_serializer=fedn__pb2.ModelUpdate.SerializeToString, - ), - 'ModelValidationRequestStream': grpc.unary_stream_rpc_method_handler( - servicer.ModelValidationRequestStream, - request_deserializer=fedn__pb2.ClientAvailableMessage.FromString, - response_serializer=fedn__pb2.ModelValidationRequest.SerializeToString, - ), - 'ModelValidationStream': grpc.unary_stream_rpc_method_handler( - servicer.ModelValidationStream, - request_deserializer=fedn__pb2.ClientAvailableMessage.FromString, - response_serializer=fedn__pb2.ModelValidation.SerializeToString, - ), - 'SendModelUpdate': grpc.unary_unary_rpc_method_handler( - servicer.SendModelUpdate, - request_deserializer=fedn__pb2.ModelUpdate.FromString, - response_serializer=fedn__pb2.Response.SerializeToString, - ), - 'SendModelValidation': grpc.unary_unary_rpc_method_handler( - servicer.SendModelValidation, - request_deserializer=fedn__pb2.ModelValidation.FromString, - response_serializer=fedn__pb2.Response.SerializeToString, - ), + 'ModelUpdateRequestStream': grpc.unary_stream_rpc_method_handler( + servicer.ModelUpdateRequestStream, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelUpdateRequest.SerializeToString, + ), + 'ModelUpdateStream': grpc.unary_stream_rpc_method_handler( + servicer.ModelUpdateStream, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelUpdate.SerializeToString, + ), + 'ModelValidationRequestStream': grpc.unary_stream_rpc_method_handler( + servicer.ModelValidationRequestStream, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelValidationRequest.SerializeToString, + ), + 'ModelValidationStream': grpc.unary_stream_rpc_method_handler( + servicer.ModelValidationStream, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelValidation.SerializeToString, + ), + 'SendModelUpdate': grpc.unary_unary_rpc_method_handler( + servicer.SendModelUpdate, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelUpdate.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.SerializeToString, + ), + 'SendModelValidation': grpc.unary_unary_rpc_method_handler( + servicer.SendModelValidation, + request_deserializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelValidation.FromString, + response_serializer=fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( - 'grpc.Combiner', rpc_method_handlers) + 'grpc.Combiner', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) - # This class is part of an EXPERIMENTAL API. - + # This class is part of an EXPERIMENTAL API. class Combiner(object): """Missing associated documentation comment in .proto file.""" @staticmethod def ModelUpdateRequestStream(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_stream(request, target, '/grpc.Combiner/ModelUpdateRequestStream', - fedn__pb2.ClientAvailableMessage.SerializeToString, - fedn__pb2.ModelUpdateRequest.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelUpdateRequest.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def ModelUpdateStream(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_stream(request, target, '/grpc.Combiner/ModelUpdateStream', - fedn__pb2.ClientAvailableMessage.SerializeToString, - fedn__pb2.ModelUpdate.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelUpdate.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def ModelValidationRequestStream(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_stream(request, target, '/grpc.Combiner/ModelValidationRequestStream', - fedn__pb2.ClientAvailableMessage.SerializeToString, - fedn__pb2.ModelValidationRequest.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelValidationRequest.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def ModelValidationStream(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_stream(request, target, '/grpc.Combiner/ModelValidationStream', - fedn__pb2.ClientAvailableMessage.SerializeToString, - fedn__pb2.ModelValidation.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ClientAvailableMessage.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelValidation.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def SendModelUpdate(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Combiner/SendModelUpdate', - fedn__pb2.ModelUpdate.SerializeToString, - fedn__pb2.Response.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelUpdate.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) @staticmethod def SendModelValidation(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): return grpc.experimental.unary_unary(request, target, '/grpc.Combiner/SendModelValidation', - fedn__pb2.ModelValidation.SerializeToString, - fedn__pb2.Response.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + fedn_dot_network_dot_grpc_dot_fedn__pb2.ModelValidation.SerializeToString, + fedn_dot_network_dot_grpc_dot_fedn__pb2.Response.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/fedn/fedn/network/storage/statestore/mongostatestore.py b/fedn/fedn/network/storage/statestore/mongostatestore.py index aa7671802..8cd869efb 100644 --- a/fedn/fedn/network/storage/statestore/mongostatestore.py +++ b/fedn/fedn/network/storage/statestore/mongostatestore.py @@ -531,6 +531,7 @@ def get_model_trail(self): :return: dictionary of model_id: committed_at :rtype: dict """ + # TODO Make it so that model order from db is preserved. result = self.model.find_one({"key": "model_trail"}) try: if result is not None: diff --git a/fedn/fedn/utils/helpers/__init__.py b/fedn/fedn/utils/helpers/__init__.py new file mode 100644 index 000000000..d568d0d56 --- /dev/null +++ b/fedn/fedn/utils/helpers/__init__.py @@ -0,0 +1,3 @@ +""" The helpers package is responsible for serialization/deserialization and numerics operations +for various machine learning frameworks. """ +# flake8: noqa diff --git a/fedn/fedn/utils/helpers/helperbase.py b/fedn/fedn/utils/helpers/helperbase.py new file mode 100644 index 000000000..0801eecfc --- /dev/null +++ b/fedn/fedn/utils/helpers/helperbase.py @@ -0,0 +1,141 @@ +import os +import tempfile +from abc import ABC, abstractmethod + + +class HelperBase(ABC): + """ Abstract class defining helpers. """ + + def __init__(self): + """ Initialize helper. """ + + self.name = self.__class__.__name__ + + @abstractmethod + def increment_average(self, m1, m2, a, W): + """ Compute one increment of incremental weighted averaging. + + :param m1: Current model weights in array-like format. + :param m2: New model weights in array-like format. + :param a: Number of examples in new model. + :param W: Total number of examples. + :return: Incremental weighted average of model weights. + """ + pass + + @abstractmethod + def add(self, m1, m2, a=1.0, b=1.0): + """ m1*a + m2*b + + :param model: Current model weights with keys from torch state_dict. + :type model: OrderedDict + :param model_next: New model weights with keys from torch state_dict. + :type model_next: OrderedDict + :return: Incremental weighted average of model weights. + :rtype: OrderedDict + """ + pass + + @abstractmethod + def subtract(self, m1, m2, a=1.0, b=1.0): + """ m1*a - m2*b. + + :param m1: Current model weights with keys from torch state_dict. + :type m1: OrderedDict + :param m2: New model weights with keys from torch state_dict. + :type m2: OrderedDict + :return: m1*a-m2*b + :rtype: OrderedDict + """ + pass + + @abstractmethod + def divide(self, m1, m2): + """ Subtract weights. + + :param m1: Current model weights with keys from torch state_dict. + :type m1: OrderedDict + :param m2: New model weights with keys from torch state_dict. + :type m2: OrderedDict + :return: m1/m2. + :rtype: OrderedDict + """ + pass + + @abstractmethod + def multiply(self, m1, m2): + """ Multiply m1 by m2. + + :param m1: Current model weights with keys from torch state_dict. + :type m1: OrderedDict + :param m2: New model weights with keys from torch state_dict. + :type m2: OrderedDict + :return: m1.*m2 + :rtype: OrderedDict + """ + + pass + + @abstractmethod + def sqrt(self, m1): + """ Sqrt of m1, element-wise. + + :param m1: Current model weights with keys from torch state_dict. + :type model: OrderedDict + :param model_next: New model weights with keys from torch state_dict. + :type model_next: OrderedDict + :return: sqrt(m1) + :rtype: OrderedDict + """ + pass + + @abstractmethod + def power(self, m1, a): + """ m1 raised to the power of m2. + + :param m1: Current model weights with keys from torch state_dict. + :type m1: OrderedDict + :param m2: New model weights with keys from torch state_dict. + :type a: float + :return: m1.^m2 + :rtype: OrderedDict + """ + pass + + @abstractmethod + def norm(self, m): + """Compute the L1-norm of the tensor m. """ + pass + + @abstractmethod + def ones(self, m1, a): + """ Return ones times a with same shape as m1. """ + pass + + @abstractmethod + def save(self, model, path): + """ Serialize weights to file. The serialized model must be a single binary object. + + :param model: Weights in array-like format. + :param path: Path to file. + + """ + pass + + @abstractmethod + def load(self, fh): + """ Load weights from file or filelike. + + :param fh: file path, filehandle, filelike. + :return: Weights in array-like format. + """ + pass + + def get_tmp_path(self): + """ Return a temporary output path compatible with save_model, load_model. + + :return: Path to file. + """ + fd, path = tempfile.mkstemp(suffix='.npz') + os.close(fd) + return path diff --git a/fedn/fedn/utils/helpers.py b/fedn/fedn/utils/helpers/helpers.py similarity index 84% rename from fedn/fedn/utils/helpers.py rename to fedn/fedn/utils/helpers/helpers.py index ba8ebd48d..841af58a0 100644 --- a/fedn/fedn/utils/helpers.py +++ b/fedn/fedn/utils/helpers/helpers.py @@ -1,7 +1,7 @@ import importlib import json -PLUGIN_PATH = "fedn.utils.plugins.{}" +HELPER_PLUGIN_PATH = "fedn.utils.helpers.plugins.{}" def get_helper(helper_module_name): @@ -10,9 +10,9 @@ def get_helper(helper_module_name): :param helper_module_name: The name of the helper plugin module. :type helper_module_name: str :return: A helper instance. - :rtype: class: `fedn.utils.helpers.HelperBase` + :rtype: class: `fedn.utils.helpers.helpers.HelperBase` """ - helper_plugin = PLUGIN_PATH.format(helper_module_name) + helper_plugin = HELPER_PLUGIN_PATH.format(helper_module_name) helper = importlib.import_module(helper_plugin) return helper.Helper() diff --git a/fedn/fedn/utils/helpers/plugins/__init__.py b/fedn/fedn/utils/helpers/plugins/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/fedn/fedn/utils/helpers/plugins/numpyhelper.py b/fedn/fedn/utils/helpers/plugins/numpyhelper.py new file mode 100644 index 000000000..542ff4c16 --- /dev/null +++ b/fedn/fedn/utils/helpers/plugins/numpyhelper.py @@ -0,0 +1,152 @@ + +import numpy as np + +from fedn.utils.helpers.helperbase import HelperBase + + +class Helper(HelperBase): + """ FEDn helper class for pytorch models. """ + + def __init__(self): + """ Initialize helper. """ + super().__init__() + self.name = "pytorchhelper" + + def increment_average(self, m1, m2, n, N): + """ Update a weighted incremental average of model weights. + + :param m1: Current parameters. + :type model: list of numpy ndarray + :param m2: next parameters. + :type model_next: list of numpy ndarray + :param n: Number of examples used for updating m2. + :type n: int + :param N: Total number of examples (accumulated). + :type N: int + :return: Updated incremental weighted average. + :rtype: list of numpy ndarray + """ + + return [np.add(x, n*(y-x)/N) for x, y in zip(m1, m2)] + + def add(self, m1, m2, a=1.0, b=1.0): + """ m1*a + m2*b + + :param model: Current model weights with keys from torch state_dict. + :type model: OrderedDict + :param model_next: New model weights with keys from torch state_dict. + :type model_next: OrderedDict + :return: Incremental weighted average of model weights. + :rtype: OrderedDict + """ + + return [x*a+y*b for x, y in zip(m1, m2)] + + def subtract(self, m1, m2, a=1.0, b=1.0): + """ m1*a - m2*b. + + :param m1: Current model weights with keys from torch state_dict. + :type m1: OrderedDict + :param m2: New model weights with keys from torch state_dict. + :type m2: OrderedDict + :return: m1*a-m2*b + :rtype: OrderedDict + """ + return self.add(m1, m2, a, -b) + + def divide(self, m1, m2): + """ Subtract weights. + + :param m1: Current model weights with keys from torch state_dict. + :type m1: OrderedDict + :param m2: New model weights with keys from torch state_dict. + :type m2: OrderedDict + :return: m1/m2. + :rtype: OrderedDict + """ + + return [np.divide(x, y) for x, y in zip(m1, m2)] + + def multiply(self, m1, m2): + """ Multiply m1 by m2. + + :param m1: Current model weights with keys from torch state_dict. + :type m1: OrderedDict + :param m2: New model weights with keys from torch state_dict. + :type m2: OrderedDict + :return: m1.*m2 + :rtype: OrderedDict + """ + + return [np.multiply(x, y) for (x, y) in zip(m1, m2)] + + def sqrt(self, m1): + """ Sqrt of m1, element-wise. + + :param m1: Current model weights with keys from torch state_dict. + :type model: OrderedDict + :param model_next: New model weights with keys from torch state_dict. + :type model_next: OrderedDict + :return: sqrt(m1) + :rtype: OrderedDict + """ + + return [np.sqrt(x) for x in m1] + + def power(self, m1, a): + """ m1 raised to the power of m2. + + :param m1: Current model weights with keys from torch state_dict. + :type m1: OrderedDict + :param m2: New model weights with keys from torch state_dict. + :type a: float + :return: m1.^m2 + :rtype: OrderedDict + """ + + return [np.power(x, a) for x in m1] + + def norm(self, m): + """Compute the L1 norm of m. """ + n = 0.0 + for x in m: + n += np.linalg.norm(x, 1) + return n + + def ones(self, m1, a): + + res = [] + for x in m1: + res.append(np.ones(np.shape(x))*a) + return res + + def save(self, weights, path=None): + """ Serialize weights to file. The serialized model must be a single binary object. + + :param weights: List of weights in numpy format. + :param path: Path to file. + :return: Path to file. + """ + if not path: + path = self.get_tmp_path() + + weights_dict = {} + for i, w in enumerate(weights): + weights_dict[str(i)] = w + + np.savez_compressed(path, **weights_dict) + + return path + + def load(self, fh): + """ Load weights from file or filelike. + + :param fh: file path, filehandle, filelike. + :return: List of weights in numpy format. + """ + a = np.load(fh) + + weights = [] + for i in range(len(a.files)): + weights.append(a[str(i)]) + return weights diff --git a/fedn/fedn/utils/plugins/tests/test_pytorchhelper.py b/fedn/fedn/utils/helpers/tests/test_numpyhelper.py similarity index 63% rename from fedn/fedn/utils/plugins/tests/test_pytorchhelper.py rename to fedn/fedn/utils/helpers/tests/test_numpyhelper.py index 4eb98c7f9..96b4f7c03 100644 --- a/fedn/fedn/utils/plugins/tests/test_pytorchhelper.py +++ b/fedn/fedn/utils/helpers/tests/test_numpyhelper.py @@ -3,32 +3,34 @@ import numpy as np -from fedn.utils.plugins.pytorchhelper import Helper as PyTorchHelper +from fedn.utils.helpers.helpers.plugins.numpyhelper import \ + Helper as NumpyHelper -class TestPyTorchHelper(unittest.TestCase): +class TestNumpyHelper(unittest.TestCase): """Test the PyTorchHelper class.""" def setUp(self): - self.helper = PyTorchHelper() + self.helper = NumpyHelper() def test_increment_average(self): - """Test the increment_average method. The weights are stored as OrderedDicts.""" + """Test the increment_average method. The weights are stored as list of numpyarray.""" # Model as OrderedDict with keys as torch layers and values as numpy arrays - model = {'layer1': np.array([1, 2, 3])} - model_next = {'layer1': np.array([4, 5, 6])} + model = [np.array([1, 2, 3])] + model_next = [np.array([4, 5, 6])] a = 10 W = 20 result = self.helper.increment_average(model, model_next, a, W) + print(result) - # Check OrderedDict values match - np.testing.assert_array_equal(result['layer1'], np.array([2.5, 3.5, 4.5])) + # Check values match + np.testing.assert_array_equal(result, [np.array([2.5, 3.5, 4.5])]) - # Model as OrderedDict with keys as torch layers and values as lists - model = {'layer1': [1, 2, 3]} - model_next = {'layer1': [4, 5, 6]} + # Model as with keys as torch layers and values as lists + model = [[1, 2, 3]] + model_next = [[4, 5, 6]] a = 10 W = 20 @@ -41,7 +43,7 @@ def test_save_load(self): """Test the save and load methods.""" # Create a model - model = {'layer1': np.array([1, 2, 3])} + model = [np.array([1, 2, 3])] # Save the model self.helper.save(model, 'test_model') @@ -53,7 +55,7 @@ def test_save_load(self): result = self.helper.load('test_model.npz') # Check OrderedDict values match - np.testing.assert_array_equal(result['layer1'], np.array([1, 2, 3])) + np.testing.assert_array_equal(result, [np.array([1, 2, 3])]) # Remove the model file os.remove('test_model.npz') diff --git a/fedn/fedn/utils/plugins/__init__.py b/fedn/fedn/utils/plugins/__init__.py deleted file mode 100644 index 162a2d351..000000000 --- a/fedn/fedn/utils/plugins/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" The plugins package is responsible for loading model helper functions supporting different ML frameworks. The :class:`fedn.utils.plugins.helperbase.HelperBase` is -an abstract class which user can implement their own helper functions to support different ML frameworks. """ -# flake8: noqa diff --git a/fedn/fedn/utils/plugins/helperbase.py b/fedn/fedn/utils/plugins/helperbase.py deleted file mode 100644 index 595899ff6..000000000 --- a/fedn/fedn/utils/plugins/helperbase.py +++ /dev/null @@ -1,52 +0,0 @@ -import os -import tempfile -from abc import ABC, abstractmethod - - -class HelperBase(ABC): - """ Abstract class defining helpers. """ - - def __init__(self): - """ Initialize helper. """ - - self.name = self.__class__.__name__ - - @abstractmethod - def increment_average(self, model, model_next, a, W): - """ Compute one increment of incremental weighted averaging. - - :param model: Current model weights in array-like format. - :param model_next: New model weights in array-like format. - :param a: Number of examples in new model. - :param W: Total number of examples. - :return: Incremental weighted average of model weights. - """ - pass - - @abstractmethod - def save(self, model, path): - """ Serialize weights to file. The serialized model must be a single binary object. - - :param model: Weights in array-like format. - :param path: Path to file. - - """ - pass - - @abstractmethod - def load(self, fh): - """ Load weights from file or filelike. - - :param fh: file path, filehandle, filelike. - :return: Weights in array-like format. - """ - pass - - def get_tmp_path(self): - """ Return a temporary output path compatible with save_model, load_model. - - :return: Path to file. - """ - fd, path = tempfile.mkstemp(suffix='.npz') - os.close(fd) - return path diff --git a/fedn/fedn/utils/plugins/kerashelper.py b/fedn/fedn/utils/plugins/kerashelper.py deleted file mode 100644 index 7a931ae34..000000000 --- a/fedn/fedn/utils/plugins/kerashelper.py +++ /dev/null @@ -1,85 +0,0 @@ -import numpy as np - -from .helperbase import HelperBase - - -class Helper(HelperBase): - """ FEDn helper class for keras.Sequential. """ - - def __init__(self): - """ Initialize helper. """ - self.name = "kerashelper" - super().__init__() - - # function to calculate an incremental weighted average of the weights - def increment_average(self, model, model_next, num_examples, total_examples): - """ Incremental weighted average of model weights. - - :param model: Current model weights. - :type model: list of numpy arrays. - :param model_next: New model weights. - :type model_next: list of numpy arrays. - :param num_examples: Number of examples in new model. - :type num_examples: int - :param total_examples: Total number of examples. - :type total_examples: int - :return: Incremental weighted average of model weights. - :rtype: list of numpy arrays. - """ - # Incremental weighted average - w = num_examples / total_examples - weights = [] - for i in range(len(model)): - weights.append(w * model_next[i] + (1 - w) * model[i]) - - return weights - - # function to calculate an incremental weighted average of the weights using numpy.add - def increment_average_add(self, model, model_next, num_examples, total_examples): - """ Incremental weighted average of model weights. - - :param model: Current model weights. - :type model: list of numpy arrays. - :param model_next: New model weights. - :type model_next: list of numpy arrays. - :param num_examples: Number of examples in new model. - :type num_examples: int - :param total_examples: Total number of examples. - :type total_examples: int - :return: Incremental weighted average of model weights. - :rtype: list of numpy arrays. - """ - # Incremental weighted average - w = np.add(model, num_examples*(np.array(model_next) - np.array(model)) / total_examples) - return w - - def save(self, weights, path=None): - """ Serialize weights to file. The serialized model must be a single binary object. - - :param weights: List of weights in numpy format. - :param path: Path to file. - :return: Path to file. - """ - if not path: - path = self.get_tmp_path() - - weights_dict = {} - for i, w in enumerate(weights): - weights_dict[str(i)] = w - - np.savez_compressed(path, **weights_dict) - - return path - - def load(self, fh): - """ Load weights from file or filelike. - - :param fh: file path, filehandle, filelike. - :return: List of weights in numpy format. - """ - a = np.load(fh) - - weights = [] - for i in range(len(a.files)): - weights.append(a[str(i)]) - return weights diff --git a/fedn/fedn/utils/plugins/numpyarrayhelper.py b/fedn/fedn/utils/plugins/numpyarrayhelper.py deleted file mode 100644 index 275d3b51c..000000000 --- a/fedn/fedn/utils/plugins/numpyarrayhelper.py +++ /dev/null @@ -1,49 +0,0 @@ -import tempfile - -import numpy as np - -from .helperbase import HelperBase - - -class Helper(HelperBase): - """ FEDn helper class for numpy arrays. """ - - def increment_average(self, model, model_next, n): - """ Update an incremental average. - - :param model: Current model weights. - :type model: numpy array. - :param model_next: New model weights. - :type model_next: numpy array. - :param n: Number of examples in new model. - :type n: int - :return: Incremental weighted average of model weights. - :rtype: :class:`numpy.array` - """ - return np.add(model, (model_next - model) / n) - - def save(self, model, path=None): - """ Serialize weights/parameters to file. - - :param model: Weights/parameters in numpy array format. - :type model: numpy array. - :param path: Path to file. - :type path: str - :return: Path to file. - :rtype: str - """ - if not path: - _, path = tempfile.mkstemp() - np.savetxt(path, model) - return path - - def load(self, path): - """ Load weights/parameters from file or filelike. - - :param path: Path to file. - :type path: str - :return: Weights/parameters in numpy array format. - :rtype: :class:`numpy.array` - """ - model = np.loadtxt(path) - return model diff --git a/fedn/fedn/utils/plugins/pytorchhelper.py b/fedn/fedn/utils/plugins/pytorchhelper.py deleted file mode 100644 index 17b01200c..000000000 --- a/fedn/fedn/utils/plugins/pytorchhelper.py +++ /dev/null @@ -1,63 +0,0 @@ -from collections import OrderedDict - -import numpy as np - -from .helperbase import HelperBase - - -class Helper(HelperBase): - """ FEDn helper class for pytorch. """ - - def __init__(self): - """ Initialize helper. """ - super().__init__() - self.name = "pytorchhelper" - - def increment_average(self, model, model_next, num_examples, total_examples): - """ Update a weighted incremental average of model weights. - - :param model: Current model weights with keys from torch state_dict. - :type model: OrderedDict - :param model_next: New model weights with keys from torch state_dict. - :type model_next: OrderedDict - :param num_examples: Number of examples in new model. - :type num_examples: int - :param total_examples: Total number of examples. - :type total_examples: int - :return: Incremental weighted average of model weights. - :rtype: OrderedDict - """ - w = OrderedDict() - for name in model.keys(): - tensorDiff = model_next[name] - model[name] - w[name] = model[name] + num_examples*tensorDiff / total_examples - return w - - def save(self, model, path=None): - """ Serialize weights to file. The serialized model must be a single binary object. - - :param model: Weights of model with keys from torch state_dict. - :type model: OrderedDict - :param path: File path. - :type path: str - :return: Path to file (generated as tmp file unless path is set). - :rtype: str - """ - if not path: - path = self.get_tmp_path() - np.savez_compressed(path, **model) - return path - - def load(self, path): - """ Load weights from file or filelike. - - :param path: file path, filehandle, filelike. - :type path: str - :return: Weights of model with keys from torch state_dict. - :rtype: OrderedDict - """ - a = np.load(path) - weights_np = OrderedDict() - for i in a.files: - weights_np[i] = a[i] - return weights_np diff --git a/fedn/fedn/utils/plugins/tests/test_kerashelper.py b/fedn/fedn/utils/plugins/tests/test_kerashelper.py deleted file mode 100644 index 5e392b47c..000000000 --- a/fedn/fedn/utils/plugins/tests/test_kerashelper.py +++ /dev/null @@ -1,94 +0,0 @@ -import os -import unittest - -import numpy as np - -from fedn.utils.plugins.kerashelper import Helper as KerasHelper - - -class TestKerasHelper(unittest.TestCase): - """Test the KerasHelper class.""" - - def setUp(self): - self.helper = KerasHelper() - - def test_increment_average(self): - """Test the increment_average method.""" - # Test with a list - model = [1, 2, 3] - model_next = [4, 5, 6] - a = 10 - W = 20 - - result = self.helper.increment_average(model, model_next, a, W) - - self.assertEqual(result, [2.5, 3.5, 4.5]) - - # Test with a numpy array - model = np.array([1, 2, 3]) - model_next = np.array([4, 5, 6]) - - result = self.helper.increment_average(model, model_next, a, W) - - np.testing.assert_array_equal(result, np.array([2.5, 3.5, 4.5])) - - # test with a list of numpy arrays - model = [np.array([1, 2, 3])] - model_next = [np.array([4, 5, 6])] - - result = self.helper.increment_average(model, model_next, a, W) - - np.testing.assert_array_equal(result, np.array([[2.5, 3.5, 4.5]])) - - def test_increment_average_add(self): - """Test the increment_average_add method.""" - model = [1, 2, 3] - model_next = [4, 5, 6] - a = 10 - W = 20 - - result = self.helper.increment_average_add(model, model_next, a, W) - - np.testing.assert_array_equal(result, np.array([2.5, 3.5, 4.5])) - - # Test with a numpy array - model = np.array([1, 2, 3]) - model_next = np.array([4, 5, 6]) - - result = self.helper.increment_average_add(model, model_next, a, W) - - np.testing.assert_array_equal(result, np.array([2.5, 3.5, 4.5])) - - # test with a list of numpy arrays - model = [np.array([1, 2, 3])] - model_next = [np.array([4, 5, 6])] - - result = self.helper.increment_average_add(model, model_next, a, W) - - np.testing.assert_array_equal(result, np.array([[2.5, 3.5, 4.5]])) - - def test_save(self): - """Test the save method.""" - weights = [1, 2, 3] - - result = self.helper.save(weights, 'test.npz') - - self.assertEqual(result, 'test.npz') - - def test_load(self): - """Test the load method.""" - weights = [1, 2, 3] - - result = self.helper.save(weights, 'test.npz') - result = self.helper.load('test.npz') - - self.assertEqual(result, [1, 2, 3]) - - # Tear down method, remove test.npz - def tearDown(self): - if os.path.exists('test.npz'): - os.remove('test.npz') - - -if __name__ == '__main__': - unittest.main() diff --git a/fedn/fedn/utils/tests/test_helpers.py b/fedn/fedn/utils/tests/test_helpers.py index 9dfcdd36f..8dfd4eaeb 100644 --- a/fedn/fedn/utils/tests/test_helpers.py +++ b/fedn/fedn/utils/tests/test_helpers.py @@ -1,7 +1,7 @@ import os import unittest -from fedn.utils.helpers import get_helper, save_metadata, save_metrics +from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics class TestHelpers(unittest.TestCase): diff --git a/fedn/genprot.sh b/fedn/genprot.sh index ac516362c..bb08ada35 100755 --- a/fedn/genprot.sh +++ b/fedn/genprot.sh @@ -1,4 +1,4 @@ #!/bin/bash echo "Generating protocol" -python3 -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. fedn/common/net/grpc/*.proto +python3 -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. fedn/network/grpc/*.proto echo "DONE"