flwr-nightly 1.14.0.dev20241216__py3-none-any.whl → 1.15.0.dev20250112__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flwr/cli/cli_user_auth_interceptor.py +6 -2
- flwr/cli/log.py +8 -6
- flwr/cli/login/login.py +11 -4
- flwr/cli/ls.py +7 -4
- flwr/cli/new/templates/app/.gitignore.tpl +3 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
- flwr/cli/run/run.py +7 -2
- flwr/cli/stop.py +3 -2
- flwr/cli/utils.py +83 -14
- flwr/client/app.py +17 -9
- flwr/client/client.py +0 -32
- flwr/client/grpc_rere_client/client_interceptor.py +6 -0
- flwr/client/grpc_rere_client/grpc_adapter.py +16 -0
- flwr/client/message_handler/message_handler.py +0 -2
- flwr/client/numpy_client.py +0 -44
- flwr/client/supernode/app.py +1 -2
- flwr/common/auth_plugin/auth_plugin.py +33 -23
- flwr/common/constant.py +2 -0
- flwr/common/grpc.py +154 -3
- flwr/common/record/recordset.py +1 -1
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +45 -0
- flwr/common/telemetry.py +13 -3
- flwr/common/typing.py +20 -0
- flwr/proto/exec_pb2.py +12 -24
- flwr/proto/exec_pb2.pyi +27 -54
- flwr/proto/fleet_pb2.py +40 -27
- flwr/proto/fleet_pb2.pyi +84 -0
- flwr/proto/fleet_pb2_grpc.py +66 -0
- flwr/proto/fleet_pb2_grpc.pyi +20 -0
- flwr/server/app.py +54 -33
- flwr/server/run_serverapp.py +8 -9
- flwr/server/serverapp/app.py +17 -2
- flwr/server/superlink/driver/serverappio_grpc.py +1 -1
- flwr/server/superlink/driver/serverappio_servicer.py +29 -6
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -165
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +16 -0
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -1
- flwr/server/superlink/fleet/vce/vce_api.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +36 -24
- flwr/server/superlink/linkstate/linkstate.py +14 -4
- flwr/server/superlink/linkstate/sqlite_linkstate.py +56 -31
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +13 -0
- flwr/simulation/app.py +15 -4
- flwr/simulation/run_simulation.py +35 -7
- flwr/superexec/exec_grpc.py +1 -1
- flwr/superexec/exec_servicer.py +23 -2
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/METADATA +5 -5
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/RECORD +60 -60
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/entry_points.txt +0 -0
@@ -72,14 +72,14 @@ CREATE TABLE IF NOT EXISTS node(
|
|
72
72
|
|
73
73
|
SQL_CREATE_TABLE_CREDENTIAL = """
|
74
74
|
CREATE TABLE IF NOT EXISTS credential(
|
75
|
-
private_key
|
76
|
-
public_key
|
75
|
+
private_key BLOB PRIMARY KEY,
|
76
|
+
public_key BLOB
|
77
77
|
);
|
78
78
|
"""
|
79
79
|
|
80
80
|
SQL_CREATE_TABLE_PUBLIC_KEY = """
|
81
81
|
CREATE TABLE IF NOT EXISTS public_key(
|
82
|
-
public_key
|
82
|
+
public_key BLOB PRIMARY KEY
|
83
83
|
);
|
84
84
|
"""
|
85
85
|
|
@@ -635,9 +635,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
635
635
|
|
636
636
|
return {UUID(row["task_id"]) for row in rows}
|
637
637
|
|
638
|
-
def create_node(
|
639
|
-
self, ping_interval: float, public_key: Optional[bytes] = None
|
640
|
-
) -> int:
|
638
|
+
def create_node(self, ping_interval: float) -> int:
|
641
639
|
"""Create, store in the link state, and return `node_id`."""
|
642
640
|
# Sample a random uint64 as node_id
|
643
641
|
uint64_node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES)
|
@@ -645,13 +643,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
645
643
|
# Convert the uint64 value to sint64 for SQLite
|
646
644
|
sint64_node_id = convert_uint64_to_sint64(uint64_node_id)
|
647
645
|
|
648
|
-
query = "SELECT node_id FROM node WHERE public_key = :public_key;"
|
649
|
-
row = self.query(query, {"public_key": public_key})
|
650
|
-
|
651
|
-
if len(row) > 0:
|
652
|
-
log(ERROR, "Unexpected node registration failure.")
|
653
|
-
return 0
|
654
|
-
|
655
646
|
query = (
|
656
647
|
"INSERT INTO node "
|
657
648
|
"(node_id, online_until, ping_interval, public_key) "
|
@@ -665,7 +656,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
665
656
|
sint64_node_id,
|
666
657
|
time.time() + ping_interval,
|
667
658
|
ping_interval,
|
668
|
-
|
659
|
+
b"", # Initialize with an empty public key
|
669
660
|
),
|
670
661
|
)
|
671
662
|
except sqlite3.IntegrityError:
|
@@ -675,7 +666,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
675
666
|
# Note: we need to return the uint64 value of the node_id
|
676
667
|
return uint64_node_id
|
677
668
|
|
678
|
-
def delete_node(self, node_id: int
|
669
|
+
def delete_node(self, node_id: int) -> None:
|
679
670
|
"""Delete a node."""
|
680
671
|
# Convert the uint64 value to sint64 for SQLite
|
681
672
|
sint64_node_id = convert_uint64_to_sint64(node_id)
|
@@ -683,10 +674,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
683
674
|
query = "DELETE FROM node WHERE node_id = ?"
|
684
675
|
params = (sint64_node_id,)
|
685
676
|
|
686
|
-
if public_key is not None:
|
687
|
-
query += " AND public_key = ?"
|
688
|
-
params += (public_key,) # type: ignore
|
689
|
-
|
690
677
|
if self.conn is None:
|
691
678
|
raise AttributeError("LinkState is not initialized.")
|
692
679
|
|
@@ -694,7 +681,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
694
681
|
with self.conn:
|
695
682
|
rows = self.conn.execute(query, params)
|
696
683
|
if rows.rowcount < 1:
|
697
|
-
raise ValueError("
|
684
|
+
raise ValueError(f"Node {node_id} not found")
|
698
685
|
except KeyError as exc:
|
699
686
|
log(ERROR, {"query": query, "data": params, "exception": exc})
|
700
687
|
|
@@ -722,6 +709,41 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
722
709
|
result: set[int] = {convert_sint64_to_uint64(row["node_id"]) for row in rows}
|
723
710
|
return result
|
724
711
|
|
712
|
+
def set_node_public_key(self, node_id: int, public_key: bytes) -> None:
|
713
|
+
"""Set `public_key` for the specified `node_id`."""
|
714
|
+
# Convert the uint64 value to sint64 for SQLite
|
715
|
+
sint64_node_id = convert_uint64_to_sint64(node_id)
|
716
|
+
|
717
|
+
# Check if the node exists in the `node` table
|
718
|
+
query = "SELECT 1 FROM node WHERE node_id = ?"
|
719
|
+
if not self.query(query, (sint64_node_id,)):
|
720
|
+
raise ValueError(f"Node {node_id} not found")
|
721
|
+
|
722
|
+
# Check if the public key is already in use in the `node` table
|
723
|
+
query = "SELECT 1 FROM node WHERE public_key = ?"
|
724
|
+
if self.query(query, (public_key,)):
|
725
|
+
raise ValueError("Public key already in use")
|
726
|
+
|
727
|
+
# Update the `node` table to set the public key for the given node ID
|
728
|
+
query = "UPDATE node SET public_key = ? WHERE node_id = ?"
|
729
|
+
self.query(query, (public_key, sint64_node_id))
|
730
|
+
|
731
|
+
def get_node_public_key(self, node_id: int) -> Optional[bytes]:
|
732
|
+
"""Get `public_key` for the specified `node_id`."""
|
733
|
+
# Convert the uint64 value to sint64 for SQLite
|
734
|
+
sint64_node_id = convert_uint64_to_sint64(node_id)
|
735
|
+
|
736
|
+
# Query the public key for the given node_id
|
737
|
+
query = "SELECT public_key FROM node WHERE node_id = ?"
|
738
|
+
rows = self.query(query, (sint64_node_id,))
|
739
|
+
|
740
|
+
# If no result is found, return None
|
741
|
+
if not rows:
|
742
|
+
raise ValueError(f"Node {node_id} not found")
|
743
|
+
|
744
|
+
# Return the public key if it is not empty, otherwise return None
|
745
|
+
return rows[0]["public_key"] or None
|
746
|
+
|
725
747
|
def get_node_id(self, node_public_key: bytes) -> Optional[int]:
|
726
748
|
"""Retrieve stored `node_id` filtered by `node_public_keys`."""
|
727
749
|
query = "SELECT node_id FROM node WHERE public_key = :public_key;"
|
@@ -761,8 +783,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
761
783
|
"federation_options, pending_at, starting_at, running_at, finished_at, "
|
762
784
|
"sub_status, details) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"
|
763
785
|
)
|
764
|
-
if fab_hash:
|
765
|
-
fab_id, fab_version = "", ""
|
766
786
|
override_config_json = json.dumps(override_config)
|
767
787
|
data = [
|
768
788
|
sint64_run_id,
|
@@ -820,6 +840,12 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
820
840
|
public_key = None
|
821
841
|
return public_key
|
822
842
|
|
843
|
+
def clear_supernode_auth_keys_and_credentials(self) -> None:
|
844
|
+
"""Clear stored `node_public_keys` and credentials in the link state if any."""
|
845
|
+
queries = ["DELETE FROM public_key;", "DELETE FROM credential;"]
|
846
|
+
for query in queries:
|
847
|
+
self.query(query)
|
848
|
+
|
823
849
|
def store_node_public_keys(self, public_keys: set[bytes]) -> None:
|
824
850
|
"""Store a set of `node_public_keys` in the link state."""
|
825
851
|
query = "INSERT INTO public_key (public_key) VALUES (?)"
|
@@ -978,17 +1004,16 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
978
1004
|
"""Acknowledge a ping received from a node, serving as a heartbeat."""
|
979
1005
|
sint64_node_id = convert_uint64_to_sint64(node_id)
|
980
1006
|
|
981
|
-
#
|
982
|
-
query = "
|
983
|
-
|
984
|
-
self.query(
|
985
|
-
query, (time.time() + ping_interval, ping_interval, sint64_node_id)
|
986
|
-
)
|
987
|
-
return True
|
988
|
-
except sqlite3.IntegrityError:
|
989
|
-
log(ERROR, "`node_id` does not exist.")
|
1007
|
+
# Check if the node exists in the `node` table
|
1008
|
+
query = "SELECT 1 FROM node WHERE node_id = ?"
|
1009
|
+
if not self.query(query, (sint64_node_id,)):
|
990
1010
|
return False
|
991
1011
|
|
1012
|
+
# Update `online_until` and `ping_interval` for the given `node_id`
|
1013
|
+
query = "UPDATE node SET online_until = ?, ping_interval = ? WHERE node_id = ?"
|
1014
|
+
self.query(query, (time.time() + ping_interval, ping_interval, sint64_node_id))
|
1015
|
+
return True
|
1016
|
+
|
992
1017
|
def get_serverapp_context(self, run_id: int) -> Optional[Context]:
|
993
1018
|
"""Get the context for the specified `run_id`."""
|
994
1019
|
# Retrieve context if any
|
@@ -21,6 +21,7 @@ from typing import Optional
|
|
21
21
|
import grpc
|
22
22
|
|
23
23
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
24
|
+
from flwr.common.grpc import generic_create_grpc_server
|
24
25
|
from flwr.common.logger import log
|
25
26
|
from flwr.proto.simulationio_pb2_grpc import ( # pylint: disable=E0611
|
26
27
|
add_SimulationIoServicer_to_server,
|
@@ -28,7 +29,6 @@ from flwr.proto.simulationio_pb2_grpc import ( # pylint: disable=E0611
|
|
28
29
|
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
29
30
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
30
31
|
|
31
|
-
from ..fleet.grpc_bidi.grpc_server import generic_create_grpc_server
|
32
32
|
from .simulationio_servicer import SimulationIoServicer
|
33
33
|
|
34
34
|
|
@@ -54,6 +54,7 @@ from flwr.proto.simulationio_pb2 import ( # pylint: disable=E0611
|
|
54
54
|
)
|
55
55
|
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
56
56
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
57
|
+
from flwr.server.superlink.utils import abort_if
|
57
58
|
|
58
59
|
|
59
60
|
class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
|
@@ -110,6 +111,15 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
|
|
110
111
|
"""Push Simulation process outputs."""
|
111
112
|
log(DEBUG, "SimultionIoServicer.PushSimulationOutputs")
|
112
113
|
state = self.state_factory.state()
|
114
|
+
|
115
|
+
# Abort if the run is not running
|
116
|
+
abort_if(
|
117
|
+
request.run_id,
|
118
|
+
[Status.PENDING, Status.STARTING, Status.FINISHED],
|
119
|
+
state,
|
120
|
+
context,
|
121
|
+
)
|
122
|
+
|
113
123
|
state.set_serverapp_context(request.run_id, context_from_proto(request.context))
|
114
124
|
return PushSimulationOutputsResponse()
|
115
125
|
|
@@ -120,6 +130,9 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
|
|
120
130
|
log(DEBUG, "SimultionIoServicer.UpdateRunStatus")
|
121
131
|
state = self.state_factory.state()
|
122
132
|
|
133
|
+
# Abort if the run is finished
|
134
|
+
abort_if(request.run_id, [Status.FINISHED], state, context)
|
135
|
+
|
123
136
|
# Update the run status
|
124
137
|
state.update_run_status(
|
125
138
|
run_id=request.run_id, new_status=run_status_from_proto(request.run_status)
|
flwr/simulation/app.py
CHANGED
@@ -24,7 +24,8 @@ from typing import Optional
|
|
24
24
|
|
25
25
|
from flwr.cli.config_utils import get_fab_metadata
|
26
26
|
from flwr.cli.install import install_from_fab
|
27
|
-
from flwr.
|
27
|
+
from flwr.cli.utils import get_sha256_hash
|
28
|
+
from flwr.common import EventType, event
|
28
29
|
from flwr.common.args import add_args_flwr_app_common
|
29
30
|
from flwr.common.config import (
|
30
31
|
get_flwr_dir,
|
@@ -48,6 +49,7 @@ from flwr.common.logger import (
|
|
48
49
|
from flwr.common.serde import (
|
49
50
|
configs_record_from_proto,
|
50
51
|
context_from_proto,
|
52
|
+
context_to_proto,
|
51
53
|
fab_from_proto,
|
52
54
|
run_from_proto,
|
53
55
|
run_status_to_proto,
|
@@ -201,8 +203,17 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
|
|
201
203
|
verbose: bool = fed_opt.get("verbose", False)
|
202
204
|
enable_tf_gpu_growth: bool = fed_opt.get("enable_tf_gpu_growth", False)
|
203
205
|
|
206
|
+
event(
|
207
|
+
EventType.FLWR_SIMULATION_RUN_ENTER,
|
208
|
+
event_details={
|
209
|
+
"backend": "ray",
|
210
|
+
"num-supernodes": num_supernodes,
|
211
|
+
"run-id-hash": get_sha256_hash(run.run_id),
|
212
|
+
},
|
213
|
+
)
|
214
|
+
|
204
215
|
# Launch the simulation
|
205
|
-
_run_simulation(
|
216
|
+
updated_context = _run_simulation(
|
206
217
|
server_app_attr=server_app_attr,
|
207
218
|
client_app_attr=client_app_attr,
|
208
219
|
num_supernodes=num_supernodes,
|
@@ -213,11 +224,11 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
|
|
213
224
|
verbose_logging=verbose,
|
214
225
|
server_app_run_config=fused_config,
|
215
226
|
is_app=True,
|
216
|
-
exit_event=EventType.
|
227
|
+
exit_event=EventType.FLWR_SIMULATION_RUN_LEAVE,
|
217
228
|
)
|
218
229
|
|
219
230
|
# Send resulting context
|
220
|
-
context_proto =
|
231
|
+
context_proto = context_to_proto(updated_context)
|
221
232
|
out_req = PushSimulationOutputsRequest(
|
222
233
|
run_id=run.run_id, context=context_proto
|
223
234
|
)
|
@@ -24,9 +24,11 @@ import threading
|
|
24
24
|
import traceback
|
25
25
|
from logging import DEBUG, ERROR, INFO, WARNING
|
26
26
|
from pathlib import Path
|
27
|
+
from queue import Empty, Queue
|
27
28
|
from typing import Any, Optional
|
28
29
|
|
29
30
|
from flwr.cli.config_utils import load_and_validate
|
31
|
+
from flwr.cli.utils import get_sha256_hash
|
30
32
|
from flwr.client import ClientApp
|
31
33
|
from flwr.common import Context, EventType, RecordSet, event, log, now
|
32
34
|
from flwr.common.config import get_fused_config_from_dir, parse_config_args
|
@@ -126,7 +128,7 @@ def run_simulation_from_cli() -> None:
|
|
126
128
|
run = Run.create_empty(run_id)
|
127
129
|
run.override_config = override_config
|
128
130
|
|
129
|
-
_run_simulation(
|
131
|
+
_ = _run_simulation(
|
130
132
|
server_app_attr=server_app_attr,
|
131
133
|
client_app_attr=client_app_attr,
|
132
134
|
num_supernodes=args.num_supernodes,
|
@@ -206,7 +208,7 @@ def run_simulation(
|
|
206
208
|
"\n\tflwr.simulation.run_simulationt(...)",
|
207
209
|
)
|
208
210
|
|
209
|
-
_run_simulation(
|
211
|
+
_ = _run_simulation(
|
210
212
|
num_supernodes=num_supernodes,
|
211
213
|
client_app=client_app,
|
212
214
|
server_app=server_app,
|
@@ -229,6 +231,7 @@ def run_serverapp_th(
|
|
229
231
|
has_exception: threading.Event,
|
230
232
|
enable_tf_gpu_growth: bool,
|
231
233
|
run_id: int,
|
234
|
+
ctx_queue: "Queue[Context]",
|
232
235
|
) -> threading.Thread:
|
233
236
|
"""Run SeverApp in a thread."""
|
234
237
|
|
@@ -241,6 +244,7 @@ def run_serverapp_th(
|
|
241
244
|
_server_app_run_config: UserConfig,
|
242
245
|
_server_app_attr: Optional[str],
|
243
246
|
_server_app: Optional[ServerApp],
|
247
|
+
_ctx_queue: "Queue[Context]",
|
244
248
|
) -> None:
|
245
249
|
"""Run SeverApp, after check if GPU memory growth has to be set.
|
246
250
|
|
@@ -261,13 +265,14 @@ def run_serverapp_th(
|
|
261
265
|
)
|
262
266
|
|
263
267
|
# Run ServerApp
|
264
|
-
_run(
|
268
|
+
updated_context = _run(
|
265
269
|
driver=_driver,
|
266
270
|
context=context,
|
267
271
|
server_app_dir=_server_app_dir,
|
268
272
|
server_app_attr=_server_app_attr,
|
269
273
|
loaded_server_app=_server_app,
|
270
274
|
)
|
275
|
+
_ctx_queue.put(updated_context)
|
271
276
|
except Exception as ex: # pylint: disable=broad-exception-caught
|
272
277
|
log(ERROR, "ServerApp thread raised an exception: %s", ex)
|
273
278
|
log(ERROR, traceback.format_exc())
|
@@ -291,6 +296,7 @@ def run_serverapp_th(
|
|
291
296
|
server_app_run_config,
|
292
297
|
server_app_attr,
|
293
298
|
server_app,
|
299
|
+
ctx_queue,
|
294
300
|
),
|
295
301
|
)
|
296
302
|
serverapp_th.start()
|
@@ -313,7 +319,7 @@ def _main_loop(
|
|
313
319
|
server_app: Optional[ServerApp] = None,
|
314
320
|
server_app_attr: Optional[str] = None,
|
315
321
|
server_app_run_config: Optional[UserConfig] = None,
|
316
|
-
) ->
|
322
|
+
) -> Context:
|
317
323
|
"""Start ServerApp on a separate thread, then launch Simulation Engine."""
|
318
324
|
# Initialize StateFactory
|
319
325
|
state_factory = LinkStateFactory(":flwr-in-memory-state:")
|
@@ -323,6 +329,13 @@ def _main_loop(
|
|
323
329
|
server_app_thread_has_exception = threading.Event()
|
324
330
|
serverapp_th = None
|
325
331
|
success = True
|
332
|
+
updated_context = Context(
|
333
|
+
run_id=run.run_id,
|
334
|
+
node_id=0,
|
335
|
+
node_config=UserConfig(),
|
336
|
+
state=RecordSet(),
|
337
|
+
run_config=UserConfig(),
|
338
|
+
)
|
326
339
|
try:
|
327
340
|
# Register run
|
328
341
|
log(DEBUG, "Pre-registering run with id %s", run.run_id)
|
@@ -337,6 +350,7 @@ def _main_loop(
|
|
337
350
|
# Initialize Driver
|
338
351
|
driver = InMemoryDriver(state_factory=state_factory)
|
339
352
|
driver.set_run(run_id=run.run_id)
|
353
|
+
output_context_queue: Queue[Context] = Queue()
|
340
354
|
|
341
355
|
# Get and run ServerApp thread
|
342
356
|
serverapp_th = run_serverapp_th(
|
@@ -349,6 +363,7 @@ def _main_loop(
|
|
349
363
|
has_exception=server_app_thread_has_exception,
|
350
364
|
enable_tf_gpu_growth=enable_tf_gpu_growth,
|
351
365
|
run_id=run.run_id,
|
366
|
+
ctx_queue=output_context_queue,
|
352
367
|
)
|
353
368
|
|
354
369
|
# Start Simulation Engine
|
@@ -366,6 +381,11 @@ def _main_loop(
|
|
366
381
|
flwr_dir=flwr_dir,
|
367
382
|
)
|
368
383
|
|
384
|
+
updated_context = output_context_queue.get(timeout=3)
|
385
|
+
|
386
|
+
except Empty:
|
387
|
+
log(DEBUG, "Queue timeout. No context received.")
|
388
|
+
|
369
389
|
except Exception as ex:
|
370
390
|
log(ERROR, "An exception occurred !! %s", ex)
|
371
391
|
log(ERROR, traceback.format_exc())
|
@@ -375,13 +395,20 @@ def _main_loop(
|
|
375
395
|
finally:
|
376
396
|
# Trigger stop event
|
377
397
|
f_stop.set()
|
378
|
-
event(
|
398
|
+
event(
|
399
|
+
exit_event,
|
400
|
+
event_details={
|
401
|
+
"run-id-hash": get_sha256_hash(run.run_id),
|
402
|
+
"success": success,
|
403
|
+
},
|
404
|
+
)
|
379
405
|
if serverapp_th:
|
380
406
|
serverapp_th.join()
|
381
407
|
if server_app_thread_has_exception.is_set():
|
382
408
|
raise RuntimeError("Exception in ServerApp thread")
|
383
409
|
|
384
410
|
log(DEBUG, "Stopping Simulation Engine now.")
|
411
|
+
return updated_context
|
385
412
|
|
386
413
|
|
387
414
|
# pylint: disable=too-many-arguments,too-many-locals,too-many-positional-arguments
|
@@ -401,7 +428,7 @@ def _run_simulation(
|
|
401
428
|
enable_tf_gpu_growth: bool = False,
|
402
429
|
verbose_logging: bool = False,
|
403
430
|
is_app: bool = False,
|
404
|
-
) ->
|
431
|
+
) -> Context:
|
405
432
|
"""Launch the Simulation Engine."""
|
406
433
|
if backend_config is None:
|
407
434
|
backend_config = {}
|
@@ -480,7 +507,8 @@ def _run_simulation(
|
|
480
507
|
# Set logger propagation to False to prevent duplicated log output in Colab.
|
481
508
|
logger = set_logger_propagation(logger, False)
|
482
509
|
|
483
|
-
_main_loop(*args)
|
510
|
+
updated_context = _main_loop(*args)
|
511
|
+
return updated_context
|
484
512
|
|
485
513
|
|
486
514
|
def _parse_args_run_simulation() -> argparse.ArgumentParser:
|
flwr/superexec/exec_grpc.py
CHANGED
@@ -23,11 +23,11 @@ import grpc
|
|
23
23
|
|
24
24
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
25
25
|
from flwr.common.auth_plugin import ExecAuthPlugin
|
26
|
+
from flwr.common.grpc import generic_create_grpc_server
|
26
27
|
from flwr.common.logger import log
|
27
28
|
from flwr.common.typing import UserConfig
|
28
29
|
from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
|
29
30
|
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
30
|
-
from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
|
31
31
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
32
32
|
from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
|
33
33
|
|
flwr/superexec/exec_servicer.py
CHANGED
@@ -181,8 +181,20 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
181
181
|
"ExecServicer initialized without user authentication",
|
182
182
|
)
|
183
183
|
raise grpc.RpcError() # This line is unreachable
|
184
|
+
|
185
|
+
# Get login details
|
186
|
+
details = self.auth_plugin.get_login_details()
|
187
|
+
|
188
|
+
# Return empty response if details is None
|
189
|
+
if details is None:
|
190
|
+
return GetLoginDetailsResponse()
|
191
|
+
|
184
192
|
return GetLoginDetailsResponse(
|
185
|
-
|
193
|
+
auth_type=details.auth_type,
|
194
|
+
device_code=details.device_code,
|
195
|
+
verification_uri_complete=details.verification_uri_complete,
|
196
|
+
expires_in=details.expires_in,
|
197
|
+
interval=details.interval,
|
186
198
|
)
|
187
199
|
|
188
200
|
def GetAuthTokens(
|
@@ -196,8 +208,17 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
196
208
|
"ExecServicer initialized without user authentication",
|
197
209
|
)
|
198
210
|
raise grpc.RpcError() # This line is unreachable
|
211
|
+
|
212
|
+
# Get auth tokens
|
213
|
+
credentials = self.auth_plugin.get_auth_tokens(request.device_code)
|
214
|
+
|
215
|
+
# Return empty response if credentials is None
|
216
|
+
if credentials is None:
|
217
|
+
return GetAuthTokensResponse()
|
218
|
+
|
199
219
|
return GetAuthTokensResponse(
|
200
|
-
|
220
|
+
access_token=credentials.access_token,
|
221
|
+
refresh_token=credentials.refresh_token,
|
201
222
|
)
|
202
223
|
|
203
224
|
|
{flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: flwr-nightly
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.15.0.dev20250112
|
4
4
|
Summary: Flower: A Friendly Federated AI Framework
|
5
5
|
Home-page: https://flower.ai
|
6
6
|
License: Apache-2.0
|
@@ -43,11 +43,11 @@ Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
43
43
|
Requires-Dist: ray (==2.10.0) ; (python_version >= "3.9" and python_version < "3.12") and (extra == "simulation")
|
44
44
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
45
45
|
Requires-Dist: rich (>=13.5.0,<14.0.0)
|
46
|
-
Requires-Dist: starlette (>=0.
|
46
|
+
Requires-Dist: starlette (>=0.45.2,<0.46.0) ; extra == "rest"
|
47
47
|
Requires-Dist: tomli (>=2.0.1,<3.0.0)
|
48
48
|
Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
|
49
49
|
Requires-Dist: typer (>=0.12.5,<0.13.0)
|
50
|
-
Requires-Dist: uvicorn[standard] (>=0.
|
50
|
+
Requires-Dist: uvicorn[standard] (>=0.34.0,<0.35.0) ; extra == "rest"
|
51
51
|
Project-URL: Documentation, https://flower.ai
|
52
52
|
Project-URL: Repository, https://github.com/adap/flower
|
53
53
|
Description-Content-Type: text/markdown
|
@@ -88,7 +88,7 @@ design of Flower is based on a few guiding principles:
|
|
88
88
|
|
89
89
|
- **Framework-agnostic**: Different machine learning frameworks have different
|
90
90
|
strengths. Flower can be used with any machine learning framework, for
|
91
|
-
example, [PyTorch](https://pytorch.org), [TensorFlow](https://tensorflow.org), [Hugging Face Transformers](https://huggingface.co/), [PyTorch Lightning](https://pytorchlightning.ai/), [scikit-learn](https://scikit-learn.org/), [JAX](https://jax.readthedocs.io/), [TFLite](https://tensorflow.org/lite/), [MONAI](https://docs.monai.io/en/latest/index.html), [fastai](https://www.fast.ai/), [MLX](https://ml-explore.github.io/mlx/build/html/index.html), [XGBoost](https://xgboost.readthedocs.io/en/stable/), [Pandas](https://pandas.pydata.org/) for federated analytics, or even raw [NumPy](https://numpy.org/)
|
91
|
+
example, [PyTorch](https://pytorch.org), [TensorFlow](https://tensorflow.org), [Hugging Face Transformers](https://huggingface.co/), [PyTorch Lightning](https://pytorchlightning.ai/), [scikit-learn](https://scikit-learn.org/), [JAX](https://jax.readthedocs.io/), [TFLite](https://tensorflow.org/lite/), [MONAI](https://docs.monai.io/en/latest/index.html), [fastai](https://www.fast.ai/), [MLX](https://ml-explore.github.io/mlx/build/html/index.html), [XGBoost](https://xgboost.readthedocs.io/en/stable/), [LeRobot](https://github.com/huggingface/lerobot) for federated robots, [Pandas](https://pandas.pydata.org/) for federated analytics, or even raw [NumPy](https://numpy.org/)
|
92
92
|
for users who enjoy computing gradients by hand.
|
93
93
|
|
94
94
|
- **Understandable**: Flower is written with maintainability in mind. The
|
@@ -118,7 +118,7 @@ Flower's goal is to make federated learning accessible to everyone. This series
|
|
118
118
|
|
119
119
|
4. **Custom Clients for Federated Learning**
|
120
120
|
|
121
|
-
[](https://colab.research.google.com/github/adap/flower/blob/main/
|
121
|
+
[](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-customize-the-client-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-customize-the-client-pytorch.ipynb))
|
122
122
|
|
123
123
|
Stay tuned, more tutorials are coming soon. Topics include **Privacy and Security in Federated Learning**, and **Scaling Federated Learning**.
|
124
124
|
|