From 679a783c54a504b7f19dc2a861bf9712a57270dd Mon Sep 17 00:00:00 2001 From: jafermarq Date: Mon, 15 Jul 2024 23:01:19 +0200 Subject: [PATCH 01/27] init --- src/py/flwr/simulation/run_simulation.py | 49 ++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 8c70bf8374d0..04debbdb65d6 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -18,8 +18,10 @@ import asyncio import json import logging +import sys import threading import traceback +from argparse import Namespace from logging import DEBUG, ERROR, INFO, WARNING from time import sleep from typing import Dict, Optional @@ -41,11 +43,44 @@ ) +def _check_args_do_not_interfere(args: Namespace) -> bool: + """Ensure decoupling of flags for different ways to start the simulation.""" + keys = ["app", "config-override"] + conflict_keys = ["num-supernodes", "client-app", "server-app"] + for key_ in keys: + if getattr(args, key_) and any(getattr(args, key) for key in conflict_keys): + resolve_log_message = ",".join([f"--{key}" for key in conflict_keys]) + log( + ERROR, + "Passing `--%s` alongside with any of %s", + key_, + resolve_log_message, + ) + return False + + # Ensure all args are set (required for the non-FAB mode of execution) + if not all(getattr(args, key) for key in conflict_keys): + resolve_log_message = ",".join([f"--{key}" for key in conflict_keys]) + log(ERROR, "Passing all of %s keys are required.", resolve_log_message) + return False + + return True + + # Entry point from CLI def run_simulation_from_cli() -> None: """Run Simulation Engine from the CLI.""" args = _parse_args_run_simulation().parse_args() + # We are supporting two modes for the CLI entrypoint: + # 1) Running a FAB or FAB-like dir containing a pyproject.toml + # 2) Running any ClinetApp and SeverApp w/o pyproject.toml being present + # For 2) some CLI args are cumpolsory but these aren't for 1) + # We first do these checks + args_check_pass = _check_args_do_not_interfere(args) + if not args_check_pass: + sys.exit("Simulation Engine cannot start.") + # Load JSON config backend_config_dict = json.loads(args.backend_config) @@ -419,20 +454,28 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: ) parser.add_argument( "--server-app", - required=True, help="For example: `server:app` or `project.package.module:wrapper.app`", ) parser.add_argument( "--client-app", - required=True, help="For example: `client:app` or `project.package.module:wrapper.app`", ) parser.add_argument( "--num-supernodes", type=int, - required=True, help="Number of simulated SuperNodes.", ) + parser.add_argument( + "--app", + default=None, + help="Either a path to a FAB (.fab) file or path to a directory " + "containing a FAB-like structure and a pyproject.toml.", + ) + parser.add_argument( + "--config-override", + default=None, + help="Override configuration key-value pairs.", + ) parser.add_argument( "--backend", default="ray", From 6b7e88d8837fae072e4f40c42bdf9df9d43dac21 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Mon, 15 Jul 2024 23:10:30 +0200 Subject: [PATCH 02/27] fix args checks --- src/py/flwr/simulation/run_simulation.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 04debbdb65d6..80221d4e6083 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -45,23 +45,25 @@ def _check_args_do_not_interfere(args: Namespace) -> bool: """Ensure decoupling of flags for different ways to start the simulation.""" - keys = ["app", "config-override"] - conflict_keys = ["num-supernodes", "client-app", "server-app"] + keys = ["app", "config_override"] + conflict_keys = ["num_supernodes", "client_app", "server_app"] + + def _resolve_message() -> str: + return ",".join([f"`--{key}`".replace("_", "-") for key in conflict_keys]) + for key_ in keys: if getattr(args, key_) and any(getattr(args, key) for key in conflict_keys): - resolve_log_message = ",".join([f"--{key}" for key in conflict_keys]) log( ERROR, - "Passing `--%s` alongside with any of %s", + "Passing `--%s` alongside with any of {%s}", key_, - resolve_log_message, + _resolve_message(), ) return False # Ensure all args are set (required for the non-FAB mode of execution) if not all(getattr(args, key) for key in conflict_keys): - resolve_log_message = ",".join([f"--{key}" for key in conflict_keys]) - log(ERROR, "Passing all of %s keys are required.", resolve_log_message) + log(ERROR, "Passing all of %s keys are required.", _resolve_message()) return False return True From 4d4b0499f8bf66b55e763efaa70d1c71e7b736b2 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 00:29:24 +0200 Subject: [PATCH 03/27] basic functionality; no override; no from .fab --- src/py/flwr/simulation/run_simulation.py | 134 +++++++++++++++++++---- 1 file changed, 111 insertions(+), 23 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 80221d4e6083..8421b54442de 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -23,11 +23,14 @@ import traceback from argparse import Namespace from logging import DEBUG, ERROR, INFO, WARNING +from pathlib import Path from time import sleep -from typing import Dict, Optional +from typing import Dict, List, Optional +from flwr.cli.config_utils import get_fab_metadata, load_and_validate from flwr.client import ClientApp from flwr.common import EventType, event, log +from flwr.common.config import get_fused_config from flwr.common.constant import RUN_ID_NUM_BYTES from flwr.common.logger import set_logger_propagation, update_console_handler from flwr.common.typing import Run @@ -45,61 +48,136 @@ def _check_args_do_not_interfere(args: Namespace) -> bool: """Ensure decoupling of flags for different ways to start the simulation.""" - keys = ["app", "config_override"] - conflict_keys = ["num_supernodes", "client_app", "server_app"] + mode_one_args = ["app", "config_override"] + mode_two_args = ["num_supernodes", "client_app", "server_app"] - def _resolve_message() -> str: + def _resolve_message(conflict_keys: List[str]) -> str: return ",".join([f"`--{key}`".replace("_", "-") for key in conflict_keys]) - for key_ in keys: - if getattr(args, key_) and any(getattr(args, key) for key in conflict_keys): + if any(getattr(args, key) for key in mode_one_args): + if any(getattr(args, key) for key in mode_two_args): log( ERROR, - "Passing `--%s` alongside with any of {%s}", - key_, - _resolve_message(), + "Passing any of {%s} alongside with any of {%s}", + _resolve_message(mode_one_args), + _resolve_message(mode_two_args), ) return False + if not args.app: + log(ERROR, "You need to pass --app") + return False + + return True + # Ensure all args are set (required for the non-FAB mode of execution) - if not all(getattr(args, key) for key in conflict_keys): - log(ERROR, "Passing all of %s keys are required.", _resolve_message()) + if not all(getattr(args, key) for key in mode_two_args): + log( + ERROR, + "Passing all of %s keys are required.", + _resolve_message(mode_two_args), + ) return False return True # Entry point from CLI +# pytlint: disable=too-many-locals def run_simulation_from_cli() -> None: """Run Simulation Engine from the CLI.""" args = _parse_args_run_simulation().parse_args() # We are supporting two modes for the CLI entrypoint: # 1) Running a FAB or FAB-like dir containing a pyproject.toml - # 2) Running any ClinetApp and SeverApp w/o pyproject.toml being present + # 2) Running any ClientApp and SeverApp w/o pyproject.toml being present # For 2) some CLI args are cumpolsory but these aren't for 1) # We first do these checks args_check_pass = _check_args_do_not_interfere(args) if not args_check_pass: sys.exit("Simulation Engine cannot start.") + client_app_attr = None + server_app_attr = None + fab_version = "" + fab_id = "" + run_id = ( + generate_rand_int_from_bytes(RUN_ID_NUM_BYTES) + if args.run_id is None + else args.run_id + ) + fused_config = None + if args.app: + # mode 1 + app = Path(args.app) + if app.suffix == ".fab": + fab_version, fab_id = get_fab_metadata(app) + # TODO: how to continue here? the idea is to support: + # TODO: `flower-simulation <>.fab` + else: + + # Load pyproject.toml + config, errors, warnings = load_and_validate(app / "pyproject.toml") + if errors: + raise ValueError(errors) + + if warnings: + log(WARNING, warnings) + + if config is None: + raise ValueError( + "Config extracted from FAB's pyproject.toml is not valid" + ) + + # Get ClientApp and SeverApp components + flower_components = config["tool"]["flwr"]["components"] + client_app_attr = flower_components["clientapp"] + server_app_attr = flower_components["serverapp"] + + # Read number of supernodes to simulate + fed_config = config["tool"]["flwr"]["federations"] + num_supernodes = fed_config[fed_config["default"]]["options"][ + "num-supernodes" + ] + + override_config = args.config_override if args.config_override else {} + + else: + # mode 2 + client_app_attr = args.client_app + server_app_attr = args.server_app + num_supernodes = args.num_supernodes + override_config = {} + + # Create run + run = Run( + run_id=run_id, + fab_id=fab_id, + fab_version=fab_version, + override_config=override_config, + ) + + print(override_config) + # Override config if in fab-mode + if args.app and args.config_override: + # Fuse configs, this is needed for the serverapp + fused_config = get_fused_config(run, args.flwr_dir) + # TODO: this does nothing unless there is a fab-id/version, relax? + # Load JSON config backend_config_dict = json.loads(args.backend_config) _run_simulation( - server_app_attr=args.server_app, - client_app_attr=args.client_app, - num_supernodes=args.num_supernodes, + server_app_attr=server_app_attr, + client_app_attr=client_app_attr, + num_supernodes=num_supernodes, backend_name=args.backend, backend_config=backend_config_dict, app_dir=args.app_dir, - run=( - Run(run_id=args.run_id, fab_id="", fab_version="", override_config={}) - if args.run_id - else None - ), + run=run, enable_tf_gpu_growth=args.enable_tf_gpu_growth, verbose_logging=args.verbose, + server_app_run_config=fused_config, ) @@ -249,6 +327,7 @@ def _main_loop( client_app_attr: Optional[str] = None, server_app: Optional[ServerApp] = None, server_app_attr: Optional[str] = None, + server_app_run_config: Optional[Dict[str, str]] = None, ) -> None: """Launch SuperLink with Simulation Engine, then ServerApp on a separate thread.""" # Initialize StateFactory @@ -262,7 +341,9 @@ def _main_loop( # Register run log(DEBUG, "Pre-registering run with id %s", run.run_id) state_factory.state().run_ids[run.run_id] = run # type: ignore - server_app_run_config: Dict[str, str] = {} + + if server_app_run_config is None: + server_app_run_config = {} # Initialize Driver driver = InMemoryDriver(run_id=run.run_id, state_factory=state_factory) @@ -321,6 +402,7 @@ def _run_simulation( backend_config: Optional[BackendConfig] = None, client_app_attr: Optional[str] = None, server_app_attr: Optional[str] = None, + server_app_run_config: Optional[Dict[str, str]] = None, app_dir: str = "", flwr_dir: Optional[str] = None, run: Optional[Run] = None, @@ -354,14 +436,18 @@ def _run_simulation( parameters. Values supported in are those included by `flwr.common.typing.ConfigsRecordValues`. - client_app_attr : str + client_app_attr : Optional[str] A path to a `ClientApp` module to be loaded: For example: `client:app` or `project.package.module:wrapper.app`." - server_app_attr : str + server_app_attr : Optional[str] A path to a `ServerApp` module to be loaded: For example: `server:app` or `project.package.module:wrapper.app`." + server_app_run_config : Optional[Dict[str, str]] + Config dictionary that parameterizes the run config. It will be made accesible + to the ServerApp. + app_dir : str Add specified directory to the PYTHONPATH and load `ClientApp` from there. (Default: current working directory.) @@ -425,6 +511,7 @@ def _run_simulation( client_app_attr, server_app, server_app_attr, + server_app_run_config, ) # Detect if there is an Asyncio event loop already running. # If yes, disable logger propagation. In environmnets @@ -469,6 +556,7 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: ) parser.add_argument( "--app", + type=str, default=None, help="Either a path to a FAB (.fab) file or path to a directory " "containing a FAB-like structure and a pyproject.toml.", From 6d99d9081ceb9e5ad66272bb5ad1a26bc658522b Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 16 Jul 2024 10:14:55 +0200 Subject: [PATCH 04/27] feat(framework:skip) Add config function for fusing dicts --- src/py/flwr/common/config.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index e2b06ff86110..3d9654780056 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -86,6 +86,16 @@ def _fuse_dicts( return fused_dict +def get_fused_config_from_dir( + project_dir: Path, override_config: Dict[str, str] +) -> Dict[str, str]: + """Merge the overrides from a given dict with the config from a Flower App.""" + default_config = get_project_config(project_dir)["tool"]["flwr"].get("config", {}) + flat_default_config = flatten_dict(default_config) + + return _fuse_dicts(flat_default_config, override_config) + + def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]: """Merge the overrides from a `Run` with the config from a FAB. @@ -97,10 +107,7 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]: project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir) - default_config = get_project_config(project_dir)["tool"]["flwr"].get("config", {}) - flat_default_config = flatten_dict(default_config) - - return _fuse_dicts(flat_default_config, run.override_config) + return get_fused_config_from_dir(project_dir, run.override_config) def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, str]: From 8430e28d0e48d164b3508cd1a26d333628449ec2 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 10:20:14 +0200 Subject: [PATCH 05/27] simplify --- src/py/flwr/simulation/run_simulation.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 8421b54442de..2adeb0fd5505 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -27,7 +27,7 @@ from time import sleep from typing import Dict, List, Optional -from flwr.cli.config_utils import get_fab_metadata, load_and_validate +from flwr.cli.config_utils import load_and_validate from flwr.client import ClientApp from flwr.common import EventType, event, log from flwr.common.config import get_fused_config @@ -110,12 +110,7 @@ def run_simulation_from_cli() -> None: if args.app: # mode 1 app = Path(args.app) - if app.suffix == ".fab": - fab_version, fab_id = get_fab_metadata(app) - # TODO: how to continue here? the idea is to support: - # TODO: `flower-simulation <>.fab` - else: - + if app.is_dir(): # Load pyproject.toml config, errors, warnings = load_and_validate(app / "pyproject.toml") if errors: @@ -139,6 +134,9 @@ def run_simulation_from_cli() -> None: num_supernodes = fed_config[fed_config["default"]]["options"][ "num-supernodes" ] + else: + log(ERROR, "--app is not a directory") + sys.exit("Simulation Engine cannot start.") override_config = args.config_override if args.config_override else {} @@ -558,8 +556,7 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: "--app", type=str, default=None, - help="Either a path to a FAB (.fab) file or path to a directory " - "containing a FAB-like structure and a pyproject.toml.", + help="Path to a directory containing a FAB-like structure with a pyproject.toml.", ) parser.add_argument( "--config-override", From 11086ba4a488e16c2951c3775a81fe73b88096dc Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 10:32:33 +0200 Subject: [PATCH 06/27] using fused config -- but not in `ClientApp` yet --- src/py/flwr/simulation/run_simulation.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 2adeb0fd5505..41b9f296b751 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -30,7 +30,7 @@ from flwr.cli.config_utils import load_and_validate from flwr.client import ClientApp from flwr.common import EventType, event, log -from flwr.common.config import get_fused_config +from flwr.common.config import get_fused_config_from_dir, parse_config_args from flwr.common.constant import RUN_ID_NUM_BYTES from flwr.common.logger import set_logger_propagation, update_console_handler from flwr.common.typing import Run @@ -106,7 +106,6 @@ def run_simulation_from_cli() -> None: if args.run_id is None else args.run_id ) - fused_config = None if args.app: # mode 1 app = Path(args.app) @@ -138,7 +137,8 @@ def run_simulation_from_cli() -> None: log(ERROR, "--app is not a directory") sys.exit("Simulation Engine cannot start.") - override_config = args.config_override if args.config_override else {} + override_config = parse_config_args(args.config_override) + fused_config = get_fused_config_from_dir(app, override_config) else: # mode 2 @@ -146,6 +146,7 @@ def run_simulation_from_cli() -> None: server_app_attr = args.server_app num_supernodes = args.num_supernodes override_config = {} + fused_config = None # Create run run = Run( @@ -155,13 +156,6 @@ def run_simulation_from_cli() -> None: override_config=override_config, ) - print(override_config) - # Override config if in fab-mode - if args.app and args.config_override: - # Fuse configs, this is needed for the serverapp - fused_config = get_fused_config(run, args.flwr_dir) - # TODO: this does nothing unless there is a fab-id/version, relax? - # Load JSON config backend_config_dict = json.loads(args.backend_config) @@ -556,7 +550,8 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: "--app", type=str, default=None, - help="Path to a directory containing a FAB-like structure with a pyproject.toml.", + help="Path to a directory containing a FAB-like structure with a " + "pyproject.toml.", ) parser.add_argument( "--config-override", From 46244de8bbaf03ce41e514b70ba4e5c8ae22f75a Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 11:33:36 +0200 Subject: [PATCH 07/27] updates --- src/py/flwr/common/config.py | 4 +++- src/py/flwr/simulation/run_simulation.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 3d9654780056..6049fcbcceed 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -90,7 +90,9 @@ def get_fused_config_from_dir( project_dir: Path, override_config: Dict[str, str] ) -> Dict[str, str]: """Merge the overrides from a given dict with the config from a Flower App.""" - default_config = get_project_config(project_dir)["tool"]["flwr"].get("config", {}) + default_config = get_project_config(project_dir)["tool"]["flwr"]["app"].get( + "config", {} + ) flat_default_config = flatten_dict(default_config) return _fuse_dicts(flat_default_config, override_config) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 41b9f296b751..7321d751a046 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -124,7 +124,7 @@ def run_simulation_from_cli() -> None: ) # Get ClientApp and SeverApp components - flower_components = config["tool"]["flwr"]["components"] + flower_components = config["tool"]["flwr"]["app"]["components"] client_app_attr = flower_components["clientapp"] server_app_attr = flower_components["serverapp"] From da0d8f1c19616fb3d16b88a161e1662093b25ebd Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 11:46:33 +0200 Subject: [PATCH 08/27] init --- src/py/flwr/client/node_state.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index 393ca4564a35..4619de3b9a2f 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -20,7 +20,7 @@ from typing import Dict, Optional from flwr.common import Context, RecordSet -from flwr.common.config import get_fused_config +from flwr.common.config import get_fused_config, get_fused_config_from_dir from flwr.common.typing import Run @@ -49,10 +49,21 @@ def register_context( run_id: int, run: Optional[Run] = None, flwr_dir: Optional[Path] = None, + fab_dir: Optional[str] = None, ) -> None: """Register new run context for this node.""" if run_id not in self.run_infos: - initial_run_config = get_fused_config(run, flwr_dir) if run else {} + initial_run_config = {} + if fab_dir: + # Load from FAB-like directory + fab_path = Path(fab_dir) + if fab_path.is_dir(): + initial_run_config = get_fused_config_from_dir( + fab_path, run.override_config + ) + else: + # Load from .fab + initial_run_config = get_fused_config(run, flwr_dir) if run else {} self.run_infos[run_id] = RunInfo( initial_run_config=initial_run_config, context=Context( From e2d23478a110878416588ee919819c6067795f10 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 11:50:52 +0200 Subject: [PATCH 09/27] fix --- src/py/flwr/client/node_state.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index 4619de3b9a2f..643a697a6c80 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -58,8 +58,9 @@ def register_context( # Load from FAB-like directory fab_path = Path(fab_dir) if fab_path.is_dir(): + override_config = run.override_config if run else {} initial_run_config = get_fused_config_from_dir( - fab_path, run.override_config + fab_path, override_config ) else: # Load from .fab From 122cb3123ec33ad9c416216063b360eab297d8e5 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 16 Jul 2024 12:05:09 +0100 Subject: [PATCH 10/27] Apply suggestions from code review Co-authored-by: Daniel J. Beutel --- src/py/flwr/client/node_state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index 643a697a6c80..a26d3fcf6f54 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -48,8 +48,8 @@ def register_context( self, run_id: int, run: Optional[Run] = None, - flwr_dir: Optional[Path] = None, - fab_dir: Optional[str] = None, + flwr_path: Optional[Path] = None, + app_dir: Optional[str] = None, ) -> None: """Register new run context for this node.""" if run_id not in self.run_infos: From 307c29e1de1164e74252e1556de35d29d81b329f Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 13:09:03 +0200 Subject: [PATCH 11/27] updates --- src/py/flwr/client/app.py | 6 +++--- src/py/flwr/client/node_state.py | 10 +++++----- src/py/flwr/client/supernode/app.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 348ef8910dd3..127bb423851f 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -195,7 +195,7 @@ def _start_client_internal( ] = None, max_retries: Optional[int] = None, max_wait_time: Optional[float] = None, - flwr_dir: Optional[Path] = None, + flwr_path: Optional[Path] = None, ) -> None: """Start a Flower client node which connects to a Flower server. @@ -241,7 +241,7 @@ class `flwr.client.Client` (default: None) The maximum duration before the client stops trying to connect to the server in case of connection error. If set to None, there is no limit to the total time. - flwr_dir: Optional[Path] (default: None) + flwr_path: Optional[Path] (default: None) The fully resolved path containing installed Flower Apps. """ if insecure is None: @@ -402,7 +402,7 @@ def _on_backoff(retry_state: RetryState) -> None: # Register context for this run node_state.register_context( - run_id=run_id, run=runs[run_id], flwr_dir=flwr_dir + run_id=run_id, run=runs[run_id], flwr_path=flwr_path ) # Retrieve context for this run diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index a26d3fcf6f54..1c5ce7b6ba55 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -54,17 +54,17 @@ def register_context( """Register new run context for this node.""" if run_id not in self.run_infos: initial_run_config = {} - if fab_dir: + if app_dir: # Load from FAB-like directory - fab_path = Path(fab_dir) - if fab_path.is_dir(): + app_path = Path(app_dir) + if app_path.is_dir(): override_config = run.override_config if run else {} initial_run_config = get_fused_config_from_dir( - fab_path, override_config + app_path, override_config ) else: # Load from .fab - initial_run_config = get_fused_config(run, flwr_dir) if run else {} + initial_run_config = get_fused_config(run, flwr_path) if run else {} self.run_infos[run_id] = RunInfo( initial_run_config=initial_run_config, context=Context( diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 027c3376b7f3..a364318c766c 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -78,7 +78,7 @@ def run_supernode() -> None: max_retries=args.max_retries, max_wait_time=args.max_wait_time, node_config=parse_config_args(args.node_config), - flwr_dir=get_flwr_dir(args.flwr_dir), + flwr_path=get_flwr_dir(args.flwr_dir), ) # Graceful shutdown From 3ec626d32853706094ab1d00c82e492b74476b43 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 13:19:35 +0200 Subject: [PATCH 12/27] updates --- src/py/flwr/client/node_state.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index 1c5ce7b6ba55..08c19967ea3d 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -55,13 +55,15 @@ def register_context( if run_id not in self.run_infos: initial_run_config = {} if app_dir: - # Load from FAB-like directory + # Load from app directory app_path = Path(app_dir) if app_path.is_dir(): override_config = run.override_config if run else {} initial_run_config = get_fused_config_from_dir( app_path, override_config ) + else: + raise ValueError("The specified `app_dir` must be a directory.") else: # Load from .fab initial_run_config = get_fused_config(run, flwr_path) if run else {} From 909d410b752d0aee27482aed9355d6b3c12da776 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 13:56:05 +0200 Subject: [PATCH 13/27] clientapp gets runconfig from `pyproject.toml` --- .../server/superlink/fleet/vce/vce_api.py | 13 +++++-- .../superlink/fleet/vce/vce_api_test.py | 1 + src/py/flwr/simulation/run_simulation.py | 39 +++++++++++++------ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index b652207961a1..320f839e9e01 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -61,7 +61,9 @@ def _register_nodes( def _register_node_states( - nodes_mapping: NodeToPartitionMapping, run: Run + nodes_mapping: NodeToPartitionMapping, + run: Run, + app_dir: Optional[str] = None, ) -> Dict[int, NodeState]: """Create NodeState objects and pre-register the context for the run.""" node_states: Dict[int, NodeState] = {} @@ -76,7 +78,9 @@ def _register_node_states( ) # Pre-register Context objects - node_states[node_id].register_context(run_id=run.run_id, run=run) + node_states[node_id].register_context( + run_id=run.run_id, run=run, app_dir=app_dir + ) return node_states @@ -256,6 +260,7 @@ def start_vce( backend_name: str, backend_config_json_stream: str, app_dir: str, + is_app: bool, f_stop: threading.Event, run: Run, flwr_dir: Optional[str] = None, @@ -309,7 +314,9 @@ def start_vce( ) # Construct mapping of NodeStates - node_states = _register_node_states(nodes_mapping=nodes_mapping, run=run) + node_states = _register_node_states( + nodes_mapping=nodes_mapping, run=run, app_dir=app_dir if is_app else None + ) # Load backend config log(DEBUG, "Supported backends: %s", list(supported_backends.keys())) diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py index 4dfc08560523..33c359af5cc8 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py @@ -174,6 +174,7 @@ def start_and_shutdown( backend_config_json_stream=backend_config, state_factory=state_factory, app_dir=app_dir, + is_app=False, f_stop=f_stop, run=run, existing_nodes_mapping=nodes_mapping, diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 7321d751a046..d089b4a7d173 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -54,6 +54,11 @@ def _check_args_do_not_interfere(args: Namespace) -> bool: def _resolve_message(conflict_keys: List[str]) -> str: return ",".join([f"`--{key}`".replace("_", "-") for key in conflict_keys]) + # When passing `--app`, `--app-dir` is ignored + if args.app and args.app_dir: + log(ERROR, "Either `--app` or `--app-dir` can be set, but not both.") + return False + if any(getattr(args, key) for key in mode_one_args): if any(getattr(args, key) for key in mode_two_args): log( @@ -83,7 +88,7 @@ def _resolve_message(conflict_keys: List[str]) -> str: # Entry point from CLI -# pytlint: disable=too-many-locals +# pylint: disable=too-many-locals def run_simulation_from_cli() -> None: """Run Simulation Engine from the CLI.""" args = _parse_args_run_simulation().parse_args() @@ -97,10 +102,6 @@ def run_simulation_from_cli() -> None: if not args_check_pass: sys.exit("Simulation Engine cannot start.") - client_app_attr = None - server_app_attr = None - fab_version = "" - fab_id = "" run_id = ( generate_rand_int_from_bytes(RUN_ID_NUM_BYTES) if args.run_id is None @@ -108,10 +109,10 @@ def run_simulation_from_cli() -> None: ) if args.app: # mode 1 - app = Path(args.app) - if app.is_dir(): + app_path = Path(args.app) + if app_path.is_dir(): # Load pyproject.toml - config, errors, warnings = load_and_validate(app / "pyproject.toml") + config, errors, warnings = load_and_validate(app_path / "pyproject.toml") if errors: raise ValueError(errors) @@ -138,7 +139,9 @@ def run_simulation_from_cli() -> None: sys.exit("Simulation Engine cannot start.") override_config = parse_config_args(args.config_override) - fused_config = get_fused_config_from_dir(app, override_config) + fused_config = get_fused_config_from_dir(app_path, override_config) + app_dir = args.app + is_app = True else: # mode 2 @@ -147,12 +150,14 @@ def run_simulation_from_cli() -> None: num_supernodes = args.num_supernodes override_config = {} fused_config = None + app_dir = args.app_dir + is_app = False # Create run run = Run( run_id=run_id, - fab_id=fab_id, - fab_version=fab_version, + fab_id="", + fab_version="", override_config=override_config, ) @@ -165,11 +170,12 @@ def run_simulation_from_cli() -> None: num_supernodes=num_supernodes, backend_name=args.backend, backend_config=backend_config_dict, - app_dir=args.app_dir, + app_dir=app_dir, run=run, enable_tf_gpu_growth=args.enable_tf_gpu_growth, verbose_logging=args.verbose, server_app_run_config=fused_config, + is_app=is_app, ) @@ -312,6 +318,7 @@ def _main_loop( backend_name: str, backend_config_stream: str, app_dir: str, + is_app: bool, enable_tf_gpu_growth: bool, run: Run, flwr_dir: Optional[str] = None, @@ -361,6 +368,7 @@ def _main_loop( backend_name=backend_name, backend_config_json_stream=backend_config_stream, app_dir=app_dir, + is_app=is_app, state_factory=state_factory, f_stop=f_stop, run=run, @@ -400,6 +408,7 @@ def _run_simulation( run: Optional[Run] = None, enable_tf_gpu_growth: bool = False, verbose_logging: bool = False, + is_app: bool = False, ) -> None: r"""Launch the Simulation Engine. @@ -461,6 +470,11 @@ def _run_simulation( verbose_logging : bool (default: False) When disabled, only INFO, WARNING and ERROR log messages will be shown. If enabled, DEBUG-level logs will be displayed. + + is_app : bool (default: False) + A flag that indicates whether the simulation is running an app or not. This is + needed in order to attempt loading an app's pyproject.toml when nodes register + a context object. """ if backend_config is None: backend_config = {} @@ -496,6 +510,7 @@ def _run_simulation( backend_name, backend_config_stream, app_dir, + is_app, enable_tf_gpu_growth, run, flwr_dir, From f4a10edadec8a59a6fecb5bdbad80d29e04040e8 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 15:59:13 +0200 Subject: [PATCH 14/27] updates from review --- src/py/flwr/simulation/run_simulation.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index d089b4a7d173..dd46c0e18085 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -48,8 +48,8 @@ def _check_args_do_not_interfere(args: Namespace) -> bool: """Ensure decoupling of flags for different ways to start the simulation.""" - mode_one_args = ["app", "config_override"] - mode_two_args = ["num_supernodes", "client_app", "server_app"] + mode_one_args = ["app", "run_config"] + mode_two_args = ["client_app", "server_app"] def _resolve_message(conflict_keys: List[str]) -> str: return ",".join([f"`--{key}`".replace("_", "-") for key in conflict_keys]) @@ -129,16 +129,11 @@ def run_simulation_from_cli() -> None: client_app_attr = flower_components["clientapp"] server_app_attr = flower_components["serverapp"] - # Read number of supernodes to simulate - fed_config = config["tool"]["flwr"]["federations"] - num_supernodes = fed_config[fed_config["default"]]["options"][ - "num-supernodes" - ] else: log(ERROR, "--app is not a directory") sys.exit("Simulation Engine cannot start.") - override_config = parse_config_args(args.config_override) + override_config = parse_config_args(args.run_config) fused_config = get_fused_config_from_dir(app_path, override_config) app_dir = args.app is_app = True @@ -147,7 +142,6 @@ def run_simulation_from_cli() -> None: # mode 2 client_app_attr = args.client_app server_app_attr = args.server_app - num_supernodes = args.num_supernodes override_config = {} fused_config = None app_dir = args.app_dir @@ -167,7 +161,7 @@ def run_simulation_from_cli() -> None: _run_simulation( server_app_attr=server_app_attr, client_app_attr=client_app_attr, - num_supernodes=num_supernodes, + num_supernodes=args.num_supernodes, backend_name=args.backend, backend_config=backend_config_dict, app_dir=app_dir, @@ -559,6 +553,7 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: parser.add_argument( "--num-supernodes", type=int, + required=True, help="Number of simulated SuperNodes.", ) parser.add_argument( @@ -569,7 +564,7 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: "pyproject.toml.", ) parser.add_argument( - "--config-override", + "--run-config", default=None, help="Override configuration key-value pairs.", ) From 1b09bb7533d3e6c7e8e31a4d23577b28fb820d2d Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 16:37:55 +0200 Subject: [PATCH 15/27] init --- src/py/flwr/cli/run/run.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 512da83d13fe..866c4082ef1b 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -14,6 +14,7 @@ # ============================================================================== """Flower command line interface `run` command.""" +import subprocess import sys from logging import DEBUG from pathlib import Path @@ -29,7 +30,6 @@ from flwr.common.logger import log from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611 from flwr.proto.exec_pb2_grpc import ExecStub -from flwr.simulation.run_simulation import _run_simulation # pylint: disable-next=too-many-locals @@ -107,7 +107,7 @@ def run( if "address" in federation: _run_with_superexec(federation, directory, config_overrides) else: - _run_without_superexec(config, federation, federation_name) + _run_without_superexec(directory, federation, federation_name) def _run_with_superexec( @@ -169,10 +169,8 @@ def on_channel_state_change(channel_connectivity: str) -> None: def _run_without_superexec( - config: Dict[str, Any], federation: Dict[str, Any], federation_name: str + directory: Optional[Path], federation: Dict[str, Any], federation_name: str ) -> None: - server_app_ref = config["tool"]["flwr"]["components"]["serverapp"] - client_app_ref = config["tool"]["flwr"]["components"]["clientapp"] try: num_supernodes = federation["options"]["num-supernodes"] @@ -188,8 +186,18 @@ def _run_without_superexec( ) raise typer.Exit(code=1) from err - _run_simulation( - server_app_attr=server_app_ref, - client_app_attr=client_app_ref, - num_supernodes=num_supernodes, + command = [ + "flower-simulation", + "--app", + f"{directory}", + "--num-supernodes", + f"{num_supernodes}", + ] + proc = subprocess.Popen( # pylint: disable=consider-using-with + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, ) + + # TODO: how to show logs? From 9bf5fa3f7ac1c9b5b93ba2f41a4bcf36c4936987 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 16 Jul 2024 17:16:12 +0100 Subject: [PATCH 16/27] Update src/py/flwr/cli/run/run.py Co-authored-by: Daniel J. Beutel --- src/py/flwr/cli/run/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 866c4082ef1b..9a5b236dd968 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -169,7 +169,7 @@ def on_channel_state_change(channel_connectivity: str) -> None: def _run_without_superexec( - directory: Optional[Path], federation: Dict[str, Any], federation_name: str + app_path: Optional[Path], federation: Dict[str, Any], federation_name: str ) -> None: try: From 1fbf1e0c5d3b7946d68ad0e0a3bb3000ed307d76 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 18:16:58 +0200 Subject: [PATCH 17/27] w/ previous --- src/py/flwr/cli/run/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 9a5b236dd968..3392dbc317ba 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -189,7 +189,7 @@ def _run_without_superexec( command = [ "flower-simulation", "--app", - f"{directory}", + f"{app_path}", "--num-supernodes", f"{num_supernodes}", ] From 571b9ee59a65e06d77c862dba30e42aacd28c546 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Tue, 16 Jul 2024 21:17:52 +0200 Subject: [PATCH 18/27] Update src/py/flwr/simulation/run_simulation.py --- src/py/flwr/simulation/run_simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index dd46c0e18085..2923e5fd5c1f 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -96,7 +96,7 @@ def run_simulation_from_cli() -> None: # We are supporting two modes for the CLI entrypoint: # 1) Running a FAB or FAB-like dir containing a pyproject.toml # 2) Running any ClientApp and SeverApp w/o pyproject.toml being present - # For 2) some CLI args are cumpolsory but these aren't for 1) + # For 2), some CLI args are compulsory, but they are not required for 1) # We first do these checks args_check_pass = _check_args_do_not_interfere(args) if not args_check_pass: From 109f521cbcc1ea755c85bea3386d65dafe248678 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Tue, 16 Jul 2024 21:18:16 +0200 Subject: [PATCH 19/27] Update src/py/flwr/simulation/run_simulation.py --- src/py/flwr/simulation/run_simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 2923e5fd5c1f..92486151cc1a 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -94,7 +94,7 @@ def run_simulation_from_cli() -> None: args = _parse_args_run_simulation().parse_args() # We are supporting two modes for the CLI entrypoint: - # 1) Running a FAB or FAB-like dir containing a pyproject.toml + # 1) Running an app dir containing a `pyproject.toml` # 2) Running any ClientApp and SeverApp w/o pyproject.toml being present # For 2), some CLI args are compulsory, but they are not required for 1) # We first do these checks From 211d83818225f72af3dca94e6b62943a62a44748 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Tue, 16 Jul 2024 21:19:10 +0200 Subject: [PATCH 20/27] Update src/py/flwr/simulation/run_simulation.py --- src/py/flwr/simulation/run_simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 92486151cc1a..b26629e526d8 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -108,7 +108,7 @@ def run_simulation_from_cli() -> None: else args.run_id ) if args.app: - # mode 1 + # Mode 1 app_path = Path(args.app) if app_path.is_dir(): # Load pyproject.toml From acc533c32eb864d5fc89314172695caf2f83a4b4 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Tue, 16 Jul 2024 21:19:23 +0200 Subject: [PATCH 21/27] Update src/py/flwr/simulation/run_simulation.py --- src/py/flwr/simulation/run_simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index b26629e526d8..b65ef47392e8 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -139,7 +139,7 @@ def run_simulation_from_cli() -> None: is_app = True else: - # mode 2 + # Mode 2 client_app_attr = args.client_app server_app_attr = args.server_app override_config = {} From 5cfe3849f824119ed1a536dfae847b3966558d0f Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 21:28:19 +0200 Subject: [PATCH 22/27] revert `run_simulation.py` changes --- src/py/flwr/simulation/run_simulation.py | 39 ++++++++++++------------ 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index b65ef47392e8..8a3c7739e595 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -110,29 +110,28 @@ def run_simulation_from_cli() -> None: if args.app: # Mode 1 app_path = Path(args.app) - if app_path.is_dir(): - # Load pyproject.toml - config, errors, warnings = load_and_validate(app_path / "pyproject.toml") - if errors: - raise ValueError(errors) - - if warnings: - log(WARNING, warnings) - - if config is None: - raise ValueError( - "Config extracted from FAB's pyproject.toml is not valid" - ) - - # Get ClientApp and SeverApp components - flower_components = config["tool"]["flwr"]["app"]["components"] - client_app_attr = flower_components["clientapp"] - server_app_attr = flower_components["serverapp"] - - else: + if not app_path.is_dir(): log(ERROR, "--app is not a directory") sys.exit("Simulation Engine cannot start.") + # Load pyproject.toml + config, errors, warnings = load_and_validate( + app_path / "pyproject.toml", check_module=False + ) + if errors: + raise ValueError(errors) + + if warnings: + log(WARNING, warnings) + + if config is None: + raise ValueError("Config extracted from FAB's pyproject.toml is not valid") + + # Get ClientApp and SeverApp components + app_components = config["tool"]["flwr"]["app"]["components"] + client_app_attr = app_components["clientapp"] + server_app_attr = app_components["serverapp"] + override_config = parse_config_args(args.run_config) fused_config = get_fused_config_from_dir(app_path, override_config) app_dir = args.app From cb6924bf7222b66dc6340e28cc50af2478479001 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 22:05:47 +0200 Subject: [PATCH 23/27] basic run process --- src/py/flwr/cli/run/run.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index a153c86df699..75678388835b 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -192,11 +192,6 @@ def _run_without_superexec( "--num-supernodes", f"{num_supernodes}", ] - proc = subprocess.Popen( # pylint: disable=consider-using-with - command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - # TODO: how to show logs? + # Run the simulation + subprocess.run(command) From 587d1b768232e79a5be55e81b2dc4ca9f2f63bea Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 22:11:13 +0200 Subject: [PATCH 24/27] check --- src/py/flwr/cli/run/run.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 75678388835b..b777584f2691 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -194,4 +194,7 @@ def _run_without_superexec( ] # Run the simulation - subprocess.run(command) + subprocess.run( + command, + check=True, + ) From ff71787a498bbe2e8459fb9065418751db7bc7b2 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 22:20:34 +0200 Subject: [PATCH 25/27] pass config-override to `flower-simulation --- src/py/flwr/cli/run/run.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index b777584f2691..a18b699db6a1 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -107,7 +107,7 @@ def run( if "address" in federation: _run_with_superexec(federation, directory, config_overrides) else: - _run_without_superexec(directory, federation, federation_name) + _run_without_superexec(directory, federation, federation_name, config_overrides) def _run_with_superexec( @@ -169,7 +169,10 @@ def on_channel_state_change(channel_connectivity: str) -> None: def _run_without_superexec( - app_path: Optional[Path], federation: Dict[str, Any], federation_name: str + app_path: Optional[Path], + federation: Dict[str, Any], + federation_name: str, + config_overrides: Optional[str], ) -> None: try: num_supernodes = federation["options"]["num-supernodes"] @@ -191,6 +194,8 @@ def _run_without_superexec( f"{app_path}", "--num-supernodes", f"{num_supernodes}", + "--run-config", + f"{config_overrides}", ] # Run the simulation From 67d86a28b543a5c233f78dd2b1c7d3585e17392e Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Tue, 16 Jul 2024 22:21:47 +0200 Subject: [PATCH 26/27] Add to --- src/py/flwr/cli/run/run.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index b777584f2691..152965c38cda 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -197,4 +197,5 @@ def _run_without_superexec( subprocess.run( command, check=True, + text=True, ) From 4b3199ac68d8eaaa1dad759a6c3ddd450b90cd53 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 16 Jul 2024 22:30:15 +0200 Subject: [PATCH 27/27] correct handling configs override --- src/py/flwr/cli/run/run.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index fd15f4f4dfc3..5db575abcb87 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -194,10 +194,11 @@ def _run_without_superexec( f"{app_path}", "--num-supernodes", f"{num_supernodes}", - "--run-config", - f"{config_overrides}", ] + if config_overrides: + command.extend(["--run-config", f"{config_overrides}"]) + # Run the simulation subprocess.run( command,