flwr-nightly 1.10.0.dev20240714__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.
- flwr/cli/build.py +1 -1
- flwr/cli/config_utils.py +15 -15
- flwr/cli/install.py +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +6 -12
- flwr/cli/new/templates/app/pyproject.hf.toml.tpl +6 -9
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +8 -5
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +6 -9
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +6 -9
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +6 -9
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +6 -9
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +6 -9
- flwr/cli/run/run.py +97 -56
- flwr/client/supernode/app.py +25 -14
- flwr/common/config.py +1 -1
- flwr/server/run_serverapp.py +1 -1
- flwr/server/superlink/fleet/vce/vce_api.py +45 -28
- flwr/simulation/run_simulation.py +42 -25
- flwr/superexec/simulation.py +157 -0
- {flwr_nightly-1.10.0.dev20240714.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/METADATA +1 -1
- {flwr_nightly-1.10.0.dev20240714.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/RECORD +23 -22
- {flwr_nightly-1.10.0.dev20240714.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.10.0.dev20240714.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.10.0.dev20240714.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/entry_points.txt +0 -0
flwr/cli/build.py
CHANGED
flwr/cli/config_utils.py
CHANGED
|
@@ -60,7 +60,7 @@ def get_fab_metadata(fab_file: Union[Path, bytes]) -> Tuple[str, str]:
|
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
62
|
conf["project"]["version"],
|
|
63
|
-
f"{conf['
|
|
63
|
+
f"{conf['tool']['flwr']['publisher']}/{conf['project']['name']}",
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
|
|
@@ -136,20 +136,20 @@ def validate_fields(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]]
|
|
|
136
136
|
if "authors" not in config["project"]:
|
|
137
137
|
warnings.append('Recommended property "authors" missing in [project]')
|
|
138
138
|
|
|
139
|
-
if "
|
|
140
|
-
errors.append("Missing [
|
|
139
|
+
if "tool" not in config or "flwr" not in config["tool"]:
|
|
140
|
+
errors.append("Missing [tool.flwr] section")
|
|
141
141
|
else:
|
|
142
|
-
if "publisher" not in config["
|
|
143
|
-
errors.append('Property "publisher" missing in [
|
|
144
|
-
if "config" in config["
|
|
145
|
-
_validate_run_config(config["
|
|
146
|
-
if "components" not in config["
|
|
147
|
-
errors.append("Missing [
|
|
142
|
+
if "publisher" not in config["tool"]["flwr"]:
|
|
143
|
+
errors.append('Property "publisher" missing in [tool.flwr]')
|
|
144
|
+
if "config" in config["tool"]["flwr"]:
|
|
145
|
+
_validate_run_config(config["tool"]["flwr"]["config"], errors)
|
|
146
|
+
if "components" not in config["tool"]["flwr"]:
|
|
147
|
+
errors.append("Missing [tool.flwr.components] section")
|
|
148
148
|
else:
|
|
149
|
-
if "serverapp" not in config["
|
|
150
|
-
errors.append('Property "serverapp" missing in [
|
|
151
|
-
if "clientapp" not in config["
|
|
152
|
-
errors.append('Property "clientapp" missing in [
|
|
149
|
+
if "serverapp" not in config["tool"]["flwr"]["components"]:
|
|
150
|
+
errors.append('Property "serverapp" missing in [tool.flwr.components]')
|
|
151
|
+
if "clientapp" not in config["tool"]["flwr"]["components"]:
|
|
152
|
+
errors.append('Property "clientapp" missing in [tool.flwr.components]')
|
|
153
153
|
|
|
154
154
|
return len(errors) == 0, errors, warnings
|
|
155
155
|
|
|
@@ -165,14 +165,14 @@ def validate(
|
|
|
165
165
|
|
|
166
166
|
# Validate serverapp
|
|
167
167
|
is_valid, reason = object_ref.validate(
|
|
168
|
-
config["
|
|
168
|
+
config["tool"]["flwr"]["components"]["serverapp"], check_module
|
|
169
169
|
)
|
|
170
170
|
if not is_valid and isinstance(reason, str):
|
|
171
171
|
return False, [reason], []
|
|
172
172
|
|
|
173
173
|
# Validate clientapp
|
|
174
174
|
is_valid, reason = object_ref.validate(
|
|
175
|
-
config["
|
|
175
|
+
config["tool"]["flwr"]["components"]["clientapp"], check_module
|
|
176
176
|
)
|
|
177
177
|
|
|
178
178
|
if not is_valid and isinstance(reason, str):
|
flwr/cli/install.py
CHANGED
|
@@ -149,7 +149,7 @@ def validate_and_install(
|
|
|
149
149
|
)
|
|
150
150
|
raise typer.Exit(code=1)
|
|
151
151
|
|
|
152
|
-
publisher = config["
|
|
152
|
+
publisher = config["tool"]["flwr"]["publisher"]
|
|
153
153
|
project_name = config["project"]["name"]
|
|
154
154
|
version = config["project"]["version"]
|
|
155
155
|
|
|
@@ -6,9 +6,6 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
|
-
authors = [
|
|
10
|
-
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
|
-
]
|
|
12
9
|
license = { text = "Apache License (2.0)" }
|
|
13
10
|
dependencies = [
|
|
14
11
|
"flwr[simulation]>=1.9.0,<2.0",
|
|
@@ -25,18 +22,15 @@ dependencies = [
|
|
|
25
22
|
[tool.hatch.build.targets.wheel]
|
|
26
23
|
packages = ["."]
|
|
27
24
|
|
|
28
|
-
[
|
|
25
|
+
[tool.flwr]
|
|
29
26
|
publisher = "$username"
|
|
30
27
|
|
|
31
|
-
[
|
|
28
|
+
[tool.flwr.components]
|
|
32
29
|
serverapp = "$import_name.app:server"
|
|
33
30
|
clientapp = "$import_name.app:client"
|
|
34
31
|
|
|
35
|
-
[
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
[flower.engine.simulation.supernode]
|
|
39
|
-
num = $num_clients
|
|
32
|
+
[tool.flwr.federations]
|
|
33
|
+
default = "localhost"
|
|
40
34
|
|
|
41
|
-
[
|
|
42
|
-
|
|
35
|
+
[tool.flwr.federations.localhost]
|
|
36
|
+
options.num-supernodes = 10
|
|
@@ -6,9 +6,6 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
|
-
authors = [
|
|
10
|
-
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
|
-
]
|
|
12
9
|
license = { text = "Apache License (2.0)" }
|
|
13
10
|
dependencies = [
|
|
14
11
|
"flwr[simulation]>=1.9.0,<2.0",
|
|
@@ -23,15 +20,15 @@ dependencies = [
|
|
|
23
20
|
[tool.hatch.build.targets.wheel]
|
|
24
21
|
packages = ["."]
|
|
25
22
|
|
|
26
|
-
[
|
|
23
|
+
[tool.flwr]
|
|
27
24
|
publisher = "$username"
|
|
28
25
|
|
|
29
|
-
[
|
|
26
|
+
[tool.flwr.components]
|
|
30
27
|
serverapp = "$import_name.server:app"
|
|
31
28
|
clientapp = "$import_name.client:app"
|
|
32
29
|
|
|
33
|
-
[
|
|
34
|
-
|
|
30
|
+
[tool.flwr.federations]
|
|
31
|
+
default = "localhost"
|
|
35
32
|
|
|
36
|
-
[
|
|
37
|
-
num =
|
|
33
|
+
[tool.flwr.federations.localhost]
|
|
34
|
+
options.num-supernodes = 10
|
|
@@ -6,9 +6,6 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
|
-
authors = [
|
|
10
|
-
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
|
-
]
|
|
12
9
|
license = {text = "Apache License (2.0)"}
|
|
13
10
|
dependencies = [
|
|
14
11
|
"flwr[simulation]>=1.9.0,<2.0",
|
|
@@ -20,9 +17,15 @@ dependencies = [
|
|
|
20
17
|
[tool.hatch.build.targets.wheel]
|
|
21
18
|
packages = ["."]
|
|
22
19
|
|
|
23
|
-
[
|
|
20
|
+
[tool.flwr]
|
|
24
21
|
publisher = "$username"
|
|
25
22
|
|
|
26
|
-
[
|
|
23
|
+
[tool.flwr.components]
|
|
27
24
|
serverapp = "$import_name.server:app"
|
|
28
25
|
clientapp = "$import_name.client:app"
|
|
26
|
+
|
|
27
|
+
[tool.flwr.federations]
|
|
28
|
+
default = "localhost"
|
|
29
|
+
|
|
30
|
+
[tool.flwr.federations.localhost]
|
|
31
|
+
options.num-supernodes = 10
|
|
@@ -6,9 +6,6 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
|
-
authors = [
|
|
10
|
-
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
|
-
]
|
|
12
9
|
license = { text = "Apache License (2.0)" }
|
|
13
10
|
dependencies = [
|
|
14
11
|
"flwr[simulation]>=1.9.0,<2.0",
|
|
@@ -20,15 +17,15 @@ dependencies = [
|
|
|
20
17
|
[tool.hatch.build.targets.wheel]
|
|
21
18
|
packages = ["."]
|
|
22
19
|
|
|
23
|
-
[
|
|
20
|
+
[tool.flwr]
|
|
24
21
|
publisher = "$username"
|
|
25
22
|
|
|
26
|
-
[
|
|
23
|
+
[tool.flwr.components]
|
|
27
24
|
serverapp = "$import_name.server:app"
|
|
28
25
|
clientapp = "$import_name.client:app"
|
|
29
26
|
|
|
30
|
-
[
|
|
31
|
-
|
|
27
|
+
[tool.flwr.federations]
|
|
28
|
+
default = "localhost"
|
|
32
29
|
|
|
33
|
-
[
|
|
34
|
-
num =
|
|
30
|
+
[tool.flwr.federations.localhost]
|
|
31
|
+
options.num-supernodes = 10
|
|
@@ -6,9 +6,6 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
|
-
authors = [
|
|
10
|
-
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
|
-
]
|
|
12
9
|
license = { text = "Apache License (2.0)" }
|
|
13
10
|
dependencies = [
|
|
14
11
|
"flwr[simulation]>=1.9.0,<2.0",
|
|
@@ -18,15 +15,15 @@ dependencies = [
|
|
|
18
15
|
[tool.hatch.build.targets.wheel]
|
|
19
16
|
packages = ["."]
|
|
20
17
|
|
|
21
|
-
[
|
|
18
|
+
[tool.flwr]
|
|
22
19
|
publisher = "$username"
|
|
23
20
|
|
|
24
|
-
[
|
|
21
|
+
[tool.flwr.components]
|
|
25
22
|
serverapp = "$import_name.server:app"
|
|
26
23
|
clientapp = "$import_name.client:app"
|
|
27
24
|
|
|
28
|
-
[
|
|
29
|
-
|
|
25
|
+
[tool.flwr.federations]
|
|
26
|
+
default = "localhost"
|
|
30
27
|
|
|
31
|
-
[
|
|
32
|
-
num =
|
|
28
|
+
[tool.flwr.federations.localhost]
|
|
29
|
+
options.num-supernodes = 10
|
|
@@ -6,9 +6,6 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
|
-
authors = [
|
|
10
|
-
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
|
-
]
|
|
12
9
|
license = { text = "Apache License (2.0)" }
|
|
13
10
|
dependencies = [
|
|
14
11
|
"flwr[simulation]>=1.9.0,<2.0",
|
|
@@ -20,15 +17,15 @@ dependencies = [
|
|
|
20
17
|
[tool.hatch.build.targets.wheel]
|
|
21
18
|
packages = ["."]
|
|
22
19
|
|
|
23
|
-
[
|
|
20
|
+
[tool.flwr]
|
|
24
21
|
publisher = "$username"
|
|
25
22
|
|
|
26
|
-
[
|
|
23
|
+
[tool.flwr.components]
|
|
27
24
|
serverapp = "$import_name.server:app"
|
|
28
25
|
clientapp = "$import_name.client:app"
|
|
29
26
|
|
|
30
|
-
[
|
|
31
|
-
|
|
27
|
+
[tool.flwr.federations]
|
|
28
|
+
default = "localhost"
|
|
32
29
|
|
|
33
|
-
[
|
|
34
|
-
num =
|
|
30
|
+
[tool.flwr.federations.localhost]
|
|
31
|
+
options.num-supernodes = 10
|
|
@@ -6,9 +6,6 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
|
-
authors = [
|
|
10
|
-
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
|
-
]
|
|
12
9
|
license = { text = "Apache License (2.0)" }
|
|
13
10
|
dependencies = [
|
|
14
11
|
"flwr[simulation]>=1.9.0,<2.0",
|
|
@@ -19,15 +16,15 @@ dependencies = [
|
|
|
19
16
|
[tool.hatch.build.targets.wheel]
|
|
20
17
|
packages = ["."]
|
|
21
18
|
|
|
22
|
-
[
|
|
19
|
+
[tool.flwr]
|
|
23
20
|
publisher = "$username"
|
|
24
21
|
|
|
25
|
-
[
|
|
22
|
+
[tool.flwr.components]
|
|
26
23
|
serverapp = "$import_name.server:app"
|
|
27
24
|
clientapp = "$import_name.client:app"
|
|
28
25
|
|
|
29
|
-
[
|
|
30
|
-
|
|
26
|
+
[tool.flwr.federations]
|
|
27
|
+
default = "localhost"
|
|
31
28
|
|
|
32
|
-
[
|
|
33
|
-
num =
|
|
29
|
+
[tool.flwr.federations.localhost]
|
|
30
|
+
options.num-supernodes = 10
|
|
@@ -6,9 +6,6 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
|
-
authors = [
|
|
10
|
-
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
|
-
]
|
|
12
9
|
license = { text = "Apache License (2.0)" }
|
|
13
10
|
dependencies = [
|
|
14
11
|
"flwr[simulation]>=1.9.0,<2.0",
|
|
@@ -19,15 +16,15 @@ dependencies = [
|
|
|
19
16
|
[tool.hatch.build.targets.wheel]
|
|
20
17
|
packages = ["."]
|
|
21
18
|
|
|
22
|
-
[
|
|
19
|
+
[tool.flwr]
|
|
23
20
|
publisher = "$username"
|
|
24
21
|
|
|
25
|
-
[
|
|
22
|
+
[tool.flwr.components]
|
|
26
23
|
serverapp = "$import_name.server:app"
|
|
27
24
|
clientapp = "$import_name.client:app"
|
|
28
25
|
|
|
29
|
-
[
|
|
30
|
-
|
|
26
|
+
[tool.flwr.federations]
|
|
27
|
+
default = "localhost"
|
|
31
28
|
|
|
32
|
-
[
|
|
33
|
-
num =
|
|
29
|
+
[tool.flwr.federations.localhost]
|
|
30
|
+
options.num-supernodes = 10
|
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,30 +32,15 @@ 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
|
-
|
|
60
|
-
typer.
|
|
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],
|
|
@@ -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 =
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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"
|
|
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
|
|
131
|
-
|
|
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=
|
|
139
|
-
insecure=
|
|
140
|
-
root_certificates=
|
|
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=
|
|
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
|
+
)
|
flwr/client/supernode/app.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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(
|
|
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
|
-
|
|
207
|
+
default_app_ref,
|
|
197
208
|
)
|
|
198
209
|
# Insert sys.path
|
|
199
|
-
dir_path = Path(
|
|
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(
|
|
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(
|
|
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["
|
|
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)["
|
|
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)
|
flwr/server/run_serverapp.py
CHANGED
|
@@ -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["
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
229
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
336
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""Simulation engine executor."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
from logging import ERROR, INFO, WARN
|
|
21
|
+
from typing import Dict, Optional
|
|
22
|
+
|
|
23
|
+
from typing_extensions import override
|
|
24
|
+
|
|
25
|
+
from flwr.cli.config_utils import load_and_validate
|
|
26
|
+
from flwr.cli.install import install_from_fab
|
|
27
|
+
from flwr.common.constant import RUN_ID_NUM_BYTES
|
|
28
|
+
from flwr.common.logger import log
|
|
29
|
+
from flwr.server.superlink.state.utils import generate_rand_int_from_bytes
|
|
30
|
+
|
|
31
|
+
from .executor import Executor, RunTracker
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SimulationEngine(Executor):
|
|
35
|
+
"""Simulation engine executor.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
num_supernodes: Opitonal[str] (default: None)
|
|
40
|
+
Total number of nodes to involve in the simulation.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
num_supernodes: Optional[str] = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
self.num_supernodes = num_supernodes
|
|
48
|
+
|
|
49
|
+
@override
|
|
50
|
+
def set_config(
|
|
51
|
+
self,
|
|
52
|
+
config: Dict[str, str],
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Set executor config arguments.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
config : Dict[str, str]
|
|
59
|
+
A dictionary for configuration values.
|
|
60
|
+
Supported configuration key/value pairs:
|
|
61
|
+
- "num-supernodes": str
|
|
62
|
+
Number of nodes to register for the simulation.
|
|
63
|
+
"""
|
|
64
|
+
if not config:
|
|
65
|
+
return
|
|
66
|
+
if num_supernodes := config.get("num-supernodes"):
|
|
67
|
+
self.num_supernodes = num_supernodes
|
|
68
|
+
|
|
69
|
+
# Validate config
|
|
70
|
+
if self.num_supernodes is None:
|
|
71
|
+
log(
|
|
72
|
+
ERROR,
|
|
73
|
+
"To start a run with the simulation plugin, please specify "
|
|
74
|
+
"the number of SuperNodes. This can be done by using the "
|
|
75
|
+
"`--executor-config` argument when launching the SuperExec.",
|
|
76
|
+
)
|
|
77
|
+
raise ValueError("`num-supernodes` must not be `None`")
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
def start_run(
|
|
81
|
+
self, fab_file: bytes, override_config: Dict[str, str]
|
|
82
|
+
) -> Optional[RunTracker]:
|
|
83
|
+
"""Start run using the Flower Simulation Engine."""
|
|
84
|
+
try:
|
|
85
|
+
if override_config:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"Overriding the run config is not yet supported with the "
|
|
88
|
+
"simulation executor.",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Install FAB to flwr dir
|
|
92
|
+
fab_path = install_from_fab(fab_file, None, True)
|
|
93
|
+
|
|
94
|
+
# Install FAB Python package
|
|
95
|
+
subprocess.check_call(
|
|
96
|
+
[sys.executable, "-m", "pip", "install", "--no-deps", str(fab_path)],
|
|
97
|
+
stdout=subprocess.DEVNULL,
|
|
98
|
+
stderr=subprocess.DEVNULL,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Load and validate config
|
|
102
|
+
config, errors, warnings = load_and_validate(fab_path / "pyproject.toml")
|
|
103
|
+
if errors:
|
|
104
|
+
raise ValueError(errors)
|
|
105
|
+
|
|
106
|
+
if warnings:
|
|
107
|
+
log(WARN, warnings)
|
|
108
|
+
|
|
109
|
+
if config is None:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
"Config extracted from FAB's pyproject.toml is not valid"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Get ClientApp and SeverApp components
|
|
115
|
+
flower_components = config["tool"]["flwr"]["components"]
|
|
116
|
+
clientapp = flower_components["clientapp"]
|
|
117
|
+
serverapp = flower_components["serverapp"]
|
|
118
|
+
|
|
119
|
+
# In Simulation there is no SuperLink, still we create a run_id
|
|
120
|
+
run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
|
|
121
|
+
log(INFO, "Created run %s", str(run_id))
|
|
122
|
+
|
|
123
|
+
# Prepare commnand
|
|
124
|
+
command = [
|
|
125
|
+
"flower-simulation",
|
|
126
|
+
"--client-app",
|
|
127
|
+
f"{clientapp}",
|
|
128
|
+
"--server-app",
|
|
129
|
+
f"{serverapp}",
|
|
130
|
+
"--num-supernodes",
|
|
131
|
+
f"{self.num_supernodes}",
|
|
132
|
+
"--run-id",
|
|
133
|
+
str(run_id),
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
# Start Simulation
|
|
137
|
+
proc = subprocess.Popen( # pylint: disable=consider-using-with
|
|
138
|
+
command,
|
|
139
|
+
stdout=subprocess.PIPE,
|
|
140
|
+
stderr=subprocess.PIPE,
|
|
141
|
+
text=True,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
log(INFO, "Started run %s", str(run_id))
|
|
145
|
+
|
|
146
|
+
return RunTracker(
|
|
147
|
+
run_id=run_id,
|
|
148
|
+
proc=proc,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# pylint: disable-next=broad-except
|
|
152
|
+
except Exception as e:
|
|
153
|
+
log(ERROR, "Could not start run: %s", str(e))
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
executor = SimulationEngine()
|
{flwr_nightly-1.10.0.dev20240714.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/RECORD
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
flwr/__init__.py,sha256=VmBWedrCxqmt4QvUHBLqyVEH6p7zaFMD_oCHerXHSVw,937
|
|
2
2
|
flwr/cli/__init__.py,sha256=cZJVgozlkC6Ni2Hd_FAIrqefrkCGOV18fikToq-6iLw,720
|
|
3
3
|
flwr/cli/app.py,sha256=FBcSrE35ll88VE11ib67qgsJe2GYDN25UswV9-cYcX8,1267
|
|
4
|
-
flwr/cli/build.py,sha256=
|
|
5
|
-
flwr/cli/config_utils.py,sha256=
|
|
4
|
+
flwr/cli/build.py,sha256=otFX_fKyMKFDQun9ku7VLK5S5vfmrCFmxxkvgKTa9QQ,4743
|
|
5
|
+
flwr/cli/config_utils.py,sha256=bLkKx4irCvjMX2NU81Oax6OWJUPkjQqHefK_HqD9NgA,6587
|
|
6
6
|
flwr/cli/example.py,sha256=1bGDYll3BXQY2kRqSN-oICqS5n1b9m0g0RvXTopXHl4,2215
|
|
7
|
-
flwr/cli/install.py,sha256=
|
|
7
|
+
flwr/cli/install.py,sha256=ve2Bimhxq-p7dXsWkzoGuto3uVcA356DeKCMlgHIL9k,6585
|
|
8
8
|
flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
|
|
9
9
|
flwr/cli/new/new.py,sha256=vSLxyWOtD33U8mTGeqOhw5OjeDpFStekAVLUH9GJq6k,9432
|
|
10
10
|
flwr/cli/new/templates/__init__.py,sha256=4luU8RL-CK8JJCstQ_ON809W9bNTkY1l9zSaPKBkgwY,725
|
|
@@ -41,16 +41,16 @@ flwr/cli/new/templates/app/code/task.jax.py.tpl,sha256=u4o3V019EH79szOw2xzVeC5r9
|
|
|
41
41
|
flwr/cli/new/templates/app/code/task.mlx.py.tpl,sha256=nrfZ1aGOs_ayb70j7XdAmwFYa-rN10d9GIMIKLzctUE,2614
|
|
42
42
|
flwr/cli/new/templates/app/code/task.pytorch.py.tpl,sha256=TU4uNtJ9wtxeVvoHD3_K89EFWmrIvdECdASzRX-4Uvk,3694
|
|
43
43
|
flwr/cli/new/templates/app/code/task.tensorflow.py.tpl,sha256=cPOUUS07QbblT9PGFucwu9lY1clRA4-W4DQGA7cpcao,1044
|
|
44
|
-
flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl,sha256=
|
|
45
|
-
flwr/cli/new/templates/app/pyproject.hf.toml.tpl,sha256=
|
|
46
|
-
flwr/cli/new/templates/app/pyproject.jax.toml.tpl,sha256=
|
|
47
|
-
flwr/cli/new/templates/app/pyproject.mlx.toml.tpl,sha256=
|
|
48
|
-
flwr/cli/new/templates/app/pyproject.numpy.toml.tpl,sha256=
|
|
49
|
-
flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=
|
|
50
|
-
flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=
|
|
51
|
-
flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=
|
|
44
|
+
flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl,sha256=7BojTJ1ePuRTANNcNBqLY7jgehvbRRawXJFiQhExYWk,742
|
|
45
|
+
flwr/cli/new/templates/app/pyproject.hf.toml.tpl,sha256=NSXJ9qxjjuumg84bX8y7spoKaVHm9DUjofYBGbCBPDc,714
|
|
46
|
+
flwr/cli/new/templates/app/pyproject.jax.toml.tpl,sha256=iIdKwB_FEA2oVtL8uSOj43uredcbRpEzy-rnEzCv-UM,606
|
|
47
|
+
flwr/cli/new/templates/app/pyproject.mlx.toml.tpl,sha256=rq1mERo0-PFJHbIX7tv0B_uzhvnbrqXKkC7j93aGvjE,623
|
|
48
|
+
flwr/cli/new/templates/app/pyproject.numpy.toml.tpl,sha256=C6XuAL9eJzp0SiDGOEQJQvW4qyrzEmiJRCwOKxq8xq4,561
|
|
49
|
+
flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=ApZ8sHILExQE20pVjA03_dI_wO-oHtg_C8qXVHzFU3g,630
|
|
50
|
+
flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=5-dv9fD-omZA9Y4qXX1_Hcaei_Lh5-p4jdTKvWafU7M,610
|
|
51
|
+
flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=md9I4C86ZRF7p50hZhF5bw0Aax7vu_bvEldoaAkU5Uk,609
|
|
52
52
|
flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
|
|
53
|
-
flwr/cli/run/run.py,sha256=
|
|
53
|
+
flwr/cli/run/run.py,sha256=PW_b1EprTB5NZyx0hAWWPFtpGR9tM7WJ9tlmDahU8J4,6818
|
|
54
54
|
flwr/cli/utils.py,sha256=l65Ul0YsSBPuypk0uorAtEDmLEYiUrzpCXi6zCg9mJ4,4506
|
|
55
55
|
flwr/client/__init__.py,sha256=wzJZsYJIHf_8-PMzvfbinyzzjgh1UP1vLrAw2_yEbKI,1345
|
|
56
56
|
flwr/client/app.py,sha256=5v5EsA1zbViJAp998dCVRXvsyigZ-x3JEIKQ_fLeA48,26102
|
|
@@ -83,11 +83,11 @@ flwr/client/numpy_client.py,sha256=u76GWAdHmJM88Agm2EgLQSvO8Jnk225mJTk-_TmPjFE,1
|
|
|
83
83
|
flwr/client/rest_client/__init__.py,sha256=5KGlp7pjc1dhNRkKlaNtUfQmg8wrRFh9lS3P3uRS-7Q,735
|
|
84
84
|
flwr/client/rest_client/connection.py,sha256=aY_UzrNyE8g-xPAK_POZZZ93mERHTe-pOhNP-uZ8GyU,12147
|
|
85
85
|
flwr/client/supernode/__init__.py,sha256=SUhWOzcgXRNXk1V9UgB5-FaWukqqrOEajVUHEcPkwyQ,865
|
|
86
|
-
flwr/client/supernode/app.py,sha256=
|
|
86
|
+
flwr/client/supernode/app.py,sha256=LMJiuodmTUOAQ9MEbl4V2HjwBVJquJ9lZI38RGBh15Q,15598
|
|
87
87
|
flwr/client/typing.py,sha256=dxoTBnTMfqXr5J7G3y-uNjqxYCddvxhu89spfj4Lm2U,1048
|
|
88
88
|
flwr/common/__init__.py,sha256=4cBLNNnNTwHDnL_HCxhU5ILCSZ6fYh3A_aMBtlvHTVw,3721
|
|
89
89
|
flwr/common/address.py,sha256=wRu1Luezx1PWadwV9OA_KNko01oVvbRnPqfzaDn8QOk,1882
|
|
90
|
-
flwr/common/config.py,sha256=
|
|
90
|
+
flwr/common/config.py,sha256=W8EEfey1IHytkdXII3fTExc3uFxNm_Ysf35inG3cTkg,5000
|
|
91
91
|
flwr/common/constant.py,sha256=1XxuRezsr9fl3xvQNPR2kyFkwNeG_f5vZayv0PFh0kY,3012
|
|
92
92
|
flwr/common/context.py,sha256=CQt4uzCDvCIr2WdkrWq0obAz92k2_ucXGrWtBZCxP_M,2256
|
|
93
93
|
flwr/common/date.py,sha256=OcQuwpb2HxcblTqYm6H223ufop5UZw5N_fzalbpOVzY,891
|
|
@@ -187,7 +187,7 @@ flwr/server/driver/driver.py,sha256=NT_yaeit7_kZEIsCEqOWPID1GrVD3ywH4xZ2wtIh5lM,
|
|
|
187
187
|
flwr/server/driver/grpc_driver.py,sha256=4Azmzq4RWzcLbOqBBEF-I78krWVWZ6bT0U42S25zMvY,9659
|
|
188
188
|
flwr/server/driver/inmemory_driver.py,sha256=RcK94_NtjGZ4aZDIscnU7A3Uv1u8jGx29-xcbjQvZTM,6444
|
|
189
189
|
flwr/server/history.py,sha256=bBOHKyX1eQONIsUx4EUU-UnAk1i0EbEl8ioyMq_UWQ8,5063
|
|
190
|
-
flwr/server/run_serverapp.py,sha256=
|
|
190
|
+
flwr/server/run_serverapp.py,sha256=PkQy67LRxKr1dqH--VUOLtrgLw8JVFVNNr0hFqiINh0,9492
|
|
191
191
|
flwr/server/server.py,sha256=wsXsxMZ9SQ0B42nBnUlcV83NJPycgrgg5bFwcQ4BYBE,17821
|
|
192
192
|
flwr/server/server_app.py,sha256=1hul76ospG8L_KooK_ewn1sWPNTNYLTtZMeGNOBNruA,6267
|
|
193
193
|
flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,1349
|
|
@@ -239,7 +239,7 @@ flwr/server/superlink/fleet/vce/__init__.py,sha256=36MHKiefnJeyjwMQzVUK4m06Ojon3
|
|
|
239
239
|
flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=oBIzmnrSSRvH_H0vRGEGWhWzQQwqe3zn6e13RsNwlIY,1466
|
|
240
240
|
flwr/server/superlink/fleet/vce/backend/backend.py,sha256=iG3KSIY7DzNfcxmuLfTs7VdQJnqPCvvn5DFkTWKG5lI,2227
|
|
241
241
|
flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=SnjZ1WOcrfMZNgiDdTHcFeXJqrY7UHx8kvO62mqU9S4,7489
|
|
242
|
-
flwr/server/superlink/fleet/vce/vce_api.py,sha256=
|
|
242
|
+
flwr/server/superlink/fleet/vce/vce_api.py,sha256=GVPxAgFR-K-hfWOphsZo-PFGmkLze4eytrLaE6TV6l8,12632
|
|
243
243
|
flwr/server/superlink/state/__init__.py,sha256=Gj2OTFLXvA-mAjBvwuKDM3rDrVaQPcIoybSa2uskMTE,1003
|
|
244
244
|
flwr/server/superlink/state/in_memory_state.py,sha256=fb-f4RGiqXON0DC7aSEMNuNIjH406BhBYrNNX5Kza2g,13061
|
|
245
245
|
flwr/server/superlink/state/sqlite_state.py,sha256=dO374mTkvhWQSiwbqwUXVnAYHev-j2mHaX9v8wFmmMA,29044
|
|
@@ -262,15 +262,16 @@ flwr/simulation/ray_transport/__init__.py,sha256=wzcEEwUUlulnXsg6raCA1nGpP3LlAQD
|
|
|
262
262
|
flwr/simulation/ray_transport/ray_actor.py,sha256=3j0HgzjrlYjnzdTRy8aA4Nf6VoUvxi1hGRQkGSU5z6c,19020
|
|
263
263
|
flwr/simulation/ray_transport/ray_client_proxy.py,sha256=4KWWGSnfEBe3aGc0Ln5_1yRcZ52wKmOA7gXJKkMglvM,7302
|
|
264
264
|
flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
|
|
265
|
-
flwr/simulation/run_simulation.py,sha256=
|
|
265
|
+
flwr/simulation/run_simulation.py,sha256=wjN1spmdEmNZn06LDiuih1k3HHFKkHCKPbq-DieKw3A,17742
|
|
266
266
|
flwr/superexec/__init__.py,sha256=9h94ogLxi6eJ3bUuJYq3E3pApThSabTPiSmPAGlTkHE,800
|
|
267
267
|
flwr/superexec/app.py,sha256=Zh9I64XfCoghWoT1k2DKDrcVCXIGOpw03v0WKCOg-mg,6402
|
|
268
268
|
flwr/superexec/deployment.py,sha256=o_FYkB_vamBPjeVpPbqvzr4kBYID26sXVDrLO3Ac4R0,6130
|
|
269
269
|
flwr/superexec/exec_grpc.py,sha256=vYbZyV89MuvYDH1XzVYHkKmGfOcU6FWh8rTcIJk2TIQ,1910
|
|
270
270
|
flwr/superexec/exec_servicer.py,sha256=4R1f_9v0vly_bXpIYaXAeV1tO5LAy1AYygGGGNZmlQk,2194
|
|
271
271
|
flwr/superexec/executor.py,sha256=5ua0AU2cfisyD79dosP-POF3w0FRH2I5Wko_PPKLWqU,2153
|
|
272
|
-
|
|
273
|
-
flwr_nightly-1.10.0.
|
|
274
|
-
flwr_nightly-1.10.0.
|
|
275
|
-
flwr_nightly-1.10.0.
|
|
276
|
-
flwr_nightly-1.10.0.
|
|
272
|
+
flwr/superexec/simulation.py,sha256=flYwBU9lL3WV2jfGgp6bCjRNRlxmZ-ajkEHXab6HSug,5104
|
|
273
|
+
flwr_nightly-1.10.0.dev20240715.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
274
|
+
flwr_nightly-1.10.0.dev20240715.dist-info/METADATA,sha256=23pOsDu0OCNNq2lJKcXgdhg__CcotuXHOKOYTO9js04,15632
|
|
275
|
+
flwr_nightly-1.10.0.dev20240715.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
276
|
+
flwr_nightly-1.10.0.dev20240715.dist-info/entry_points.txt,sha256=7qBQcA-bDGDxnJmLd9FYqglFQubjCNqyg9M8a-lukps,336
|
|
277
|
+
flwr_nightly-1.10.0.dev20240715.dist-info/RECORD,,
|
{flwr_nightly-1.10.0.dev20240714.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/LICENSE
RENAMED
|
File without changes
|
{flwr_nightly-1.10.0.dev20240714.dist-info → flwr_nightly-1.10.0.dev20240715.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|