From e135f806a120c0cd1dfeab81f9003ca1392a40f9 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Thu, 11 Jul 2024 20:36:26 +0200 Subject: [PATCH 01/12] init --- src/py/flwr/client/app.py | 8 +++++--- .../flwr/client/grpc_adapter_client/connection.py | 2 +- src/py/flwr/client/grpc_rere_client/connection.py | 5 +++-- .../client/message_handler/message_handler_test.py | 4 ++-- .../mod/secure_aggregation/secaggplus_mod_test.py | 7 ++++++- src/py/flwr/client/mod/utils_test.py | 4 ++-- src/py/flwr/client/node_state.py | 11 ++++++++--- src/py/flwr/client/node_state_tests.py | 2 +- src/py/flwr/common/context.py | 13 ++++++++++++- src/py/flwr/server/compat/legacy_context.py | 2 +- src/py/flwr/server/run_serverapp.py | 4 +++- src/py/flwr/server/server_app.py | 2 +- src/py/flwr/server/server_app_test.py | 2 +- .../superlink/fleet/vce/backend/raybackend_test.py | 2 +- src/py/flwr/server/superlink/fleet/vce/vce_api.py | 4 +++- .../simulation/ray_transport/ray_client_proxy.py | 4 +++- .../ray_transport/ray_client_proxy_test.py | 8 +++++++- 17 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 851083d4abb..d25fe940353 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -319,7 +319,7 @@ def _on_backoff(retry_state: RetryState) -> None: on_backoff=_on_backoff, ) - node_state = NodeState(partition_id=partition_id) + node_state = NodeState(node_id=-1, node_config={}, partition_id=partition_id) runs: Dict[int, Run] = {} while not app_state_tracker.interrupt: @@ -336,7 +336,9 @@ def _on_backoff(retry_state: RetryState) -> None: # Register node if create_node is not None: - create_node() # pylint: disable=not-callable + node_id = create_node() # pylint: disable=not-callable + if transport in ["grpc-rere", None]: + node_state.node_id = node_id # type: ignore app_state_tracker.register_signal_handler() while not app_state_tracker.interrupt: @@ -580,7 +582,7 @@ def _init_connection(transport: Optional[str], server_address: str) -> Tuple[ Tuple[ Callable[[], Optional[Message]], Callable[[Message], None], - Optional[Callable[[], None]], + Optional[Callable[[], Optional[int]]], Optional[Callable[[], None]], Optional[Callable[[int], Run]], ] diff --git a/src/py/flwr/client/grpc_adapter_client/connection.py b/src/py/flwr/client/grpc_adapter_client/connection.py index 971b630e470..d4071f3b179 100644 --- a/src/py/flwr/client/grpc_adapter_client/connection.py +++ b/src/py/flwr/client/grpc_adapter_client/connection.py @@ -44,7 +44,7 @@ def grpc_adapter( # pylint: disable=R0913 Tuple[ Callable[[], Optional[Message]], Callable[[Message], None], - Optional[Callable[[], None]], + Optional[Callable[[], int]], Optional[Callable[[], None]], Optional[Callable[[int], Run]], ] diff --git a/src/py/flwr/client/grpc_rere_client/connection.py b/src/py/flwr/client/grpc_rere_client/connection.py index 8062ce28fcc..c4051e6c5c1 100644 --- a/src/py/flwr/client/grpc_rere_client/connection.py +++ b/src/py/flwr/client/grpc_rere_client/connection.py @@ -79,7 +79,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915 Tuple[ Callable[[], Optional[Message]], Callable[[Message], None], - Optional[Callable[[], None]], + Optional[Callable[[], int]], Optional[Callable[[], None]], Optional[Callable[[int], Run]], ] @@ -176,7 +176,7 @@ def ping() -> None: if not ping_stop_event.is_set(): ping_stop_event.wait(next_interval) - def create_node() -> None: + def create_node() -> int: """Set create_node.""" # Call FleetAPI create_node_request = CreateNodeRequest(ping_interval=PING_DEFAULT_INTERVAL) @@ -189,6 +189,7 @@ def create_node() -> None: nonlocal node, ping_thread node = cast(Node, create_node_response.node) ping_thread = start_ping_loop(ping, ping_stop_event) + return node.node_id def delete_node() -> None: """Set delete_node.""" diff --git a/src/py/flwr/client/message_handler/message_handler_test.py b/src/py/flwr/client/message_handler/message_handler_test.py index 9ce4c9620c4..96de7ce0c2c 100644 --- a/src/py/flwr/client/message_handler/message_handler_test.py +++ b/src/py/flwr/client/message_handler/message_handler_test.py @@ -145,7 +145,7 @@ def test_client_without_get_properties() -> None: actual_msg = handle_legacy_message_from_msgtype( client_fn=_get_client_fn(client), message=message, - context=Context(state=RecordSet(), run_config={}), + context=Context(node_id=1123, node_config={}, state=RecordSet(), run_config={}), ) # Assert @@ -209,7 +209,7 @@ def test_client_with_get_properties() -> None: actual_msg = handle_legacy_message_from_msgtype( client_fn=_get_client_fn(client), message=message, - context=Context(state=RecordSet(), run_config={}), + context=Context(node_id=1123, node_config={}, state=RecordSet(), run_config={}), ) # Assert diff --git a/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py b/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py index 5e4c4411e1f..d83b2cccf1f 100644 --- a/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py +++ b/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py @@ -73,7 +73,12 @@ def func(configs: Dict[str, ConfigsRecordValues]) -> ConfigsRecord: def _make_ctxt() -> Context: cfg = ConfigsRecord(SecAggPlusState().to_dict()) - return Context(RecordSet(configs_records={RECORD_KEY_STATE: cfg}), run_config={}) + return Context( + node_id=0, + node_config={}, + state=RecordSet(configs_records={RECORD_KEY_STATE: cfg}), + run_config={}, + ) def _make_set_state_fn( diff --git a/src/py/flwr/client/mod/utils_test.py b/src/py/flwr/client/mod/utils_test.py index 7a1dd898839..a5bbd0a0bb4 100644 --- a/src/py/flwr/client/mod/utils_test.py +++ b/src/py/flwr/client/mod/utils_test.py @@ -104,7 +104,7 @@ def test_multiple_mods(self) -> None: state = RecordSet() state.metrics_records[METRIC] = MetricsRecord({COUNTER: 0.0}) - context = Context(state=state, run_config={}) + context = Context(node_id=0, node_config={}, state=state, run_config={}) message = _get_dummy_flower_message() # Execute @@ -129,7 +129,7 @@ def test_filter(self) -> None: # Prepare footprint: List[str] = [] mock_app = make_mock_app("app", footprint) - context = Context(state=RecordSet(), run_config={}) + context = Context(node_id=0, node_config={}, state=RecordSet(), run_config={}) message = _get_dummy_flower_message() def filter_mod( diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index 2b090eba972..d0a349b0cae 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -17,7 +17,7 @@ from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict, Optional +from typing import Dict, Optional from flwr.common import Context, RecordSet from flwr.common.config import get_fused_config @@ -35,8 +35,11 @@ class RunInfo: class NodeState: """State of a node where client nodes execute runs.""" - def __init__(self, partition_id: Optional[int]) -> None: - self._meta: Dict[str, Any] = {} # holds metadata about the node + def __init__( + self, node_id: int, node_config: Dict[str, str], partition_id: Optional[int] + ) -> None: + self.node_id = node_id + self.node_config = node_config self.run_infos: Dict[int, RunInfo] = {} self._partition_id = partition_id @@ -52,6 +55,8 @@ def register_context( self.run_infos[run_id] = RunInfo( initial_run_config=initial_run_config, context=Context( + node_id=self.node_id, + node_config=self.node_config, state=RecordSet(), run_config=initial_run_config.copy(), partition_id=self._partition_id, diff --git a/src/py/flwr/client/node_state_tests.py b/src/py/flwr/client/node_state_tests.py index effd64a3ae7..8d7971fa528 100644 --- a/src/py/flwr/client/node_state_tests.py +++ b/src/py/flwr/client/node_state_tests.py @@ -41,7 +41,7 @@ def test_multirun_in_node_state() -> None: expected_values = {0: "1", 1: "1" * 3, 2: "1" * 2, 3: "1", 5: "1"} # NodeState - node_state = NodeState(partition_id=None) + node_state = NodeState(node_id=0, node_config={}, partition_id=None) for task in tasks: run_id = task.run_id diff --git a/src/py/flwr/common/context.py b/src/py/flwr/common/context.py index 8120723ce9e..35802c9c70a 100644 --- a/src/py/flwr/common/context.py +++ b/src/py/flwr/common/context.py @@ -27,6 +27,11 @@ class Context: Parameters ---------- + node_id : int + A UUID that identifies the node. + node_config : Dict[str, str] + A config (key/value mapping) unique to the node and independent of the + `run_config`. This config persist across runs this node participates in. state : RecordSet Holds records added by the entity in a given run and that will stay local. This means that the data it holds will never leave the system it's running from. @@ -44,16 +49,22 @@ class Context: simulation or proto typing setups. """ + node_id: int + node_config: Dict[str, str] state: RecordSet - partition_id: Optional[int] run_config: Dict[str, str] + partition_id: Optional[int] def __init__( self, + node_id: int, + node_config: Dict[str, str], state: RecordSet, run_config: Dict[str, str], partition_id: Optional[int] = None, ) -> None: + self.node_id = node_id + self.node_config = node_config self.state = state self.run_config = run_config self.partition_id = partition_id diff --git a/src/py/flwr/server/compat/legacy_context.py b/src/py/flwr/server/compat/legacy_context.py index 9e120c82410..ee09d79012d 100644 --- a/src/py/flwr/server/compat/legacy_context.py +++ b/src/py/flwr/server/compat/legacy_context.py @@ -52,4 +52,4 @@ def __init__( self.strategy = strategy self.client_manager = client_manager self.history = History() - super().__init__(state, run_config={}) + super().__init__(node_id=0, node_config={}, state=state, run_config={}) diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index b4697e99913..4cc25feb7e0 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -78,7 +78,9 @@ def _load() -> ServerApp: server_app = _load() # Initialize Context - context = Context(state=RecordSet(), run_config=server_app_run_config) + context = Context( + node_id=0, node_config={}, state=RecordSet(), run_config=server_app_run_config + ) # Call ServerApp server_app(driver=driver, context=context) diff --git a/src/py/flwr/server/server_app.py b/src/py/flwr/server/server_app.py index f19a9d91986..7020b99ce53 100644 --- a/src/py/flwr/server/server_app.py +++ b/src/py/flwr/server/server_app.py @@ -80,7 +80,7 @@ def __call__(self, driver: Driver, context: Context) -> None: return # New execution mode - context = Context(state=RecordSet(), run_config={}) + context = Context(node_id=0, node_config={}, state=RecordSet(), run_config={}) self._main(driver, context) def main(self) -> Callable[[ServerAppCallable], ServerAppCallable]: diff --git a/src/py/flwr/server/server_app_test.py b/src/py/flwr/server/server_app_test.py index 7de8774d4c8..b0672b3202e 100644 --- a/src/py/flwr/server/server_app_test.py +++ b/src/py/flwr/server/server_app_test.py @@ -29,7 +29,7 @@ def test_server_app_custom_mode() -> None: # Prepare app = ServerApp() driver = MagicMock() - context = Context(state=RecordSet(), run_config={}) + context = Context(node_id=0, node_config={}, state=RecordSet(), run_config={}) called = {"called": False} diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py index 287983003f8..da4390194d0 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py @@ -120,7 +120,7 @@ def _create_message_and_context() -> Tuple[Message, Context, float]: ) # Construct emtpy Context - context = Context(state=RecordSet(), run_config={}) + context = Context(node_id=0, node_config={}, state=RecordSet(), run_config={}) # Expected output expected_output = pi * mult_factor 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 3c0b36e1ca3..134fd34ed8f 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -284,7 +284,9 @@ def start_vce( # Construct mapping of NodeStates node_states: Dict[int, NodeState] = {} for node_id, partition_id in nodes_mapping.items(): - node_states[node_id] = NodeState(partition_id=partition_id) + node_states[node_id] = NodeState( + node_id=node_id, node_config={}, partition_id=partition_id + ) # Load backend config log(DEBUG, "Supported backends: %s", list(supported_backends.keys())) diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py index 31bc22c84bd..f2684016048 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py @@ -59,7 +59,9 @@ def _load_app() -> ClientApp: self.app_fn = _load_app self.actor_pool = actor_pool - self.proxy_state = NodeState(partition_id=self.partition_id) + self.proxy_state = NodeState( + node_id=node_id, node_config={}, partition_id=self.partition_id + ) def _submit_job(self, message: Message, timeout: Optional[float]) -> Message: """Sumbit a message to the ActorPool.""" diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py index 83f6cfe0531..8831e5f475e 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py @@ -218,7 +218,13 @@ def _load_app() -> ClientApp: _load_app, message, str(node_id), - Context(state=RecordSet(), run_config={}, partition_id=node_id), + Context( + node_id=0, + node_config={}, + state=RecordSet(), + run_config={}, + partition_id=node_id, + ), ), ) From 7cc0fdc49f2eef98fdafba6440e0913f23a61251 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Fri, 12 Jul 2024 09:44:33 +0200 Subject: [PATCH 02/12] feat(framework) Add node-config argument to flower-supernode --- src/py/flwr/client/app.py | 6 +++--- src/py/flwr/client/supernode/app.py | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index d25fe940353..40e1de6fa40 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -42,7 +42,7 @@ from flwr.common.logger import log, warn_deprecated_feature from flwr.common.message import Error from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential -from flwr.common.typing import Run +from flwr.common.typing import Run, Value from .grpc_adapter_client.connection import grpc_adapter from .grpc_client.connection import grpc_connection @@ -181,6 +181,7 @@ class `flwr.client.Client` (default: None) def _start_client_internal( *, server_address: str, + node_config: Dict[str, str], load_client_app_fn: Optional[Callable[[str, str], ClientApp]] = None, client_fn: Optional[ClientFnExt] = None, client: Optional[Client] = None, @@ -193,7 +194,6 @@ def _start_client_internal( ] = None, max_retries: Optional[int] = None, max_wait_time: Optional[float] = None, - partition_id: Optional[int] = None, flwr_dir: Optional[Path] = None, ) -> None: """Start a Flower client node which connects to a Flower server. @@ -319,7 +319,7 @@ def _on_backoff(retry_state: RetryState) -> None: on_backoff=_on_backoff, ) - node_state = NodeState(node_id=-1, node_config={}, partition_id=partition_id) + node_state = NodeState(node_id=-1, node_config=node_config, partition_id=-1) runs: Dict[int, Run] = {} while not app_state_tracker.interrupt: diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 355a2a13a0e..d18a6b39e7d 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -29,7 +29,12 @@ from flwr.client.client_app import ClientApp, LoadClientAppError from flwr.common import EventType, event -from flwr.common.config import get_flwr_dir, get_project_config, get_project_dir +from flwr.common.config import ( + get_flwr_dir, + get_project_config, + get_project_dir, + parse_config_args, +) from flwr.common.constant import ( TRANSPORT_TYPE_GRPC_ADAPTER, TRANSPORT_TYPE_GRPC_RERE, @@ -67,7 +72,7 @@ def run_supernode() -> None: authentication_keys=authentication_keys, max_retries=args.max_retries, max_wait_time=args.max_wait_time, - partition_id=args.partition_id, + node_config=parse_config_args(args.node_config), flwr_dir=get_flwr_dir(args.flwr_dir), ) @@ -93,6 +98,7 @@ def run_client_app() -> None: _start_client_internal( server_address=args.superlink, + node_config=parse_config_args(args.node_config), load_client_app_fn=load_fn, transport=args.transport, root_certificates=root_certificates, @@ -389,11 +395,11 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: help="The SuperNode's public key (as a path str) to enable authentication.", ) parser.add_argument( - "--partition-id", + "--node-config", type=int, - help="The data partition index associated with this SuperNode. Better suited " - "for prototyping purposes where a SuperNode might only load a fraction of an " - "artificially partitioned dataset (e.g. using `flwr-datasets`)", + help="A coma separated list of key/value pairs (separated by `=`) to configure " + "the SuperNode. " + "E.g, `--node-config key1='value1',partition-id=0,num-partitions=100`", ) From 03ee5b371961c8ce09419f1c7709f5ac1796e142 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Fri, 12 Jul 2024 09:47:10 +0200 Subject: [PATCH 03/12] Remove unused import --- src/py/flwr/client/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 40e1de6fa40..be4805c5772 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -42,7 +42,7 @@ from flwr.common.logger import log, warn_deprecated_feature from flwr.common.message import Error from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential -from flwr.common.typing import Run, Value +from flwr.common.typing import Run from .grpc_adapter_client.connection import grpc_adapter from .grpc_client.connection import grpc_connection From 7f11e4ded5f3de0c645383777689c63ea71823e0 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Fri, 12 Jul 2024 09:56:29 +0200 Subject: [PATCH 04/12] fixes --- src/py/flwr/client/app.py | 4 +++- src/py/flwr/client/grpc_client/connection.py | 2 +- src/py/flwr/client/rest_client/connection.py | 2 +- src/py/flwr/common/context.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index d25fe940353..530201fc196 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -336,7 +336,9 @@ def _on_backoff(retry_state: RetryState) -> None: # Register node if create_node is not None: - node_id = create_node() # pylint: disable=not-callable + node_id = ( # pylint: disable=assignment-from-none + create_node() + ) # pylint: disable=not-callable if transport in ["grpc-rere", None]: node_state.node_id = node_id # type: ignore diff --git a/src/py/flwr/client/grpc_client/connection.py b/src/py/flwr/client/grpc_client/connection.py index 3e9f261c1ec..a6417106d51 100644 --- a/src/py/flwr/client/grpc_client/connection.py +++ b/src/py/flwr/client/grpc_client/connection.py @@ -72,7 +72,7 @@ def grpc_connection( # pylint: disable=R0913, R0915 Tuple[ Callable[[], Optional[Message]], Callable[[Message], None], - Optional[Callable[[], None]], + Optional[Callable[[], Optional[int]]], Optional[Callable[[], None]], Optional[Callable[[int], Run]], ] diff --git a/src/py/flwr/client/rest_client/connection.py b/src/py/flwr/client/rest_client/connection.py index 0efa5731ae5..4e667801f10 100644 --- a/src/py/flwr/client/rest_client/connection.py +++ b/src/py/flwr/client/rest_client/connection.py @@ -90,7 +90,7 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915 Tuple[ Callable[[], Optional[Message]], Callable[[Message], None], - Optional[Callable[[], None]], + Optional[Callable[[], Optional[int]]], Optional[Callable[[], None]], Optional[Callable[[int], Run]], ] diff --git a/src/py/flwr/common/context.py b/src/py/flwr/common/context.py index 35802c9c70a..efe3f8f0166 100644 --- a/src/py/flwr/common/context.py +++ b/src/py/flwr/common/context.py @@ -55,7 +55,7 @@ class Context: run_config: Dict[str, str] partition_id: Optional[int] - def __init__( + def __init__( # pylint: disable=too-many-arguments self, node_id: int, node_config: Dict[str, str], From f29a06a7bc4504120ed3af412f5b886cb8c9139f Mon Sep 17 00:00:00 2001 From: jafermarq Date: Fri, 12 Jul 2024 09:59:23 +0200 Subject: [PATCH 05/12] better --- .../flwr/client/mod/secure_aggregation/secaggplus_mod_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py b/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py index d83b2cccf1f..2832576fb4f 100644 --- a/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py +++ b/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py @@ -74,7 +74,7 @@ def func(configs: Dict[str, ConfigsRecordValues]) -> ConfigsRecord: def _make_ctxt() -> Context: cfg = ConfigsRecord(SecAggPlusState().to_dict()) return Context( - node_id=0, + node_id=123, node_config={}, state=RecordSet(configs_records={RECORD_KEY_STATE: cfg}), run_config={}, From 2e27e612b890f638e1fa8bc8e80d4328918af0ad Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Fri, 12 Jul 2024 10:04:48 +0200 Subject: [PATCH 06/12] Update docstring --- src/py/flwr/client/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index be4805c5772..c7d32d23d68 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -204,6 +204,9 @@ def _start_client_internal( The IPv4 or IPv6 address of the server. If the Flower server runs on the same machine on port 8080, then `server_address` would be `"[::]:8080"`. + node_config: Dict[str, str] + The configuration of the node, it must contain at least a `num-partitions` + and a `partition-id` key. load_client_app_fn : Optional[Callable[[], ClientApp]] (default: None) A function that can be used to load a `ClientApp` instance. client_fn : Optional[ClientFnExt] @@ -238,9 +241,6 @@ 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. - partition_id: Optional[int] (default: None) - The data partition index associated with this node. Better suited for - prototyping purposes. flwr_dir: Optional[Path] (default: None) The fully resolved path containing installed Flower Apps. """ From bacaf404de2293c36fd2fac35e11adf22880e967 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Fri, 12 Jul 2024 10:08:55 +0200 Subject: [PATCH 07/12] Fix error --- src/py/flwr/client/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index e4cef0642fd..20e0fbb9b22 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -160,6 +160,7 @@ class `flwr.client.Client` (default: None) event(EventType.START_CLIENT_ENTER) _start_client_internal( server_address=server_address, + node_config={}, load_client_app_fn=None, client_fn=client_fn, client=client, From 11a79d6ff0cdaceb1fbf53525fa28c4528da7221 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Fri, 12 Jul 2024 17:46:32 +0200 Subject: [PATCH 08/12] Update src/py/flwr/client/supernode/app.py --- src/py/flwr/client/supernode/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index d18a6b39e7d..6de3a8557ba 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -397,7 +397,7 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--node-config", type=int, - help="A coma separated list of key/value pairs (separated by `=`) to configure " + help="A comma separated list of key/value pairs (separated by `=`) to configure " "the SuperNode. " "E.g, `--node-config key1='value1',partition-id=0,num-partitions=100`", ) From e5ee52b89fbf994d5083d05a4d6ae493fbfae1db Mon Sep 17 00:00:00 2001 From: jafermarq Date: Fri, 12 Jul 2024 17:50:02 +0200 Subject: [PATCH 09/12] tweaks --- src/py/flwr/client/app.py | 3 +-- src/py/flwr/client/supernode/app.py | 6 +++--- src/py/flwr/common/config.py | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 998bdb44e4c..ffcc95489d6 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -206,8 +206,7 @@ def _start_client_internal( server runs on the same machine on port 8080, then `server_address` would be `"[::]:8080"`. node_config: Dict[str, str] - The configuration of the node, it must contain at least a `num-partitions` - and a `partition-id` key. + The configuration of the node. load_client_app_fn : Optional[Callable[[], ClientApp]] (default: None) A function that can be used to load a `ClientApp` instance. client_fn : Optional[ClientFnExt] diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 6de3a8557ba..e1ebb9d862c 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -397,9 +397,9 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--node-config", type=int, - help="A comma separated list of key/value pairs (separated by `=`) to configure " - "the SuperNode. " - "E.g, `--node-config key1='value1',partition-id=0,num-partitions=100`", + help="A comma separated list of key/value pairs (separated by `=`) to " + "configure the SuperNode. " + "E.g, `--node-config key1=\"value1\",partition-id=0,num-partitions=100`", ) diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 9770bdb4af2..54d74353e4e 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -121,16 +121,16 @@ def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, st def parse_config_args( - config_overrides: Optional[str], + config: Optional[str], separator: str = ",", ) -> Dict[str, str]: """Parse separator separated list of key-value pairs separated by '='.""" overrides: Dict[str, str] = {} - if config_overrides is None: + if config is None: return overrides - overrides_list = config_overrides.split(separator) + overrides_list = config.split(separator) if ( len(overrides_list) == 1 and "=" not in overrides_list From 4ac4e7ef2e232f46894bd5df66350068c30ebd71 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Fri, 12 Jul 2024 17:52:19 +0200 Subject: [PATCH 10/12] format --- src/py/flwr/client/supernode/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index e1ebb9d862c..a5514d2810f 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -399,7 +399,7 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: type=int, help="A comma separated list of key/value pairs (separated by `=`) to " "configure the SuperNode. " - "E.g, `--node-config key1=\"value1\",partition-id=0,num-partitions=100`", + 'E.g, `--node-config key1="value1",partition-id=0,num-partitions=100`', ) From 947d6f230445050ef74059536a2d6f0573e0e2ad Mon Sep 17 00:00:00 2001 From: jafermarq Date: Fri, 12 Jul 2024 18:11:26 +0200 Subject: [PATCH 11/12] whoops --- src/py/flwr/client/supernode/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index a5514d2810f..95a9d0fe574 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -396,7 +396,7 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: ) parser.add_argument( "--node-config", - type=int, + type=str, help="A comma separated list of key/value pairs (separated by `=`) to " "configure the SuperNode. " 'E.g, `--node-config key1="value1",partition-id=0,num-partitions=100`', From 83f0977fca93d471615505602407f89633695e07 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Fri, 12 Jul 2024 18:20:47 +0200 Subject: [PATCH 12/12] compliant example for `--node-config` --- src/py/flwr/client/supernode/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 95a9d0fe574..d61b986bc7a 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -399,7 +399,7 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: type=str, help="A comma separated list of key/value pairs (separated by `=`) to " "configure the SuperNode. " - 'E.g, `--node-config key1="value1",partition-id=0,num-partitions=100`', + "E.g. --node-config 'key1=\"value1\",partition-id=0,num-partitions=100'", )