{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# FL Server over Secure RPC\n", "\n", "We demonstrate how to launch a gRPC server as a federated learning server with authentication. Consider only one client so that we can launch a server and a client (from another notebook) together." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "num_clients = 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load server configurations\n", "\n", "In this example, we use the `FedAvg` server aggregation algorithm (while there is only one client for easy demo, the aggregation algorithm does not matter a lot though) and the MNIST dataset by loading the server configurations from `examples/resources/configs/mnist/server_fedavg.yaml`." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "client_configs:\n", " train_configs:\n", " trainer: VanillaTrainer\n", " mode: step\n", " num_local_steps: 100\n", " optim: Adam\n", " optim_args:\n", " lr: 0.001\n", " loss_fn_path: ./resources/loss/celoss.py\n", " loss_fn_name: CELoss\n", " do_validation: true\n", " do_pre_validation: true\n", " metric_path: ./resources/metric/acc.py\n", " metric_name: accuracy\n", " use_dp: false\n", " epsilon: 1\n", " clip_grad: false\n", " clip_value: 1\n", " clip_norm: 1\n", " train_batch_size: 64\n", " val_batch_size: 64\n", " train_data_shuffle: true\n", " val_data_shuffle: false\n", " model_configs:\n", " model_path: ./resources/model/cnn.py\n", " model_name: CNN\n", " model_kwargs:\n", " num_channel: 1\n", " num_classes: 10\n", " num_pixel: 28\n", " comm_configs:\n", " compressor_configs:\n", " enable_compression: false\n", " lossy_compressor: SZ2Compressor\n", " lossless_compressor: blosc\n", " error_bounding_mode: REL\n", " error_bound: 0.001\n", " param_cutoff: 1024\n", "server_configs:\n", " num_clients: 2\n", " scheduler: SyncScheduler\n", " scheduler_kwargs:\n", " same_init_model: true\n", " aggregator: FedAvgAggregator\n", " aggregator_kwargs:\n", " client_weights_mode: equal\n", " device: cpu\n", " num_global_epochs: 10\n", " logging_output_dirname: ./output\n", " logging_output_filename: result\n", " comm_configs:\n", " grpc_configs:\n", " server_uri: localhost:50051\n", " max_message_size: 1048576\n", " use_ssl: false\n", "\n" ] } ], "source": [ "from omegaconf import OmegaConf\n", "\n", "server_config_file = \"../../examples/resources/configs/mnist/server_fedavg.yaml\"\n", "server_config = OmegaConf.load(server_config_file)\n", "print(OmegaConf.to_yaml(server_config))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "💡 It should be noted that configuration fields such as `loss_fn_path`, `metric_path`, and `model_path` are the paths to the corresponding files, so we need to change their relative paths now to make sure the paths point to the right files. \n", "\n", "⚠️ We also need change `num_clients` in `server_configs` to 1." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "server_config.client_configs.train_configs.loss_fn_path = (\n", " \"../../examples/resources/loss/celoss.py\"\n", ")\n", "server_config.client_configs.train_configs.metric_path = (\n", " \"../../examples/resources/metric/acc.py\"\n", ")\n", "server_config.client_configs.model_configs.model_path = (\n", " \"../../examples/resources/model/cnn.py\"\n", ")\n", "server_config.server_configs.num_clients = num_clients" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create secure SSL server and authenticator\n", "\n", "Secure SSL server requires both a *public certificate* and a *private key*. Generate a fresh local CA and a server certificate signed by it with the bundled console script:\n", "\n", "```bash\n", "appfl-setup-ssl\n", "```\n", "\n", "By default this writes `ca.key`, `ca.crt`, `server.key`, and `server.crt` to `~/.appfl/ssl/`. Keep `ca.key` and `server.key` private; ship `ca.crt` to every client that should trust this federation.\n", "\n", "💡 Please check this [tutorial](https://appfl.ai/en/latest/tutorials/examples_ssl.html) for more details on generating SSL certificates for production deployments (institutional PKI, Let's Encrypt, etc.).\n", "\n", "Then point the server config at the generated paths:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "ssl_dir = os.path.expanduser(\"~/.appfl/ssl\")\n", "server_config.server_configs.comm_configs.grpc_configs.use_ssl = True\n", "server_config.server_configs.comm_configs.grpc_configs.server_certificate_key = (\n", " f\"{ssl_dir}/server.key\"\n", ")\n", "server_config.server_configs.comm_configs.grpc_configs.server_certificate = (\n", " f\"{ssl_dir}/server.crt\"\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup an authenticator\n", "\n", "Now we use a naive authenticator, where the server sets a special token and uses token-match to authenticate the client. \n", "\n", "💡 It should be noted that the naive authenticator is only for easy demonstration and is not really safe in practice to protect your FL experiment. We also provide Globus authenticator, and you can also define your own ones." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "server_config.server_configs.comm_configs.grpc_configs.use_authenticator = True\n", "server_config.server_configs.comm_configs.grpc_configs.authenticator = (\n", " \"NaiveAuthenticator\"\n", ")\n", "server_config.server_configs.comm_configs.grpc_configs.authenticator_args = {\n", " \"auth_token\": \"A_SECRET_DEMO_TOKEN\"\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Start server\n", "\n", "Now, we are ready to create the server agent using the `server_config` defined and modified above and start the grpc server.\n", "\n", "After launching 🚀 the server, let's go to the notebook to launch the client to talk to the server!\n", "\n", "💡 After finishing the FL experiment, you need to manually stop the server." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:12,776 server]: Logging to ./output/result_Server_2026-05-31-21-49-12.txt\n", "/Users/zilinghan/Documents/projects/appfl/appfl-release/src/appfl/misc/utils.py:126: UserWarning: NaiveAuthenticator is a shared-secret scheme suitable only for demos and trusted-network testing. Use GlobusAuthenticator (or another identity-bound authenticator) in production.\n", " authenticator = AuthenticatorClass(**authenticator_args)\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:26,598 server]: Received [1/1] GetConfiguration request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:26,616 server]: Received GetGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:27,336 server]: Received InvokeCustomAction set_sample_size request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:32,046 server]: Received UpdateGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:32,145 server]: Received metadata from Client1:\n", "{'current_local_steps': 100,\n", " 'pre_val_accuracy': 15.93,\n", " 'pre_val_loss': 2.30059186820012,\n", " 'round': 1,\n", " 'val_accuracy': 94.22,\n", " 'val_loss': 0.19807521227426875}\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:38,616 server]: Received UpdateGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:38,718 server]: Received metadata from Client1:\n", "{'current_local_steps': 100,\n", " 'pre_val_accuracy': 94.22,\n", " 'pre_val_loss': 0.1980752093616612,\n", " 'round': 2,\n", " 'val_accuracy': 96.7,\n", " 'val_loss': 0.10678713586015307}\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:45,165 server]: Received UpdateGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:45,265 server]: Received metadata from Client1:\n", "{'current_local_steps': 100,\n", " 'pre_val_accuracy': 96.7,\n", " 'pre_val_loss': 0.10678713719188503,\n", " 'round': 3,\n", " 'val_accuracy': 97.29,\n", " 'val_loss': 0.0915415328956998}\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:51,653 server]: Received UpdateGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:51,753 server]: Received metadata from Client1:\n", "{'current_local_steps': 100,\n", " 'pre_val_accuracy': 97.29,\n", " 'pre_val_loss': 0.0915415316573967,\n", " 'round': 4,\n", " 'val_accuracy': 98.23,\n", " 'val_loss': 0.057862771333159674}\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:58,134 server]: Received UpdateGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:49:58,238 server]: Received metadata from Client1:\n", "{'current_local_steps': 100,\n", " 'pre_val_accuracy': 98.23,\n", " 'pre_val_loss': 0.05786277159120128,\n", " 'round': 5,\n", " 'val_accuracy': 98.41,\n", " 'val_loss': 0.05265393350219612}\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:04,656 server]: Received UpdateGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:04,756 server]: Received metadata from Client1:\n", "{'current_local_steps': 100,\n", " 'pre_val_accuracy': 98.41,\n", " 'pre_val_loss': 0.05265393443796769,\n", " 'round': 6,\n", " 'val_accuracy': 98.49,\n", " 'val_loss': 0.04675506340746399}\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:11,199 server]: Received UpdateGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:11,303 server]: Received metadata from Client1:\n", "{'current_local_steps': 100,\n", " 'pre_val_accuracy': 98.49,\n", " 'pre_val_loss': 0.046755062995561376,\n", " 'round': 7,\n", " 'val_accuracy': 98.56,\n", " 'val_loss': 0.04519760218074666}\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:17,691 server]: Received UpdateGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:17,792 server]: Received metadata from Client1:\n", "{'current_local_steps': 100,\n", " 'pre_val_accuracy': 98.56,\n", " 'pre_val_loss': 0.04519760190565057,\n", " 'round': 8,\n", " 'val_accuracy': 98.73,\n", " 'val_loss': 0.03950823215095943}\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:24,185 server]: Received UpdateGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:24,286 server]: Received metadata from Client1:\n", "{'current_local_steps': 100,\n", " 'pre_val_accuracy': 98.73,\n", " 'pre_val_loss': 0.039508231548491604,\n", " 'round': 9,\n", " 'val_accuracy': 98.65,\n", " 'val_loss': 0.04178397888937783}\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:30,742 server]: Received UpdateGlobalModel request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:30,843 server]: Received metadata from Client1:\n", "{'current_local_steps': 100,\n", " 'pre_val_accuracy': 98.65,\n", " 'pre_val_loss': 0.041783977746728075,\n", " 'round': 10,\n", " 'val_accuracy': 98.56,\n", " 'val_loss': 0.039475969079361746}\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:32,462 server]: Received InvokeCustomAction close_connection request from client Client1\n", "\u001b[34m\u001b[1mappfl: ✅\u001b[0m[2026-05-31 21:50:32,858 server]: Terminating the server ...\n" ] } ], "source": [ "from appfl.agent import ServerAgent\n", "from appfl.comm.grpc import GRPCServerCommunicator, serve\n", "\n", "server_agent = ServerAgent(server_agent_config=server_config)\n", "\n", "communicator = GRPCServerCommunicator(\n", " server_agent,\n", " logger=server_agent.logger,\n", " **server_config.server_configs.comm_configs.grpc_configs,\n", ")\n", "\n", "serve(\n", " communicator,\n", " **server_config.server_configs.comm_configs.grpc_configs,\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "appfl", "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.14" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }