flwr-nightly 1.8.0.dev20240226__py3-none-any.whl → 1.8.0.dev20240227__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
flwr/client/__init__.py CHANGED
@@ -19,7 +19,7 @@ from .app import run_client_app as run_client_app
19
19
  from .app import start_client as start_client
20
20
  from .app import start_numpy_client as start_numpy_client
21
21
  from .client import Client as Client
22
- from .clientapp import ClientApp as ClientApp
22
+ from .client_app import ClientApp as ClientApp
23
23
  from .numpy_client import NumPyClient as NumPyClient
24
24
  from .typing import ClientFn as ClientFn
25
25
 
flwr/client/app.py CHANGED
@@ -23,7 +23,7 @@ from pathlib import Path
23
23
  from typing import Callable, ContextManager, Optional, Tuple, Union
24
24
 
25
25
  from flwr.client.client import Client
26
- from flwr.client.clientapp import ClientApp
26
+ from flwr.client.client_app import ClientApp
27
27
  from flwr.client.typing import ClientFn
28
28
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, Message, event
29
29
  from flwr.common.address import parse_address
@@ -34,9 +34,10 @@ from flwr.common.constant import (
34
34
  TRANSPORT_TYPE_REST,
35
35
  TRANSPORT_TYPES,
36
36
  )
37
+ from flwr.common.exit_handlers import register_exit_handlers
37
38
  from flwr.common.logger import log, warn_deprecated_feature, warn_experimental_feature
38
39
 
39
- from .clientapp import load_client_app
40
+ from .client_app import load_client_app
40
41
  from .grpc_client.connection import grpc_connection
41
42
  from .grpc_rere_client.connection import grpc_request_response
42
43
  from .message_handler.message_handler import handle_control_message
@@ -104,7 +105,7 @@ def run_client_app() -> None:
104
105
  root_certificates=root_certificates,
105
106
  insecure=args.insecure,
106
107
  )
107
- event(EventType.RUN_CLIENT_APP_LEAVE)
108
+ register_exit_handlers(event_type=EventType.RUN_CLIENT_APP_LEAVE)
108
109
 
109
110
 
110
111
  def _parse_args_run_client_app() -> argparse.ArgumentParser:
@@ -15,10 +15,12 @@
15
15
  """Mods."""
16
16
 
17
17
 
18
+ from .centraldp_mods import fixedclipping_mod
18
19
  from .secure_aggregation.secaggplus_mod import secaggplus_mod
19
20
  from .utils import make_ffn
20
21
 
21
22
  __all__ = [
22
23
  "make_ffn",
23
24
  "secaggplus_mod",
25
+ "fixedclipping_mod",
24
26
  ]
@@ -0,0 +1,76 @@
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
+ """Clipping modifiers for central DP with client-side clipping."""
16
+
17
+
18
+ from flwr.client.typing import ClientAppCallable
19
+ from flwr.common import ndarrays_to_parameters, parameters_to_ndarrays
20
+ from flwr.common import recordset_compat as compat
21
+ from flwr.common.constant import MESSAGE_TYPE_FIT
22
+ from flwr.common.context import Context
23
+ from flwr.common.differential_privacy import compute_clip_model_update
24
+ from flwr.common.differential_privacy_constants import KEY_CLIPPING_NORM
25
+ from flwr.common.message import Message
26
+
27
+
28
+ def fixedclipping_mod(
29
+ msg: Message, ctxt: Context, call_next: ClientAppCallable
30
+ ) -> Message:
31
+ """Client-side fixed clipping modifier.
32
+
33
+ This mod needs to be used with the DifferentialPrivacyClientSideFixedClipping
34
+ server-side strategy wrapper.
35
+
36
+ The wrapper sends the clipping_norm value to the client.
37
+
38
+ This mod clips the client model updates before sending them to the server.
39
+
40
+ It operates on messages with type MESSAGE_TYPE_FIT.
41
+
42
+ Notes
43
+ -----
44
+ Consider the order of mods when using multiple.
45
+
46
+ Typically, fixedclipping_mod should be the last to operate on params.
47
+ """
48
+ if msg.metadata.message_type != MESSAGE_TYPE_FIT:
49
+ return call_next(msg, ctxt)
50
+ fit_ins = compat.recordset_to_fitins(msg.content, keep_input=True)
51
+ if KEY_CLIPPING_NORM not in fit_ins.config:
52
+ raise KeyError(
53
+ f"The {KEY_CLIPPING_NORM} value is not supplied by the "
54
+ f"DifferentialPrivacyClientSideFixedClipping wrapper at"
55
+ f" the server side."
56
+ )
57
+
58
+ clipping_norm = float(fit_ins.config[KEY_CLIPPING_NORM])
59
+ server_to_client_params = parameters_to_ndarrays(fit_ins.parameters)
60
+
61
+ # Call inner app
62
+ out_msg = call_next(msg, ctxt)
63
+ fit_res = compat.recordset_to_fitres(out_msg.content, keep_input=True)
64
+
65
+ client_to_server_params = parameters_to_ndarrays(fit_res.parameters)
66
+
67
+ # Clip the client update
68
+ compute_clip_model_update(
69
+ client_to_server_params,
70
+ server_to_client_params,
71
+ clipping_norm,
72
+ )
73
+
74
+ fit_res.parameters = ndarrays_to_parameters(client_to_server_params)
75
+ out_msg.content = compat.fitres_to_recordset(fit_res, keep_input=True)
76
+ return out_msg
@@ -14,6 +14,8 @@
14
14
  # ==============================================================================
15
15
  """Constants for differential privacy."""
16
16
 
17
+
18
+ KEY_CLIPPING_NORM = "clipping_norm"
17
19
  CLIENTS_DISCREPANCY_WARNING = (
18
20
  "The number of clients returning parameters (%s)"
19
21
  " differs from the number of sampled clients (%s)."
@@ -0,0 +1,87 @@
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
+ """Common function to register exit handlers for server and client."""
16
+
17
+
18
+ import sys
19
+ from signal import SIGINT, SIGTERM, signal
20
+ from threading import Thread
21
+ from types import FrameType
22
+ from typing import List, Optional
23
+
24
+ from grpc import Server
25
+
26
+ from flwr.common.telemetry import EventType, event
27
+
28
+
29
+ def register_exit_handlers(
30
+ event_type: EventType,
31
+ grpc_servers: Optional[List[Server]] = None,
32
+ bckg_threads: Optional[List[Thread]] = None,
33
+ ) -> None:
34
+ """Register exit handlers for `SIGINT` and `SIGTERM` signals.
35
+
36
+ Parameters
37
+ ----------
38
+ event_type : EventType
39
+ The telemetry event that should be logged before exit.
40
+ grpc_servers: Optional[List[Server]] (default: None)
41
+ An otpional list of gRPC servers that need to be gracefully
42
+ terminated before exiting.
43
+ bckg_threads: Optional[List[Thread]] (default: None)
44
+ An optional list of threads that need to be gracefully
45
+ terminated before exiting.
46
+ """
47
+ default_handlers = {
48
+ SIGINT: None,
49
+ SIGTERM: None,
50
+ }
51
+
52
+ def graceful_exit_handler( # type: ignore
53
+ signalnum,
54
+ frame: FrameType, # pylint: disable=unused-argument
55
+ ) -> None:
56
+ """Exit handler to be registered with `signal.signal`.
57
+
58
+ When called will reset signal handler to original signal handler from
59
+ default_handlers.
60
+ """
61
+ # Reset to default handler
62
+ signal(signalnum, default_handlers[signalnum])
63
+
64
+ event_res = event(event_type=event_type)
65
+
66
+ if grpc_servers is not None:
67
+ for grpc_server in grpc_servers:
68
+ grpc_server.stop(grace=1)
69
+
70
+ if bckg_threads is not None:
71
+ for bckg_thread in bckg_threads:
72
+ bckg_thread.join()
73
+
74
+ # Ensure event has happend
75
+ event_res.result()
76
+
77
+ # Setup things for graceful exit
78
+ sys.exit(0)
79
+
80
+ default_handlers[SIGINT] = signal( # type: ignore
81
+ SIGINT,
82
+ graceful_exit_handler, # type: ignore
83
+ )
84
+ default_handlers[SIGTERM] = signal( # type: ignore
85
+ SIGTERM,
86
+ graceful_exit_handler, # type: ignore
87
+ )
flwr/server/app.py CHANGED
@@ -22,8 +22,6 @@ import threading
22
22
  from logging import ERROR, INFO, WARN
23
23
  from os.path import isfile
24
24
  from pathlib import Path
25
- from signal import SIGINT, SIGTERM, signal
26
- from types import FrameType
27
25
  from typing import List, Optional, Tuple
28
26
 
29
27
  import grpc
@@ -36,6 +34,7 @@ from flwr.common.constant import (
36
34
  TRANSPORT_TYPE_REST,
37
35
  TRANSPORT_TYPE_VCE,
38
36
  )
37
+ from flwr.common.exit_handlers import register_exit_handlers
39
38
  from flwr.common.logger import log
40
39
  from flwr.proto.driver_pb2_grpc import ( # pylint: disable=E0611
41
40
  add_DriverServicer_to_server,
@@ -212,10 +211,10 @@ def run_driver_api() -> None:
212
211
  )
213
212
 
214
213
  # Graceful shutdown
215
- _register_exit_handlers(
214
+ register_exit_handlers(
215
+ event_type=EventType.RUN_DRIVER_API_LEAVE,
216
216
  grpc_servers=[grpc_server],
217
217
  bckg_threads=[],
218
- event_type=EventType.RUN_DRIVER_API_LEAVE,
219
218
  )
220
219
 
221
220
  # Block
@@ -280,10 +279,10 @@ def run_fleet_api() -> None:
280
279
  raise ValueError(f"Unknown fleet_api_type: {args.fleet_api_type}")
281
280
 
282
281
  # Graceful shutdown
283
- _register_exit_handlers(
282
+ register_exit_handlers(
283
+ event_type=EventType.RUN_FLEET_API_LEAVE,
284
284
  grpc_servers=grpc_servers,
285
285
  bckg_threads=bckg_threads,
286
- event_type=EventType.RUN_FLEET_API_LEAVE,
287
286
  )
288
287
 
289
288
  # Block
@@ -375,10 +374,10 @@ def run_superlink() -> None:
375
374
  raise ValueError(f"Unknown fleet_api_type: {args.fleet_api_type}")
376
375
 
377
376
  # Graceful shutdown
378
- _register_exit_handlers(
377
+ register_exit_handlers(
378
+ event_type=EventType.RUN_SUPERLINK_LEAVE,
379
379
  grpc_servers=grpc_servers,
380
380
  bckg_threads=bckg_threads,
381
- event_type=EventType.RUN_SUPERLINK_LEAVE,
382
381
  )
383
382
 
384
383
  # Block
@@ -413,52 +412,6 @@ def _try_obtain_certificates(
413
412
  return certificates
414
413
 
415
414
 
416
- def _register_exit_handlers(
417
- grpc_servers: List[grpc.Server],
418
- bckg_threads: List[threading.Thread],
419
- event_type: EventType,
420
- ) -> None:
421
- default_handlers = {
422
- SIGINT: None,
423
- SIGTERM: None,
424
- }
425
-
426
- def graceful_exit_handler( # type: ignore
427
- signalnum,
428
- frame: FrameType, # pylint: disable=unused-argument
429
- ) -> None:
430
- """Exit handler to be registered with signal.signal.
431
-
432
- When called will reset signal handler to original signal handler from
433
- default_handlers.
434
- """
435
- # Reset to default handler
436
- signal(signalnum, default_handlers[signalnum])
437
-
438
- event_res = event(event_type=event_type)
439
-
440
- for grpc_server in grpc_servers:
441
- grpc_server.stop(grace=1)
442
-
443
- for bckg_thread in bckg_threads:
444
- bckg_thread.join()
445
-
446
- # Ensure event has happend
447
- event_res.result()
448
-
449
- # Setup things for graceful exit
450
- sys.exit(0)
451
-
452
- default_handlers[SIGINT] = signal( # type: ignore
453
- SIGINT,
454
- graceful_exit_handler, # type: ignore
455
- )
456
- default_handlers[SIGTERM] = signal( # type: ignore
457
- SIGTERM,
458
- graceful_exit_handler, # type: ignore
459
- )
460
-
461
-
462
415
  def _run_driver_api_grpc(
463
416
  address: str,
464
417
  state_factory: StateFactory,
@@ -16,7 +16,10 @@
16
16
 
17
17
 
18
18
  from .bulyan import Bulyan as Bulyan
19
- from .dp_fixed_clipping import DifferentialPrivacyServerSideFixedClipping
19
+ from .dp_fixed_clipping import (
20
+ DifferentialPrivacyClientSideFixedClipping,
21
+ DifferentialPrivacyServerSideFixedClipping,
22
+ )
20
23
  from .dpfedavg_adaptive import DPFedAvgAdaptive as DPFedAvgAdaptive
21
24
  from .dpfedavg_fixed import DPFedAvgFixed as DPFedAvgFixed
22
25
  from .fault_tolerant_fedavg import FaultTolerantFedAvg as FaultTolerantFedAvg
@@ -59,4 +62,5 @@ __all__ = [
59
62
  "DPFedAvgFixed",
60
63
  "Strategy",
61
64
  "DifferentialPrivacyServerSideFixedClipping",
65
+ "DifferentialPrivacyClientSideFixedClipping",
62
66
  ]
@@ -36,7 +36,10 @@ from flwr.common.differential_privacy import (
36
36
  add_gaussian_noise_to_params,
37
37
  compute_clip_model_update,
38
38
  )
39
- from flwr.common.differential_privacy_constants import CLIENTS_DISCREPANCY_WARNING
39
+ from flwr.common.differential_privacy_constants import (
40
+ CLIENTS_DISCREPANCY_WARNING,
41
+ KEY_CLIPPING_NORM,
42
+ )
40
43
  from flwr.common.logger import log
41
44
  from flwr.server.client_manager import ClientManager
42
45
  from flwr.server.client_proxy import ClientProxy
@@ -44,7 +47,8 @@ from flwr.server.strategy.strategy import Strategy
44
47
 
45
48
 
46
49
  class DifferentialPrivacyServerSideFixedClipping(Strategy):
47
- """Wrapper for Central DP with Server Side Fixed Clipping.
50
+ """Strategy wrapper for central differential privacy with server-side fixed
51
+ clipping.
48
52
 
49
53
  Parameters
50
54
  ----------
@@ -185,3 +189,153 @@ class DifferentialPrivacyServerSideFixedClipping(Strategy):
185
189
  ) -> Optional[Tuple[float, Dict[str, Scalar]]]:
186
190
  """Evaluate model parameters using an evaluation function from the strategy."""
187
191
  return self.strategy.evaluate(server_round, parameters)
192
+
193
+
194
+ class DifferentialPrivacyClientSideFixedClipping(Strategy):
195
+ """Strategy wrapper for central differential privacy with client-side fixed
196
+ clipping.
197
+
198
+ Use `fixedclipping_mod` modifier at the client side.
199
+
200
+ In comparison to `DifferentialPrivacyServerSideFixedClipping`,
201
+ which performs clipping on the server-side, `DifferentialPrivacyClientSideFixedClipping`
202
+ expects clipping to happen on the client-side, usually by using the built-in
203
+ `fixedclipping_mod `.
204
+
205
+ Parameters
206
+ ----------
207
+ strategy : Strategy
208
+ The strategy to which DP functionalities will be added by this wrapper.
209
+ noise_multiplier : float
210
+ The noise multiplier for the Gaussian mechanism for model updates.
211
+ A value of 1.0 or higher is recommended for strong privacy.
212
+ clipping_norm : float
213
+ The value of the clipping norm.
214
+ num_sampled_clients : int
215
+ The number of clients that are sampled on each round.
216
+
217
+ Examples
218
+ --------
219
+ Create a strategy:
220
+
221
+ >>> strategy = fl.server.strategy.FedAvg(...)
222
+
223
+ Wrap the strategy with the `DifferentialPrivacyServerSideFixedClipping` wrapper:
224
+
225
+ >>> DifferentialPrivacyClientSideFixedClipping(
226
+ >>> strategy, cfg.noise_multiplier, cfg.clipping_norm, cfg.num_sampled_clients
227
+ >>> )
228
+
229
+ On the client, add the `fixedclipping_mod` to the client-side mods:
230
+
231
+ >>> app = fl.client.ClientApp(
232
+ >>> client_fn=FlowerClient().to_client(), mods=[fixedclipping_mod]
233
+ >>> )
234
+ """
235
+
236
+ # pylint: disable=too-many-arguments,too-many-instance-attributes
237
+ def __init__(
238
+ self,
239
+ strategy: Strategy,
240
+ noise_multiplier: float,
241
+ clipping_norm: float,
242
+ num_sampled_clients: int,
243
+ ) -> None:
244
+ super().__init__()
245
+
246
+ self.strategy = strategy
247
+
248
+ if noise_multiplier < 0:
249
+ raise ValueError("The noise multiplier should be a non-negative value.")
250
+
251
+ if clipping_norm <= 0:
252
+ raise ValueError("The clipping threshold should be a positive value.")
253
+
254
+ if num_sampled_clients <= 0:
255
+ raise ValueError(
256
+ "The number of sampled clients should be a positive value."
257
+ )
258
+
259
+ self.noise_multiplier = noise_multiplier
260
+ self.clipping_norm = clipping_norm
261
+ self.num_sampled_clients = num_sampled_clients
262
+
263
+ def __repr__(self) -> str:
264
+ """Compute a string representation of the strategy."""
265
+ rep = "Differential Privacy Strategy Wrapper (Client-Side Fixed Clipping)"
266
+ return rep
267
+
268
+ def initialize_parameters(
269
+ self, client_manager: ClientManager
270
+ ) -> Optional[Parameters]:
271
+ """Initialize global model parameters using given strategy."""
272
+ return self.strategy.initialize_parameters(client_manager)
273
+
274
+ def configure_fit(
275
+ self, server_round: int, parameters: Parameters, client_manager: ClientManager
276
+ ) -> List[Tuple[ClientProxy, FitIns]]:
277
+ """Configure the next round of training."""
278
+ additional_config = {KEY_CLIPPING_NORM: self.clipping_norm}
279
+ inner_strategy_config_result = self.strategy.configure_fit(
280
+ server_round, parameters, client_manager
281
+ )
282
+ for _, fit_ins in inner_strategy_config_result:
283
+ fit_ins.config.update(additional_config)
284
+
285
+ return inner_strategy_config_result
286
+
287
+ def configure_evaluate(
288
+ self, server_round: int, parameters: Parameters, client_manager: ClientManager
289
+ ) -> List[Tuple[ClientProxy, EvaluateIns]]:
290
+ """Configure the next round of evaluation."""
291
+ return self.strategy.configure_evaluate(
292
+ server_round, parameters, client_manager
293
+ )
294
+
295
+ def aggregate_fit(
296
+ self,
297
+ server_round: int,
298
+ results: List[Tuple[ClientProxy, FitRes]],
299
+ failures: List[Union[Tuple[ClientProxy, FitRes], BaseException]],
300
+ ) -> Tuple[Optional[Parameters], Dict[str, Scalar]]:
301
+ """Add noise to the aggregated parameters."""
302
+ if failures:
303
+ return None, {}
304
+
305
+ if len(results) != self.num_sampled_clients:
306
+ log(
307
+ WARNING,
308
+ CLIENTS_DISCREPANCY_WARNING,
309
+ len(results),
310
+ self.num_sampled_clients,
311
+ )
312
+
313
+ # Pass the new parameters for aggregation
314
+ aggregated_params, metrics = self.strategy.aggregate_fit(
315
+ server_round, results, failures
316
+ )
317
+
318
+ # Add Gaussian noise to the aggregated parameters
319
+ if aggregated_params:
320
+ aggregated_params = add_gaussian_noise_to_params(
321
+ aggregated_params,
322
+ self.noise_multiplier,
323
+ self.clipping_norm,
324
+ self.num_sampled_clients,
325
+ )
326
+ return aggregated_params, metrics
327
+
328
+ def aggregate_evaluate(
329
+ self,
330
+ server_round: int,
331
+ results: List[Tuple[ClientProxy, EvaluateRes]],
332
+ failures: List[Union[Tuple[ClientProxy, EvaluateRes], BaseException]],
333
+ ) -> Tuple[Optional[float], Dict[str, Scalar]]:
334
+ """Aggregate evaluation losses using the given strategy."""
335
+ return self.strategy.aggregate_evaluate(server_round, results, failures)
336
+
337
+ def evaluate(
338
+ self, server_round: int, parameters: Parameters
339
+ ) -> Optional[Tuple[float, Dict[str, Scalar]]]:
340
+ """Evaluate model parameters using an evaluation function from the strategy."""
341
+ return self.strategy.evaluate(server_round, parameters)
@@ -18,7 +18,7 @@
18
18
  from abc import ABC, abstractmethod
19
19
  from typing import Callable, Dict, Tuple
20
20
 
21
- from flwr.client.clientapp import ClientApp
21
+ from flwr.client.client_app import ClientApp
22
22
  from flwr.common.context import Context
23
23
  from flwr.common.message import Message
24
24
  from flwr.common.typing import ConfigsRecordValues
@@ -15,10 +15,12 @@
15
15
  """Ray backend for the Fleet API using the Simulation Engine."""
16
16
 
17
17
  import pathlib
18
- from logging import INFO
18
+ from logging import ERROR, INFO
19
19
  from typing import Callable, Dict, List, Tuple, Union
20
20
 
21
- from flwr.client.clientapp import ClientApp
21
+ import ray
22
+
23
+ from flwr.client.client_app import ClientApp, LoadClientAppError
22
24
  from flwr.common.context import Context
23
25
  from flwr.common.logger import log
24
26
  from flwr.common.message import Message
@@ -46,6 +48,9 @@ class RayBackend(Backend):
46
48
  log(INFO, "Initialising: %s", self.__class__.__name__)
47
49
  log(INFO, "Backend config: %s", backend_config)
48
50
 
51
+ if not pathlib.Path(work_dir).exists():
52
+ raise ValueError(f"Specified work_dir {work_dir} does not exist.")
53
+
49
54
  # Init ray and append working dir if needed
50
55
  runtime_env = (
51
56
  self._configure_runtime_env(work_dir=work_dir) if work_dir else None
@@ -138,22 +143,34 @@ class RayBackend(Backend):
138
143
  """
139
144
  node_id = message.metadata.dst_node_id
140
145
 
141
- # Submite a task to the pool
142
- future = await self.pool.submit(
143
- lambda a, a_fn, mssg, cid, state: a.run.remote(a_fn, mssg, cid, state),
144
- (app, message, str(node_id), context),
145
- )
146
+ try:
147
+ # Submite a task to the pool
148
+ future = await self.pool.submit(
149
+ lambda a, a_fn, mssg, cid, state: a.run.remote(a_fn, mssg, cid, state),
150
+ (app, message, str(node_id), context),
151
+ )
146
152
 
147
- await future
153
+ await future
148
154
 
149
- # Fetch result
150
- (
151
- out_mssg,
152
- updated_context,
153
- ) = await self.pool.fetch_result_and_return_actor_to_pool(future)
155
+ # Fetch result
156
+ (
157
+ out_mssg,
158
+ updated_context,
159
+ ) = await self.pool.fetch_result_and_return_actor_to_pool(future)
154
160
 
155
- return out_mssg, updated_context
161
+ return out_mssg, updated_context
162
+
163
+ except LoadClientAppError as load_ex:
164
+ log(
165
+ ERROR,
166
+ "An exception was raised when processing a message. Terminating %s",
167
+ self.__class__.__name__,
168
+ )
169
+ await self.terminate()
170
+ raise load_ex
156
171
 
157
172
  async def terminate(self) -> None:
158
173
  """Terminate all actors in actor pool."""
159
174
  await self.pool.terminate_all_actors()
175
+ ray.shutdown()
176
+ log(INFO, "Terminated %s", self.__class__.__name__)
@@ -19,7 +19,7 @@ import json
19
19
  from logging import ERROR, INFO
20
20
  from typing import Dict, Optional
21
21
 
22
- from flwr.client.clientapp import ClientApp, load_client_app
22
+ from flwr.client.client_app import ClientApp, load_client_app
23
23
  from flwr.client.node_state import NodeState
24
24
  from flwr.common.logger import log
25
25
  from flwr.server.superlink.state import StateFactory
@@ -25,7 +25,7 @@ import ray
25
25
  from ray import ObjectRef
26
26
  from ray.util.actor_pool import ActorPool
27
27
 
28
- from flwr.client.clientapp import ClientApp
28
+ from flwr.client.client_app import ClientApp, LoadClientAppError
29
29
  from flwr.common import Context, Message
30
30
  from flwr.common.logger import log
31
31
 
@@ -67,6 +67,9 @@ class VirtualClientEngineActor(ABC):
67
67
  # Handle task message
68
68
  out_message = app(message=message, context=context)
69
69
 
70
+ except LoadClientAppError as load_ex:
71
+ raise load_ex
72
+
70
73
  except Exception as ex:
71
74
  client_trace = traceback.format_exc()
72
75
  mssg = (
@@ -21,7 +21,7 @@ from typing import Optional
21
21
 
22
22
  from flwr import common
23
23
  from flwr.client import ClientFn
24
- from flwr.client.clientapp import ClientApp
24
+ from flwr.client.client_app import ClientApp
25
25
  from flwr.client.node_state import NodeState
26
26
  from flwr.common import Message, Metadata, RecordSet
27
27
  from flwr.common.constant import (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.8.0.dev20240226
3
+ Version: 1.8.0.dev20240227
4
4
  Summary: Flower: A Friendly Federated Learning Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -17,10 +17,10 @@ flwr/cli/new/templates/app/flower.toml.tpl,sha256=nGEU30gV6A2ZaRPt0ZVUjsxoevic4J
17
17
  flwr/cli/new/templates/app/requirements.pytorch.txt.tpl,sha256=9Z70jsiCPdsbuorhicrSdO6PVQn-3196vKZ5Ka2GkK0,87
18
18
  flwr/cli/new/templates/app/requirements.tensorflow.txt.tpl,sha256=WTbIgK5G_iG0W-xtvQLCZMxL_Og26rFXial2TkYH5dw,211
19
19
  flwr/cli/utils.py,sha256=9cCEIt8QmJXz85JNmPk1IHPd7p8E3KDn6h5CfF0nDL4,1926
20
- flwr/client/__init__.py,sha256=ypNGqhSDxEPX_6XwcQyVseBf8oS_Zvs_JkrLbVIeKxw,1186
21
- flwr/client/app.py,sha256=BxQGhdhFiw9JrKu-TcgmyshN02K9QpgWPxFVEcQ4Mb8,19983
20
+ flwr/client/__init__.py,sha256=futk_IdY_N1h8BTve4Iru51bxm7H1gv58ZPIXWi5XUA,1187
21
+ flwr/client/app.py,sha256=WUfnjDmlhL6bajdRj22PQjW2u3d6ONvWoDzOSAeIRcc,20074
22
22
  flwr/client/client.py,sha256=Vp9UkOkoHdNfn6iMYZsj_5m_GICiFfUlKEVaLad-YhM,8183
23
- flwr/client/clientapp.py,sha256=jrDgJBswP2hD1YdGgQoI3GU_NkliYWVU8glBJLOVzQY,4205
23
+ flwr/client/client_app.py,sha256=jrDgJBswP2hD1YdGgQoI3GU_NkliYWVU8glBJLOVzQY,4205
24
24
  flwr/client/dpfedavg_numpy_client.py,sha256=9Tnig4iml2J88HBKNahegjXjbfvIQyBtaIQaqjbeqsA,7435
25
25
  flwr/client/grpc_client/__init__.py,sha256=LsnbqXiJhgQcB0XzAlUQgPx011Uf7Y7yabIC1HxivJ8,735
26
26
  flwr/client/grpc_client/connection.py,sha256=QJKv39MlcDMLr2YQ80ulm-2mD3bAozEz3VKnNsymbYs,8381
@@ -29,7 +29,8 @@ flwr/client/grpc_rere_client/connection.py,sha256=QfshoyA9yYuHK15Vb0hlB0QDv0dQRq
29
29
  flwr/client/message_handler/__init__.py,sha256=abHvBRJJiiaAMNgeILQbMOa6h8WqMK2BcnvxwQZFpic,719
30
30
  flwr/client/message_handler/message_handler.py,sha256=369gEm8t1Tbp_Y74XlOGMy_mvD1zawD-dLwsBL174tY,6594
31
31
  flwr/client/message_handler/task_handler.py,sha256=ZDJBKmrn2grRMNl1rU1iGs7FiMHL5VmZiSp_6h9GHVU,1824
32
- flwr/client/mod/__init__.py,sha256=2WIdmUl-Z2CJkayrkDF21V-5Qe_iBW8AHa7F2ctK3P4,840
32
+ flwr/client/mod/__init__.py,sha256=6LRDFRjUAMevk2TQ_azVs2zczumXcyKGzAQDRWLoe7A,911
33
+ flwr/client/mod/centraldp_mods.py,sha256=zhzjikh7PG3DCpkPxBKJFR8fG4QNouhuE7l8wexMz-U,2893
33
34
  flwr/client/mod/secure_aggregation/__init__.py,sha256=AzCdezuzX2BfXUuxVRwXdv8-zUIXoU-Bf6u4LRhzvg8,796
34
35
  flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=z_5t1YzqLs91ZLW5Yoo7Ozqw9_nyVuEpJ7Noa2a34bs,19890
35
36
  flwr/client/mod/utils.py,sha256=lvETHcCYsSWz7h8I772hCV_kZspxqlMqzriMZ-SxmKc,1226
@@ -45,8 +46,9 @@ flwr/common/constant.py,sha256=jVUVKXo1cFb2HpRYqV70WKMG4RqCVrq7H6KC7zXs23Y,1572
45
46
  flwr/common/context.py,sha256=ounF-mWPPtXGwtae3sg5EhF58ScviOa3MVqxRpGVu-8,1313
46
47
  flwr/common/date.py,sha256=UWhBZj49yX9LD4BmatS_ZFZu_-kweGh0KQJ1djyWWH4,891
47
48
  flwr/common/differential_privacy.py,sha256=pVSKRhciVNtdBlhoz1H0--8N5PMLjdO_bA1PLGq4WZ8,2969
48
- flwr/common/differential_privacy_constants.py,sha256=_yGgdFMlXzhNibnETSjedA6vbnQykNMxnsgs0TELOr4,1011
49
+ flwr/common/differential_privacy_constants.py,sha256=LUP9YurHRDm5--9jATnN2ddrnBSdEX30qvcys0BlUlY,1048
49
50
  flwr/common/dp.py,sha256=Hc3lLHihjexbJaD_ft31gdv9XRcwOTgDBwJzICuok3A,2004
51
+ flwr/common/exit_handlers.py,sha256=2Nt0wLhc17KQQsLPFSRAjjhUiEFfJK6tNozdGiIY4Fs,2812
50
52
  flwr/common/grpc.py,sha256=qVLB0d6bCuaBRW5YB0vEZXsR7Bo3R2lh4ONiCocqwRI,2270
51
53
  flwr/common/logger.py,sha256=qX_gqEyrmGOH0x_r8uQ1Vskz4fGvEij9asdo4DUOPY8,4135
52
54
  flwr/common/message.py,sha256=lCbaYFKSTI_Fpot-mJsq4rTvj46ZvqNAz9LVcJBlF1Q,6901
@@ -99,7 +101,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
99
101
  flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
100
102
  flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
103
  flwr/server/__init__.py,sha256=_Wv3UkZzSXzbKWXyl2yY8tU6oqf_XqIruggpHjnmikE,1662
102
- flwr/server/app.py,sha256=0-p--_yF8rHTPxjrwZnBkEZgEeyAAqJ-i18vdCHTq5g,26328
104
+ flwr/server/app.py,sha256=wf0CCq5y7kPcf_Jy3Dh8YhopUWo3v2eEVmyF9KhWsZY,25101
103
105
  flwr/server/client_manager.py,sha256=T8UDSRJBVD3fyIDI7NTAA-NA7GPrMNNgH2OAF54RRxE,6127
104
106
  flwr/server/client_proxy.py,sha256=8ScGDvP3jHbl8DV3hyFID5N5VEVlXn8ZTQXtkdOfssI,2234
105
107
  flwr/server/compat/__init__.py,sha256=KNvRFANbIc8LFRKHsBVfxcbOSekEImWgNq_gapCkbic,812
@@ -114,10 +116,10 @@ flwr/server/run_serverapp.py,sha256=tYNbz11xMvkbcMty6u5nkLHmZhwIjSO78CfW-Aas5R8,
114
116
  flwr/server/server.py,sha256=kUIqgLIXnWcSrhEhXXkaZPRooYhTGGX-RDCYzG9J76g,17495
115
117
  flwr/server/server_app.py,sha256=avNQ7AMMKsn09ly81C3UBgOfHhM_R29l4MrzlalGoj8,5892
116
118
  flwr/server/server_config.py,sha256=yOHpkdyuhOm--Gy_4Vofvu6jCDxhyECEDpIy02beuCg,1018
117
- flwr/server/strategy/__init__.py,sha256=qYOURtpO5DtaDXzvo0wd1lv0cP3wGQxbhqn3s5i8--k,2220
119
+ flwr/server/strategy/__init__.py,sha256=-VCKDaWfVpiplrgbbtJ0rUOmQ180oIh7bgenE9UDDlM,2327
118
120
  flwr/server/strategy/aggregate.py,sha256=QyRIJtI5gnuY1NbgrcrOvkHxGIxBvApq7d9Y4xl-6W4,13468
119
121
  flwr/server/strategy/bulyan.py,sha256=8GsSVJzRSoSWE2zQUKqC3Z795grdN9xpmc3MSGGXnzM,6532
120
- flwr/server/strategy/dp_fixed_clipping.py,sha256=185P6F17uOtRByDaQSCpKj6jCGgP4271xhoMLUl2oR8,6671
122
+ flwr/server/strategy/dp_fixed_clipping.py,sha256=h7yICGeC-1CsJgOWlH8MpMVXE23gUV7GzefrvHoGYBw,12187
121
123
  flwr/server/strategy/dpfedavg_adaptive.py,sha256=hLJkPQJl1bHjwrBNg3PSRFKf3no0hg5EHiFaWhHlWqw,4877
122
124
  flwr/server/strategy/dpfedavg_fixed.py,sha256=G0yYxrPoM-MHQ889DYN3OeNiEeU0yQrjgAzcq0G653w,7219
123
125
  flwr/server/strategy/fault_tolerant_fedavg.py,sha256=veGcehB6rXT_MihNDrD1v5JY-TxJi7fybdDl-OZooDQ,5900
@@ -154,9 +156,9 @@ flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=VKDvDq5H8koOUztpmQacVzG
154
156
  flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=7JCs7NW4Qq8W5QhXxqsQNFiCLlRY-b_iD420vH1Mu-U,5906
155
157
  flwr/server/superlink/fleet/vce/__init__.py,sha256=bogHbcWSXkD7wZkqUXiLRKQTJUs7jtr5uwaGlmoA-Yc,785
156
158
  flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=oBIzmnrSSRvH_H0vRGEGWhWzQQwqe3zn6e13RsNwlIY,1466
157
- flwr/server/superlink/fleet/vce/backend/backend.py,sha256=jezKWIfh7A7a8Eblf0_ZEpWgIGA4pyFdI-duRpdmxR8,2259
158
- flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=Is79w6fMDcx0fEycIfK-VUyHA_ZVCRKp4RJ23Dd7GPM,5751
159
- flwr/server/superlink/fleet/vce/vce_api.py,sha256=8R1v9VoqnjXflQSRN-MGNR_EFiUBZ6jaMUW7uhpu838,3098
159
+ flwr/server/superlink/fleet/vce/backend/backend.py,sha256=LJsKl7oixVvptcG98Rd9ejJycNWcEVB0ODvSreLGp-A,2260
160
+ flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=EYnLpX9bTRBrLiEW0F2LLMC6kt7Zhcy10zxqeKKfIGg,6351
161
+ flwr/server/superlink/fleet/vce/vce_api.py,sha256=TiDuQVdClc38heSjD3b2jo7kZg-KZuNhzh0OdJmmwT4,3099
160
162
  flwr/server/superlink/state/__init__.py,sha256=ij-7Ms-hyordQdRmGQxY1-nVa4OhixJ0jr7_YDkys0s,1003
161
163
  flwr/server/superlink/state/in_memory_state.py,sha256=sZX5XcpnU9cafhhC4Or5oRGIbKR2AKdjIfBDtMVGNLQ,8105
162
164
  flwr/server/superlink/state/sqlite_state.py,sha256=Adc2g1DecAN9Cl9F8lekuTb885mIHiOi6sQv4nxbmSc,21203
@@ -169,11 +171,11 @@ flwr/server/utils/validator.py,sha256=IJN2475yyD_i_9kg_SJ_JodIuZh58ufpWGUDQRAqu2
169
171
  flwr/simulation/__init__.py,sha256=E2eD5FlTmZZ80u21FmWCkacrM7O4mrEHD8iXqeCaBUQ,1278
170
172
  flwr/simulation/app.py,sha256=WqJxdXTEuehwMW605p5NMmvBbKYx5tuqnV3Mp7jSWXM,13904
171
173
  flwr/simulation/ray_transport/__init__.py,sha256=FsaAnzC4cw4DqoouBCix6496k29jACkfeIam55BvW9g,734
172
- flwr/simulation/ray_transport/ray_actor.py,sha256=BZBJ5ExSK7oXibmaa8S2tv64xsgISEss2pFvHZjuYd8,19778
173
- flwr/simulation/ray_transport/ray_client_proxy.py,sha256=Cznuhs8UtmhKObn5s-euH3nbwX7B2mHCAdkKuwB6Asg,6334
174
+ flwr/simulation/ray_transport/ray_actor.py,sha256=zRETW_xuCAOLRFaYnQ-q3IBSz0LIv_0RifGuhgWaYOg,19872
175
+ flwr/simulation/ray_transport/ray_client_proxy.py,sha256=DVuequIvgbXQGYz_8oIWghnE_sPiiswAGOVfHNNF4U8,6335
174
176
  flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
175
- flwr_nightly-1.8.0.dev20240226.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
176
- flwr_nightly-1.8.0.dev20240226.dist-info/METADATA,sha256=QQYucZjlRxEFvufXKx3SamIH78YCGHb_qao8TpaIz2U,15040
177
- flwr_nightly-1.8.0.dev20240226.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
178
- flwr_nightly-1.8.0.dev20240226.dist-info/entry_points.txt,sha256=S1zLNFLrz0uPWs4Zrgo2EPY0iQiIcCJHrIAlnQkkOBI,262
179
- flwr_nightly-1.8.0.dev20240226.dist-info/RECORD,,
177
+ flwr_nightly-1.8.0.dev20240227.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
178
+ flwr_nightly-1.8.0.dev20240227.dist-info/METADATA,sha256=a6sTBhHs6TpkUp9isUB69U9Q7PoY_or1jNb7gA44z2w,15040
179
+ flwr_nightly-1.8.0.dev20240227.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
180
+ flwr_nightly-1.8.0.dev20240227.dist-info/entry_points.txt,sha256=S1zLNFLrz0uPWs4Zrgo2EPY0iQiIcCJHrIAlnQkkOBI,262
181
+ flwr_nightly-1.8.0.dev20240227.dist-info/RECORD,,
File without changes