flwr-nightly 1.10.0.dev20240713__py3-none-any.whl → 1.10.0.dev20240715__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 (34) hide show
  1. flwr/cli/build.py +1 -1
  2. flwr/cli/config_utils.py +15 -15
  3. flwr/cli/install.py +1 -1
  4. flwr/cli/new/templates/app/code/server.hf.py.tpl +13 -11
  5. flwr/cli/new/templates/app/code/server.jax.py.tpl +12 -8
  6. flwr/cli/new/templates/app/code/server.mlx.py.tpl +8 -7
  7. flwr/cli/new/templates/app/code/server.numpy.py.tpl +12 -8
  8. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +13 -14
  9. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +13 -10
  10. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +13 -13
  11. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +6 -12
  12. flwr/cli/new/templates/app/pyproject.hf.toml.tpl +6 -9
  13. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +8 -5
  14. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +6 -9
  15. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +6 -9
  16. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +6 -9
  17. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +6 -9
  18. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +6 -9
  19. flwr/cli/run/run.py +98 -57
  20. flwr/client/supernode/app.py +25 -14
  21. flwr/common/config.py +1 -1
  22. flwr/server/run_serverapp.py +1 -1
  23. flwr/server/superlink/fleet/vce/vce_api.py +45 -28
  24. flwr/simulation/run_simulation.py +42 -25
  25. flwr/superexec/app.py +11 -5
  26. flwr/superexec/deployment.py +85 -21
  27. flwr/superexec/exec_grpc.py +5 -2
  28. flwr/superexec/executor.py +18 -1
  29. flwr/superexec/simulation.py +157 -0
  30. {flwr_nightly-1.10.0.dev20240713.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/METADATA +1 -1
  31. {flwr_nightly-1.10.0.dev20240713.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/RECORD +34 -33
  32. {flwr_nightly-1.10.0.dev20240713.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/LICENSE +0 -0
  33. {flwr_nightly-1.10.0.dev20240713.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/WHEEL +0 -0
  34. {flwr_nightly-1.10.0.dev20240713.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/entry_points.txt +0 -0
flwr/cli/run/run.py CHANGED
@@ -15,18 +15,16 @@
15
15
  """Flower command line interface `run` command."""
16
16
 
17
17
  import sys
18
- from enum import Enum
19
18
  from logging import DEBUG
20
19
  from pathlib import Path
21
- from typing import Dict, Optional
20
+ from typing import Any, Dict, Optional
22
21
 
23
22
  import typer
24
23
  from typing_extensions import Annotated
25
24
 
26
- from flwr.cli import config_utils
27
25
  from flwr.cli.build import build
26
+ from flwr.cli.config_utils import load_and_validate
28
27
  from flwr.common.config import parse_config_args
29
- from flwr.common.constant import SUPEREXEC_DEFAULT_ADDRESS
30
28
  from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
31
29
  from flwr.common.logger import log
32
30
  from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
@@ -34,35 +32,20 @@ from flwr.proto.exec_pb2_grpc import ExecStub
34
32
  from flwr.simulation.run_simulation import _run_simulation
35
33
 
36
34
 
37
- class Engine(str, Enum):
38
- """Enum defining the engine to run on."""
39
-
40
- SIMULATION = "simulation"
41
-
42
-
43
35
  # pylint: disable-next=too-many-locals
44
36
  def run(
45
- engine: Annotated[
46
- Optional[Engine],
47
- typer.Option(
48
- case_sensitive=False,
49
- help="The engine to run FL with (currently only simulation is supported).",
50
- ),
51
- ] = None,
52
- use_superexec: Annotated[
53
- bool,
54
- typer.Option(
55
- case_sensitive=False, help="Use this flag to use the new SuperExec API"
56
- ),
57
- ] = False,
58
37
  directory: Annotated[
59
- Optional[Path],
60
- typer.Option(help="Path of the Flower project to run"),
38
+ Path,
39
+ typer.Argument(help="Path of the Flower project to run"),
40
+ ] = Path("."),
41
+ federation_name: Annotated[
42
+ Optional[str],
43
+ typer.Argument(help="Name of the federation to run the app on"),
61
44
  ] = None,
62
45
  config_overrides: Annotated[
63
46
  Optional[str],
64
47
  typer.Option(
65
- "--config",
48
+ "--run-config",
66
49
  "-c",
67
50
  help="Override configuration key-value pairs",
68
51
  ),
@@ -72,7 +55,7 @@ def run(
72
55
  typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
73
56
 
74
57
  pyproject_path = directory / "pyproject.toml" if directory else None
75
- config, errors, warnings = config_utils.load_and_validate(path=pyproject_path)
58
+ config, errors, warnings = load_and_validate(path=pyproject_path)
76
59
 
77
60
  if config is None:
78
61
  typer.secho(
@@ -94,50 +77,81 @@ def run(
94
77
 
95
78
  typer.secho("Success", fg=typer.colors.GREEN)
96
79
 
97
- if use_superexec:
98
- _start_superexec_run(
99
- parse_config_args(config_overrides, separator=","), directory
100
- )
101
- return
102
-
103
- server_app_ref = config["flower"]["components"]["serverapp"]
104
- client_app_ref = config["flower"]["components"]["clientapp"]
105
-
106
- if engine is None:
107
- engine = config["flower"]["engine"]["name"]
80
+ federation_name = federation_name or config["tool"]["flwr"]["federations"].get(
81
+ "default"
82
+ )
108
83
 
109
- if engine == Engine.SIMULATION:
110
- num_supernodes = config["flower"]["engine"]["simulation"]["supernode"]["num"]
111
- backend_config = config["flower"]["engine"]["simulation"].get(
112
- "backend_config", None
84
+ if federation_name is None:
85
+ typer.secho(
86
+ "❌ No federation name was provided and the project's `pyproject.toml` "
87
+ "doesn't declare a default federation (with a SuperExec address or an "
88
+ "`options.num-supernodes` value).",
89
+ fg=typer.colors.RED,
90
+ bold=True,
113
91
  )
92
+ raise typer.Exit(code=1)
114
93
 
115
- typer.secho("Starting run... ", fg=typer.colors.BLUE)
116
- _run_simulation(
117
- server_app_attr=server_app_ref,
118
- client_app_attr=client_app_ref,
119
- num_supernodes=num_supernodes,
120
- backend_config=backend_config,
121
- )
122
- else:
94
+ # Validate the federation exists in the configuration
95
+ federation = config["tool"]["flwr"]["federations"].get(federation_name)
96
+ if federation is None:
97
+ available_feds = list(config["tool"]["flwr"]["federations"])
123
98
  typer.secho(
124
- f"Engine '{engine}' is not yet supported in `flwr run`",
99
+ f" There is no `{federation_name}` federation declared in the "
100
+ "`pyproject.toml`.\n The following federations were found:\n\n"
101
+ "\n".join(available_feds) + "\n\n",
125
102
  fg=typer.colors.RED,
126
103
  bold=True,
127
104
  )
105
+ raise typer.Exit(code=1)
106
+
107
+ if "address" in federation:
108
+ _run_with_superexec(federation, directory, config_overrides)
109
+ else:
110
+ _run_without_superexec(config, federation, federation_name)
128
111
 
129
112
 
130
- def _start_superexec_run(
131
- override_config: Dict[str, str], directory: Optional[Path]
113
+ def _run_with_superexec(
114
+ federation: Dict[str, str],
115
+ directory: Optional[Path],
116
+ config_overrides: Optional[str],
132
117
  ) -> None:
118
+
133
119
  def on_channel_state_change(channel_connectivity: str) -> None:
134
120
  """Log channel connectivity."""
135
121
  log(DEBUG, channel_connectivity)
136
122
 
123
+ insecure_str = federation.get("insecure")
124
+ if root_certificates := federation.get("root-certificates"):
125
+ root_certificates_bytes = Path(root_certificates).read_bytes()
126
+ if insecure := bool(insecure_str):
127
+ typer.secho(
128
+ "❌ `root_certificates` were provided but the `insecure` parameter"
129
+ "is set to `True`.",
130
+ fg=typer.colors.RED,
131
+ bold=True,
132
+ )
133
+ raise typer.Exit(code=1)
134
+ else:
135
+ root_certificates_bytes = None
136
+ if insecure_str is None:
137
+ typer.secho(
138
+ "❌ To disable TLS, set `insecure = true` in `pyproject.toml`.",
139
+ fg=typer.colors.RED,
140
+ bold=True,
141
+ )
142
+ raise typer.Exit(code=1)
143
+ if not (insecure := bool(insecure_str)):
144
+ typer.secho(
145
+ "❌ No certificate were given yet `insecure` is set to `False`.",
146
+ fg=typer.colors.RED,
147
+ bold=True,
148
+ )
149
+ raise typer.Exit(code=1)
150
+
137
151
  channel = create_channel(
138
- server_address=SUPEREXEC_DEFAULT_ADDRESS,
139
- insecure=True,
140
- root_certificates=None,
152
+ server_address=federation["address"],
153
+ insecure=insecure,
154
+ root_certificates=root_certificates_bytes,
141
155
  max_message_length=GRPC_MAX_MESSAGE_LENGTH,
142
156
  interceptors=None,
143
157
  )
@@ -148,7 +162,34 @@ def _start_superexec_run(
148
162
 
149
163
  req = StartRunRequest(
150
164
  fab_file=Path(fab_path).read_bytes(),
151
- override_config=override_config,
165
+ override_config=parse_config_args(config_overrides, separator=","),
152
166
  )
153
167
  res = stub.StartRun(req)
154
168
  typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN)
169
+
170
+
171
+ def _run_without_superexec(
172
+ config: Dict[str, Any], federation: Dict[str, Any], federation_name: str
173
+ ) -> None:
174
+ server_app_ref = config["tool"]["flwr"]["components"]["serverapp"]
175
+ client_app_ref = config["tool"]["flwr"]["components"]["clientapp"]
176
+
177
+ try:
178
+ num_supernodes = federation["options"]["num-supernodes"]
179
+ except KeyError as err:
180
+ typer.secho(
181
+ "❌ The project's `pyproject.toml` needs to declare the number of"
182
+ " SuperNodes in the simulation. To simulate 10 SuperNodes,"
183
+ " use the following notation:\n\n"
184
+ f"[tool.flwr.federations.{federation_name}]\n"
185
+ "options.num-supernodes = 10\n",
186
+ fg=typer.colors.RED,
187
+ bold=True,
188
+ )
189
+ raise typer.Exit(code=1) from err
190
+
191
+ _run_simulation(
192
+ server_app_attr=server_app_ref,
193
+ client_app_attr=client_app_ref,
194
+ num_supernodes=num_supernodes,
195
+ )
@@ -60,7 +60,12 @@ def run_supernode() -> None:
60
60
  _warn_deprecated_server_arg(args)
61
61
 
62
62
  root_certificates = _get_certificates(args)
63
- load_fn = _get_load_client_app_fn(args, multi_app=True)
63
+ load_fn = _get_load_client_app_fn(
64
+ default_app_ref=getattr(args, "client-app"),
65
+ dir_arg=args.dir,
66
+ flwr_dir_arg=args.flwr_dir,
67
+ multi_app=True,
68
+ )
64
69
  authentication_keys = _try_setup_client_authentication(args)
65
70
 
66
71
  _start_client_internal(
@@ -93,7 +98,11 @@ def run_client_app() -> None:
93
98
  _warn_deprecated_server_arg(args)
94
99
 
95
100
  root_certificates = _get_certificates(args)
96
- load_fn = _get_load_client_app_fn(args, multi_app=False)
101
+ load_fn = _get_load_client_app_fn(
102
+ default_app_ref=getattr(args, "client-app"),
103
+ dir_arg=args.dir,
104
+ multi_app=False,
105
+ )
97
106
  authentication_keys = _try_setup_client_authentication(args)
98
107
 
99
108
  _start_client_internal(
@@ -166,7 +175,10 @@ def _get_certificates(args: argparse.Namespace) -> Optional[bytes]:
166
175
 
167
176
 
168
177
  def _get_load_client_app_fn(
169
- args: argparse.Namespace, multi_app: bool
178
+ default_app_ref: str,
179
+ dir_arg: str,
180
+ multi_app: bool,
181
+ flwr_dir_arg: Optional[str] = None,
170
182
  ) -> Callable[[str, str], ClientApp]:
171
183
  """Get the load_client_app_fn function.
172
184
 
@@ -178,25 +190,24 @@ def _get_load_client_app_fn(
178
190
  loads a default ClientApp.
179
191
  """
180
192
  # Find the Flower directory containing Flower Apps (only for multi-app)
181
- flwr_dir = Path("")
182
- if "flwr_dir" in args:
183
- if args.flwr_dir is None:
193
+ if not multi_app:
194
+ flwr_dir = Path("")
195
+ else:
196
+ if flwr_dir_arg is None:
184
197
  flwr_dir = get_flwr_dir()
185
198
  else:
186
- flwr_dir = Path(args.flwr_dir).absolute()
199
+ flwr_dir = Path(flwr_dir_arg).absolute()
187
200
 
188
201
  inserted_path = None
189
202
 
190
- default_app_ref: str = getattr(args, "client-app")
191
-
192
203
  if not multi_app:
193
204
  log(
194
205
  DEBUG,
195
206
  "Flower SuperNode will load and validate ClientApp `%s`",
196
- getattr(args, "client-app"),
207
+ default_app_ref,
197
208
  )
198
209
  # Insert sys.path
199
- dir_path = Path(args.dir).absolute()
210
+ dir_path = Path(dir_arg).absolute()
200
211
  sys.path.insert(0, str(dir_path))
201
212
  inserted_path = str(dir_path)
202
213
 
@@ -208,7 +219,7 @@ def _get_load_client_app_fn(
208
219
  # If multi-app feature is disabled
209
220
  if not multi_app:
210
221
  # Get sys path to be inserted
211
- dir_path = Path(args.dir).absolute()
222
+ dir_path = Path(dir_arg).absolute()
212
223
 
213
224
  # Set app reference
214
225
  client_app_ref = default_app_ref
@@ -221,7 +232,7 @@ def _get_load_client_app_fn(
221
232
 
222
233
  log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.")
223
234
  # Get sys path to be inserted
224
- dir_path = Path(args.dir).absolute()
235
+ dir_path = Path(dir_arg).absolute()
225
236
 
226
237
  # Set app reference
227
238
  client_app_ref = default_app_ref
@@ -237,7 +248,7 @@ def _get_load_client_app_fn(
237
248
  dir_path = Path(project_dir).absolute()
238
249
 
239
250
  # Set app reference
240
- client_app_ref = config["flower"]["components"]["clientapp"]
251
+ client_app_ref = config["tool"]["flwr"]["components"]["clientapp"]
241
252
 
242
253
  # Set sys.path
243
254
  nonlocal inserted_path
flwr/common/config.py CHANGED
@@ -97,7 +97,7 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]:
97
97
 
98
98
  project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir)
99
99
 
100
- default_config = get_project_config(project_dir)["flower"].get("config", {})
100
+ default_config = get_project_config(project_dir)["tool"]["flwr"].get("config", {})
101
101
  flat_default_config = flatten_dict(default_config)
102
102
 
103
103
  return _fuse_dicts(flat_default_config, run.override_config)
@@ -186,7 +186,7 @@ def run_server_app() -> None: # pylint: disable=too-many-branches
186
186
  run_ = driver.run
187
187
  server_app_dir = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir))
188
188
  config = get_project_config(server_app_dir)
189
- server_app_attr = config["flower"]["components"]["serverapp"]
189
+ server_app_attr = config["tool"]["flwr"]["components"]["serverapp"]
190
190
  server_app_run_config = get_fused_config(run_, flwr_dir)
191
191
  else:
192
192
  # User provided `server-app`, but not `--run-id`
@@ -16,7 +16,6 @@
16
16
 
17
17
 
18
18
  import json
19
- import sys
20
19
  import threading
21
20
  import time
22
21
  import traceback
@@ -29,6 +28,7 @@ from typing import Callable, Dict, Optional
29
28
 
30
29
  from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError
31
30
  from flwr.client.node_state import NodeState
31
+ from flwr.client.supernode.app import _get_load_client_app_fn
32
32
  from flwr.common.constant import (
33
33
  NUM_PARTITIONS_KEY,
34
34
  PARTITION_ID_KEY,
@@ -37,8 +37,8 @@ from flwr.common.constant import (
37
37
  )
38
38
  from flwr.common.logger import log
39
39
  from flwr.common.message import Error
40
- from flwr.common.object_ref import load_app
41
40
  from flwr.common.serde import message_from_taskins, message_to_taskres
41
+ from flwr.common.typing import Run
42
42
  from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611
43
43
  from flwr.server.superlink.state import State, StateFactory
44
44
 
@@ -60,6 +60,27 @@ def _register_nodes(
60
60
  return nodes_mapping
61
61
 
62
62
 
63
+ def _register_node_states(
64
+ nodes_mapping: NodeToPartitionMapping, run: Run
65
+ ) -> Dict[int, NodeState]:
66
+ """Create NodeState objects and pre-register the context for the run."""
67
+ node_states: Dict[int, NodeState] = {}
68
+ num_partitions = len(set(nodes_mapping.values()))
69
+ for node_id, partition_id in nodes_mapping.items():
70
+ node_states[node_id] = NodeState(
71
+ node_id=node_id,
72
+ node_config={
73
+ PARTITION_ID_KEY: str(partition_id),
74
+ NUM_PARTITIONS_KEY: str(num_partitions),
75
+ },
76
+ )
77
+
78
+ # Pre-register Context objects
79
+ node_states[node_id].register_context(run_id=run.run_id, run=run)
80
+
81
+ return node_states
82
+
83
+
63
84
  # pylint: disable=too-many-arguments,too-many-locals
64
85
  def worker(
65
86
  app_fn: Callable[[], ClientApp],
@@ -78,8 +99,7 @@ def worker(
78
99
  task_ins: TaskIns = taskins_queue.get(timeout=1.0)
79
100
  node_id = task_ins.task.consumer.node_id
80
101
 
81
- # Register and retrieve context
82
- node_states[node_id].register_context(run_id=task_ins.run_id)
102
+ # Retrieve context
83
103
  context = node_states[node_id].retrieve_context(run_id=task_ins.run_id)
84
104
 
85
105
  # Convert TaskIns to Message
@@ -151,7 +171,7 @@ def put_taskres_into_state(
151
171
  pass
152
172
 
153
173
 
154
- def run(
174
+ def run_api(
155
175
  app_fn: Callable[[], ClientApp],
156
176
  backend_fn: Callable[[], Backend],
157
177
  nodes_mapping: NodeToPartitionMapping,
@@ -237,6 +257,8 @@ def start_vce(
237
257
  backend_config_json_stream: str,
238
258
  app_dir: str,
239
259
  f_stop: threading.Event,
260
+ run: Run,
261
+ flwr_dir: Optional[str] = None,
240
262
  client_app: Optional[ClientApp] = None,
241
263
  client_app_attr: Optional[str] = None,
242
264
  num_supernodes: Optional[int] = None,
@@ -287,17 +309,7 @@ def start_vce(
287
309
  )
288
310
 
289
311
  # Construct mapping of NodeStates
290
- node_states: Dict[int, NodeState] = {}
291
- # Number of unique partitions
292
- num_partitions = len(set(nodes_mapping.values()))
293
- for node_id, partition_id in nodes_mapping.items():
294
- node_states[node_id] = NodeState(
295
- node_id=node_id,
296
- node_config={
297
- PARTITION_ID_KEY: str(partition_id),
298
- NUM_PARTITIONS_KEY: str(num_partitions),
299
- },
300
- )
312
+ node_states = _register_node_states(nodes_mapping=nodes_mapping, run=run)
301
313
 
302
314
  # Load backend config
303
315
  log(DEBUG, "Supported backends: %s", list(supported_backends.keys()))
@@ -326,16 +338,12 @@ def start_vce(
326
338
  def _load() -> ClientApp:
327
339
 
328
340
  if client_app_attr:
329
-
330
- if app_dir is not None:
331
- sys.path.insert(0, app_dir)
332
-
333
- app: ClientApp = load_app(client_app_attr, LoadClientAppError, app_dir)
334
-
335
- if not isinstance(app, ClientApp):
336
- raise LoadClientAppError(
337
- f"Attribute {client_app_attr} is not of type {ClientApp}",
338
- ) from None
341
+ app = _get_load_client_app_fn(
342
+ default_app_ref=client_app_attr,
343
+ dir_arg=app_dir,
344
+ flwr_dir_arg=flwr_dir,
345
+ multi_app=True,
346
+ )(run.fab_id, run.fab_version)
339
347
 
340
348
  if client_app:
341
349
  app = client_app
@@ -345,10 +353,19 @@ def start_vce(
345
353
 
346
354
  try:
347
355
  # Test if ClientApp can be loaded
348
- _ = app_fn()
356
+ client_app = app_fn()
357
+
358
+ # Cache `ClientApp`
359
+ if client_app_attr:
360
+ # Now wrap the loaded ClientApp in a dummy function
361
+ # this prevent unnecesary low-level loading of ClientApp
362
+ def _load_client_app() -> ClientApp:
363
+ return client_app
364
+
365
+ app_fn = _load_client_app
349
366
 
350
367
  # Run main simulation loop
351
- run(
368
+ run_api(
352
369
  app_fn,
353
370
  backend_fn,
354
371
  nodes_mapping,
@@ -26,14 +26,16 @@ from typing import Dict, Optional
26
26
 
27
27
  from flwr.client import ClientApp
28
28
  from flwr.common import EventType, event, log
29
+ from flwr.common.constant import RUN_ID_NUM_BYTES
29
30
  from flwr.common.logger import set_logger_propagation, update_console_handler
30
31
  from flwr.common.typing import Run
31
32
  from flwr.server.driver import Driver, InMemoryDriver
32
- from flwr.server.run_serverapp import run
33
+ from flwr.server.run_serverapp import run as run_server_app
33
34
  from flwr.server.server_app import ServerApp
34
35
  from flwr.server.superlink.fleet import vce
35
36
  from flwr.server.superlink.fleet.vce.backend.backend import BackendConfig
36
37
  from flwr.server.superlink.state import StateFactory
38
+ from flwr.server.superlink.state.utils import generate_rand_int_from_bytes
37
39
  from flwr.simulation.ray_transport.utils import (
38
40
  enable_tf_gpu_growth as enable_gpu_growth,
39
41
  )
@@ -54,7 +56,11 @@ def run_simulation_from_cli() -> None:
54
56
  backend_name=args.backend,
55
57
  backend_config=backend_config_dict,
56
58
  app_dir=args.app_dir,
57
- run_id=args.run_id,
59
+ run=(
60
+ Run(run_id=args.run_id, fab_id="", fab_version="", override_config={})
61
+ if args.run_id
62
+ else None
63
+ ),
58
64
  enable_tf_gpu_growth=args.enable_tf_gpu_growth,
59
65
  verbose_logging=args.verbose,
60
66
  )
@@ -156,7 +162,7 @@ def run_serverapp_th(
156
162
  enable_gpu_growth()
157
163
 
158
164
  # Run ServerApp
159
- run(
165
+ run_server_app(
160
166
  driver=_driver,
161
167
  server_app_dir=_server_app_dir,
162
168
  server_app_run_config=_server_app_run_config,
@@ -193,16 +199,6 @@ def run_serverapp_th(
193
199
  return serverapp_th
194
200
 
195
201
 
196
- def _override_run_id(state: StateFactory, run_id_to_replace: int, run_id: int) -> None:
197
- """Override the run_id of an existing Run."""
198
- log(DEBUG, "Pre-registering run with id %s", run_id)
199
- # Remove run
200
- run_info: Run = state.state().run_ids.pop(run_id_to_replace) # type: ignore
201
- # Update with new run_id and insert back in state
202
- run_info.run_id = run_id
203
- state.state().run_ids[run_id] = run_info # type: ignore
204
-
205
-
206
202
  # pylint: disable=too-many-locals
207
203
  def _main_loop(
208
204
  num_supernodes: int,
@@ -210,7 +206,8 @@ def _main_loop(
210
206
  backend_config_stream: str,
211
207
  app_dir: str,
212
208
  enable_tf_gpu_growth: bool,
213
- run_id: Optional[int] = None,
209
+ run: Run,
210
+ flwr_dir: Optional[str] = None,
214
211
  client_app: Optional[ClientApp] = None,
215
212
  client_app_attr: Optional[str] = None,
216
213
  server_app: Optional[ServerApp] = None,
@@ -225,16 +222,13 @@ def _main_loop(
225
222
  server_app_thread_has_exception = threading.Event()
226
223
  serverapp_th = None
227
224
  try:
228
- # Create run (with empty fab_id and fab_version)
229
- run_id_ = state_factory.state().create_run("", "", {})
225
+ # Register run
226
+ log(DEBUG, "Pre-registering run with id %s", run.run_id)
227
+ state_factory.state().run_ids[run.run_id] = run # type: ignore
230
228
  server_app_run_config: Dict[str, str] = {}
231
229
 
232
- if run_id:
233
- _override_run_id(state_factory, run_id_to_replace=run_id_, run_id=run_id)
234
- run_id_ = run_id
235
-
236
230
  # Initialize Driver
237
- driver = InMemoryDriver(run_id=run_id_, state_factory=state_factory)
231
+ driver = InMemoryDriver(run_id=run.run_id, state_factory=state_factory)
238
232
 
239
233
  # Get and run ServerApp thread
240
234
  serverapp_th = run_serverapp_th(
@@ -259,6 +253,8 @@ def _main_loop(
259
253
  app_dir=app_dir,
260
254
  state_factory=state_factory,
261
255
  f_stop=f_stop,
256
+ run=run,
257
+ flwr_dir=flwr_dir,
262
258
  )
263
259
 
264
260
  except Exception as ex:
@@ -289,7 +285,8 @@ def _run_simulation(
289
285
  client_app_attr: Optional[str] = None,
290
286
  server_app_attr: Optional[str] = None,
291
287
  app_dir: str = "",
292
- run_id: Optional[int] = None,
288
+ flwr_dir: Optional[str] = None,
289
+ run: Optional[Run] = None,
293
290
  enable_tf_gpu_growth: bool = False,
294
291
  verbose_logging: bool = False,
295
292
  ) -> None:
@@ -332,8 +329,11 @@ def _run_simulation(
332
329
  Add specified directory to the PYTHONPATH and load `ClientApp` from there.
333
330
  (Default: current working directory.)
334
331
 
335
- run_id : Optional[int]
336
- An integer specifying the ID of the run started when running this function.
332
+ flwr_dir : Optional[str]
333
+ The path containing installed Flower Apps.
334
+
335
+ run : Optional[Run]
336
+ An object carrying details about the run.
337
337
 
338
338
  enable_tf_gpu_growth : bool (default: False)
339
339
  A boolean to indicate whether to enable GPU growth on the main thread. This is
@@ -371,13 +371,19 @@ def _run_simulation(
371
371
  # Convert config to original JSON-stream format
372
372
  backend_config_stream = json.dumps(backend_config)
373
373
 
374
+ # If no `Run` object is set, create one
375
+ if run is None:
376
+ run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
377
+ run = Run(run_id=run_id, fab_id="", fab_version="", override_config={})
378
+
374
379
  args = (
375
380
  num_supernodes,
376
381
  backend_name,
377
382
  backend_config_stream,
378
383
  app_dir,
379
384
  enable_tf_gpu_growth,
380
- run_id,
385
+ run,
386
+ flwr_dir,
381
387
  client_app,
382
388
  client_app_attr,
383
389
  server_app,
@@ -465,6 +471,17 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser:
465
471
  "ClientApp and ServerApp from there."
466
472
  " Default: current working directory.",
467
473
  )
474
+ parser.add_argument(
475
+ "--flwr-dir",
476
+ default=None,
477
+ help="""The path containing installed Flower Apps.
478
+ By default, this value is equal to:
479
+
480
+ - `$FLWR_HOME/` if `$FLWR_HOME` is defined
481
+ - `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined
482
+ - `$HOME/.flwr/` in all other cases
483
+ """,
484
+ )
468
485
  parser.add_argument(
469
486
  "--run-id",
470
487
  type=int,
flwr/superexec/app.py CHANGED
@@ -24,6 +24,7 @@ import grpc
24
24
 
25
25
  from flwr.common import EventType, event, log
26
26
  from flwr.common.address import parse_address
27
+ from flwr.common.config import parse_config_args
27
28
  from flwr.common.constant import SUPEREXEC_DEFAULT_ADDRESS
28
29
  from flwr.common.exit_handlers import register_exit_handlers
29
30
  from flwr.common.object_ref import load_app, validate
@@ -55,6 +56,7 @@ def run_superexec() -> None:
55
56
  address=address,
56
57
  executor=_load_executor(args),
57
58
  certificates=certificates,
59
+ config=parse_config_args(args.executor_config),
58
60
  )
59
61
 
60
62
  grpc_servers = [superexec_server]
@@ -74,21 +76,25 @@ def _parse_args_run_superexec() -> argparse.ArgumentParser:
74
76
  parser = argparse.ArgumentParser(
75
77
  description="Start a Flower SuperExec",
76
78
  )
77
- parser.add_argument(
78
- "executor",
79
- help="For example: `deployment:exec` or `project.package.module:wrapper.exec`.",
80
- default="flwr.superexec.deployment:executor",
81
- )
82
79
  parser.add_argument(
83
80
  "--address",
84
81
  help="SuperExec (gRPC) server address (IPv4, IPv6, or a domain name)",
85
82
  default=SUPEREXEC_DEFAULT_ADDRESS,
86
83
  )
84
+ parser.add_argument(
85
+ "--executor",
86
+ help="For example: `deployment:exec` or `project.package.module:wrapper.exec`.",
87
+ default="flwr.superexec.deployment:executor",
88
+ )
87
89
  parser.add_argument(
88
90
  "--executor-dir",
89
91
  help="The directory for the executor.",
90
92
  default=".",
91
93
  )
94
+ parser.add_argument(
95
+ "--executor-config",
96
+ help="Key-value pairs for the executor config, separated by commas.",
97
+ )
92
98
  parser.add_argument(
93
99
  "--insecure",
94
100
  action="store_true",