flwr-nightly 1.13.0.dev20241021__py3-none-any.whl → 1.13.0.dev20241111__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.

Files changed (92) hide show
  1. flwr/cli/build.py +2 -2
  2. flwr/cli/config_utils.py +97 -0
  3. flwr/cli/log.py +63 -97
  4. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +1 -1
  5. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -0
  6. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  7. flwr/cli/run/run.py +34 -88
  8. flwr/client/app.py +23 -20
  9. flwr/client/clientapp/app.py +22 -18
  10. flwr/client/nodestate/__init__.py +25 -0
  11. flwr/client/nodestate/in_memory_nodestate.py +38 -0
  12. flwr/client/nodestate/nodestate.py +30 -0
  13. flwr/client/nodestate/nodestate_factory.py +37 -0
  14. flwr/client/{node_state.py → run_info_store.py} +4 -3
  15. flwr/client/supernode/app.py +6 -8
  16. flwr/common/args.py +83 -0
  17. flwr/common/config.py +10 -0
  18. flwr/common/constant.py +39 -5
  19. flwr/common/context.py +9 -4
  20. flwr/common/date.py +3 -3
  21. flwr/common/logger.py +108 -1
  22. flwr/common/object_ref.py +47 -16
  23. flwr/common/serde.py +24 -0
  24. flwr/common/telemetry.py +0 -6
  25. flwr/common/typing.py +10 -1
  26. flwr/proto/exec_pb2.py +14 -17
  27. flwr/proto/exec_pb2.pyi +14 -22
  28. flwr/proto/log_pb2.py +29 -0
  29. flwr/proto/log_pb2.pyi +39 -0
  30. flwr/proto/log_pb2_grpc.py +4 -0
  31. flwr/proto/log_pb2_grpc.pyi +4 -0
  32. flwr/proto/message_pb2.py +8 -8
  33. flwr/proto/message_pb2.pyi +4 -1
  34. flwr/proto/run_pb2.py +32 -27
  35. flwr/proto/run_pb2.pyi +26 -0
  36. flwr/proto/serverappio_pb2.py +52 -0
  37. flwr/proto/{driver_pb2.pyi → serverappio_pb2.pyi} +54 -0
  38. flwr/proto/serverappio_pb2_grpc.py +376 -0
  39. flwr/proto/serverappio_pb2_grpc.pyi +147 -0
  40. flwr/proto/simulationio_pb2.py +38 -0
  41. flwr/proto/simulationio_pb2.pyi +65 -0
  42. flwr/proto/simulationio_pb2_grpc.py +205 -0
  43. flwr/proto/simulationio_pb2_grpc.pyi +81 -0
  44. flwr/server/app.py +272 -105
  45. flwr/server/driver/driver.py +15 -1
  46. flwr/server/driver/grpc_driver.py +25 -36
  47. flwr/server/driver/inmemory_driver.py +6 -16
  48. flwr/server/run_serverapp.py +29 -23
  49. flwr/server/{superlink/state → serverapp}/__init__.py +3 -9
  50. flwr/server/serverapp/app.py +214 -0
  51. flwr/server/strategy/aggregate.py +4 -4
  52. flwr/server/strategy/fedadam.py +11 -1
  53. flwr/server/superlink/driver/__init__.py +1 -1
  54. flwr/server/superlink/driver/{driver_grpc.py → serverappio_grpc.py} +19 -16
  55. flwr/server/superlink/driver/{driver_servicer.py → serverappio_servicer.py} +125 -39
  56. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +4 -2
  57. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -2
  58. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -2
  59. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -2
  60. flwr/server/superlink/fleet/message_handler/message_handler.py +7 -7
  61. flwr/server/superlink/fleet/rest_rere/rest_api.py +7 -7
  62. flwr/server/superlink/fleet/vce/vce_api.py +23 -23
  63. flwr/server/superlink/linkstate/__init__.py +28 -0
  64. flwr/server/superlink/{state/in_memory_state.py → linkstate/in_memory_linkstate.py} +184 -36
  65. flwr/server/superlink/{state/state.py → linkstate/linkstate.py} +149 -19
  66. flwr/server/superlink/{state/state_factory.py → linkstate/linkstate_factory.py} +9 -9
  67. flwr/server/superlink/{state/sqlite_state.py → linkstate/sqlite_linkstate.py} +306 -65
  68. flwr/server/superlink/{state → linkstate}/utils.py +81 -30
  69. flwr/server/superlink/simulation/__init__.py +15 -0
  70. flwr/server/superlink/simulation/simulationio_grpc.py +65 -0
  71. flwr/server/superlink/simulation/simulationio_servicer.py +153 -0
  72. flwr/simulation/__init__.py +5 -1
  73. flwr/simulation/app.py +273 -345
  74. flwr/simulation/legacy_app.py +382 -0
  75. flwr/simulation/ray_transport/ray_client_proxy.py +2 -2
  76. flwr/simulation/run_simulation.py +57 -131
  77. flwr/simulation/simulationio_connection.py +86 -0
  78. flwr/superexec/app.py +6 -134
  79. flwr/superexec/deployment.py +61 -66
  80. flwr/superexec/exec_grpc.py +15 -8
  81. flwr/superexec/exec_servicer.py +36 -65
  82. flwr/superexec/executor.py +26 -7
  83. flwr/superexec/simulation.py +54 -107
  84. {flwr_nightly-1.13.0.dev20241021.dist-info → flwr_nightly-1.13.0.dev20241111.dist-info}/METADATA +5 -4
  85. {flwr_nightly-1.13.0.dev20241021.dist-info → flwr_nightly-1.13.0.dev20241111.dist-info}/RECORD +88 -69
  86. {flwr_nightly-1.13.0.dev20241021.dist-info → flwr_nightly-1.13.0.dev20241111.dist-info}/entry_points.txt +2 -0
  87. flwr/client/node_state_tests.py +0 -66
  88. flwr/proto/driver_pb2.py +0 -42
  89. flwr/proto/driver_pb2_grpc.py +0 -239
  90. flwr/proto/driver_pb2_grpc.pyi +0 -94
  91. {flwr_nightly-1.13.0.dev20241021.dist-info → flwr_nightly-1.13.0.dev20241111.dist-info}/LICENSE +0 -0
  92. {flwr_nightly-1.13.0.dev20241021.dist-info → flwr_nightly-1.13.0.dev20241111.dist-info}/WHEEL +0 -0
@@ -15,22 +15,32 @@
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 log
24
- from flwr.common.constant import ErrorCode
25
- from flwr.proto.error_pb2 import Error # pylint: disable=E0611
26
- from flwr.proto.node_pb2 import Node # pylint: disable=E0611
27
- from flwr.proto.task_pb2 import Task, TaskIns, TaskRes # pylint: disable=E0611
20
+ from flwr.common import ConfigsRecord, Context, serde
21
+ from flwr.common.constant import Status, SubStatus
22
+ from flwr.common.typing import RunStatus
23
+ from flwr.proto.message_pb2 import Context as ProtoContext # pylint: disable=E0611
24
+
25
+ # pylint: disable=E0611
26
+ from flwr.proto.recordset_pb2 import ConfigsRecord as ProtoConfigsRecord
28
27
 
29
28
  NODE_UNAVAILABLE_ERROR_REASON = (
30
29
  "Error: Node Unavailable - The destination node is currently unavailable. "
31
30
  "It exceeds the time limit specified in its last ping."
32
31
  )
33
32
 
33
+ VALID_RUN_STATUS_TRANSITIONS = {
34
+ (Status.PENDING, Status.STARTING),
35
+ (Status.STARTING, Status.RUNNING),
36
+ (Status.RUNNING, Status.FINISHED),
37
+ }
38
+ VALID_RUN_SUB_STATUSES = {
39
+ SubStatus.COMPLETED,
40
+ SubStatus.FAILED,
41
+ SubStatus.STOPPED,
42
+ }
43
+
34
44
 
35
45
  def generate_rand_int_from_bytes(num_bytes: int) -> int:
36
46
  """Generate a random unsigned integer from `num_bytes` bytes."""
@@ -123,26 +133,67 @@ def convert_sint64_values_in_dict_to_uint64(
123
133
  data_dict[key] = convert_sint64_to_uint64(data_dict[key])
124
134
 
125
135
 
126
- def make_node_unavailable_taskres(ref_taskins: TaskIns) -> TaskRes:
127
- """Generate a TaskRes with a node unavailable error from a TaskIns."""
128
- current_time = time.time()
129
- ttl = ref_taskins.task.ttl - (current_time - ref_taskins.task.created_at)
130
- if ttl < 0:
131
- log(ERROR, "Creating TaskRes for TaskIns that exceeds its TTL.")
132
- ttl = 0
133
- return TaskRes(
134
- task_id=str(uuid4()),
135
- group_id=ref_taskins.group_id,
136
- run_id=ref_taskins.run_id,
137
- task=Task(
138
- producer=Node(node_id=ref_taskins.task.consumer.node_id, anonymous=False),
139
- consumer=Node(node_id=ref_taskins.task.producer.node_id, anonymous=False),
140
- created_at=current_time,
141
- ttl=ttl,
142
- ancestry=[ref_taskins.task_id],
143
- task_type=ref_taskins.task.task_type,
144
- error=Error(
145
- code=ErrorCode.NODE_UNAVAILABLE, reason=NODE_UNAVAILABLE_ERROR_REASON
146
- ),
147
- ),
136
+ def context_to_bytes(context: Context) -> bytes:
137
+ """Serialize `Context` to bytes."""
138
+ return serde.context_to_proto(context).SerializeToString()
139
+
140
+
141
+ def context_from_bytes(context_bytes: bytes) -> Context:
142
+ """Deserialize `Context` from bytes."""
143
+ return serde.context_from_proto(ProtoContext.FromString(context_bytes))
144
+
145
+
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)
148
155
  )
156
+
157
+
158
+ def is_valid_transition(current_status: RunStatus, new_status: RunStatus) -> bool:
159
+ """Check if a transition between two run statuses is valid.
160
+
161
+ Parameters
162
+ ----------
163
+ current_status : RunStatus
164
+ The current status of the run.
165
+ new_status : RunStatus
166
+ The new status to transition to.
167
+
168
+ Returns
169
+ -------
170
+ bool
171
+ True if the transition is valid, False otherwise.
172
+ """
173
+ return (
174
+ current_status.status,
175
+ new_status.status,
176
+ ) in VALID_RUN_STATUS_TRANSITIONS
177
+
178
+
179
+ def has_valid_sub_status(status: RunStatus) -> bool:
180
+ """Check if the 'sub_status' field of the given status is valid.
181
+
182
+ Parameters
183
+ ----------
184
+ status : RunStatus
185
+ The status object to be checked.
186
+
187
+ Returns
188
+ -------
189
+ bool
190
+ True if the status object has a valid sub-status, False otherwise.
191
+
192
+ Notes
193
+ -----
194
+ Only an empty string (i.e., "") is considered a valid sub-status for
195
+ non-finished statuses. The sub-status of a finished status cannot be empty.
196
+ """
197
+ if status.status == Status.FINISHED:
198
+ return status.sub_status in VALID_RUN_SUB_STATUSES
199
+ return status.sub_status == ""
@@ -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,153 @@
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
+ configs_record_to_proto,
27
+ context_from_proto,
28
+ context_to_proto,
29
+ fab_to_proto,
30
+ run_status_from_proto,
31
+ run_to_proto,
32
+ )
33
+ from flwr.common.typing import Fab, RunStatus
34
+ from flwr.proto import simulationio_pb2_grpc
35
+ from flwr.proto.log_pb2 import ( # pylint: disable=E0611
36
+ PushLogsRequest,
37
+ PushLogsResponse,
38
+ )
39
+ from flwr.proto.run_pb2 import ( # pylint: disable=E0611
40
+ GetFederationOptionsRequest,
41
+ GetFederationOptionsResponse,
42
+ UpdateRunStatusRequest,
43
+ UpdateRunStatusResponse,
44
+ )
45
+ from flwr.proto.simulationio_pb2 import ( # pylint: disable=E0611
46
+ PullSimulationInputsRequest,
47
+ PullSimulationInputsResponse,
48
+ PushSimulationOutputsRequest,
49
+ PushSimulationOutputsResponse,
50
+ )
51
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
52
+ from flwr.server.superlink.linkstate import LinkStateFactory
53
+
54
+
55
+ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
56
+ """SimulationIo API servicer."""
57
+
58
+ def __init__(
59
+ self, state_factory: LinkStateFactory, ffs_factory: FfsFactory
60
+ ) -> None:
61
+ self.state_factory = state_factory
62
+ self.ffs_factory = ffs_factory
63
+ self.lock = threading.RLock()
64
+
65
+ def PullSimulationInputs(
66
+ self, request: PullSimulationInputsRequest, context: ServicerContext
67
+ ) -> PullSimulationInputsResponse:
68
+ """Pull SimultionIo process inputs."""
69
+ log(DEBUG, "SimultionIoServicer.SimultionIoInputs")
70
+ # Init access to LinkState and Ffs
71
+ state = self.state_factory.state()
72
+ ffs = self.ffs_factory.ffs()
73
+
74
+ # Lock access to LinkState, preventing obtaining the same pending run_id
75
+ with self.lock:
76
+ # Attempt getting the run_id of a pending run
77
+ run_id = state.get_pending_run_id()
78
+ # If there's no pending run, return an empty response
79
+ if run_id is None:
80
+ return PullSimulationInputsResponse()
81
+
82
+ # Retrieve Context, Run and Fab for the run_id
83
+ serverapp_ctxt = state.get_serverapp_context(run_id)
84
+ run = state.get_run(run_id)
85
+ fab = None
86
+ if run and run.fab_hash:
87
+ if result := ffs.get(run.fab_hash):
88
+ fab = Fab(run.fab_hash, result[0])
89
+ if run and fab and serverapp_ctxt:
90
+ # Update run status to STARTING
91
+ if state.update_run_status(run_id, RunStatus(Status.STARTING, "", "")):
92
+ log(INFO, "Starting run %d", run_id)
93
+ return PullSimulationInputsResponse(
94
+ context=context_to_proto(serverapp_ctxt),
95
+ run=run_to_proto(run),
96
+ fab=fab_to_proto(fab),
97
+ )
98
+
99
+ # Raise an exception if the Run or Fab is not found,
100
+ # or if the status cannot be updated to STARTING
101
+ raise RuntimeError(f"Failed to start run {run_id}")
102
+
103
+ def PushSimulationOutputs(
104
+ self, request: PushSimulationOutputsRequest, context: ServicerContext
105
+ ) -> PushSimulationOutputsResponse:
106
+ """Push Simulation process outputs."""
107
+ log(DEBUG, "SimultionIoServicer.PushSimulationOutputs")
108
+ state = self.state_factory.state()
109
+ state.set_serverapp_context(request.run_id, context_from_proto(request.context))
110
+ return PushSimulationOutputsResponse()
111
+
112
+ def UpdateRunStatus(
113
+ self, request: UpdateRunStatusRequest, context: grpc.ServicerContext
114
+ ) -> UpdateRunStatusResponse:
115
+ """Update the status of a run."""
116
+ log(DEBUG, "SimultionIoServicer.UpdateRunStatus")
117
+ state = self.state_factory.state()
118
+
119
+ # Update the run status
120
+ state.update_run_status(
121
+ run_id=request.run_id, new_status=run_status_from_proto(request.run_status)
122
+ )
123
+ return UpdateRunStatusResponse()
124
+
125
+ def PushLogs(
126
+ self, request: PushLogsRequest, context: grpc.ServicerContext
127
+ ) -> PushLogsResponse:
128
+ """Push logs."""
129
+ log(DEBUG, "SimultionIoServicer.PushLogs")
130
+ state = self.state_factory.state()
131
+
132
+ # Add logs to LinkState
133
+ merged_logs = "".join(request.logs)
134
+ state.add_serverapp_log(request.run_id, merged_logs)
135
+ return PushLogsResponse()
136
+
137
+ def GetFederationOptions(
138
+ self, request: GetFederationOptionsRequest, context: ServicerContext
139
+ ) -> GetFederationOptionsResponse:
140
+ """Get Federation Options associated with a run."""
141
+ log(DEBUG, "SimultionIoServicer.GetFederationOptions")
142
+ state = self.state_factory.state()
143
+
144
+ federation_options = state.get_federation_options(request.run_id)
145
+ if federation_options is None:
146
+ context.abort(
147
+ grpc.StatusCode.FAILED_PRECONDITION,
148
+ "Expected federation options to be set, but none available.",
149
+ )
150
+ return GetFederationOptionsResponse()
151
+ return GetFederationOptionsResponse(
152
+ federation_options=configs_record_to_proto(federation_options)
153
+ )
@@ -17,12 +17,14 @@
17
17
 
18
18
  import importlib
19
19
 
20
+ from flwr.simulation.app import run_simulation_process
20
21
  from flwr.simulation.run_simulation import run_simulation
22
+ from flwr.simulation.simulationio_connection import SimulationIoConnection
21
23
 
22
24
  is_ray_installed = importlib.util.find_spec("ray") is not None
23
25
 
24
26
  if is_ray_installed:
25
- from flwr.simulation.app import start_simulation
27
+ from flwr.simulation.legacy_app import start_simulation
26
28
  else:
27
29
  RAY_IMPORT_ERROR: str = """Unable to import module `ray`.
28
30
 
@@ -37,6 +39,8 @@ To install the necessary dependencies, install `flwr` with the `simulation` extr
37
39
 
38
40
 
39
41
  __all__ = [
42
+ "SimulationIoConnection",
40
43
  "run_simulation",
44
+ "run_simulation_process",
41
45
  "start_simulation",
42
46
  ]