flwr-nightly 1.10.0.dev20240624__py3-none-any.whl → 1.10.0.dev20240707__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.
Potentially problematic release.
This version of flwr-nightly might be problematic. Click here for more details.
- flwr/cli/build.py +2 -2
- flwr/cli/run/run.py +13 -4
- flwr/client/__init__.py +2 -0
- flwr/client/app.py +15 -10
- flwr/client/client_app.py +29 -4
- flwr/client/message_handler/message_handler.py +3 -4
- flwr/client/node_state.py +6 -3
- flwr/client/node_state_tests.py +1 -1
- flwr/client/supernode/app.py +11 -3
- flwr/client/typing.py +2 -1
- flwr/common/constant.py +3 -0
- flwr/common/context.py +11 -1
- flwr/common/logger.py +13 -0
- flwr/common/message.py +0 -17
- flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -1
- flwr/server/superlink/fleet/vce/vce_api.py +3 -8
- flwr/server/superlink/state/in_memory_state.py +4 -4
- flwr/server/superlink/state/sqlite_state.py +4 -4
- flwr/server/superlink/state/utils.py +6 -0
- flwr/simulation/app.py +51 -36
- flwr/simulation/ray_transport/ray_client_proxy.py +16 -9
- flwr/superexec/deployment.py +109 -0
- {flwr_nightly-1.10.0.dev20240624.dist-info → flwr_nightly-1.10.0.dev20240707.dist-info}/METADATA +1 -1
- {flwr_nightly-1.10.0.dev20240624.dist-info → flwr_nightly-1.10.0.dev20240707.dist-info}/RECORD +27 -26
- {flwr_nightly-1.10.0.dev20240624.dist-info → flwr_nightly-1.10.0.dev20240707.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.10.0.dev20240624.dist-info → flwr_nightly-1.10.0.dev20240707.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.10.0.dev20240624.dist-info → flwr_nightly-1.10.0.dev20240707.dist-info}/entry_points.txt +0 -0
flwr/cli/build.py
CHANGED
|
@@ -31,7 +31,7 @@ from .utils import get_sha256_hash, is_valid_project_name
|
|
|
31
31
|
def build(
|
|
32
32
|
directory: Annotated[
|
|
33
33
|
Optional[Path],
|
|
34
|
-
typer.Option(help="
|
|
34
|
+
typer.Option(help="Path of the Flower project to bundle into a FAB"),
|
|
35
35
|
] = None,
|
|
36
36
|
) -> str:
|
|
37
37
|
"""Build a Flower project into a Flower App Bundle (FAB).
|
|
@@ -118,7 +118,7 @@ def build(
|
|
|
118
118
|
fab_file.writestr(".info/CONTENT", list_file_content)
|
|
119
119
|
|
|
120
120
|
typer.secho(
|
|
121
|
-
f"🎊 Successfully built {fab_filename}
|
|
121
|
+
f"🎊 Successfully built {fab_filename}", fg=typer.colors.GREEN, bold=True
|
|
122
122
|
)
|
|
123
123
|
|
|
124
124
|
return fab_filename
|
flwr/cli/run/run.py
CHANGED
|
@@ -17,12 +17,14 @@
|
|
|
17
17
|
import sys
|
|
18
18
|
from enum import Enum
|
|
19
19
|
from logging import DEBUG
|
|
20
|
+
from pathlib import Path
|
|
20
21
|
from typing import Optional
|
|
21
22
|
|
|
22
23
|
import typer
|
|
23
24
|
from typing_extensions import Annotated
|
|
24
25
|
|
|
25
26
|
from flwr.cli import config_utils
|
|
27
|
+
from flwr.cli.build import build
|
|
26
28
|
from flwr.common.constant import SUPEREXEC_DEFAULT_ADDRESS
|
|
27
29
|
from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
|
|
28
30
|
from flwr.common.logger import log
|
|
@@ -52,10 +54,14 @@ def run(
|
|
|
52
54
|
case_sensitive=False, help="Use this flag to use the new SuperExec API"
|
|
53
55
|
),
|
|
54
56
|
] = False,
|
|
57
|
+
directory: Annotated[
|
|
58
|
+
Optional[Path],
|
|
59
|
+
typer.Option(help="Path of the Flower project to run"),
|
|
60
|
+
] = None,
|
|
55
61
|
) -> None:
|
|
56
62
|
"""Run Flower project."""
|
|
57
63
|
if use_superexec:
|
|
58
|
-
_start_superexec_run()
|
|
64
|
+
_start_superexec_run(directory)
|
|
59
65
|
return
|
|
60
66
|
|
|
61
67
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
@@ -109,7 +115,7 @@ def run(
|
|
|
109
115
|
)
|
|
110
116
|
|
|
111
117
|
|
|
112
|
-
def _start_superexec_run() -> None:
|
|
118
|
+
def _start_superexec_run(directory: Optional[Path]) -> None:
|
|
113
119
|
def on_channel_state_change(channel_connectivity: str) -> None:
|
|
114
120
|
"""Log channel connectivity."""
|
|
115
121
|
log(DEBUG, channel_connectivity)
|
|
@@ -124,5 +130,8 @@ def _start_superexec_run() -> None:
|
|
|
124
130
|
channel.subscribe(on_channel_state_change)
|
|
125
131
|
stub = ExecStub(channel)
|
|
126
132
|
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
fab_path = build(directory)
|
|
134
|
+
|
|
135
|
+
req = StartRunRequest(fab_file=Path(fab_path).read_bytes())
|
|
136
|
+
res = stub.StartRun(req)
|
|
137
|
+
typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN)
|
flwr/client/__init__.py
CHANGED
|
@@ -23,11 +23,13 @@ from .numpy_client import NumPyClient as NumPyClient
|
|
|
23
23
|
from .supernode import run_client_app as run_client_app
|
|
24
24
|
from .supernode import run_supernode as run_supernode
|
|
25
25
|
from .typing import ClientFn as ClientFn
|
|
26
|
+
from .typing import ClientFnExt as ClientFnExt
|
|
26
27
|
|
|
27
28
|
__all__ = [
|
|
28
29
|
"Client",
|
|
29
30
|
"ClientApp",
|
|
30
31
|
"ClientFn",
|
|
32
|
+
"ClientFnExt",
|
|
31
33
|
"NumPyClient",
|
|
32
34
|
"mod",
|
|
33
35
|
"run_client_app",
|
flwr/client/app.py
CHANGED
|
@@ -26,7 +26,7 @@ from grpc import RpcError
|
|
|
26
26
|
|
|
27
27
|
from flwr.client.client import Client
|
|
28
28
|
from flwr.client.client_app import ClientApp, LoadClientAppError
|
|
29
|
-
from flwr.client.typing import
|
|
29
|
+
from flwr.client.typing import ClientFnExt
|
|
30
30
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, Message, event
|
|
31
31
|
from flwr.common.address import parse_address
|
|
32
32
|
from flwr.common.constant import (
|
|
@@ -51,7 +51,7 @@ from .numpy_client import NumPyClient
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def _check_actionable_client(
|
|
54
|
-
client: Optional[Client], client_fn: Optional[
|
|
54
|
+
client: Optional[Client], client_fn: Optional[ClientFnExt]
|
|
55
55
|
) -> None:
|
|
56
56
|
if client_fn is None and client is None:
|
|
57
57
|
raise ValueError(
|
|
@@ -72,7 +72,7 @@ def _check_actionable_client(
|
|
|
72
72
|
def start_client(
|
|
73
73
|
*,
|
|
74
74
|
server_address: str,
|
|
75
|
-
client_fn: Optional[
|
|
75
|
+
client_fn: Optional[ClientFnExt] = None,
|
|
76
76
|
client: Optional[Client] = None,
|
|
77
77
|
grpc_max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
|
|
78
78
|
root_certificates: Optional[Union[bytes, str]] = None,
|
|
@@ -92,7 +92,7 @@ def start_client(
|
|
|
92
92
|
The IPv4 or IPv6 address of the server. If the Flower
|
|
93
93
|
server runs on the same machine on port 8080, then `server_address`
|
|
94
94
|
would be `"[::]:8080"`.
|
|
95
|
-
client_fn : Optional[
|
|
95
|
+
client_fn : Optional[ClientFnExt]
|
|
96
96
|
A callable that instantiates a Client. (default: None)
|
|
97
97
|
client : Optional[flwr.client.Client]
|
|
98
98
|
An implementation of the abstract base
|
|
@@ -136,7 +136,7 @@ def start_client(
|
|
|
136
136
|
|
|
137
137
|
Starting an SSL-enabled gRPC client using system certificates:
|
|
138
138
|
|
|
139
|
-
>>> def client_fn(
|
|
139
|
+
>>> def client_fn(node_id: int, partition_id: Optional[int]):
|
|
140
140
|
>>> return FlowerClient()
|
|
141
141
|
>>>
|
|
142
142
|
>>> start_client(
|
|
@@ -180,7 +180,7 @@ def _start_client_internal(
|
|
|
180
180
|
*,
|
|
181
181
|
server_address: str,
|
|
182
182
|
load_client_app_fn: Optional[Callable[[str, str], ClientApp]] = None,
|
|
183
|
-
client_fn: Optional[
|
|
183
|
+
client_fn: Optional[ClientFnExt] = None,
|
|
184
184
|
client: Optional[Client] = None,
|
|
185
185
|
grpc_max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
|
|
186
186
|
root_certificates: Optional[Union[bytes, str]] = None,
|
|
@@ -191,6 +191,7 @@ def _start_client_internal(
|
|
|
191
191
|
] = None,
|
|
192
192
|
max_retries: Optional[int] = None,
|
|
193
193
|
max_wait_time: Optional[float] = None,
|
|
194
|
+
partition_id: Optional[int] = None,
|
|
194
195
|
) -> None:
|
|
195
196
|
"""Start a Flower client node which connects to a Flower server.
|
|
196
197
|
|
|
@@ -202,7 +203,7 @@ def _start_client_internal(
|
|
|
202
203
|
would be `"[::]:8080"`.
|
|
203
204
|
load_client_app_fn : Optional[Callable[[], ClientApp]] (default: None)
|
|
204
205
|
A function that can be used to load a `ClientApp` instance.
|
|
205
|
-
client_fn : Optional[
|
|
206
|
+
client_fn : Optional[ClientFnExt]
|
|
206
207
|
A callable that instantiates a Client. (default: None)
|
|
207
208
|
client : Optional[flwr.client.Client]
|
|
208
209
|
An implementation of the abstract base
|
|
@@ -234,6 +235,9 @@ def _start_client_internal(
|
|
|
234
235
|
The maximum duration before the client stops trying to
|
|
235
236
|
connect to the server in case of connection error.
|
|
236
237
|
If set to None, there is no limit to the total time.
|
|
238
|
+
partitioni_id: Optional[int] (default: None)
|
|
239
|
+
The data partition index associated with this node. Better suited for
|
|
240
|
+
prototyping purposes.
|
|
237
241
|
"""
|
|
238
242
|
if insecure is None:
|
|
239
243
|
insecure = root_certificates is None
|
|
@@ -244,7 +248,8 @@ def _start_client_internal(
|
|
|
244
248
|
if client_fn is None:
|
|
245
249
|
# Wrap `Client` instance in `client_fn`
|
|
246
250
|
def single_client_factory(
|
|
247
|
-
|
|
251
|
+
node_id: int, # pylint: disable=unused-argument
|
|
252
|
+
partition_id: Optional[int], # pylint: disable=unused-argument
|
|
248
253
|
) -> Client:
|
|
249
254
|
if client is None: # Added this to keep mypy happy
|
|
250
255
|
raise ValueError(
|
|
@@ -293,7 +298,7 @@ def _start_client_internal(
|
|
|
293
298
|
retry_invoker = RetryInvoker(
|
|
294
299
|
wait_gen_factory=exponential,
|
|
295
300
|
recoverable_exceptions=connection_error_type,
|
|
296
|
-
max_tries=max_retries,
|
|
301
|
+
max_tries=max_retries + 1 if max_retries is not None else None,
|
|
297
302
|
max_time=max_wait_time,
|
|
298
303
|
on_giveup=lambda retry_state: (
|
|
299
304
|
log(
|
|
@@ -309,7 +314,7 @@ def _start_client_internal(
|
|
|
309
314
|
on_backoff=_on_backoff,
|
|
310
315
|
)
|
|
311
316
|
|
|
312
|
-
node_state = NodeState()
|
|
317
|
+
node_state = NodeState(partition_id=partition_id)
|
|
313
318
|
# run_id -> (fab_id, fab_version)
|
|
314
319
|
run_info: Dict[int, Tuple[str, str]] = {}
|
|
315
320
|
|
flwr/client/client_app.py
CHANGED
|
@@ -15,19 +15,42 @@
|
|
|
15
15
|
"""Flower ClientApp."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
import inspect
|
|
18
19
|
from typing import Callable, List, Optional
|
|
19
20
|
|
|
21
|
+
from flwr.client.client import Client
|
|
20
22
|
from flwr.client.message_handler.message_handler import (
|
|
21
23
|
handle_legacy_message_from_msgtype,
|
|
22
24
|
)
|
|
23
25
|
from flwr.client.mod.utils import make_ffn
|
|
24
|
-
from flwr.client.typing import
|
|
26
|
+
from flwr.client.typing import ClientFnExt, Mod
|
|
25
27
|
from flwr.common import Context, Message, MessageType
|
|
26
|
-
from flwr.common.logger import warn_preview_feature
|
|
28
|
+
from flwr.common.logger import warn_deprecated_feature, warn_preview_feature
|
|
27
29
|
|
|
28
30
|
from .typing import ClientAppCallable
|
|
29
31
|
|
|
30
32
|
|
|
33
|
+
def _inspect_maybe_adapt_client_fn_signature(client_fn: ClientFnExt) -> ClientFnExt:
|
|
34
|
+
client_fn_args = inspect.signature(client_fn).parameters
|
|
35
|
+
|
|
36
|
+
if not all(key in client_fn_args for key in ["node_id", "partition_id"]):
|
|
37
|
+
warn_deprecated_feature(
|
|
38
|
+
"`client_fn` now expects a signature `def client_fn(node_id: int, "
|
|
39
|
+
"partition_id: Optional[int])`.\nYou provided `client_fn` with signature: "
|
|
40
|
+
f"{dict(client_fn_args.items())}"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Wrap depcreated client_fn inside a function with the expected signature
|
|
44
|
+
def adaptor_fn(
|
|
45
|
+
node_id: int, partition_id: Optional[int] # pylint: disable=unused-argument
|
|
46
|
+
) -> Client:
|
|
47
|
+
return client_fn(str(partition_id)) # type: ignore
|
|
48
|
+
|
|
49
|
+
return adaptor_fn
|
|
50
|
+
|
|
51
|
+
return client_fn
|
|
52
|
+
|
|
53
|
+
|
|
31
54
|
class ClientAppException(Exception):
|
|
32
55
|
"""Exception raised when an exception is raised while executing a ClientApp."""
|
|
33
56
|
|
|
@@ -48,7 +71,7 @@ class ClientApp:
|
|
|
48
71
|
>>> class FlowerClient(NumPyClient):
|
|
49
72
|
>>> # ...
|
|
50
73
|
>>>
|
|
51
|
-
>>> def client_fn(
|
|
74
|
+
>>> def client_fn(node_id: int, partition_id: Optional[int]):
|
|
52
75
|
>>> return FlowerClient().to_client()
|
|
53
76
|
>>>
|
|
54
77
|
>>> app = ClientApp(client_fn)
|
|
@@ -65,7 +88,7 @@ class ClientApp:
|
|
|
65
88
|
|
|
66
89
|
def __init__(
|
|
67
90
|
self,
|
|
68
|
-
client_fn: Optional[
|
|
91
|
+
client_fn: Optional[ClientFnExt] = None, # Only for backward compatibility
|
|
69
92
|
mods: Optional[List[Mod]] = None,
|
|
70
93
|
) -> None:
|
|
71
94
|
self._mods: List[Mod] = mods if mods is not None else []
|
|
@@ -74,6 +97,8 @@ class ClientApp:
|
|
|
74
97
|
self._call: Optional[ClientAppCallable] = None
|
|
75
98
|
if client_fn is not None:
|
|
76
99
|
|
|
100
|
+
client_fn = _inspect_maybe_adapt_client_fn_signature(client_fn)
|
|
101
|
+
|
|
77
102
|
def ffn(
|
|
78
103
|
message: Message,
|
|
79
104
|
context: Context,
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Client-side message handler."""
|
|
16
16
|
|
|
17
|
-
|
|
18
17
|
from logging import WARN
|
|
19
18
|
from typing import Optional, Tuple, cast
|
|
20
19
|
|
|
@@ -25,7 +24,7 @@ from flwr.client.client import (
|
|
|
25
24
|
maybe_call_get_properties,
|
|
26
25
|
)
|
|
27
26
|
from flwr.client.numpy_client import NumPyClient
|
|
28
|
-
from flwr.client.typing import
|
|
27
|
+
from flwr.client.typing import ClientFnExt
|
|
29
28
|
from flwr.common import ConfigsRecord, Context, Message, Metadata, RecordSet, log
|
|
30
29
|
from flwr.common.constant import MessageType, MessageTypeLegacy
|
|
31
30
|
from flwr.common.recordset_compat import (
|
|
@@ -90,10 +89,10 @@ def handle_control_message(message: Message) -> Tuple[Optional[Message], int]:
|
|
|
90
89
|
|
|
91
90
|
|
|
92
91
|
def handle_legacy_message_from_msgtype(
|
|
93
|
-
client_fn:
|
|
92
|
+
client_fn: ClientFnExt, message: Message, context: Context
|
|
94
93
|
) -> Message:
|
|
95
94
|
"""Handle legacy message in the inner most mod."""
|
|
96
|
-
client = client_fn(
|
|
95
|
+
client = client_fn(message.metadata.dst_node_id, context.partition_id)
|
|
97
96
|
|
|
98
97
|
# Check if NumPyClient is returend
|
|
99
98
|
if isinstance(client, NumPyClient):
|
flwr/client/node_state.py
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"""Node state."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from typing import Any, Dict
|
|
18
|
+
from typing import Any, Dict, Optional
|
|
19
19
|
|
|
20
20
|
from flwr.common import Context, RecordSet
|
|
21
21
|
|
|
@@ -23,14 +23,17 @@ from flwr.common import Context, RecordSet
|
|
|
23
23
|
class NodeState:
|
|
24
24
|
"""State of a node where client nodes execute runs."""
|
|
25
25
|
|
|
26
|
-
def __init__(self) -> None:
|
|
26
|
+
def __init__(self, partition_id: Optional[int]) -> None:
|
|
27
27
|
self._meta: Dict[str, Any] = {} # holds metadata about the node
|
|
28
28
|
self.run_contexts: Dict[int, Context] = {}
|
|
29
|
+
self._partition_id = partition_id
|
|
29
30
|
|
|
30
31
|
def register_context(self, run_id: int) -> None:
|
|
31
32
|
"""Register new run context for this node."""
|
|
32
33
|
if run_id not in self.run_contexts:
|
|
33
|
-
self.run_contexts[run_id] = Context(
|
|
34
|
+
self.run_contexts[run_id] = Context(
|
|
35
|
+
state=RecordSet(), partition_id=self._partition_id
|
|
36
|
+
)
|
|
34
37
|
|
|
35
38
|
def retrieve_context(self, run_id: int) -> Context:
|
|
36
39
|
"""Get run context given a run_id."""
|
flwr/client/node_state_tests.py
CHANGED
flwr/client/supernode/app.py
CHANGED
|
@@ -67,6 +67,7 @@ def run_supernode() -> None:
|
|
|
67
67
|
authentication_keys=authentication_keys,
|
|
68
68
|
max_retries=args.max_retries,
|
|
69
69
|
max_wait_time=args.max_wait_time,
|
|
70
|
+
partition_id=args.partition_id,
|
|
70
71
|
)
|
|
71
72
|
|
|
72
73
|
# Graceful shutdown
|
|
@@ -344,8 +345,8 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
344
345
|
"--max-retries",
|
|
345
346
|
type=int,
|
|
346
347
|
default=None,
|
|
347
|
-
help="The maximum number of times the client will try to
|
|
348
|
-
"
|
|
348
|
+
help="The maximum number of times the client will try to reconnect to the"
|
|
349
|
+
"SuperLink before giving up in case of a connection error. By default,"
|
|
349
350
|
"it is set to None, meaning there is no limit to the number of tries.",
|
|
350
351
|
)
|
|
351
352
|
parser.add_argument(
|
|
@@ -353,7 +354,7 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
353
354
|
type=float,
|
|
354
355
|
default=None,
|
|
355
356
|
help="The maximum duration before the client stops trying to"
|
|
356
|
-
"connect to the
|
|
357
|
+
"connect to the SuperLink in case of connection error. By default, it"
|
|
357
358
|
"is set to None, meaning there is no limit to the total time.",
|
|
358
359
|
)
|
|
359
360
|
parser.add_argument(
|
|
@@ -373,6 +374,13 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
373
374
|
type=str,
|
|
374
375
|
help="The SuperNode's public key (as a path str) to enable authentication.",
|
|
375
376
|
)
|
|
377
|
+
parser.add_argument(
|
|
378
|
+
"--partition-id",
|
|
379
|
+
type=int,
|
|
380
|
+
help="The data partition index associated with this SuperNode. Better suited "
|
|
381
|
+
"for prototyping purposes where a SuperNode might only load a fraction of an "
|
|
382
|
+
"artificially partitioned dataset (e.g. using `flwr-datasets`)",
|
|
383
|
+
)
|
|
376
384
|
|
|
377
385
|
|
|
378
386
|
def _try_setup_client_authentication(
|
flwr/client/typing.py
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"""Custom types for Flower clients."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from typing import Callable
|
|
18
|
+
from typing import Callable, Optional
|
|
19
19
|
|
|
20
20
|
from flwr.common import Context, Message
|
|
21
21
|
|
|
@@ -23,6 +23,7 @@ from .client import Client as Client
|
|
|
23
23
|
|
|
24
24
|
# Compatibility
|
|
25
25
|
ClientFn = Callable[[str], Client]
|
|
26
|
+
ClientFnExt = Callable[[int, Optional[int]], Client]
|
|
26
27
|
|
|
27
28
|
ClientAppCallable = Callable[[Message, Context], Message]
|
|
28
29
|
Mod = Callable[[Message, Context, ClientAppCallable], Message]
|
flwr/common/constant.py
CHANGED
|
@@ -46,6 +46,9 @@ PING_BASE_MULTIPLIER = 0.8
|
|
|
46
46
|
PING_RANDOM_RANGE = (-0.1, 0.1)
|
|
47
47
|
PING_MAX_INTERVAL = 1e300
|
|
48
48
|
|
|
49
|
+
# IDs
|
|
50
|
+
RUN_ID_NUM_BYTES = 8
|
|
51
|
+
NODE_ID_NUM_BYTES = 8
|
|
49
52
|
GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY = "flower-version"
|
|
50
53
|
GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY = "should-exit"
|
|
51
54
|
|
flwr/common/context.py
CHANGED
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
|
+
from typing import Optional
|
|
19
20
|
|
|
20
21
|
from .record import RecordSet
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
@dataclass
|
|
24
25
|
class Context:
|
|
25
|
-
"""
|
|
26
|
+
"""Context of your run.
|
|
26
27
|
|
|
27
28
|
Parameters
|
|
28
29
|
----------
|
|
@@ -33,6 +34,15 @@ class Context:
|
|
|
33
34
|
executing mods. It can also be used as a memory to access
|
|
34
35
|
at different points during the lifecycle of this entity (e.g. across
|
|
35
36
|
multiple rounds)
|
|
37
|
+
partition_id : Optional[int] (default: None)
|
|
38
|
+
An index that specifies the data partition that the ClientApp using this Context
|
|
39
|
+
object should make use of. Setting this attribute is better suited for
|
|
40
|
+
simulation or proto typing setups.
|
|
36
41
|
"""
|
|
37
42
|
|
|
38
43
|
state: RecordSet
|
|
44
|
+
partition_id: Optional[int]
|
|
45
|
+
|
|
46
|
+
def __init__(self, state: RecordSet, partition_id: Optional[int] = None) -> None:
|
|
47
|
+
self.state = state
|
|
48
|
+
self.partition_id = partition_id
|
flwr/common/logger.py
CHANGED
|
@@ -197,6 +197,19 @@ def warn_deprecated_feature(name: str) -> None:
|
|
|
197
197
|
)
|
|
198
198
|
|
|
199
199
|
|
|
200
|
+
def warn_unsupported_feature(name: str) -> None:
|
|
201
|
+
"""Warn the user when they use an unsupported feature."""
|
|
202
|
+
log(
|
|
203
|
+
WARN,
|
|
204
|
+
"""UNSUPPORTED FEATURE: %s
|
|
205
|
+
|
|
206
|
+
This is an unsupported feature. It will be removed
|
|
207
|
+
entirely in future versions of Flower.
|
|
208
|
+
""",
|
|
209
|
+
name,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
200
213
|
def set_logger_propagation(
|
|
201
214
|
child_logger: logging.Logger, value: bool = True
|
|
202
215
|
) -> logging.Logger:
|
flwr/common/message.py
CHANGED
|
@@ -48,10 +48,6 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
48
48
|
message_type : str
|
|
49
49
|
A string that encodes the action to be executed on
|
|
50
50
|
the receiving end.
|
|
51
|
-
partition_id : Optional[int]
|
|
52
|
-
An identifier that can be used when loading a particular
|
|
53
|
-
data partition for a ClientApp. Making use of this identifier
|
|
54
|
-
is more relevant when conducting simulations.
|
|
55
51
|
"""
|
|
56
52
|
|
|
57
53
|
def __init__( # pylint: disable=too-many-arguments
|
|
@@ -64,7 +60,6 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
64
60
|
group_id: str,
|
|
65
61
|
ttl: float,
|
|
66
62
|
message_type: str,
|
|
67
|
-
partition_id: int | None = None,
|
|
68
63
|
) -> None:
|
|
69
64
|
var_dict = {
|
|
70
65
|
"_run_id": run_id,
|
|
@@ -75,7 +70,6 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
75
70
|
"_group_id": group_id,
|
|
76
71
|
"_ttl": ttl,
|
|
77
72
|
"_message_type": message_type,
|
|
78
|
-
"_partition_id": partition_id,
|
|
79
73
|
}
|
|
80
74
|
self.__dict__.update(var_dict)
|
|
81
75
|
|
|
@@ -149,16 +143,6 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
149
143
|
"""Set message_type."""
|
|
150
144
|
self.__dict__["_message_type"] = value
|
|
151
145
|
|
|
152
|
-
@property
|
|
153
|
-
def partition_id(self) -> int | None:
|
|
154
|
-
"""An identifier telling which data partition a ClientApp should use."""
|
|
155
|
-
return cast(int, self.__dict__["_partition_id"])
|
|
156
|
-
|
|
157
|
-
@partition_id.setter
|
|
158
|
-
def partition_id(self, value: int) -> None:
|
|
159
|
-
"""Set partition_id."""
|
|
160
|
-
self.__dict__["_partition_id"] = value
|
|
161
|
-
|
|
162
146
|
def __repr__(self) -> str:
|
|
163
147
|
"""Return a string representation of this instance."""
|
|
164
148
|
view = ", ".join([f"{k.lstrip('_')}={v!r}" for k, v in self.__dict__.items()])
|
|
@@ -398,5 +382,4 @@ def _create_reply_metadata(msg: Message, ttl: float) -> Metadata:
|
|
|
398
382
|
group_id=msg.metadata.group_id,
|
|
399
383
|
ttl=ttl,
|
|
400
384
|
message_type=msg.metadata.message_type,
|
|
401
|
-
partition_id=msg.metadata.partition_id,
|
|
402
385
|
)
|
|
@@ -57,7 +57,6 @@ async def worker(
|
|
|
57
57
|
queue: "asyncio.Queue[TaskIns]",
|
|
58
58
|
node_states: Dict[int, NodeState],
|
|
59
59
|
state_factory: StateFactory,
|
|
60
|
-
nodes_mapping: NodeToPartitionMapping,
|
|
61
60
|
backend: Backend,
|
|
62
61
|
) -> None:
|
|
63
62
|
"""Get TaskIns from queue and pass it to an actor in the pool to execute it."""
|
|
@@ -74,8 +73,6 @@ async def worker(
|
|
|
74
73
|
|
|
75
74
|
# Convert TaskIns to Message
|
|
76
75
|
message = message_from_taskins(task_ins)
|
|
77
|
-
# Set partition_id
|
|
78
|
-
message.metadata.partition_id = nodes_mapping[node_id]
|
|
79
76
|
|
|
80
77
|
# Let backend process message
|
|
81
78
|
out_mssg, updated_context = await backend.process_message(
|
|
@@ -187,9 +184,7 @@ async def run(
|
|
|
187
184
|
# Add workers (they submit Messages to Backend)
|
|
188
185
|
worker_tasks = [
|
|
189
186
|
asyncio.create_task(
|
|
190
|
-
worker(
|
|
191
|
-
app_fn, queue, node_states, state_factory, nodes_mapping, backend
|
|
192
|
-
)
|
|
187
|
+
worker(app_fn, queue, node_states, state_factory, backend)
|
|
193
188
|
)
|
|
194
189
|
for _ in range(backend.num_workers)
|
|
195
190
|
]
|
|
@@ -291,8 +286,8 @@ def start_vce(
|
|
|
291
286
|
|
|
292
287
|
# Construct mapping of NodeStates
|
|
293
288
|
node_states: Dict[int, NodeState] = {}
|
|
294
|
-
for node_id in nodes_mapping:
|
|
295
|
-
node_states[node_id] = NodeState()
|
|
289
|
+
for node_id, partition_id in nodes_mapping.items():
|
|
290
|
+
node_states[node_id] = NodeState(partition_id=partition_id)
|
|
296
291
|
|
|
297
292
|
# Load backend config
|
|
298
293
|
log(DEBUG, "Supported backends: %s", list(supported_backends.keys()))
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"""In-memory State implementation."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import os
|
|
19
18
|
import threading
|
|
20
19
|
import time
|
|
21
20
|
from logging import ERROR
|
|
@@ -23,12 +22,13 @@ from typing import Dict, List, Optional, Set, Tuple
|
|
|
23
22
|
from uuid import UUID, uuid4
|
|
24
23
|
|
|
25
24
|
from flwr.common import log, now
|
|
25
|
+
from flwr.common.constant import NODE_ID_NUM_BYTES, RUN_ID_NUM_BYTES
|
|
26
26
|
from flwr.common.typing import Run
|
|
27
27
|
from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611
|
|
28
28
|
from flwr.server.superlink.state.state import State
|
|
29
29
|
from flwr.server.utils import validate_task_ins_or_res
|
|
30
30
|
|
|
31
|
-
from .utils import make_node_unavailable_taskres
|
|
31
|
+
from .utils import generate_rand_int_from_bytes, make_node_unavailable_taskres
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class InMemoryState(State): # pylint: disable=R0902,R0904
|
|
@@ -216,7 +216,7 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
|
|
|
216
216
|
) -> int:
|
|
217
217
|
"""Create, store in state, and return `node_id`."""
|
|
218
218
|
# Sample a random int64 as node_id
|
|
219
|
-
node_id
|
|
219
|
+
node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES)
|
|
220
220
|
|
|
221
221
|
with self.lock:
|
|
222
222
|
if node_id in self.node_ids:
|
|
@@ -279,7 +279,7 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
|
|
|
279
279
|
"""Create a new run for the specified `fab_id` and `fab_version`."""
|
|
280
280
|
# Sample a random int64 as run_id
|
|
281
281
|
with self.lock:
|
|
282
|
-
run_id
|
|
282
|
+
run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
|
|
283
283
|
|
|
284
284
|
if run_id not in self.run_ids:
|
|
285
285
|
self.run_ids[run_id] = Run(
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"""SQLite based implemenation of server state."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import os
|
|
19
18
|
import re
|
|
20
19
|
import sqlite3
|
|
21
20
|
import time
|
|
@@ -24,6 +23,7 @@ from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union, cast
|
|
|
24
23
|
from uuid import UUID, uuid4
|
|
25
24
|
|
|
26
25
|
from flwr.common import log, now
|
|
26
|
+
from flwr.common.constant import NODE_ID_NUM_BYTES, RUN_ID_NUM_BYTES
|
|
27
27
|
from flwr.common.typing import Run
|
|
28
28
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
29
29
|
from flwr.proto.recordset_pb2 import RecordSet # pylint: disable=E0611
|
|
@@ -31,7 +31,7 @@ from flwr.proto.task_pb2 import Task, TaskIns, TaskRes # pylint: disable=E0611
|
|
|
31
31
|
from flwr.server.utils.validator import validate_task_ins_or_res
|
|
32
32
|
|
|
33
33
|
from .state import State
|
|
34
|
-
from .utils import make_node_unavailable_taskres
|
|
34
|
+
from .utils import generate_rand_int_from_bytes, make_node_unavailable_taskres
|
|
35
35
|
|
|
36
36
|
SQL_CREATE_TABLE_NODE = """
|
|
37
37
|
CREATE TABLE IF NOT EXISTS node(
|
|
@@ -541,7 +541,7 @@ class SqliteState(State): # pylint: disable=R0904
|
|
|
541
541
|
) -> int:
|
|
542
542
|
"""Create, store in state, and return `node_id`."""
|
|
543
543
|
# Sample a random int64 as node_id
|
|
544
|
-
node_id
|
|
544
|
+
node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES)
|
|
545
545
|
|
|
546
546
|
query = "SELECT node_id FROM node WHERE public_key = :public_key;"
|
|
547
547
|
row = self.query(query, {"public_key": public_key})
|
|
@@ -616,7 +616,7 @@ class SqliteState(State): # pylint: disable=R0904
|
|
|
616
616
|
def create_run(self, fab_id: str, fab_version: str) -> int:
|
|
617
617
|
"""Create a new run for the specified `fab_id` and `fab_version`."""
|
|
618
618
|
# Sample a random int64 as run_id
|
|
619
|
-
run_id
|
|
619
|
+
run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
|
|
620
620
|
|
|
621
621
|
# Check conflicts
|
|
622
622
|
query = "SELECT COUNT(*) FROM run WHERE run_id = ?;"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import time
|
|
19
19
|
from logging import ERROR
|
|
20
|
+
from os import urandom
|
|
20
21
|
from uuid import uuid4
|
|
21
22
|
|
|
22
23
|
from flwr.common import log
|
|
@@ -31,6 +32,11 @@ NODE_UNAVAILABLE_ERROR_REASON = (
|
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
def generate_rand_int_from_bytes(num_bytes: int) -> int:
|
|
36
|
+
"""Generate a random `num_bytes` integer."""
|
|
37
|
+
return int.from_bytes(urandom(num_bytes), "little", signed=True)
|
|
38
|
+
|
|
39
|
+
|
|
34
40
|
def make_node_unavailable_taskres(ref_taskins: TaskIns) -> TaskRes:
|
|
35
41
|
"""Generate a TaskRes with a node unavailable error from a TaskIns."""
|
|
36
42
|
current_time = time.time()
|
flwr/simulation/app.py
CHANGED
|
@@ -27,14 +27,16 @@ from typing import Any, Dict, List, Optional, Type, Union
|
|
|
27
27
|
import ray
|
|
28
28
|
from ray.util.scheduling_strategies import NodeAffinitySchedulingStrategy
|
|
29
29
|
|
|
30
|
-
from flwr.client import
|
|
30
|
+
from flwr.client import ClientFnExt
|
|
31
31
|
from flwr.common import EventType, event
|
|
32
|
-
from flwr.common.
|
|
32
|
+
from flwr.common.constant import NODE_ID_NUM_BYTES
|
|
33
|
+
from flwr.common.logger import log, set_logger_propagation, warn_unsupported_feature
|
|
33
34
|
from flwr.server.client_manager import ClientManager
|
|
34
35
|
from flwr.server.history import History
|
|
35
36
|
from flwr.server.server import Server, init_defaults, run_fl
|
|
36
37
|
from flwr.server.server_config import ServerConfig
|
|
37
38
|
from flwr.server.strategy import Strategy
|
|
39
|
+
from flwr.server.superlink.state.utils import generate_rand_int_from_bytes
|
|
38
40
|
from flwr.simulation.ray_transport.ray_actor import (
|
|
39
41
|
ClientAppActor,
|
|
40
42
|
VirtualClientEngineActor,
|
|
@@ -51,7 +53,7 @@ Invalid Arguments in method:
|
|
|
51
53
|
`start_simulation(
|
|
52
54
|
*,
|
|
53
55
|
client_fn: ClientFn,
|
|
54
|
-
num_clients:
|
|
56
|
+
num_clients: int,
|
|
55
57
|
clients_ids: Optional[List[str]] = None,
|
|
56
58
|
client_resources: Optional[Dict[str, float]] = None,
|
|
57
59
|
server: Optional[Server] = None,
|
|
@@ -70,13 +72,29 @@ REASON:
|
|
|
70
72
|
|
|
71
73
|
"""
|
|
72
74
|
|
|
75
|
+
NodeToPartitionMapping = Dict[int, int]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _create_node_id_to_partition_mapping(
|
|
79
|
+
num_clients: int,
|
|
80
|
+
) -> NodeToPartitionMapping:
|
|
81
|
+
"""Generate a node_id:partition_id mapping."""
|
|
82
|
+
nodes_mapping: NodeToPartitionMapping = {} # {node-id; partition-id}
|
|
83
|
+
for i in range(num_clients):
|
|
84
|
+
while True:
|
|
85
|
+
node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES)
|
|
86
|
+
if node_id not in nodes_mapping:
|
|
87
|
+
break
|
|
88
|
+
nodes_mapping[node_id] = i
|
|
89
|
+
return nodes_mapping
|
|
90
|
+
|
|
73
91
|
|
|
74
92
|
# pylint: disable=too-many-arguments,too-many-statements,too-many-branches
|
|
75
93
|
def start_simulation(
|
|
76
94
|
*,
|
|
77
|
-
client_fn:
|
|
78
|
-
num_clients:
|
|
79
|
-
clients_ids: Optional[List[str]] = None,
|
|
95
|
+
client_fn: ClientFnExt,
|
|
96
|
+
num_clients: int,
|
|
97
|
+
clients_ids: Optional[List[str]] = None, # UNSUPPORTED, WILL BE REMOVED
|
|
80
98
|
client_resources: Optional[Dict[str, float]] = None,
|
|
81
99
|
server: Optional[Server] = None,
|
|
82
100
|
config: Optional[ServerConfig] = None,
|
|
@@ -92,23 +110,24 @@ def start_simulation(
|
|
|
92
110
|
|
|
93
111
|
Parameters
|
|
94
112
|
----------
|
|
95
|
-
client_fn :
|
|
96
|
-
A function creating
|
|
97
|
-
`
|
|
98
|
-
of type Client. Note that the created client
|
|
99
|
-
and will often be destroyed after a single method
|
|
100
|
-
instances are not long-lived, they should not attempt
|
|
101
|
-
method invocations. Any state required by the instance
|
|
102
|
-
hyperparameters, ...) should be (re-)created in either the
|
|
103
|
-
or the call to any of the client methods (e.g., load
|
|
104
|
-
`evaluate` method itself).
|
|
105
|
-
num_clients :
|
|
106
|
-
The total number of clients in this simulation.
|
|
107
|
-
`clients_ids` is not set and vice-versa.
|
|
113
|
+
client_fn : ClientFnExt
|
|
114
|
+
A function creating Client instances. The function must have the signature
|
|
115
|
+
`client_fn(node_id: int, partition_id: Optional[int]). It should return
|
|
116
|
+
a single client instance of type Client. Note that the created client
|
|
117
|
+
instances are ephemeral and will often be destroyed after a single method
|
|
118
|
+
invocation. Since client instances are not long-lived, they should not attempt
|
|
119
|
+
to carry state over method invocations. Any state required by the instance
|
|
120
|
+
(model, dataset, hyperparameters, ...) should be (re-)created in either the
|
|
121
|
+
call to `client_fn` or the call to any of the client methods (e.g., load
|
|
122
|
+
evaluation data in the `evaluate` method itself).
|
|
123
|
+
num_clients : int
|
|
124
|
+
The total number of clients in this simulation.
|
|
108
125
|
clients_ids : Optional[List[str]]
|
|
126
|
+
UNSUPPORTED, WILL BE REMOVED. USE `num_clients` INSTEAD.
|
|
109
127
|
List `client_id`s for each client. This is only required if
|
|
110
128
|
`num_clients` is not set. Setting both `num_clients` and `clients_ids`
|
|
111
129
|
with `len(clients_ids)` not equal to `num_clients` generates an error.
|
|
130
|
+
Using this argument will raise an error.
|
|
112
131
|
client_resources : Optional[Dict[str, float]] (default: `{"num_cpus": 1, "num_gpus": 0.0}`)
|
|
113
132
|
CPU and GPU resources for a single client. Supported keys
|
|
114
133
|
are `num_cpus` and `num_gpus`. To understand the GPU utilization caused by
|
|
@@ -158,7 +177,6 @@ def start_simulation(
|
|
|
158
177
|
is an advanced feature. For all details, please refer to the Ray documentation:
|
|
159
178
|
https://docs.ray.io/en/latest/ray-core/scheduling/index.html
|
|
160
179
|
|
|
161
|
-
|
|
162
180
|
Returns
|
|
163
181
|
-------
|
|
164
182
|
hist : flwr.server.history.History
|
|
@@ -170,6 +188,14 @@ def start_simulation(
|
|
|
170
188
|
{"num_clients": len(clients_ids) if clients_ids is not None else num_clients},
|
|
171
189
|
)
|
|
172
190
|
|
|
191
|
+
if clients_ids is not None:
|
|
192
|
+
warn_unsupported_feature(
|
|
193
|
+
"Passing `clients_ids` to `start_simulation` is deprecated and not longer "
|
|
194
|
+
"used by `start_simulation`. Use `num_clients` exclusively instead."
|
|
195
|
+
)
|
|
196
|
+
log(ERROR, "`clients_ids` argument used.")
|
|
197
|
+
sys.exit()
|
|
198
|
+
|
|
173
199
|
# Set logger propagation
|
|
174
200
|
loop: Optional[asyncio.AbstractEventLoop] = None
|
|
175
201
|
try:
|
|
@@ -196,20 +222,8 @@ def start_simulation(
|
|
|
196
222
|
initialized_config,
|
|
197
223
|
)
|
|
198
224
|
|
|
199
|
-
#
|
|
200
|
-
|
|
201
|
-
if clients_ids is not None:
|
|
202
|
-
if (num_clients is not None) and (len(clients_ids) != num_clients):
|
|
203
|
-
log(ERROR, INVALID_ARGUMENTS_START_SIMULATION)
|
|
204
|
-
sys.exit()
|
|
205
|
-
else:
|
|
206
|
-
cids = clients_ids
|
|
207
|
-
else:
|
|
208
|
-
if num_clients is None:
|
|
209
|
-
log(ERROR, INVALID_ARGUMENTS_START_SIMULATION)
|
|
210
|
-
sys.exit()
|
|
211
|
-
else:
|
|
212
|
-
cids = [str(x) for x in range(num_clients)]
|
|
225
|
+
# Create node-id to partition-id mapping
|
|
226
|
+
nodes_mapping = _create_node_id_to_partition_mapping(num_clients)
|
|
213
227
|
|
|
214
228
|
# Default arguments for Ray initialization
|
|
215
229
|
if not ray_init_args:
|
|
@@ -308,10 +322,11 @@ def start_simulation(
|
|
|
308
322
|
)
|
|
309
323
|
|
|
310
324
|
# Register one RayClientProxy object for each client with the ClientManager
|
|
311
|
-
for
|
|
325
|
+
for node_id, partition_id in nodes_mapping.items():
|
|
312
326
|
client_proxy = RayActorClientProxy(
|
|
313
327
|
client_fn=client_fn,
|
|
314
|
-
|
|
328
|
+
node_id=node_id,
|
|
329
|
+
partition_id=partition_id,
|
|
315
330
|
actor_pool=pool,
|
|
316
331
|
)
|
|
317
332
|
initialized_server.client_manager().register(client=client_proxy)
|
|
@@ -20,7 +20,7 @@ from logging import ERROR
|
|
|
20
20
|
from typing import Optional
|
|
21
21
|
|
|
22
22
|
from flwr import common
|
|
23
|
-
from flwr.client import
|
|
23
|
+
from flwr.client import ClientFnExt
|
|
24
24
|
from flwr.client.client_app import ClientApp
|
|
25
25
|
from flwr.client.node_state import NodeState
|
|
26
26
|
from flwr.common import DEFAULT_TTL, Message, Metadata, RecordSet
|
|
@@ -44,16 +44,22 @@ class RayActorClientProxy(ClientProxy):
|
|
|
44
44
|
"""Flower client proxy which delegates work using Ray."""
|
|
45
45
|
|
|
46
46
|
def __init__(
|
|
47
|
-
self,
|
|
47
|
+
self,
|
|
48
|
+
client_fn: ClientFnExt,
|
|
49
|
+
node_id: int,
|
|
50
|
+
partition_id: int,
|
|
51
|
+
actor_pool: VirtualClientEngineActorPool,
|
|
48
52
|
):
|
|
49
|
-
super().__init__(cid)
|
|
53
|
+
super().__init__(cid=str(node_id))
|
|
54
|
+
self.node_id = node_id
|
|
55
|
+
self.partition_id = partition_id
|
|
50
56
|
|
|
51
57
|
def _load_app() -> ClientApp:
|
|
52
58
|
return ClientApp(client_fn=client_fn)
|
|
53
59
|
|
|
54
60
|
self.app_fn = _load_app
|
|
55
61
|
self.actor_pool = actor_pool
|
|
56
|
-
self.proxy_state = NodeState()
|
|
62
|
+
self.proxy_state = NodeState(partition_id=self.partition_id)
|
|
57
63
|
|
|
58
64
|
def _submit_job(self, message: Message, timeout: Optional[float]) -> Message:
|
|
59
65
|
"""Sumbit a message to the ActorPool."""
|
|
@@ -67,11 +73,13 @@ class RayActorClientProxy(ClientProxy):
|
|
|
67
73
|
|
|
68
74
|
try:
|
|
69
75
|
self.actor_pool.submit_client_job(
|
|
70
|
-
lambda a, a_fn, mssg,
|
|
71
|
-
|
|
76
|
+
lambda a, a_fn, mssg, partition_id, state: a.run.remote(
|
|
77
|
+
a_fn, mssg, partition_id, state
|
|
78
|
+
),
|
|
79
|
+
(self.app_fn, message, str(self.partition_id), state),
|
|
72
80
|
)
|
|
73
81
|
out_mssg, updated_context = self.actor_pool.get_client_result(
|
|
74
|
-
self.
|
|
82
|
+
str(self.partition_id), timeout
|
|
75
83
|
)
|
|
76
84
|
|
|
77
85
|
# Update state
|
|
@@ -103,11 +111,10 @@ class RayActorClientProxy(ClientProxy):
|
|
|
103
111
|
message_id="",
|
|
104
112
|
group_id=str(group_id) if group_id is not None else "",
|
|
105
113
|
src_node_id=0,
|
|
106
|
-
dst_node_id=
|
|
114
|
+
dst_node_id=self.node_id,
|
|
107
115
|
reply_to_message="",
|
|
108
116
|
ttl=timeout if timeout else DEFAULT_TTL,
|
|
109
117
|
message_type=message_type,
|
|
110
|
-
partition_id=int(self.cid),
|
|
111
118
|
),
|
|
112
119
|
)
|
|
113
120
|
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
"""Deployment engine executor."""
|
|
16
|
+
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
from logging import ERROR, INFO
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
from typing_extensions import override
|
|
23
|
+
|
|
24
|
+
from flwr.cli.config_utils import get_fab_metadata
|
|
25
|
+
from flwr.cli.install import install_from_fab
|
|
26
|
+
from flwr.common.grpc import create_channel
|
|
27
|
+
from flwr.common.logger import log
|
|
28
|
+
from flwr.proto.driver_pb2 import CreateRunRequest # pylint: disable=E0611
|
|
29
|
+
from flwr.proto.driver_pb2_grpc import DriverStub
|
|
30
|
+
from flwr.server.driver.grpc_driver import DEFAULT_SERVER_ADDRESS_DRIVER
|
|
31
|
+
|
|
32
|
+
from .executor import Executor, RunTracker
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DeploymentEngine(Executor):
|
|
36
|
+
"""Deployment engine executor."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
address: str = DEFAULT_SERVER_ADDRESS_DRIVER,
|
|
41
|
+
root_certificates: Optional[bytes] = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
self.address = address
|
|
44
|
+
self.root_certificates = root_certificates
|
|
45
|
+
self.stub: Optional[DriverStub] = None
|
|
46
|
+
|
|
47
|
+
def _connect(self) -> None:
|
|
48
|
+
if self.stub is None:
|
|
49
|
+
channel = create_channel(
|
|
50
|
+
server_address=self.address,
|
|
51
|
+
insecure=(self.root_certificates is None),
|
|
52
|
+
root_certificates=self.root_certificates,
|
|
53
|
+
)
|
|
54
|
+
self.stub = DriverStub(channel)
|
|
55
|
+
|
|
56
|
+
def _create_run(self, fab_id: str, fab_version: str) -> int:
|
|
57
|
+
if self.stub is None:
|
|
58
|
+
self._connect()
|
|
59
|
+
|
|
60
|
+
assert self.stub is not None
|
|
61
|
+
|
|
62
|
+
req = CreateRunRequest(fab_id=fab_id, fab_version=fab_version)
|
|
63
|
+
res = self.stub.CreateRun(request=req)
|
|
64
|
+
return int(res.run_id)
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
def start_run(self, fab_file: bytes) -> Optional[RunTracker]:
|
|
68
|
+
"""Start run using the Flower Deployment Engine."""
|
|
69
|
+
try:
|
|
70
|
+
# Install FAB to flwr dir
|
|
71
|
+
fab_version, fab_id = get_fab_metadata(fab_file)
|
|
72
|
+
fab_path = install_from_fab(fab_file, None, True)
|
|
73
|
+
|
|
74
|
+
# Install FAB Python package
|
|
75
|
+
subprocess.check_call(
|
|
76
|
+
[sys.executable, "-m", "pip", "install", str(fab_path)],
|
|
77
|
+
stdout=subprocess.DEVNULL,
|
|
78
|
+
stderr=subprocess.DEVNULL,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Call SuperLink to create run
|
|
82
|
+
run_id: int = self._create_run(fab_id, fab_version)
|
|
83
|
+
log(INFO, "Created run %s", str(run_id))
|
|
84
|
+
|
|
85
|
+
# Start ServerApp
|
|
86
|
+
proc = subprocess.Popen( # pylint: disable=consider-using-with
|
|
87
|
+
[
|
|
88
|
+
"flower-server-app",
|
|
89
|
+
"--run-id",
|
|
90
|
+
str(run_id),
|
|
91
|
+
"--insecure",
|
|
92
|
+
],
|
|
93
|
+
stdout=subprocess.PIPE,
|
|
94
|
+
stderr=subprocess.PIPE,
|
|
95
|
+
text=True,
|
|
96
|
+
)
|
|
97
|
+
log(INFO, "Started run %s", str(run_id))
|
|
98
|
+
|
|
99
|
+
return RunTracker(
|
|
100
|
+
run_id=run_id,
|
|
101
|
+
proc=proc,
|
|
102
|
+
)
|
|
103
|
+
# pylint: disable-next=broad-except
|
|
104
|
+
except Exception as e:
|
|
105
|
+
log(ERROR, "Could not start run: %s", str(e))
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
executor = DeploymentEngine()
|
{flwr_nightly-1.10.0.dev20240624.dist-info → flwr_nightly-1.10.0.dev20240707.dist-info}/RECORD
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
flwr/__init__.py,sha256=VmBWedrCxqmt4QvUHBLqyVEH6p7zaFMD_oCHerXHSVw,937
|
|
2
2
|
flwr/cli/__init__.py,sha256=cZJVgozlkC6Ni2Hd_FAIrqefrkCGOV18fikToq-6iLw,720
|
|
3
3
|
flwr/cli/app.py,sha256=FBcSrE35ll88VE11ib67qgsJe2GYDN25UswV9-cYcX8,1267
|
|
4
|
-
flwr/cli/build.py,sha256=
|
|
4
|
+
flwr/cli/build.py,sha256=G0wgNrgxir_H0Qb_YlT2itxETEb-9q_3RQflqIqNXTU,4737
|
|
5
5
|
flwr/cli/config_utils.py,sha256=ugUlqH52yxTPMtKw6q4xv5k2OVWUy89cwyJ5LB2RLgk,6037
|
|
6
6
|
flwr/cli/example.py,sha256=1bGDYll3BXQY2kRqSN-oICqS5n1b9m0g0RvXTopXHl4,2215
|
|
7
7
|
flwr/cli/install.py,sha256=Wz7Hqg2PE9N-w5CnqlH9Zr8mzADN2J7NLcUhgldZLWU,6579
|
|
@@ -50,12 +50,12 @@ flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=wxN6I8uvWZ4MErvTbQJ
|
|
|
50
50
|
flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=wFeJuhqnBPQtKCBvnE3ySBpxmbeNdxcsq2Eb_RmSDIg,655
|
|
51
51
|
flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=zkxLTQRvujF76sIlzNNGPVU7Y9nVCwNBxAx82AOBaJY,654
|
|
52
52
|
flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
|
|
53
|
-
flwr/cli/run/run.py,sha256=
|
|
53
|
+
flwr/cli/run/run.py,sha256=WsOknYnwm_iD-s6jAHxjGKbm0PgV3VZdQ04v6s4nPQY,4449
|
|
54
54
|
flwr/cli/utils.py,sha256=l65Ul0YsSBPuypk0uorAtEDmLEYiUrzpCXi6zCg9mJ4,4506
|
|
55
|
-
flwr/client/__init__.py,sha256=
|
|
56
|
-
flwr/client/app.py,sha256=
|
|
55
|
+
flwr/client/__init__.py,sha256=wzJZsYJIHf_8-PMzvfbinyzzjgh1UP1vLrAw2_yEbKI,1345
|
|
56
|
+
flwr/client/app.py,sha256=NiueYu7-_2_qwb2GYnp-Lzk39gkgeNN9I36psNbjl2U,24784
|
|
57
57
|
flwr/client/client.py,sha256=Vp9UkOkoHdNfn6iMYZsj_5m_GICiFfUlKEVaLad-YhM,8183
|
|
58
|
-
flwr/client/client_app.py,sha256=
|
|
58
|
+
flwr/client/client_app.py,sha256=cvY-km3JEOWKxUio4xvksNFBk2FQQXliUfQTlDty71w,9648
|
|
59
59
|
flwr/client/dpfedavg_numpy_client.py,sha256=ylZ-LpBIKmL1HCiS8kq4pkp2QGalc8rYEzDHdRG3VRQ,7435
|
|
60
60
|
flwr/client/grpc_adapter_client/__init__.py,sha256=QyNWIbsq9DpyMk7oemiO1P3TBFfkfkctnJ1JoAkTl3s,742
|
|
61
61
|
flwr/client/grpc_adapter_client/connection.py,sha256=kUObTsJ_4OwxE4tvIlWwn_iSWk2snjLWMo19tqdXo1s,3841
|
|
@@ -67,7 +67,7 @@ flwr/client/grpc_rere_client/connection.py,sha256=MazJNpMkatxXXcmkwNCWfCzCqMXW-n
|
|
|
67
67
|
flwr/client/grpc_rere_client/grpc_adapter.py,sha256=woljH8yr1pyLH4W4Azogyy7Nafn6y9DHBnDCIIVKwCw,4711
|
|
68
68
|
flwr/client/heartbeat.py,sha256=cx37mJBH8LyoIN4Lks85wtqT1mnU5GulQnr4pGCvAq0,2404
|
|
69
69
|
flwr/client/message_handler/__init__.py,sha256=QxxQuBNpFPTHx3KiUNvQSlqMKlEnbRR1kFfc1KVje08,719
|
|
70
|
-
flwr/client/message_handler/message_handler.py,sha256=
|
|
70
|
+
flwr/client/message_handler/message_handler.py,sha256=rLuzcpRqSFofQ8QIB6OGnh9HC5uxUAnkQQIJaP0JHvM,6574
|
|
71
71
|
flwr/client/message_handler/task_handler.py,sha256=ZDJBKmrn2grRMNl1rU1iGs7FiMHL5VmZiSp_6h9GHVU,1824
|
|
72
72
|
flwr/client/mod/__init__.py,sha256=37XeXZLFq_tzFVKVtC9JaigM2bSAU7BrGQvMPCE3Q28,1159
|
|
73
73
|
flwr/client/mod/centraldp_mods.py,sha256=UGwNuqpmOWfLdfJITFgdi1TG-nLjuSb-cbEyoyfDgxQ,5415
|
|
@@ -77,27 +77,27 @@ flwr/client/mod/secure_aggregation/__init__.py,sha256=A7DzZ3uvXTUkuHBzrxJMWQQD4R
|
|
|
77
77
|
flwr/client/mod/secure_aggregation/secagg_mod.py,sha256=wI9tuIEvMUETz-wVIEbPYvh-1nK9CEylBLGoVpNhL94,1095
|
|
78
78
|
flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=fZTfIELkYS64lpgxQKL66s-QHjCn-159qfLoNoIMJjc,19699
|
|
79
79
|
flwr/client/mod/utils.py,sha256=UAJXiB0wwVyLkCkpW_i5BXikdBR65p8sNFr7VNHm2nk,1226
|
|
80
|
-
flwr/client/node_state.py,sha256=
|
|
81
|
-
flwr/client/node_state_tests.py,sha256=
|
|
80
|
+
flwr/client/node_state.py,sha256=z4ol2a20wvspTkn53q24Gnt-1csZRR8JjWnk4A_-Agk,1922
|
|
81
|
+
flwr/client/node_state_tests.py,sha256=fadnOTT3VAuzzs_UAbOukcuyx-oQPv2lBq92qTuUecw,2212
|
|
82
82
|
flwr/client/numpy_client.py,sha256=u76GWAdHmJM88Agm2EgLQSvO8Jnk225mJTk-_TmPjFE,10283
|
|
83
83
|
flwr/client/rest_client/__init__.py,sha256=5KGlp7pjc1dhNRkKlaNtUfQmg8wrRFh9lS3P3uRS-7Q,735
|
|
84
84
|
flwr/client/rest_client/connection.py,sha256=vw264e67jq7kgqgqHLvgXFbQwoHll2yET9XKzJkXtnM,11957
|
|
85
85
|
flwr/client/supernode/__init__.py,sha256=SUhWOzcgXRNXk1V9UgB5-FaWukqqrOEajVUHEcPkwyQ,865
|
|
86
|
-
flwr/client/supernode/app.py,sha256=
|
|
87
|
-
flwr/client/typing.py,sha256=
|
|
86
|
+
flwr/client/supernode/app.py,sha256=WuTp0vt2ILyKHOCl9OUtFfdlUExt-eEUy9SOFVlJAEw,14857
|
|
87
|
+
flwr/client/typing.py,sha256=RJGVF64Z0nqW-qmdFuFaY4Jig3dMUFgNhFi-5dq-8-I,1069
|
|
88
88
|
flwr/common/__init__.py,sha256=4cBLNNnNTwHDnL_HCxhU5ILCSZ6fYh3A_aMBtlvHTVw,3721
|
|
89
89
|
flwr/common/address.py,sha256=wRu1Luezx1PWadwV9OA_KNko01oVvbRnPqfzaDn8QOk,1882
|
|
90
90
|
flwr/common/config.py,sha256=afI57MxKWrLzrh0rlrSxnAlMCI7crgnllouAKxznWLY,2628
|
|
91
|
-
flwr/common/constant.py,sha256=
|
|
92
|
-
flwr/common/context.py,sha256=
|
|
91
|
+
flwr/common/constant.py,sha256=qNmxEV3_pOO7MeTAA9qwIh4KoCPStcX3Gm8GRPIRx_4,2890
|
|
92
|
+
flwr/common/context.py,sha256=j894PRbqybFSw4Shw8DFMleDx3SUYTbtNk5xLFyNHGw,1790
|
|
93
93
|
flwr/common/date.py,sha256=OcQuwpb2HxcblTqYm6H223ufop5UZw5N_fzalbpOVzY,891
|
|
94
94
|
flwr/common/differential_privacy.py,sha256=WZWrL7C9XaB9l9NDkLDI5PvM7jwcoTTFu08ZVG8-M5Q,6113
|
|
95
95
|
flwr/common/differential_privacy_constants.py,sha256=c7b7tqgvT7yMK0XN9ndiTBs4mQf6d3qk6K7KBZGlV4Q,1074
|
|
96
96
|
flwr/common/dp.py,sha256=SZ3MtJKpjxUeQeyb2pqWSF0S_h9rZtCGYPToIxqcNj8,2004
|
|
97
97
|
flwr/common/exit_handlers.py,sha256=2Nt0wLhc17KQQsLPFSRAjjhUiEFfJK6tNozdGiIY4Fs,2812
|
|
98
98
|
flwr/common/grpc.py,sha256=_9838_onFLx7W6_lakUN35ziKpdcKp7fA-0jE0EhcEQ,2460
|
|
99
|
-
flwr/common/logger.py,sha256=
|
|
100
|
-
flwr/common/message.py,sha256=
|
|
99
|
+
flwr/common/logger.py,sha256=2WW8xV49EHNTBGQP9N83ekYYLVgzjtxLYCM-m0UXwgw,7426
|
|
100
|
+
flwr/common/message.py,sha256=QmFYYXA-3e9M8tGO-3NPyAI8yvdmcpdYaA_noR1DE88,13194
|
|
101
101
|
flwr/common/object_ref.py,sha256=PQR0tztVOkD1nn_uGuNz4bHm7z4fwsosTsUKvWIGF5Y,6506
|
|
102
102
|
flwr/common/parameter.py,sha256=-bFAUayToYDF50FZGrBC1hQYJCQDtB2bbr3ZuVLMtdE,2095
|
|
103
103
|
flwr/common/pyproject.py,sha256=EI_ovbCHGmhYrdPx0RSDi5EkFZFof-8m1PA54c0ZTjc,1385
|
|
@@ -233,14 +233,14 @@ flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=yoSU-6nCJF9ASHGNpSY69nZ
|
|
|
233
233
|
flwr/server/superlink/fleet/vce/__init__.py,sha256=36MHKiefnJeyjwMQzVUK4m06Ojon3WDcwZGQsAcyVhQ,783
|
|
234
234
|
flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=oBIzmnrSSRvH_H0vRGEGWhWzQQwqe3zn6e13RsNwlIY,1466
|
|
235
235
|
flwr/server/superlink/fleet/vce/backend/backend.py,sha256=LJsKl7oixVvptcG98Rd9ejJycNWcEVB0ODvSreLGp-A,2260
|
|
236
|
-
flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=
|
|
237
|
-
flwr/server/superlink/fleet/vce/vce_api.py,sha256=
|
|
236
|
+
flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=dwaebZfzvzlvjkMflH5hJ19-Sszvxt0AWwIEGk9BliU,7495
|
|
237
|
+
flwr/server/superlink/fleet/vce/vce_api.py,sha256=skPxli2QKTn-zElEt7lak5Q4N5v8NZB-EeROKac2bAs,12387
|
|
238
238
|
flwr/server/superlink/state/__init__.py,sha256=Gj2OTFLXvA-mAjBvwuKDM3rDrVaQPcIoybSa2uskMTE,1003
|
|
239
|
-
flwr/server/superlink/state/in_memory_state.py,sha256=
|
|
240
|
-
flwr/server/superlink/state/sqlite_state.py,sha256=
|
|
239
|
+
flwr/server/superlink/state/in_memory_state.py,sha256=5bkz3oqbpPymhorCtbicxQg31RJC0vT4AT-o0Ex0LRQ,12895
|
|
240
|
+
flwr/server/superlink/state/sqlite_state.py,sha256=tDE6fWVMwkXugy6ri75K-H5TXs-2MuBaVpFi9kKX01c,28680
|
|
241
241
|
flwr/server/superlink/state/state.py,sha256=Ap-TzFPWTRd0a4uCq0XhE-jDPfPbzGvQEDskVOpCt98,8037
|
|
242
242
|
flwr/server/superlink/state/state_factory.py,sha256=Fo8pBQ1WWrVJK5TOEPZ_zgJE69_mfTGjTO6czh6571o,2021
|
|
243
|
-
flwr/server/superlink/state/utils.py,sha256=
|
|
243
|
+
flwr/server/superlink/state/utils.py,sha256=155ngcaSePy7nD8X4LHgpuVok6fcH5_CPNRiFAbLWDA,2407
|
|
244
244
|
flwr/server/typing.py,sha256=2zSG-KuDAgwFPuzgVjTLDaEqJ8gXXGqFR2RD-qIk730,913
|
|
245
245
|
flwr/server/utils/__init__.py,sha256=pltsPHJoXmUIr3utjwwYxu7_ZAGy5u4MVHzv9iA5Un8,908
|
|
246
246
|
flwr/server/utils/tensorboard.py,sha256=l6aMVdtZbbfCX8uwFW-WxH6P171-R-tulMcPhlykowo,5485
|
|
@@ -252,19 +252,20 @@ flwr/server/workflow/secure_aggregation/__init__.py,sha256=3XlgDOjD_hcukTGl6Bc1B
|
|
|
252
252
|
flwr/server/workflow/secure_aggregation/secagg_workflow.py,sha256=wpAkYPId0nfK6SgpUAtsCni4_MQLd-uqJ81tUKu3xlI,5838
|
|
253
253
|
flwr/server/workflow/secure_aggregation/secaggplus_workflow.py,sha256=BRqhlnVe8CYNoUvb_KCfRXay02NTT6a-pCrMaOqAxGc,29038
|
|
254
254
|
flwr/simulation/__init__.py,sha256=9x8OCkK3jpFAPJB1aeEMOddz6V58bExQPtwE8Z3q-RY,1359
|
|
255
|
-
flwr/simulation/app.py,sha256=
|
|
255
|
+
flwr/simulation/app.py,sha256=8NDXoQ8oC11khXIGnydrsUh5JfaH7c2Fwzix8vDFK1I,15144
|
|
256
256
|
flwr/simulation/ray_transport/__init__.py,sha256=wzcEEwUUlulnXsg6raCA1nGpP3LlAQDtJ8zNkCXcVbA,734
|
|
257
257
|
flwr/simulation/ray_transport/ray_actor.py,sha256=bu6gEnbHYtlUxLtzjzpEUtvkQDRzl1PVMjJuCDZvfgQ,19196
|
|
258
|
-
flwr/simulation/ray_transport/ray_client_proxy.py,sha256=
|
|
258
|
+
flwr/simulation/ray_transport/ray_client_proxy.py,sha256=zGLVebfwFhBo1CAqEQ0MtW-fPG8ark3e4n6OksFGch4,6954
|
|
259
259
|
flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
|
|
260
260
|
flwr/simulation/run_simulation.py,sha256=m2FK0LzLSOR2HC94XDowLLwvHuIhaV6q78HsT_MKLMU,16858
|
|
261
261
|
flwr/superexec/__init__.py,sha256=9h94ogLxi6eJ3bUuJYq3E3pApThSabTPiSmPAGlTkHE,800
|
|
262
262
|
flwr/superexec/app.py,sha256=kCCsCo51_peIMmKc4-tmiN5qO29cPqZOqqs-QVH0oA4,6097
|
|
263
|
+
flwr/superexec/deployment.py,sha256=yWIhS8rozuDo_jQ-Uw-RX_9-Lsn3Pb2uqe7OZYjXSQ0,3733
|
|
263
264
|
flwr/superexec/exec_grpc.py,sha256=u-rztpOleqSGqgvNE-ZLw1HchNsBHU1-eB3m52GZ0pQ,1852
|
|
264
265
|
flwr/superexec/exec_servicer.py,sha256=qf8CT4RLXnY8omOy75kwfsWmMnfTD42B4ENTh5S-BCY,2120
|
|
265
266
|
flwr/superexec/executor.py,sha256=GouXCY2LiZ-ffsOoZ_z-fh4JwbzMmhTl-gwpWFgGWTY,1688
|
|
266
|
-
flwr_nightly-1.10.0.
|
|
267
|
-
flwr_nightly-1.10.0.
|
|
268
|
-
flwr_nightly-1.10.0.
|
|
269
|
-
flwr_nightly-1.10.0.
|
|
270
|
-
flwr_nightly-1.10.0.
|
|
267
|
+
flwr_nightly-1.10.0.dev20240707.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
268
|
+
flwr_nightly-1.10.0.dev20240707.dist-info/METADATA,sha256=BAQMk6LhS1e9XGOHFlZ3zCYWnGrca2VtuMzMNeOgxGA,15614
|
|
269
|
+
flwr_nightly-1.10.0.dev20240707.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
270
|
+
flwr_nightly-1.10.0.dev20240707.dist-info/entry_points.txt,sha256=7qBQcA-bDGDxnJmLd9FYqglFQubjCNqyg9M8a-lukps,336
|
|
271
|
+
flwr_nightly-1.10.0.dev20240707.dist-info/RECORD,,
|
{flwr_nightly-1.10.0.dev20240624.dist-info → flwr_nightly-1.10.0.dev20240707.dist-info}/LICENSE
RENAMED
|
File without changes
|
{flwr_nightly-1.10.0.dev20240624.dist-info → flwr_nightly-1.10.0.dev20240707.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|