flwr-nightly 1.8.0.dev20240315__py3-none-any.whl → 1.15.0.dev20250115__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- flwr/cli/app.py +16 -2
- flwr/cli/build.py +181 -0
- flwr/cli/cli_user_auth_interceptor.py +90 -0
- flwr/cli/config_utils.py +343 -0
- flwr/cli/example.py +4 -1
- flwr/cli/install.py +253 -0
- flwr/cli/log.py +182 -0
- flwr/{server/superlink/state → cli/login}/__init__.py +4 -10
- flwr/cli/login/login.py +88 -0
- flwr/cli/ls.py +327 -0
- flwr/cli/new/__init__.py +1 -0
- flwr/cli/new/new.py +210 -66
- flwr/cli/new/templates/app/.gitignore.tpl +163 -0
- flwr/cli/new/templates/app/LICENSE.tpl +202 -0
- flwr/cli/new/templates/app/README.baseline.md.tpl +127 -0
- flwr/cli/new/templates/app/README.flowertune.md.tpl +66 -0
- flwr/cli/new/templates/app/README.md.tpl +16 -32
- flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +1 -0
- flwr/cli/new/templates/app/code/__init__.py.tpl +1 -1
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +58 -0
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +55 -0
- flwr/cli/new/templates/app/code/client.jax.py.tpl +50 -0
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +73 -0
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +7 -7
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +30 -21
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +63 -0
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +57 -1
- flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +36 -0
- flwr/cli/new/templates/app/code/flwr_tune/__init__.py +15 -0
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +126 -0
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +87 -0
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +78 -0
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +94 -0
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +83 -0
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +80 -0
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +46 -0
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +38 -0
- flwr/cli/new/templates/app/code/server.jax.py.tpl +26 -0
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +31 -0
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +22 -9
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +21 -18
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +36 -0
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +29 -1
- flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +1 -0
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +102 -0
- flwr/cli/new/templates/app/code/task.jax.py.tpl +57 -0
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +102 -0
- flwr/cli/new/templates/app/code/task.numpy.py.tpl +7 -0
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +29 -24
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +67 -0
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +53 -0
- flwr/cli/new/templates/app/code/utils.baseline.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +138 -0
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +68 -0
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +46 -0
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +35 -0
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +39 -0
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +25 -12
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +29 -14
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +35 -0
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +29 -14
- flwr/cli/run/__init__.py +1 -0
- flwr/cli/run/run.py +212 -34
- flwr/cli/stop.py +130 -0
- flwr/cli/utils.py +240 -5
- flwr/client/__init__.py +3 -2
- flwr/client/app.py +432 -255
- flwr/client/client.py +1 -11
- flwr/client/client_app.py +74 -13
- flwr/client/clientapp/__init__.py +22 -0
- flwr/client/clientapp/app.py +259 -0
- flwr/client/clientapp/clientappio_servicer.py +244 -0
- flwr/client/clientapp/utils.py +115 -0
- flwr/client/dpfedavg_numpy_client.py +7 -8
- flwr/client/grpc_adapter_client/__init__.py +15 -0
- flwr/client/grpc_adapter_client/connection.py +98 -0
- flwr/client/grpc_client/connection.py +21 -7
- flwr/client/grpc_rere_client/__init__.py +1 -1
- flwr/client/grpc_rere_client/client_interceptor.py +176 -0
- flwr/client/grpc_rere_client/connection.py +163 -56
- flwr/client/grpc_rere_client/grpc_adapter.py +167 -0
- flwr/client/heartbeat.py +74 -0
- flwr/client/message_handler/__init__.py +1 -1
- flwr/client/message_handler/message_handler.py +10 -11
- flwr/client/mod/__init__.py +5 -5
- flwr/client/mod/centraldp_mods.py +4 -2
- flwr/client/mod/comms_mods.py +5 -4
- flwr/client/mod/localdp_mod.py +10 -5
- flwr/client/mod/secure_aggregation/__init__.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +26 -26
- flwr/client/mod/utils.py +2 -4
- flwr/client/nodestate/__init__.py +26 -0
- flwr/client/nodestate/in_memory_nodestate.py +38 -0
- flwr/client/nodestate/nodestate.py +31 -0
- flwr/client/nodestate/nodestate_factory.py +38 -0
- flwr/client/numpy_client.py +8 -31
- flwr/client/rest_client/__init__.py +1 -1
- flwr/client/rest_client/connection.py +199 -176
- flwr/client/run_info_store.py +112 -0
- flwr/client/supernode/__init__.py +24 -0
- flwr/client/supernode/app.py +321 -0
- flwr/client/typing.py +1 -0
- flwr/common/__init__.py +17 -11
- flwr/common/address.py +47 -3
- flwr/common/args.py +153 -0
- flwr/common/auth_plugin/__init__.py +24 -0
- flwr/common/auth_plugin/auth_plugin.py +121 -0
- flwr/common/config.py +243 -0
- flwr/common/constant.py +135 -1
- flwr/common/context.py +32 -2
- flwr/common/date.py +22 -4
- flwr/common/differential_privacy.py +2 -2
- flwr/common/dp.py +2 -4
- flwr/common/exit_handlers.py +3 -3
- flwr/common/grpc.py +164 -5
- flwr/common/logger.py +230 -12
- flwr/common/message.py +191 -106
- flwr/common/object_ref.py +179 -44
- flwr/common/pyproject.py +1 -0
- flwr/common/record/__init__.py +2 -1
- flwr/common/record/configsrecord.py +58 -18
- flwr/common/record/metricsrecord.py +57 -17
- flwr/common/record/parametersrecord.py +88 -20
- flwr/common/record/recordset.py +153 -30
- flwr/common/record/typeddict.py +30 -55
- flwr/common/recordset_compat.py +31 -12
- flwr/common/retry_invoker.py +123 -30
- flwr/common/secure_aggregation/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/shamir.py +11 -11
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +68 -4
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +17 -17
- flwr/common/secure_aggregation/quantization.py +8 -8
- flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
- flwr/common/secure_aggregation/secaggplus_utils.py +10 -12
- flwr/common/serde.py +304 -23
- flwr/common/telemetry.py +65 -29
- flwr/common/typing.py +120 -19
- flwr/common/version.py +17 -3
- flwr/proto/clientappio_pb2.py +45 -0
- flwr/proto/clientappio_pb2.pyi +132 -0
- flwr/proto/clientappio_pb2_grpc.py +135 -0
- flwr/proto/clientappio_pb2_grpc.pyi +53 -0
- flwr/proto/exec_pb2.py +62 -0
- flwr/proto/exec_pb2.pyi +212 -0
- flwr/proto/exec_pb2_grpc.py +237 -0
- flwr/proto/exec_pb2_grpc.pyi +93 -0
- flwr/proto/fab_pb2.py +31 -0
- flwr/proto/fab_pb2.pyi +65 -0
- flwr/proto/fab_pb2_grpc.py +4 -0
- flwr/proto/fab_pb2_grpc.pyi +4 -0
- flwr/proto/fleet_pb2.py +42 -23
- flwr/proto/fleet_pb2.pyi +123 -1
- flwr/proto/fleet_pb2_grpc.py +170 -0
- flwr/proto/fleet_pb2_grpc.pyi +61 -0
- flwr/proto/grpcadapter_pb2.py +32 -0
- flwr/proto/grpcadapter_pb2.pyi +43 -0
- flwr/proto/grpcadapter_pb2_grpc.py +66 -0
- flwr/proto/grpcadapter_pb2_grpc.pyi +24 -0
- flwr/proto/log_pb2.py +29 -0
- flwr/proto/log_pb2.pyi +39 -0
- flwr/proto/log_pb2_grpc.py +4 -0
- flwr/proto/log_pb2_grpc.pyi +4 -0
- flwr/proto/message_pb2.py +41 -0
- flwr/proto/message_pb2.pyi +128 -0
- flwr/proto/message_pb2_grpc.py +4 -0
- flwr/proto/message_pb2_grpc.pyi +4 -0
- flwr/proto/node_pb2.py +2 -2
- flwr/proto/node_pb2.pyi +1 -4
- flwr/proto/recordset_pb2.py +35 -33
- flwr/proto/recordset_pb2.pyi +40 -14
- flwr/proto/run_pb2.py +64 -0
- flwr/proto/run_pb2.pyi +268 -0
- flwr/proto/run_pb2_grpc.py +4 -0
- flwr/proto/run_pb2_grpc.pyi +4 -0
- flwr/proto/serverappio_pb2.py +52 -0
- flwr/proto/{driver_pb2.pyi → serverappio_pb2.pyi} +62 -20
- flwr/proto/serverappio_pb2_grpc.py +410 -0
- flwr/proto/serverappio_pb2_grpc.pyi +160 -0
- flwr/proto/simulationio_pb2.py +38 -0
- flwr/proto/simulationio_pb2.pyi +65 -0
- flwr/proto/simulationio_pb2_grpc.py +239 -0
- flwr/proto/simulationio_pb2_grpc.pyi +94 -0
- flwr/proto/task_pb2.py +7 -8
- flwr/proto/task_pb2.pyi +8 -5
- flwr/proto/transport_pb2.py +8 -8
- flwr/proto/transport_pb2.pyi +9 -6
- flwr/server/__init__.py +2 -10
- flwr/server/app.py +579 -402
- flwr/server/client_manager.py +8 -6
- flwr/server/compat/app.py +6 -62
- flwr/server/compat/app_utils.py +14 -9
- flwr/server/compat/driver_client_proxy.py +25 -59
- flwr/server/compat/legacy_context.py +5 -4
- flwr/server/driver/__init__.py +2 -0
- flwr/server/driver/driver.py +36 -131
- flwr/server/driver/grpc_driver.py +220 -81
- flwr/server/driver/inmemory_driver.py +183 -0
- flwr/server/history.py +28 -29
- flwr/server/run_serverapp.py +15 -126
- flwr/server/server.py +50 -44
- flwr/server/server_app.py +59 -10
- flwr/server/serverapp/__init__.py +22 -0
- flwr/server/serverapp/app.py +256 -0
- flwr/server/serverapp_components.py +52 -0
- flwr/server/strategy/__init__.py +2 -2
- flwr/server/strategy/aggregate.py +37 -23
- flwr/server/strategy/bulyan.py +9 -9
- flwr/server/strategy/dp_adaptive_clipping.py +25 -25
- flwr/server/strategy/dp_fixed_clipping.py +23 -22
- flwr/server/strategy/dpfedavg_adaptive.py +8 -8
- flwr/server/strategy/dpfedavg_fixed.py +13 -12
- flwr/server/strategy/fault_tolerant_fedavg.py +11 -11
- flwr/server/strategy/fedadagrad.py +9 -9
- flwr/server/strategy/fedadam.py +20 -10
- flwr/server/strategy/fedavg.py +16 -16
- flwr/server/strategy/fedavg_android.py +17 -17
- flwr/server/strategy/fedavgm.py +9 -9
- flwr/server/strategy/fedmedian.py +5 -5
- flwr/server/strategy/fedopt.py +6 -6
- flwr/server/strategy/fedprox.py +7 -7
- flwr/server/strategy/fedtrimmedavg.py +8 -8
- flwr/server/strategy/fedxgb_bagging.py +12 -12
- flwr/server/strategy/fedxgb_cyclic.py +10 -10
- flwr/server/strategy/fedxgb_nn_avg.py +6 -6
- flwr/server/strategy/fedyogi.py +9 -9
- flwr/server/strategy/krum.py +9 -9
- flwr/server/strategy/qfedavg.py +16 -16
- flwr/server/strategy/strategy.py +10 -10
- flwr/server/superlink/driver/__init__.py +2 -2
- flwr/server/superlink/driver/serverappio_grpc.py +61 -0
- flwr/server/superlink/driver/serverappio_servicer.py +361 -0
- flwr/server/superlink/ffs/__init__.py +24 -0
- flwr/server/superlink/ffs/disk_ffs.py +108 -0
- flwr/server/superlink/ffs/ffs.py +79 -0
- flwr/server/superlink/ffs/ffs_factory.py +47 -0
- flwr/server/superlink/fleet/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_adapter/__init__.py +15 -0
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +162 -0
- flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +4 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +5 -154
- flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +120 -13
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +228 -0
- flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +156 -13
- flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/rest_rere/rest_api.py +119 -81
- flwr/server/superlink/fleet/vce/__init__.py +1 -0
- flwr/server/superlink/fleet/vce/backend/__init__.py +4 -4
- flwr/server/superlink/fleet/vce/backend/backend.py +8 -9
- flwr/server/superlink/fleet/vce/backend/raybackend.py +87 -68
- flwr/server/superlink/fleet/vce/vce_api.py +208 -146
- flwr/server/superlink/linkstate/__init__.py +28 -0
- flwr/server/superlink/linkstate/in_memory_linkstate.py +569 -0
- flwr/server/superlink/linkstate/linkstate.py +376 -0
- flwr/server/superlink/{state/state_factory.py → linkstate/linkstate_factory.py} +19 -10
- flwr/server/superlink/linkstate/sqlite_linkstate.py +1196 -0
- flwr/server/superlink/linkstate/utils.py +399 -0
- flwr/server/superlink/simulation/__init__.py +15 -0
- flwr/server/superlink/simulation/simulationio_grpc.py +65 -0
- flwr/server/superlink/simulation/simulationio_servicer.py +186 -0
- flwr/server/superlink/utils.py +65 -0
- flwr/server/typing.py +2 -0
- flwr/server/utils/__init__.py +1 -1
- flwr/server/utils/tensorboard.py +5 -5
- flwr/server/utils/validator.py +40 -45
- flwr/server/workflow/default_workflows.py +70 -26
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -0
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +40 -27
- flwr/simulation/__init__.py +12 -5
- flwr/simulation/app.py +247 -315
- flwr/simulation/legacy_app.py +404 -0
- flwr/simulation/ray_transport/__init__.py +1 -1
- flwr/simulation/ray_transport/ray_actor.py +42 -67
- flwr/simulation/ray_transport/ray_client_proxy.py +37 -17
- flwr/simulation/ray_transport/utils.py +1 -0
- flwr/simulation/run_simulation.py +306 -163
- flwr/simulation/simulationio_connection.py +89 -0
- flwr/superexec/__init__.py +15 -0
- flwr/superexec/app.py +59 -0
- flwr/superexec/deployment.py +188 -0
- flwr/superexec/exec_grpc.py +80 -0
- flwr/superexec/exec_servicer.py +231 -0
- flwr/superexec/exec_user_auth_interceptor.py +101 -0
- flwr/superexec/executor.py +96 -0
- flwr/superexec/simulation.py +124 -0
- {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.15.0.dev20250115.dist-info}/METADATA +33 -26
- flwr_nightly-1.15.0.dev20250115.dist-info/RECORD +328 -0
- flwr_nightly-1.15.0.dev20250115.dist-info/entry_points.txt +12 -0
- flwr/cli/flower_toml.py +0 -140
- flwr/cli/new/templates/app/flower.toml.tpl +0 -13
- flwr/cli/new/templates/app/requirements.numpy.txt.tpl +0 -2
- flwr/cli/new/templates/app/requirements.pytorch.txt.tpl +0 -4
- flwr/cli/new/templates/app/requirements.tensorflow.txt.tpl +0 -4
- flwr/client/node_state.py +0 -48
- flwr/client/node_state_tests.py +0 -65
- flwr/proto/driver_pb2.py +0 -44
- flwr/proto/driver_pb2_grpc.py +0 -169
- flwr/proto/driver_pb2_grpc.pyi +0 -66
- flwr/server/superlink/driver/driver_grpc.py +0 -54
- flwr/server/superlink/driver/driver_servicer.py +0 -129
- flwr/server/superlink/state/in_memory_state.py +0 -230
- flwr/server/superlink/state/sqlite_state.py +0 -630
- flwr/server/superlink/state/state.py +0 -154
- flwr_nightly-1.8.0.dev20240315.dist-info/RECORD +0 -211
- flwr_nightly-1.8.0.dev20240315.dist-info/entry_points.txt +0 -9
- {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.15.0.dev20250115.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.15.0.dev20250115.dist-info}/WHEEL +0 -0
flwr/server/run_serverapp.py
CHANGED
@@ -15,40 +15,38 @@
|
|
15
15
|
"""Run ServerApp."""
|
16
16
|
|
17
17
|
|
18
|
-
import
|
19
|
-
import sys
|
20
|
-
from logging import DEBUG, INFO, WARN
|
21
|
-
from pathlib import Path
|
18
|
+
from logging import DEBUG, ERROR
|
22
19
|
from typing import Optional
|
23
20
|
|
24
|
-
from flwr.common import Context, EventType,
|
25
|
-
from flwr.common.
|
21
|
+
from flwr.common import Context, EventType, event
|
22
|
+
from flwr.common.exit_handlers import register_exit_handlers
|
23
|
+
from flwr.common.logger import log
|
26
24
|
from flwr.common.object_ref import load_app
|
27
25
|
|
28
|
-
from .driver
|
26
|
+
from .driver import Driver
|
29
27
|
from .server_app import LoadServerAppError, ServerApp
|
30
28
|
|
31
29
|
|
32
30
|
def run(
|
33
31
|
driver: Driver,
|
32
|
+
context: Context,
|
34
33
|
server_app_dir: str,
|
35
34
|
server_app_attr: Optional[str] = None,
|
36
35
|
loaded_server_app: Optional[ServerApp] = None,
|
37
|
-
) ->
|
36
|
+
) -> Context:
|
38
37
|
"""Run ServerApp with a given Driver."""
|
39
38
|
if not (server_app_attr is None) ^ (loaded_server_app is None):
|
40
39
|
raise ValueError(
|
41
40
|
"Either `server_app_attr` or `loaded_server_app` should be set "
|
42
|
-
"but not both.
|
41
|
+
"but not both."
|
43
42
|
)
|
44
43
|
|
45
|
-
if server_app_dir is not None:
|
46
|
-
sys.path.insert(0, server_app_dir)
|
47
|
-
|
48
44
|
# Load ServerApp if needed
|
49
45
|
def _load() -> ServerApp:
|
50
46
|
if server_app_attr:
|
51
|
-
server_app: ServerApp = load_app(
|
47
|
+
server_app: ServerApp = load_app(
|
48
|
+
server_app_attr, LoadServerAppError, server_app_dir
|
49
|
+
)
|
52
50
|
|
53
51
|
if not isinstance(server_app, ServerApp):
|
54
52
|
raise LoadServerAppError(
|
@@ -61,127 +59,18 @@ def run(
|
|
61
59
|
|
62
60
|
server_app = _load()
|
63
61
|
|
64
|
-
# Initialize Context
|
65
|
-
context = Context(state=RecordSet())
|
66
|
-
|
67
62
|
# Call ServerApp
|
68
63
|
server_app(driver=driver, context=context)
|
69
64
|
|
70
65
|
log(DEBUG, "ServerApp finished running.")
|
66
|
+
return context
|
71
67
|
|
72
68
|
|
73
69
|
def run_server_app() -> None:
|
74
70
|
"""Run Flower server app."""
|
75
71
|
event(EventType.RUN_SERVER_APP_ENTER)
|
76
|
-
|
77
|
-
args = _parse_args_run_server_app().parse_args()
|
78
|
-
|
79
|
-
update_console_handler(
|
80
|
-
level=DEBUG if args.verbose else INFO,
|
81
|
-
timestamps=args.verbose,
|
82
|
-
colored=True,
|
83
|
-
)
|
84
|
-
|
85
|
-
# Obtain certificates
|
86
|
-
if args.insecure:
|
87
|
-
if args.root_certificates is not None:
|
88
|
-
sys.exit(
|
89
|
-
"Conflicting options: The '--insecure' flag disables HTTPS, "
|
90
|
-
"but '--root-certificates' was also specified. Please remove "
|
91
|
-
"the '--root-certificates' option when running in insecure mode, "
|
92
|
-
"or omit '--insecure' to use HTTPS."
|
93
|
-
)
|
94
|
-
log(
|
95
|
-
WARN,
|
96
|
-
"Option `--insecure` was set. "
|
97
|
-
"Starting insecure HTTP client connected to %s.",
|
98
|
-
args.server,
|
99
|
-
)
|
100
|
-
root_certificates = None
|
101
|
-
else:
|
102
|
-
# Load the certificates if provided, or load the system certificates
|
103
|
-
cert_path = args.root_certificates
|
104
|
-
if cert_path is None:
|
105
|
-
root_certificates = None
|
106
|
-
else:
|
107
|
-
root_certificates = Path(cert_path).read_bytes()
|
108
|
-
log(
|
109
|
-
DEBUG,
|
110
|
-
"Starting secure HTTPS client connected to %s "
|
111
|
-
"with the following certificates: %s.",
|
112
|
-
args.server,
|
113
|
-
cert_path,
|
114
|
-
)
|
115
|
-
|
116
|
-
log(
|
117
|
-
DEBUG,
|
118
|
-
"Flower will load ServerApp `%s`",
|
119
|
-
getattr(args, "server-app"),
|
120
|
-
)
|
121
|
-
|
122
72
|
log(
|
123
|
-
|
124
|
-
"
|
125
|
-
root_certificates,
|
73
|
+
ERROR,
|
74
|
+
"The command `flower-server-app` has been replaced by `flwr run`.",
|
126
75
|
)
|
127
|
-
|
128
|
-
server_app_dir = args.dir
|
129
|
-
server_app_attr = getattr(args, "server-app")
|
130
|
-
|
131
|
-
# Initialize Driver
|
132
|
-
driver = Driver(
|
133
|
-
driver_service_address=args.server,
|
134
|
-
root_certificates=root_certificates,
|
135
|
-
)
|
136
|
-
|
137
|
-
# Run the Server App with the Driver
|
138
|
-
run(driver=driver, server_app_dir=server_app_dir, server_app_attr=server_app_attr)
|
139
|
-
|
140
|
-
# Clean up
|
141
|
-
driver.close()
|
142
|
-
|
143
|
-
event(EventType.RUN_SERVER_APP_LEAVE)
|
144
|
-
|
145
|
-
|
146
|
-
def _parse_args_run_server_app() -> argparse.ArgumentParser:
|
147
|
-
"""Parse flower-server-app command line arguments."""
|
148
|
-
parser = argparse.ArgumentParser(
|
149
|
-
description="Start a Flower server app",
|
150
|
-
)
|
151
|
-
|
152
|
-
parser.add_argument(
|
153
|
-
"server-app",
|
154
|
-
help="For example: `server:app` or `project.package.module:wrapper.app`",
|
155
|
-
)
|
156
|
-
parser.add_argument(
|
157
|
-
"--insecure",
|
158
|
-
action="store_true",
|
159
|
-
help="Run the server app without HTTPS. By default, the app runs with "
|
160
|
-
"HTTPS enabled. Use this flag only if you understand the risks.",
|
161
|
-
)
|
162
|
-
parser.add_argument(
|
163
|
-
"--verbose",
|
164
|
-
action="store_true",
|
165
|
-
help="Set the logging to `DEBUG`.",
|
166
|
-
)
|
167
|
-
parser.add_argument(
|
168
|
-
"--root-certificates",
|
169
|
-
metavar="ROOT_CERT",
|
170
|
-
type=str,
|
171
|
-
help="Specifies the path to the PEM-encoded root certificate file for "
|
172
|
-
"establishing secure HTTPS connections.",
|
173
|
-
)
|
174
|
-
parser.add_argument(
|
175
|
-
"--server",
|
176
|
-
default="0.0.0.0:9091",
|
177
|
-
help="Server address",
|
178
|
-
)
|
179
|
-
parser.add_argument(
|
180
|
-
"--dir",
|
181
|
-
default="",
|
182
|
-
help="Add specified directory to the PYTHONPATH and load Flower "
|
183
|
-
"app from there."
|
184
|
-
" Default: current working directory.",
|
185
|
-
)
|
186
|
-
|
187
|
-
return parser
|
76
|
+
register_exit_handlers(event_type=EventType.RUN_SERVER_APP_LEAVE)
|
flwr/server/server.py
CHANGED
@@ -19,7 +19,7 @@ import concurrent.futures
|
|
19
19
|
import io
|
20
20
|
import timeit
|
21
21
|
from logging import INFO, WARN
|
22
|
-
from typing import
|
22
|
+
from typing import Optional, Union
|
23
23
|
|
24
24
|
from flwr.common import (
|
25
25
|
Code,
|
@@ -41,17 +41,17 @@ from flwr.server.strategy import FedAvg, Strategy
|
|
41
41
|
|
42
42
|
from .server_config import ServerConfig
|
43
43
|
|
44
|
-
FitResultsAndFailures =
|
45
|
-
|
46
|
-
|
44
|
+
FitResultsAndFailures = tuple[
|
45
|
+
list[tuple[ClientProxy, FitRes]],
|
46
|
+
list[Union[tuple[ClientProxy, FitRes], BaseException]],
|
47
47
|
]
|
48
|
-
EvaluateResultsAndFailures =
|
49
|
-
|
50
|
-
|
48
|
+
EvaluateResultsAndFailures = tuple[
|
49
|
+
list[tuple[ClientProxy, EvaluateRes]],
|
50
|
+
list[Union[tuple[ClientProxy, EvaluateRes], BaseException]],
|
51
51
|
]
|
52
|
-
ReconnectResultsAndFailures =
|
53
|
-
|
54
|
-
|
52
|
+
ReconnectResultsAndFailures = tuple[
|
53
|
+
list[tuple[ClientProxy, DisconnectRes]],
|
54
|
+
list[Union[tuple[ClientProxy, DisconnectRes], BaseException]],
|
55
55
|
]
|
56
56
|
|
57
57
|
|
@@ -84,14 +84,14 @@ class Server:
|
|
84
84
|
return self._client_manager
|
85
85
|
|
86
86
|
# pylint: disable=too-many-locals
|
87
|
-
def fit(self, num_rounds: int, timeout: Optional[float]) ->
|
87
|
+
def fit(self, num_rounds: int, timeout: Optional[float]) -> tuple[History, float]:
|
88
88
|
"""Run federated averaging for a number of rounds."""
|
89
89
|
history = History()
|
90
90
|
|
91
91
|
# Initialize parameters
|
92
92
|
log(INFO, "[INIT]")
|
93
93
|
self.parameters = self._get_initial_parameters(server_round=0, timeout=timeout)
|
94
|
-
log(INFO, "
|
94
|
+
log(INFO, "Starting evaluation of initial global parameters")
|
95
95
|
res = self.strategy.evaluate(0, parameters=self.parameters)
|
96
96
|
if res is not None:
|
97
97
|
log(
|
@@ -102,6 +102,8 @@ class Server:
|
|
102
102
|
)
|
103
103
|
history.add_loss_centralized(server_round=0, loss=res[0])
|
104
104
|
history.add_metrics_centralized(server_round=0, metrics=res[1])
|
105
|
+
else:
|
106
|
+
log(INFO, "Evaluation returned no results (`None`)")
|
105
107
|
|
106
108
|
# Run federated learning for num_rounds
|
107
109
|
start_time = timeit.default_timer()
|
@@ -161,7 +163,7 @@ class Server:
|
|
161
163
|
server_round: int,
|
162
164
|
timeout: Optional[float],
|
163
165
|
) -> Optional[
|
164
|
-
|
166
|
+
tuple[Optional[float], dict[str, Scalar], EvaluateResultsAndFailures]
|
165
167
|
]:
|
166
168
|
"""Validate current global model on a number of clients."""
|
167
169
|
# Get clients and their respective instructions from strategy
|
@@ -195,9 +197,9 @@ class Server:
|
|
195
197
|
)
|
196
198
|
|
197
199
|
# Aggregate the evaluation results
|
198
|
-
aggregated_result:
|
200
|
+
aggregated_result: tuple[
|
199
201
|
Optional[float],
|
200
|
-
|
202
|
+
dict[str, Scalar],
|
201
203
|
] = self.strategy.aggregate_evaluate(server_round, results, failures)
|
202
204
|
|
203
205
|
loss_aggregated, metrics_aggregated = aggregated_result
|
@@ -208,7 +210,7 @@ class Server:
|
|
208
210
|
server_round: int,
|
209
211
|
timeout: Optional[float],
|
210
212
|
) -> Optional[
|
211
|
-
|
213
|
+
tuple[Optional[Parameters], dict[str, Scalar], FitResultsAndFailures]
|
212
214
|
]:
|
213
215
|
"""Perform a single round of federated averaging."""
|
214
216
|
# Get clients and their respective instructions from strategy
|
@@ -243,9 +245,9 @@ class Server:
|
|
243
245
|
)
|
244
246
|
|
245
247
|
# Aggregate training results
|
246
|
-
aggregated_result:
|
248
|
+
aggregated_result: tuple[
|
247
249
|
Optional[Parameters],
|
248
|
-
|
250
|
+
dict[str, Scalar],
|
249
251
|
] = self.strategy.aggregate_fit(server_round, results, failures)
|
250
252
|
|
251
253
|
parameters_aggregated, metrics_aggregated = aggregated_result
|
@@ -282,12 +284,19 @@ class Server:
|
|
282
284
|
get_parameters_res = random_client.get_parameters(
|
283
285
|
ins=ins, timeout=timeout, group_id=server_round
|
284
286
|
)
|
285
|
-
|
287
|
+
if get_parameters_res.status.code == Code.OK:
|
288
|
+
log(INFO, "Received initial parameters from one random client")
|
289
|
+
else:
|
290
|
+
log(
|
291
|
+
WARN,
|
292
|
+
"Failed to receive initial parameters from the client."
|
293
|
+
" Empty initial parameters will be used.",
|
294
|
+
)
|
286
295
|
return get_parameters_res.parameters
|
287
296
|
|
288
297
|
|
289
298
|
def reconnect_clients(
|
290
|
-
client_instructions:
|
299
|
+
client_instructions: list[tuple[ClientProxy, ReconnectIns]],
|
291
300
|
max_workers: Optional[int],
|
292
301
|
timeout: Optional[float],
|
293
302
|
) -> ReconnectResultsAndFailures:
|
@@ -303,8 +312,8 @@ def reconnect_clients(
|
|
303
312
|
)
|
304
313
|
|
305
314
|
# Gather results
|
306
|
-
results:
|
307
|
-
failures:
|
315
|
+
results: list[tuple[ClientProxy, DisconnectRes]] = []
|
316
|
+
failures: list[Union[tuple[ClientProxy, DisconnectRes], BaseException]] = []
|
308
317
|
for future in finished_fs:
|
309
318
|
failure = future.exception()
|
310
319
|
if failure is not None:
|
@@ -319,7 +328,7 @@ def reconnect_client(
|
|
319
328
|
client: ClientProxy,
|
320
329
|
reconnect: ReconnectIns,
|
321
330
|
timeout: Optional[float],
|
322
|
-
) ->
|
331
|
+
) -> tuple[ClientProxy, DisconnectRes]:
|
323
332
|
"""Instruct client to disconnect and (optionally) reconnect later."""
|
324
333
|
disconnect = client.reconnect(
|
325
334
|
reconnect,
|
@@ -330,7 +339,7 @@ def reconnect_client(
|
|
330
339
|
|
331
340
|
|
332
341
|
def fit_clients(
|
333
|
-
client_instructions:
|
342
|
+
client_instructions: list[tuple[ClientProxy, FitIns]],
|
334
343
|
max_workers: Optional[int],
|
335
344
|
timeout: Optional[float],
|
336
345
|
group_id: int,
|
@@ -347,8 +356,8 @@ def fit_clients(
|
|
347
356
|
)
|
348
357
|
|
349
358
|
# Gather results
|
350
|
-
results:
|
351
|
-
failures:
|
359
|
+
results: list[tuple[ClientProxy, FitRes]] = []
|
360
|
+
failures: list[Union[tuple[ClientProxy, FitRes], BaseException]] = []
|
352
361
|
for future in finished_fs:
|
353
362
|
_handle_finished_future_after_fit(
|
354
363
|
future=future, results=results, failures=failures
|
@@ -358,7 +367,7 @@ def fit_clients(
|
|
358
367
|
|
359
368
|
def fit_client(
|
360
369
|
client: ClientProxy, ins: FitIns, timeout: Optional[float], group_id: int
|
361
|
-
) ->
|
370
|
+
) -> tuple[ClientProxy, FitRes]:
|
362
371
|
"""Refine parameters on a single client."""
|
363
372
|
fit_res = client.fit(ins, timeout=timeout, group_id=group_id)
|
364
373
|
return client, fit_res
|
@@ -366,8 +375,8 @@ def fit_client(
|
|
366
375
|
|
367
376
|
def _handle_finished_future_after_fit(
|
368
377
|
future: concurrent.futures.Future, # type: ignore
|
369
|
-
results:
|
370
|
-
failures:
|
378
|
+
results: list[tuple[ClientProxy, FitRes]],
|
379
|
+
failures: list[Union[tuple[ClientProxy, FitRes], BaseException]],
|
371
380
|
) -> None:
|
372
381
|
"""Convert finished future into either a result or a failure."""
|
373
382
|
# Check if there was an exception
|
@@ -377,7 +386,7 @@ def _handle_finished_future_after_fit(
|
|
377
386
|
return
|
378
387
|
|
379
388
|
# Successfully received a result from a client
|
380
|
-
result:
|
389
|
+
result: tuple[ClientProxy, FitRes] = future.result()
|
381
390
|
_, res = result
|
382
391
|
|
383
392
|
# Check result status code
|
@@ -390,7 +399,7 @@ def _handle_finished_future_after_fit(
|
|
390
399
|
|
391
400
|
|
392
401
|
def evaluate_clients(
|
393
|
-
client_instructions:
|
402
|
+
client_instructions: list[tuple[ClientProxy, EvaluateIns]],
|
394
403
|
max_workers: Optional[int],
|
395
404
|
timeout: Optional[float],
|
396
405
|
group_id: int,
|
@@ -407,8 +416,8 @@ def evaluate_clients(
|
|
407
416
|
)
|
408
417
|
|
409
418
|
# Gather results
|
410
|
-
results:
|
411
|
-
failures:
|
419
|
+
results: list[tuple[ClientProxy, EvaluateRes]] = []
|
420
|
+
failures: list[Union[tuple[ClientProxy, EvaluateRes], BaseException]] = []
|
412
421
|
for future in finished_fs:
|
413
422
|
_handle_finished_future_after_evaluate(
|
414
423
|
future=future, results=results, failures=failures
|
@@ -421,7 +430,7 @@ def evaluate_client(
|
|
421
430
|
ins: EvaluateIns,
|
422
431
|
timeout: Optional[float],
|
423
432
|
group_id: int,
|
424
|
-
) ->
|
433
|
+
) -> tuple[ClientProxy, EvaluateRes]:
|
425
434
|
"""Evaluate parameters on a single client."""
|
426
435
|
evaluate_res = client.evaluate(ins, timeout=timeout, group_id=group_id)
|
427
436
|
return client, evaluate_res
|
@@ -429,8 +438,8 @@ def evaluate_client(
|
|
429
438
|
|
430
439
|
def _handle_finished_future_after_evaluate(
|
431
440
|
future: concurrent.futures.Future, # type: ignore
|
432
|
-
results:
|
433
|
-
failures:
|
441
|
+
results: list[tuple[ClientProxy, EvaluateRes]],
|
442
|
+
failures: list[Union[tuple[ClientProxy, EvaluateRes], BaseException]],
|
434
443
|
) -> None:
|
435
444
|
"""Convert finished future into either a result or a failure."""
|
436
445
|
# Check if there was an exception
|
@@ -440,7 +449,7 @@ def _handle_finished_future_after_evaluate(
|
|
440
449
|
return
|
441
450
|
|
442
451
|
# Successfully received a result from a client
|
443
|
-
result:
|
452
|
+
result: tuple[ClientProxy, EvaluateRes] = future.result()
|
444
453
|
_, res = result
|
445
454
|
|
446
455
|
# Check result status code
|
@@ -457,7 +466,7 @@ def init_defaults(
|
|
457
466
|
config: Optional[ServerConfig],
|
458
467
|
strategy: Optional[Strategy],
|
459
468
|
client_manager: Optional[ClientManager],
|
460
|
-
) ->
|
469
|
+
) -> tuple[Server, ServerConfig]:
|
461
470
|
"""Create server instance if none was given."""
|
462
471
|
if server is None:
|
463
472
|
if client_manager is None:
|
@@ -486,12 +495,9 @@ def run_fl(
|
|
486
495
|
|
487
496
|
log(INFO, "")
|
488
497
|
log(INFO, "[SUMMARY]")
|
489
|
-
log(INFO, "Run finished %s
|
490
|
-
for
|
491
|
-
|
492
|
-
log(INFO, "%s", line.strip("\n"))
|
493
|
-
else:
|
494
|
-
log(INFO, "\t%s", line.strip("\n"))
|
498
|
+
log(INFO, "Run finished %s round(s) in %.2fs", config.num_rounds, elapsed_time)
|
499
|
+
for line in io.StringIO(str(hist)):
|
500
|
+
log(INFO, "\t%s", line.strip("\n"))
|
495
501
|
log(INFO, "")
|
496
502
|
|
497
503
|
# Graceful shutdown
|
flwr/server/server_app.py
CHANGED
@@ -17,7 +17,11 @@
|
|
17
17
|
|
18
18
|
from typing import Callable, Optional
|
19
19
|
|
20
|
-
from flwr.common import Context
|
20
|
+
from flwr.common import Context
|
21
|
+
from flwr.common.logger import (
|
22
|
+
warn_deprecated_feature_with_example,
|
23
|
+
warn_preview_feature,
|
24
|
+
)
|
21
25
|
from flwr.server.strategy import Strategy
|
22
26
|
|
23
27
|
from .client_manager import ClientManager
|
@@ -25,7 +29,20 @@ from .compat import start_driver
|
|
25
29
|
from .driver import Driver
|
26
30
|
from .server import Server
|
27
31
|
from .server_config import ServerConfig
|
28
|
-
from .typing import ServerAppCallable
|
32
|
+
from .typing import ServerAppCallable, ServerFn
|
33
|
+
|
34
|
+
SERVER_FN_USAGE_EXAMPLE = """
|
35
|
+
|
36
|
+
def server_fn(context: Context):
|
37
|
+
server_config = ServerConfig(num_rounds=3)
|
38
|
+
strategy = FedAvg()
|
39
|
+
return ServerAppComponents(
|
40
|
+
strategy=strategy,
|
41
|
+
server_config=server_config,
|
42
|
+
)
|
43
|
+
|
44
|
+
app = ServerApp(server_fn=server_fn)
|
45
|
+
"""
|
29
46
|
|
30
47
|
|
31
48
|
class ServerApp:
|
@@ -35,13 +52,15 @@ class ServerApp:
|
|
35
52
|
--------
|
36
53
|
Use the `ServerApp` with an existing `Strategy`:
|
37
54
|
|
38
|
-
>>>
|
39
|
-
>>>
|
55
|
+
>>> def server_fn(context: Context):
|
56
|
+
>>> server_config = ServerConfig(num_rounds=3)
|
57
|
+
>>> strategy = FedAvg()
|
58
|
+
>>> return ServerAppComponents(
|
59
|
+
>>> strategy=strategy,
|
60
|
+
>>> server_config=server_config,
|
61
|
+
>>> )
|
40
62
|
>>>
|
41
|
-
>>> app = ServerApp()
|
42
|
-
>>> server_config=server_config,
|
43
|
-
>>> strategy=strategy,
|
44
|
-
>>> )
|
63
|
+
>>> app = ServerApp(server_fn=server_fn)
|
45
64
|
|
46
65
|
Use the `ServerApp` with a custom main function:
|
47
66
|
|
@@ -52,23 +71,52 @@ class ServerApp:
|
|
52
71
|
>>> print("ServerApp running")
|
53
72
|
"""
|
54
73
|
|
74
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
55
75
|
def __init__(
|
56
76
|
self,
|
57
77
|
server: Optional[Server] = None,
|
58
78
|
config: Optional[ServerConfig] = None,
|
59
79
|
strategy: Optional[Strategy] = None,
|
60
80
|
client_manager: Optional[ClientManager] = None,
|
81
|
+
server_fn: Optional[ServerFn] = None,
|
61
82
|
) -> None:
|
83
|
+
if any([server, config, strategy, client_manager]):
|
84
|
+
warn_deprecated_feature_with_example(
|
85
|
+
deprecation_message="Passing either `server`, `config`, `strategy` or "
|
86
|
+
"`client_manager` directly to the ServerApp "
|
87
|
+
"constructor is deprecated.",
|
88
|
+
example_message="Pass `ServerApp` arguments wrapped "
|
89
|
+
"in a `flwr.server.ServerAppComponents` object that gets "
|
90
|
+
"returned by a function passed as the `server_fn` argument "
|
91
|
+
"to the `ServerApp` constructor. For example: ",
|
92
|
+
code_example=SERVER_FN_USAGE_EXAMPLE,
|
93
|
+
)
|
94
|
+
|
95
|
+
if server_fn:
|
96
|
+
raise ValueError(
|
97
|
+
"Passing `server_fn` is incompatible with passing the "
|
98
|
+
"other arguments (now deprecated) to ServerApp. "
|
99
|
+
"Use `server_fn` exclusively."
|
100
|
+
)
|
101
|
+
|
62
102
|
self._server = server
|
63
103
|
self._config = config
|
64
104
|
self._strategy = strategy
|
65
105
|
self._client_manager = client_manager
|
106
|
+
self._server_fn = server_fn
|
66
107
|
self._main: Optional[ServerAppCallable] = None
|
67
108
|
|
68
109
|
def __call__(self, driver: Driver, context: Context) -> None:
|
69
110
|
"""Execute `ServerApp`."""
|
70
111
|
# Compatibility mode
|
71
112
|
if not self._main:
|
113
|
+
if self._server_fn:
|
114
|
+
# Execute server_fn()
|
115
|
+
components = self._server_fn(context)
|
116
|
+
self._server = components.server
|
117
|
+
self._config = components.config
|
118
|
+
self._strategy = components.strategy
|
119
|
+
self._client_manager = components.client_manager
|
72
120
|
start_driver(
|
73
121
|
server=self._server,
|
74
122
|
config=self._config,
|
@@ -79,7 +127,6 @@ class ServerApp:
|
|
79
127
|
return
|
80
128
|
|
81
129
|
# New execution mode
|
82
|
-
context = Context(state=RecordSet())
|
83
130
|
self._main(driver, context)
|
84
131
|
|
85
132
|
def main(self) -> Callable[[ServerAppCallable], ServerAppCallable]:
|
@@ -105,7 +152,7 @@ class ServerApp:
|
|
105
152
|
>>> server_config = ServerConfig(num_rounds=3)
|
106
153
|
>>> strategy = FedAvg()
|
107
154
|
>>>
|
108
|
-
>>> app = ServerApp(
|
155
|
+
>>> app = ServerApp(
|
109
156
|
>>> server_config=server_config,
|
110
157
|
>>> strategy=strategy,
|
111
158
|
>>> )
|
@@ -120,6 +167,8 @@ class ServerApp:
|
|
120
167
|
""",
|
121
168
|
)
|
122
169
|
|
170
|
+
warn_preview_feature("ServerApp-register-main-function")
|
171
|
+
|
123
172
|
# Register provided function with the ServerApp object
|
124
173
|
self._main = main_fn
|
125
174
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
# ==============================================================================
|
15
|
+
"""Flower AppIO service."""
|
16
|
+
|
17
|
+
|
18
|
+
from .app import flwr_serverapp as flwr_serverapp
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
"flwr_serverapp",
|
22
|
+
]
|