flwr-nightly 1.13.0.dev20241105__py3-none-any.whl → 1.13.0.dev20241107__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/run/run.py +16 -5
- flwr/client/app.py +10 -6
- flwr/client/nodestate/__init__.py +25 -0
- flwr/client/nodestate/in_memory_nodestate.py +38 -0
- flwr/client/nodestate/nodestate.py +30 -0
- flwr/client/nodestate/nodestate_factory.py +37 -0
- flwr/client/run_info_store.py +1 -0
- flwr/common/config.py +10 -0
- flwr/common/constant.py +1 -1
- flwr/common/context.py +9 -4
- flwr/common/object_ref.py +40 -33
- flwr/common/serde.py +2 -0
- flwr/proto/exec_pb2.py +14 -17
- flwr/proto/exec_pb2.pyi +6 -20
- flwr/proto/message_pb2.py +8 -8
- flwr/proto/message_pb2.pyi +4 -1
- flwr/server/app.py +140 -107
- flwr/server/driver/driver.py +1 -1
- flwr/server/driver/grpc_driver.py +2 -6
- flwr/server/driver/inmemory_driver.py +1 -3
- flwr/server/run_serverapp.py +5 -2
- flwr/server/serverapp/app.py +1 -1
- flwr/server/superlink/driver/serverappio_servicer.py +2 -0
- flwr/server/superlink/linkstate/in_memory_linkstate.py +15 -16
- flwr/server/superlink/linkstate/linkstate.py +18 -1
- flwr/server/superlink/linkstate/sqlite_linkstate.py +41 -21
- flwr/server/superlink/linkstate/utils.py +14 -30
- flwr/server/superlink/simulation/__init__.py +15 -0
- flwr/server/superlink/simulation/simulationio_grpc.py +65 -0
- flwr/server/superlink/simulation/simulationio_servicer.py +132 -0
- flwr/simulation/__init__.py +2 -0
- flwr/simulation/run_simulation.py +4 -1
- flwr/simulation/simulationio_connection.py +86 -0
- flwr/superexec/deployment.py +8 -4
- flwr/superexec/exec_servicer.py +2 -2
- flwr/superexec/executor.py +4 -3
- flwr/superexec/simulation.py +8 -8
- {flwr_nightly-1.13.0.dev20241105.dist-info → flwr_nightly-1.13.0.dev20241107.dist-info}/METADATA +1 -1
- {flwr_nightly-1.13.0.dev20241105.dist-info → flwr_nightly-1.13.0.dev20241107.dist-info}/RECORD +42 -34
- {flwr_nightly-1.13.0.dev20241105.dist-info → flwr_nightly-1.13.0.dev20241107.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.13.0.dev20241105.dist-info → flwr_nightly-1.13.0.dev20241107.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.13.0.dev20241105.dist-info → flwr_nightly-1.13.0.dev20241107.dist-info}/entry_points.txt +0 -0
|
@@ -15,18 +15,15 @@
|
|
|
15
15
|
"""Utility functions for State."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import time
|
|
19
|
-
from logging import ERROR
|
|
20
18
|
from os import urandom
|
|
21
|
-
from uuid import uuid4
|
|
22
19
|
|
|
23
|
-
from flwr.common import
|
|
24
|
-
from flwr.common.constant import
|
|
20
|
+
from flwr.common import ConfigsRecord, Context, serde
|
|
21
|
+
from flwr.common.constant import Status, SubStatus
|
|
25
22
|
from flwr.common.typing import RunStatus
|
|
26
|
-
from flwr.proto.error_pb2 import Error # pylint: disable=E0611
|
|
27
23
|
from flwr.proto.message_pb2 import Context as ProtoContext # pylint: disable=E0611
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
|
|
25
|
+
# pylint: disable=E0611
|
|
26
|
+
from flwr.proto.recordset_pb2 import ConfigsRecord as ProtoConfigsRecord
|
|
30
27
|
|
|
31
28
|
NODE_UNAVAILABLE_ERROR_REASON = (
|
|
32
29
|
"Error: Node Unavailable - The destination node is currently unavailable. "
|
|
@@ -146,28 +143,15 @@ def context_from_bytes(context_bytes: bytes) -> Context:
|
|
|
146
143
|
return serde.context_from_proto(ProtoContext.FromString(context_bytes))
|
|
147
144
|
|
|
148
145
|
|
|
149
|
-
def
|
|
150
|
-
"""
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
group_id=ref_taskins.group_id,
|
|
159
|
-
run_id=ref_taskins.run_id,
|
|
160
|
-
task=Task(
|
|
161
|
-
producer=Node(node_id=ref_taskins.task.consumer.node_id, anonymous=False),
|
|
162
|
-
consumer=Node(node_id=ref_taskins.task.producer.node_id, anonymous=False),
|
|
163
|
-
created_at=current_time,
|
|
164
|
-
ttl=ttl,
|
|
165
|
-
ancestry=[ref_taskins.task_id],
|
|
166
|
-
task_type=ref_taskins.task.task_type,
|
|
167
|
-
error=Error(
|
|
168
|
-
code=ErrorCode.NODE_UNAVAILABLE, reason=NODE_UNAVAILABLE_ERROR_REASON
|
|
169
|
-
),
|
|
170
|
-
),
|
|
146
|
+
def configsrecord_to_bytes(configs_record: ConfigsRecord) -> bytes:
|
|
147
|
+
"""Serialize a `ConfigsRecord` to bytes."""
|
|
148
|
+
return serde.configs_record_to_proto(configs_record).SerializeToString()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def configsrecord_from_bytes(configsrecord_bytes: bytes) -> ConfigsRecord:
|
|
152
|
+
"""Deserialize `ConfigsRecord` from bytes."""
|
|
153
|
+
return serde.configs_record_from_proto(
|
|
154
|
+
ProtoConfigsRecord.FromString(configsrecord_bytes)
|
|
171
155
|
)
|
|
172
156
|
|
|
173
157
|
|
|
@@ -0,0 +1,15 @@
|
|
|
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 SimulationIo service."""
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
"""SimulationIo gRPC API."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from logging import INFO
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import grpc
|
|
22
|
+
|
|
23
|
+
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
|
24
|
+
from flwr.common.logger import log
|
|
25
|
+
from flwr.proto.simulationio_pb2_grpc import ( # pylint: disable=E0611
|
|
26
|
+
add_SimulationIoServicer_to_server,
|
|
27
|
+
)
|
|
28
|
+
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
|
29
|
+
from flwr.server.superlink.linkstate import LinkStateFactory
|
|
30
|
+
|
|
31
|
+
from ..fleet.grpc_bidi.grpc_server import generic_create_grpc_server
|
|
32
|
+
from .simulationio_servicer import SimulationIoServicer
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_simulationio_api_grpc(
|
|
36
|
+
address: str,
|
|
37
|
+
state_factory: LinkStateFactory,
|
|
38
|
+
ffs_factory: FfsFactory,
|
|
39
|
+
certificates: Optional[tuple[bytes, bytes, bytes]],
|
|
40
|
+
) -> grpc.Server:
|
|
41
|
+
"""Run SimulationIo API (gRPC, request-response)."""
|
|
42
|
+
# Create SimulationIo API gRPC server
|
|
43
|
+
simulationio_servicer: grpc.Server = SimulationIoServicer(
|
|
44
|
+
state_factory=state_factory,
|
|
45
|
+
ffs_factory=ffs_factory,
|
|
46
|
+
)
|
|
47
|
+
simulationio_add_servicer_to_server_fn = add_SimulationIoServicer_to_server
|
|
48
|
+
simulationio_grpc_server = generic_create_grpc_server(
|
|
49
|
+
servicer_and_add_fn=(
|
|
50
|
+
simulationio_servicer,
|
|
51
|
+
simulationio_add_servicer_to_server_fn,
|
|
52
|
+
),
|
|
53
|
+
server_address=address,
|
|
54
|
+
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
55
|
+
certificates=certificates,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
log(
|
|
59
|
+
INFO,
|
|
60
|
+
"Flower Simulation Engine: Starting SimulationIo API on %s",
|
|
61
|
+
address,
|
|
62
|
+
)
|
|
63
|
+
simulationio_grpc_server.start()
|
|
64
|
+
|
|
65
|
+
return simulationio_grpc_server
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
"""SimulationIo API servicer."""
|
|
16
|
+
|
|
17
|
+
import threading
|
|
18
|
+
from logging import DEBUG, INFO
|
|
19
|
+
|
|
20
|
+
import grpc
|
|
21
|
+
from grpc import ServicerContext
|
|
22
|
+
|
|
23
|
+
from flwr.common.constant import Status
|
|
24
|
+
from flwr.common.logger import log
|
|
25
|
+
from flwr.common.serde import (
|
|
26
|
+
context_from_proto,
|
|
27
|
+
context_to_proto,
|
|
28
|
+
fab_to_proto,
|
|
29
|
+
run_status_from_proto,
|
|
30
|
+
run_to_proto,
|
|
31
|
+
)
|
|
32
|
+
from flwr.common.typing import Fab, RunStatus
|
|
33
|
+
from flwr.proto import simulationio_pb2_grpc
|
|
34
|
+
from flwr.proto.log_pb2 import ( # pylint: disable=E0611
|
|
35
|
+
PushLogsRequest,
|
|
36
|
+
PushLogsResponse,
|
|
37
|
+
)
|
|
38
|
+
from flwr.proto.run_pb2 import ( # pylint: disable=E0611
|
|
39
|
+
UpdateRunStatusRequest,
|
|
40
|
+
UpdateRunStatusResponse,
|
|
41
|
+
)
|
|
42
|
+
from flwr.proto.simulationio_pb2 import ( # pylint: disable=E0611
|
|
43
|
+
PullSimulationInputsRequest,
|
|
44
|
+
PullSimulationInputsResponse,
|
|
45
|
+
PushSimulationOutputsRequest,
|
|
46
|
+
PushSimulationOutputsResponse,
|
|
47
|
+
)
|
|
48
|
+
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
|
49
|
+
from flwr.server.superlink.linkstate import LinkStateFactory
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
|
|
53
|
+
"""SimulationIo API servicer."""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self, state_factory: LinkStateFactory, ffs_factory: FfsFactory
|
|
57
|
+
) -> None:
|
|
58
|
+
self.state_factory = state_factory
|
|
59
|
+
self.ffs_factory = ffs_factory
|
|
60
|
+
self.lock = threading.RLock()
|
|
61
|
+
|
|
62
|
+
def PullSimulationInputs(
|
|
63
|
+
self, request: PullSimulationInputsRequest, context: ServicerContext
|
|
64
|
+
) -> PullSimulationInputsResponse:
|
|
65
|
+
"""Pull SimultionIo process inputs."""
|
|
66
|
+
log(DEBUG, "SimultionIoServicer.SimultionIoInputs")
|
|
67
|
+
# Init access to LinkState and Ffs
|
|
68
|
+
state = self.state_factory.state()
|
|
69
|
+
ffs = self.ffs_factory.ffs()
|
|
70
|
+
|
|
71
|
+
# Lock access to LinkState, preventing obtaining the same pending run_id
|
|
72
|
+
with self.lock:
|
|
73
|
+
# Attempt getting the run_id of a pending run
|
|
74
|
+
run_id = state.get_pending_run_id()
|
|
75
|
+
# If there's no pending run, return an empty response
|
|
76
|
+
if run_id is None:
|
|
77
|
+
return PullSimulationInputsResponse()
|
|
78
|
+
|
|
79
|
+
# Retrieve Context, Run and Fab for the run_id
|
|
80
|
+
serverapp_ctxt = state.get_serverapp_context(run_id)
|
|
81
|
+
run = state.get_run(run_id)
|
|
82
|
+
fab = None
|
|
83
|
+
if run and run.fab_hash:
|
|
84
|
+
if result := ffs.get(run.fab_hash):
|
|
85
|
+
fab = Fab(run.fab_hash, result[0])
|
|
86
|
+
if run and fab and serverapp_ctxt:
|
|
87
|
+
# Update run status to STARTING
|
|
88
|
+
if state.update_run_status(run_id, RunStatus(Status.STARTING, "", "")):
|
|
89
|
+
log(INFO, "Starting run %d", run_id)
|
|
90
|
+
return PullSimulationInputsResponse(
|
|
91
|
+
context=context_to_proto(serverapp_ctxt),
|
|
92
|
+
run=run_to_proto(run),
|
|
93
|
+
fab=fab_to_proto(fab),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Raise an exception if the Run or Fab is not found,
|
|
97
|
+
# or if the status cannot be updated to STARTING
|
|
98
|
+
raise RuntimeError(f"Failed to start run {run_id}")
|
|
99
|
+
|
|
100
|
+
def PushSimulationOutputs(
|
|
101
|
+
self, request: PushSimulationOutputsRequest, context: ServicerContext
|
|
102
|
+
) -> PushSimulationOutputsResponse:
|
|
103
|
+
"""Push Simulation process outputs."""
|
|
104
|
+
log(DEBUG, "SimultionIoServicer.PushSimulationOutputs")
|
|
105
|
+
state = self.state_factory.state()
|
|
106
|
+
state.set_serverapp_context(request.run_id, context_from_proto(request.context))
|
|
107
|
+
return PushSimulationOutputsResponse()
|
|
108
|
+
|
|
109
|
+
def UpdateRunStatus(
|
|
110
|
+
self, request: UpdateRunStatusRequest, context: grpc.ServicerContext
|
|
111
|
+
) -> UpdateRunStatusResponse:
|
|
112
|
+
"""Update the status of a run."""
|
|
113
|
+
log(DEBUG, "SimultionIoServicer.UpdateRunStatus")
|
|
114
|
+
state = self.state_factory.state()
|
|
115
|
+
|
|
116
|
+
# Update the run status
|
|
117
|
+
state.update_run_status(
|
|
118
|
+
run_id=request.run_id, new_status=run_status_from_proto(request.run_status)
|
|
119
|
+
)
|
|
120
|
+
return UpdateRunStatusResponse()
|
|
121
|
+
|
|
122
|
+
def PushLogs(
|
|
123
|
+
self, request: PushLogsRequest, context: grpc.ServicerContext
|
|
124
|
+
) -> PushLogsResponse:
|
|
125
|
+
"""Push logs."""
|
|
126
|
+
log(DEBUG, "ServerAppIoServicer.PushLogs")
|
|
127
|
+
state = self.state_factory.state()
|
|
128
|
+
|
|
129
|
+
# Add logs to LinkState
|
|
130
|
+
merged_logs = "".join(request.logs)
|
|
131
|
+
state.add_serverapp_log(request.run_id, merged_logs)
|
|
132
|
+
return PushLogsResponse()
|
flwr/simulation/__init__.py
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import importlib
|
|
19
19
|
|
|
20
20
|
from flwr.simulation.run_simulation import run_simulation
|
|
21
|
+
from flwr.simulation.simulationio_connection import SimulationIoConnection
|
|
21
22
|
|
|
22
23
|
is_ray_installed = importlib.util.find_spec("ray") is not None
|
|
23
24
|
|
|
@@ -37,6 +38,7 @@ To install the necessary dependencies, install `flwr` with the `simulation` extr
|
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
__all__ = [
|
|
41
|
+
"SimulationIoConnection",
|
|
40
42
|
"run_simulation",
|
|
41
43
|
"start_simulation",
|
|
42
44
|
]
|
|
@@ -234,6 +234,7 @@ def run_serverapp_th(
|
|
|
234
234
|
f_stop: threading.Event,
|
|
235
235
|
has_exception: threading.Event,
|
|
236
236
|
enable_tf_gpu_growth: bool,
|
|
237
|
+
run_id: int,
|
|
237
238
|
) -> threading.Thread:
|
|
238
239
|
"""Run SeverApp in a thread."""
|
|
239
240
|
|
|
@@ -258,6 +259,7 @@ def run_serverapp_th(
|
|
|
258
259
|
|
|
259
260
|
# Initialize Context
|
|
260
261
|
context = Context(
|
|
262
|
+
run_id=run_id,
|
|
261
263
|
node_id=0,
|
|
262
264
|
node_config={},
|
|
263
265
|
state=RecordSet(),
|
|
@@ -345,7 +347,7 @@ def _main_loop(
|
|
|
345
347
|
|
|
346
348
|
# Initialize Driver
|
|
347
349
|
driver = InMemoryDriver(state_factory=state_factory)
|
|
348
|
-
driver.
|
|
350
|
+
driver.set_run(run_id=run.run_id)
|
|
349
351
|
|
|
350
352
|
# Get and run ServerApp thread
|
|
351
353
|
serverapp_th = run_serverapp_th(
|
|
@@ -357,6 +359,7 @@ def _main_loop(
|
|
|
357
359
|
f_stop=f_stop,
|
|
358
360
|
has_exception=server_app_thread_has_exception,
|
|
359
361
|
enable_tf_gpu_growth=enable_tf_gpu_growth,
|
|
362
|
+
run_id=run.run_id,
|
|
360
363
|
)
|
|
361
364
|
|
|
362
365
|
# Buffer time so the `ServerApp` in separate thread is ready
|
|
@@ -0,0 +1,86 @@
|
|
|
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 SimulationIo connection."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from logging import DEBUG, WARNING
|
|
19
|
+
from typing import Optional, cast
|
|
20
|
+
|
|
21
|
+
import grpc
|
|
22
|
+
|
|
23
|
+
from flwr.common.constant import SIMULATIONIO_API_DEFAULT_ADDRESS
|
|
24
|
+
from flwr.common.grpc import create_channel
|
|
25
|
+
from flwr.common.logger import log
|
|
26
|
+
from flwr.proto.simulationio_pb2_grpc import SimulationIoStub # pylint: disable=E0611
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SimulationIoConnection:
|
|
30
|
+
"""`SimulationIoConnection` provides an interface to the SimulationIo API.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
simulationio_service_address : str (default: "[::]:9094")
|
|
35
|
+
The address (URL, IPv6, IPv4) of the SuperLink SimulationIo API service.
|
|
36
|
+
root_certificates : Optional[bytes] (default: None)
|
|
37
|
+
The PEM-encoded root certificates as a byte string.
|
|
38
|
+
If provided, a secure connection using the certificates will be
|
|
39
|
+
established to an SSL-enabled Flower server.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__( # pylint: disable=too-many-arguments
|
|
43
|
+
self,
|
|
44
|
+
simulationio_service_address: str = SIMULATIONIO_API_DEFAULT_ADDRESS,
|
|
45
|
+
root_certificates: Optional[bytes] = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
self._addr = simulationio_service_address
|
|
48
|
+
self._cert = root_certificates
|
|
49
|
+
self._grpc_stub: Optional[SimulationIoStub] = None
|
|
50
|
+
self._channel: Optional[grpc.Channel] = None
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def _is_connected(self) -> bool:
|
|
54
|
+
"""Check if connected to the SimulationIo API server."""
|
|
55
|
+
return self._channel is not None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def _stub(self) -> SimulationIoStub:
|
|
59
|
+
"""SimulationIo stub."""
|
|
60
|
+
if not self._is_connected:
|
|
61
|
+
self._connect()
|
|
62
|
+
return cast(SimulationIoStub, self._grpc_stub)
|
|
63
|
+
|
|
64
|
+
def _connect(self) -> None:
|
|
65
|
+
"""Connect to the SimulationIo API."""
|
|
66
|
+
if self._is_connected:
|
|
67
|
+
log(WARNING, "Already connected")
|
|
68
|
+
return
|
|
69
|
+
self._channel = create_channel(
|
|
70
|
+
server_address=self._addr,
|
|
71
|
+
insecure=(self._cert is None),
|
|
72
|
+
root_certificates=self._cert,
|
|
73
|
+
)
|
|
74
|
+
self._grpc_stub = SimulationIoStub(self._channel)
|
|
75
|
+
log(DEBUG, "[SimulationIO] Connected to %s", self._addr)
|
|
76
|
+
|
|
77
|
+
def _disconnect(self) -> None:
|
|
78
|
+
"""Disconnect from the SimulationIo API."""
|
|
79
|
+
if not self._is_connected:
|
|
80
|
+
log(DEBUG, "Already disconnected")
|
|
81
|
+
return
|
|
82
|
+
channel: grpc.Channel = self._channel
|
|
83
|
+
self._channel = None
|
|
84
|
+
self._grpc_stub = None
|
|
85
|
+
channel.close()
|
|
86
|
+
log(DEBUG, "[SimulationIO] Disconnected")
|
flwr/superexec/deployment.py
CHANGED
|
@@ -21,7 +21,7 @@ from typing import Optional
|
|
|
21
21
|
|
|
22
22
|
from typing_extensions import override
|
|
23
23
|
|
|
24
|
-
from flwr.common import Context, RecordSet
|
|
24
|
+
from flwr.common import ConfigsRecord, Context, RecordSet
|
|
25
25
|
from flwr.common.constant import SERVERAPPIO_API_DEFAULT_ADDRESS, Status, SubStatus
|
|
26
26
|
from flwr.common.logger import log
|
|
27
27
|
from flwr.common.typing import Fab, RunStatus, UserConfig
|
|
@@ -133,13 +133,17 @@ class DeploymentEngine(Executor):
|
|
|
133
133
|
f"FAB ({fab.hash_str}) hash from request doesn't match contents"
|
|
134
134
|
)
|
|
135
135
|
|
|
136
|
-
run_id = self.linkstate.create_run(
|
|
136
|
+
run_id = self.linkstate.create_run(
|
|
137
|
+
None, None, fab_hash, override_config, ConfigsRecord()
|
|
138
|
+
)
|
|
137
139
|
return run_id
|
|
138
140
|
|
|
139
141
|
def _create_context(self, run_id: int) -> None:
|
|
140
142
|
"""Register a Context for a Run."""
|
|
141
143
|
# Create an empty context for the Run
|
|
142
|
-
context = Context(
|
|
144
|
+
context = Context(
|
|
145
|
+
run_id=run_id, node_id=0, node_config={}, state=RecordSet(), run_config={}
|
|
146
|
+
)
|
|
143
147
|
|
|
144
148
|
# Register the context at the LinkState
|
|
145
149
|
self.linkstate.set_serverapp_context(run_id=run_id, context=context)
|
|
@@ -149,7 +153,7 @@ class DeploymentEngine(Executor):
|
|
|
149
153
|
self,
|
|
150
154
|
fab_file: bytes,
|
|
151
155
|
override_config: UserConfig,
|
|
152
|
-
|
|
156
|
+
federation_options: ConfigsRecord,
|
|
153
157
|
) -> Optional[int]:
|
|
154
158
|
"""Start run using the Flower Deployment Engine."""
|
|
155
159
|
run_id = None
|
flwr/superexec/exec_servicer.py
CHANGED
|
@@ -24,7 +24,7 @@ import grpc
|
|
|
24
24
|
|
|
25
25
|
from flwr.common.constant import LOG_STREAM_INTERVAL, Status
|
|
26
26
|
from flwr.common.logger import log
|
|
27
|
-
from flwr.common.serde import user_config_from_proto
|
|
27
|
+
from flwr.common.serde import configs_record_from_proto, user_config_from_proto
|
|
28
28
|
from flwr.proto import exec_pb2_grpc # pylint: disable=E0611
|
|
29
29
|
from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
|
30
30
|
StartRunRequest,
|
|
@@ -61,7 +61,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
61
61
|
run_id = self.executor.start_run(
|
|
62
62
|
request.fab.content,
|
|
63
63
|
user_config_from_proto(request.override_config),
|
|
64
|
-
|
|
64
|
+
configs_record_from_proto(request.federation_options),
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
if run_id is None:
|
flwr/superexec/executor.py
CHANGED
|
@@ -19,6 +19,7 @@ from dataclasses import dataclass, field
|
|
|
19
19
|
from subprocess import Popen
|
|
20
20
|
from typing import Optional
|
|
21
21
|
|
|
22
|
+
from flwr.common import ConfigsRecord
|
|
22
23
|
from flwr.common.typing import UserConfig
|
|
23
24
|
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
|
24
25
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
|
@@ -71,7 +72,7 @@ class Executor(ABC):
|
|
|
71
72
|
self,
|
|
72
73
|
fab_file: bytes,
|
|
73
74
|
override_config: UserConfig,
|
|
74
|
-
|
|
75
|
+
federation_options: ConfigsRecord,
|
|
75
76
|
) -> Optional[int]:
|
|
76
77
|
"""Start a run using the given Flower FAB ID and version.
|
|
77
78
|
|
|
@@ -84,8 +85,8 @@ class Executor(ABC):
|
|
|
84
85
|
The Flower App Bundle file bytes.
|
|
85
86
|
override_config: UserConfig
|
|
86
87
|
The config overrides dict sent by the user (using `flwr run`).
|
|
87
|
-
|
|
88
|
-
The federation options
|
|
88
|
+
federation_options: ConfigsRecord
|
|
89
|
+
The federation options sent by the user (using `flwr run`).
|
|
89
90
|
|
|
90
91
|
Returns
|
|
91
92
|
-------
|
flwr/superexec/simulation.py
CHANGED
|
@@ -25,6 +25,7 @@ from typing_extensions import override
|
|
|
25
25
|
|
|
26
26
|
from flwr.cli.config_utils import load_and_validate
|
|
27
27
|
from flwr.cli.install import install_from_fab
|
|
28
|
+
from flwr.common import ConfigsRecord
|
|
28
29
|
from flwr.common.config import unflatten_dict
|
|
29
30
|
from flwr.common.constant import RUN_ID_NUM_BYTES
|
|
30
31
|
from flwr.common.logger import log
|
|
@@ -124,7 +125,7 @@ class SimulationEngine(Executor):
|
|
|
124
125
|
self,
|
|
125
126
|
fab_file: bytes,
|
|
126
127
|
override_config: UserConfig,
|
|
127
|
-
|
|
128
|
+
federation_options: ConfigsRecord,
|
|
128
129
|
) -> Optional[int]:
|
|
129
130
|
"""Start run using the Flower Simulation Engine."""
|
|
130
131
|
if self.num_supernodes is None:
|
|
@@ -163,14 +164,13 @@ class SimulationEngine(Executor):
|
|
|
163
164
|
"Config extracted from FAB's pyproject.toml is not valid"
|
|
164
165
|
)
|
|
165
166
|
|
|
166
|
-
#
|
|
167
|
-
|
|
167
|
+
# Unflatten underlaying dict
|
|
168
|
+
fed_opt = unflatten_dict({**federation_options})
|
|
168
169
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
verbose: Optional[bool] = federation_config_flat.get("verbose")
|
|
170
|
+
# Read data
|
|
171
|
+
num_supernodes = fed_opt.get("num-supernodes", self.num_supernodes)
|
|
172
|
+
backend_cfg = fed_opt.get("backend", {})
|
|
173
|
+
verbose: Optional[bool] = fed_opt.get("verbose")
|
|
174
174
|
|
|
175
175
|
# In Simulation there is no SuperLink, still we create a run_id
|
|
176
176
|
run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
|