flwr-nightly 1.9.0.dev20240429__py3-none-any.whl → 1.9.0.dev20240501__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/app.py +2 -0
- flwr/cli/build.py +151 -0
- flwr/cli/new/new.py +1 -0
- flwr/cli/new/templates/app/README.md.tpl +1 -1
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +15 -29
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +9 -1
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +29 -0
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
- flwr/client/grpc_rere_client/client_interceptor.py +9 -1
- flwr/client/mod/centraldp_mods.py +4 -2
- flwr/client/mod/localdp_mod.py +9 -3
- flwr/common/logger.py +26 -0
- flwr/common/message.py +72 -82
- flwr/common/record/recordset.py +5 -4
- flwr/server/strategy/dp_adaptive_clipping.py +5 -3
- flwr/server/strategy/dp_fixed_clipping.py +6 -3
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +5 -0
- flwr/simulation/__init__.py +2 -2
- flwr/simulation/app.py +16 -1
- flwr/simulation/run_simulation.py +3 -0
- {flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/METADATA +2 -1
- {flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/RECORD +28 -26
- {flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/entry_points.txt +1 -1
- {flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/WHEEL +0 -0
flwr/cli/app.py
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import typer
|
|
18
18
|
|
|
19
|
+
from .build import build
|
|
19
20
|
from .example import example
|
|
20
21
|
from .new import new
|
|
21
22
|
from .run import run
|
|
@@ -32,6 +33,7 @@ app = typer.Typer(
|
|
|
32
33
|
app.command()(new)
|
|
33
34
|
app.command()(example)
|
|
34
35
|
app.command()(run)
|
|
36
|
+
app.command()(build)
|
|
35
37
|
|
|
36
38
|
if __name__ == "__main__":
|
|
37
39
|
app()
|
flwr/cli/build.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
"""Flower command line interface `build` command."""
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import os
|
|
19
|
+
import zipfile
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
import pathspec
|
|
24
|
+
import typer
|
|
25
|
+
from typing_extensions import Annotated
|
|
26
|
+
|
|
27
|
+
from .config_utils import load_and_validate_with_defaults
|
|
28
|
+
from .utils import is_valid_project_name
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# pylint: disable=too-many-locals
|
|
32
|
+
def build(
|
|
33
|
+
directory: Annotated[
|
|
34
|
+
Optional[Path],
|
|
35
|
+
typer.Option(help="The Flower project directory to bundle into a FAB"),
|
|
36
|
+
] = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Build a Flower project into a Flower App Bundle (FAB).
|
|
39
|
+
|
|
40
|
+
You can run `flwr build` without any argument to bundle the current directory:
|
|
41
|
+
|
|
42
|
+
`flwr build`
|
|
43
|
+
|
|
44
|
+
You can also build a specific directory:
|
|
45
|
+
|
|
46
|
+
`flwr build --directory ./projects/flower-hello-world`
|
|
47
|
+
"""
|
|
48
|
+
if directory is None:
|
|
49
|
+
directory = Path.cwd()
|
|
50
|
+
|
|
51
|
+
directory = directory.resolve()
|
|
52
|
+
if not directory.is_dir():
|
|
53
|
+
typer.secho(
|
|
54
|
+
f"❌ The path {directory} is not a valid directory.",
|
|
55
|
+
fg=typer.colors.RED,
|
|
56
|
+
bold=True,
|
|
57
|
+
)
|
|
58
|
+
raise typer.Exit(code=1)
|
|
59
|
+
|
|
60
|
+
if not is_valid_project_name(directory.name):
|
|
61
|
+
typer.secho(
|
|
62
|
+
f"❌ The project name {directory.name} is invalid, "
|
|
63
|
+
"a valid project name must start with a letter or an underscore, "
|
|
64
|
+
"and can only contain letters, digits, and underscores.",
|
|
65
|
+
fg=typer.colors.RED,
|
|
66
|
+
bold=True,
|
|
67
|
+
)
|
|
68
|
+
raise typer.Exit(code=1)
|
|
69
|
+
|
|
70
|
+
conf, errors, warnings = load_and_validate_with_defaults(
|
|
71
|
+
directory / "pyproject.toml"
|
|
72
|
+
)
|
|
73
|
+
if conf is None:
|
|
74
|
+
typer.secho(
|
|
75
|
+
"Project configuration could not be loaded.\npyproject.toml is invalid:\n"
|
|
76
|
+
+ "\n".join([f"- {line}" for line in errors]),
|
|
77
|
+
fg=typer.colors.RED,
|
|
78
|
+
bold=True,
|
|
79
|
+
)
|
|
80
|
+
raise typer.Exit(code=1)
|
|
81
|
+
|
|
82
|
+
if warnings:
|
|
83
|
+
typer.secho(
|
|
84
|
+
"Project configuration is missing the following "
|
|
85
|
+
"recommended properties:\n" + "\n".join([f"- {line}" for line in warnings]),
|
|
86
|
+
fg=typer.colors.RED,
|
|
87
|
+
bold=True,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Load .gitignore rules if present
|
|
91
|
+
ignore_spec = _load_gitignore(directory)
|
|
92
|
+
|
|
93
|
+
# Set the name of the zip file
|
|
94
|
+
fab_filename = (
|
|
95
|
+
f"{conf['flower']['publisher']}"
|
|
96
|
+
f".{directory.name}"
|
|
97
|
+
f".{conf['project']['version'].replace('.', '-')}.fab"
|
|
98
|
+
)
|
|
99
|
+
list_file_content = ""
|
|
100
|
+
|
|
101
|
+
allowed_extensions = {".py", ".toml", ".md"}
|
|
102
|
+
|
|
103
|
+
with zipfile.ZipFile(fab_filename, "w", zipfile.ZIP_DEFLATED) as fab_file:
|
|
104
|
+
for root, _, files in os.walk(directory, topdown=True):
|
|
105
|
+
# Filter directories and files based on .gitignore
|
|
106
|
+
files = [
|
|
107
|
+
f
|
|
108
|
+
for f in files
|
|
109
|
+
if not ignore_spec.match_file(Path(root) / f)
|
|
110
|
+
and f != fab_filename
|
|
111
|
+
and Path(f).suffix in allowed_extensions
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
for file in files:
|
|
115
|
+
file_path = Path(root) / file
|
|
116
|
+
archive_path = file_path.relative_to(directory)
|
|
117
|
+
fab_file.write(file_path, archive_path)
|
|
118
|
+
|
|
119
|
+
# Calculate file info
|
|
120
|
+
sha256_hash = _get_sha256_hash(file_path)
|
|
121
|
+
file_size_bits = os.path.getsize(file_path) * 8 # size in bits
|
|
122
|
+
list_file_content += f"{archive_path},{sha256_hash},{file_size_bits}\n"
|
|
123
|
+
|
|
124
|
+
# Add CONTENT and CONTENT.jwt to the zip file
|
|
125
|
+
fab_file.writestr(".info/CONTENT", list_file_content)
|
|
126
|
+
|
|
127
|
+
typer.secho(
|
|
128
|
+
f"🎊 Successfully built {fab_filename}.", fg=typer.colors.GREEN, bold=True
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _get_sha256_hash(file_path: Path) -> str:
|
|
133
|
+
"""Calculate the SHA-256 hash of a file."""
|
|
134
|
+
sha256 = hashlib.sha256()
|
|
135
|
+
with open(file_path, "rb") as f:
|
|
136
|
+
while True:
|
|
137
|
+
data = f.read(65536) # Read in 64kB blocks
|
|
138
|
+
if not data:
|
|
139
|
+
break
|
|
140
|
+
sha256.update(data)
|
|
141
|
+
return sha256.hexdigest()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _load_gitignore(directory: Path) -> pathspec.PathSpec:
|
|
145
|
+
"""Load and parse .gitignore file, returning a pathspec."""
|
|
146
|
+
gitignore_path = directory / ".gitignore"
|
|
147
|
+
patterns = ["__pycache__/"] # Default pattern
|
|
148
|
+
if gitignore_path.exists():
|
|
149
|
+
with open(gitignore_path, encoding="UTF-8") as file:
|
|
150
|
+
patterns.extend(file.readlines())
|
|
151
|
+
return pathspec.PathSpec.from_lines("gitwildmatch", patterns)
|
flwr/cli/new/new.py
CHANGED
|
@@ -153,6 +153,7 @@ def new(
|
|
|
153
153
|
# Depending on the framework, generate task.py file
|
|
154
154
|
frameworks_with_tasks = [
|
|
155
155
|
MlFramework.PYTORCH.value.lower(),
|
|
156
|
+
MlFramework.TENSORFLOW.value.lower(),
|
|
156
157
|
]
|
|
157
158
|
if framework_str in frameworks_with_tasks:
|
|
158
159
|
files[f"{import_name}/task.py"] = {
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
"""$project_name: A Flower / TensorFlow app."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from flwr.client import NumPyClient, ClientApp
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
from flwr.client import ClientApp, NumPyClient
|
|
7
|
-
from flwr_datasets import FederatedDataset
|
|
5
|
+
from $import_name.task import load_data, load_model
|
|
8
6
|
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# Define Flower client
|
|
8
|
+
# Define Flower Client and client_fn
|
|
13
9
|
class FlowerClient(NumPyClient):
|
|
14
|
-
def __init__(self, model,
|
|
10
|
+
def __init__(self, model, x_train, y_train, x_test, y_test):
|
|
15
11
|
self.model = model
|
|
16
|
-
self.x_train
|
|
17
|
-
self.
|
|
18
|
-
|
|
12
|
+
self.x_train = x_train
|
|
13
|
+
self.y_train = y_train
|
|
14
|
+
self.x_test = x_test
|
|
15
|
+
self.y_test = y_test
|
|
16
|
+
|
|
19
17
|
def get_parameters(self, config):
|
|
20
18
|
return self.model.get_weights()
|
|
21
19
|
|
|
@@ -30,25 +28,13 @@ class FlowerClient(NumPyClient):
|
|
|
30
28
|
return loss, len(self.x_test), {"accuracy": accuracy}
|
|
31
29
|
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# Load model and data (MobileNetV2, CIFAR-10)
|
|
39
|
-
model = tf.keras.applications.MobileNetV2((32, 32, 3), classes=10, weights=None)
|
|
40
|
-
model.compile("adam", "sparse_categorical_crossentropy", metrics=["accuracy"])
|
|
41
|
-
|
|
42
|
-
# Download and partition dataset
|
|
43
|
-
partition = fds.load_partition(int(cid), "train")
|
|
44
|
-
partition.set_format("numpy")
|
|
45
|
-
|
|
46
|
-
# Divide data on each node: 80% train, 20% test
|
|
47
|
-
partition = partition.train_test_split(test_size=0.2, seed=42)
|
|
48
|
-
train_data = partition["train"]["img"] / 255.0, partition["train"]["label"]
|
|
49
|
-
test_data = partition["test"]["img"] / 255.0, partition["test"]["label"]
|
|
31
|
+
def client_fn(cid):
|
|
32
|
+
# Load model and data
|
|
33
|
+
net = load_model()
|
|
34
|
+
x_train, y_train, x_test, y_test = load_data(int(cid), 2)
|
|
50
35
|
|
|
51
|
-
|
|
36
|
+
# Return Client instance
|
|
37
|
+
return FlowerClient(net, x_train, y_train, x_test, y_test).to_client()
|
|
52
38
|
|
|
53
39
|
|
|
54
40
|
# Flower ClientApp
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
"""$project_name: A Flower / TensorFlow app."""
|
|
2
2
|
|
|
3
|
+
from flwr.common import ndarrays_to_parameters
|
|
3
4
|
from flwr.server import ServerApp, ServerConfig
|
|
4
5
|
from flwr.server.strategy import FedAvg
|
|
5
6
|
|
|
7
|
+
from $import_name.task import load_model
|
|
8
|
+
|
|
6
9
|
# Define config
|
|
7
10
|
config = ServerConfig(num_rounds=3)
|
|
8
11
|
|
|
12
|
+
parameters = ndarrays_to_parameters(load_model().get_weights())
|
|
13
|
+
|
|
14
|
+
# Define strategy
|
|
9
15
|
strategy = FedAvg(
|
|
10
16
|
fraction_fit=1.0,
|
|
11
17
|
fraction_evaluate=1.0,
|
|
12
18
|
min_available_clients=2,
|
|
19
|
+
initial_parameters=parameters,
|
|
13
20
|
)
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
|
|
23
|
+
# Create ServerApp
|
|
16
24
|
app = ServerApp(
|
|
17
25
|
config=config,
|
|
18
26
|
strategy=strategy,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""$project_name: A Flower / TensorFlow app."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import tensorflow as tf
|
|
6
|
+
from flwr_datasets import FederatedDataset
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Make TensorFlow log less verbose
|
|
10
|
+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
|
|
11
|
+
|
|
12
|
+
def load_model():
|
|
13
|
+
# Load model and data (MobileNetV2, CIFAR-10)
|
|
14
|
+
model = tf.keras.applications.MobileNetV2((32, 32, 3), classes=10, weights=None)
|
|
15
|
+
model.compile("adam", "sparse_categorical_crossentropy", metrics=["accuracy"])
|
|
16
|
+
return model
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_data(partition_id, num_partitions):
|
|
20
|
+
# Download and partition dataset
|
|
21
|
+
fds = FederatedDataset(dataset="cifar10", partitioners={"train": num_partitions})
|
|
22
|
+
partition = fds.load_partition(partition_id, "train")
|
|
23
|
+
partition.set_format("numpy")
|
|
24
|
+
|
|
25
|
+
# Divide data on each node: 80% train, 20% test
|
|
26
|
+
partition = partition.train_test_split(test_size=0.2)
|
|
27
|
+
x_train, y_train = partition["train"]["img"] / 255.0, partition["train"]["label"]
|
|
28
|
+
x_test, y_test = partition["test"]["img"] / 255.0, partition["test"]["label"]
|
|
29
|
+
return x_train, y_train, x_test, y_test
|
|
@@ -9,7 +9,7 @@ description = ""
|
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
11
|
]
|
|
12
|
-
license = {text = "Apache License (2.0)"}
|
|
12
|
+
license = { text = "Apache License (2.0)" }
|
|
13
13
|
dependencies = [
|
|
14
14
|
"flwr[simulation]>=1.8.0,<2.0",
|
|
15
15
|
"numpy>=1.21.0",
|
|
@@ -9,7 +9,7 @@ description = ""
|
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
11
|
]
|
|
12
|
-
license = {text = "Apache License (2.0)"}
|
|
12
|
+
license = { text = "Apache License (2.0)" }
|
|
13
13
|
dependencies = [
|
|
14
14
|
"flwr[simulation]>=1.8.0,<2.0",
|
|
15
15
|
"flwr-datasets[vision]>=0.0.2,<1.0.0",
|
|
@@ -9,7 +9,7 @@ description = ""
|
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
11
|
]
|
|
12
|
-
license = {text = "Apache License (2.0)"}
|
|
12
|
+
license = { text = "Apache License (2.0)" }
|
|
13
13
|
dependencies = [
|
|
14
14
|
"flwr[simulation]>=1.8.0,<2.0",
|
|
15
15
|
"flwr-datasets[vision]>=0.0.2,<1.0.0",
|
|
@@ -9,7 +9,7 @@ description = ""
|
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "The Flower Authors", email = "hello@flower.ai" },
|
|
11
11
|
]
|
|
12
|
-
license = {text = "Apache License (2.0)"}
|
|
12
|
+
license = { text = "Apache License (2.0)" }
|
|
13
13
|
dependencies = [
|
|
14
14
|
"flwr[simulation]>=1.8.0,<2.0",
|
|
15
15
|
"flwr-datasets[vision]>=0.0.2,<1.0.0",
|
|
@@ -32,6 +32,7 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
|
32
32
|
CreateNodeRequest,
|
|
33
33
|
DeleteNodeRequest,
|
|
34
34
|
GetRunRequest,
|
|
35
|
+
PingRequest,
|
|
35
36
|
PullTaskInsRequest,
|
|
36
37
|
PushTaskResRequest,
|
|
37
38
|
)
|
|
@@ -45,6 +46,7 @@ Request = Union[
|
|
|
45
46
|
PullTaskInsRequest,
|
|
46
47
|
PushTaskResRequest,
|
|
47
48
|
GetRunRequest,
|
|
49
|
+
PingRequest,
|
|
48
50
|
]
|
|
49
51
|
|
|
50
52
|
|
|
@@ -115,7 +117,13 @@ class AuthenticateClientInterceptor(grpc.UnaryUnaryClientInterceptor): # type:
|
|
|
115
117
|
postprocess = True
|
|
116
118
|
elif isinstance(
|
|
117
119
|
request,
|
|
118
|
-
(
|
|
120
|
+
(
|
|
121
|
+
DeleteNodeRequest,
|
|
122
|
+
PullTaskInsRequest,
|
|
123
|
+
PushTaskResRequest,
|
|
124
|
+
GetRunRequest,
|
|
125
|
+
PingRequest,
|
|
126
|
+
),
|
|
119
127
|
):
|
|
120
128
|
if self.shared_secret is None:
|
|
121
129
|
raise RuntimeError("Failure to compute hmac")
|
|
@@ -82,7 +82,9 @@ def fixedclipping_mod(
|
|
|
82
82
|
clipping_norm,
|
|
83
83
|
)
|
|
84
84
|
|
|
85
|
-
log(
|
|
85
|
+
log(
|
|
86
|
+
INFO, "fixedclipping_mod: parameters are clipped by value: %.4f.", clipping_norm
|
|
87
|
+
)
|
|
86
88
|
|
|
87
89
|
fit_res.parameters = ndarrays_to_parameters(client_to_server_params)
|
|
88
90
|
out_msg.content = compat.fitres_to_recordset(fit_res, keep_input=True)
|
|
@@ -146,7 +148,7 @@ def adaptiveclipping_mod(
|
|
|
146
148
|
)
|
|
147
149
|
log(
|
|
148
150
|
INFO,
|
|
149
|
-
"adaptiveclipping_mod: parameters are clipped by value:
|
|
151
|
+
"adaptiveclipping_mod: parameters are clipped by value: %.4f.",
|
|
150
152
|
clipping_norm,
|
|
151
153
|
)
|
|
152
154
|
|
flwr/client/mod/localdp_mod.py
CHANGED
|
@@ -128,7 +128,9 @@ class LocalDpMod:
|
|
|
128
128
|
self.clipping_norm,
|
|
129
129
|
)
|
|
130
130
|
log(
|
|
131
|
-
INFO,
|
|
131
|
+
INFO,
|
|
132
|
+
"LocalDpMod: parameters are clipped by value: %.4f.",
|
|
133
|
+
self.clipping_norm,
|
|
132
134
|
)
|
|
133
135
|
|
|
134
136
|
fit_res.parameters = ndarrays_to_parameters(client_to_server_params)
|
|
@@ -137,11 +139,15 @@ class LocalDpMod:
|
|
|
137
139
|
add_localdp_gaussian_noise_to_params(
|
|
138
140
|
fit_res.parameters, self.sensitivity, self.epsilon, self.delta
|
|
139
141
|
)
|
|
142
|
+
|
|
143
|
+
noise_value_sd = (
|
|
144
|
+
self.sensitivity * np.sqrt(2 * np.log(1.25 / self.delta)) / self.epsilon
|
|
145
|
+
)
|
|
140
146
|
log(
|
|
141
147
|
INFO,
|
|
142
148
|
"LocalDpMod: local DP noise with "
|
|
143
|
-
"standard deviation:
|
|
144
|
-
|
|
149
|
+
"standard deviation: %.4f added to parameters.",
|
|
150
|
+
noise_value_sd,
|
|
145
151
|
)
|
|
146
152
|
|
|
147
153
|
out_msg.content = compat.fitres_to_recordset(fit_res, keep_input=True)
|
flwr/common/logger.py
CHANGED
|
@@ -188,3 +188,29 @@ def warn_deprecated_feature(name: str) -> None:
|
|
|
188
188
|
""",
|
|
189
189
|
name,
|
|
190
190
|
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def set_logger_propagation(
|
|
194
|
+
child_logger: logging.Logger, value: bool = True
|
|
195
|
+
) -> logging.Logger:
|
|
196
|
+
"""Set the logger propagation attribute.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
child_logger : logging.Logger
|
|
201
|
+
Child logger object
|
|
202
|
+
value : bool
|
|
203
|
+
Boolean setting for propagation. If True, both parent and child logger
|
|
204
|
+
display messages. Otherwise, only the child logger displays a message.
|
|
205
|
+
This False setting prevents duplicate logs in Colab notebooks.
|
|
206
|
+
Reference: https://stackoverflow.com/a/19561320
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
logging.Logger
|
|
211
|
+
Child logger object with updated propagation setting
|
|
212
|
+
"""
|
|
213
|
+
child_logger.propagate = value
|
|
214
|
+
if not child_logger.propagate:
|
|
215
|
+
child_logger.log(logging.DEBUG, "Logger propagate set to False")
|
|
216
|
+
return child_logger
|
flwr/common/message.py
CHANGED
|
@@ -19,6 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
import time
|
|
20
20
|
import warnings
|
|
21
21
|
from dataclasses import dataclass
|
|
22
|
+
from typing import Optional, cast
|
|
22
23
|
|
|
23
24
|
from .record import RecordSet
|
|
24
25
|
|
|
@@ -55,17 +56,6 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
55
56
|
is more relevant when conducting simulations.
|
|
56
57
|
"""
|
|
57
58
|
|
|
58
|
-
_run_id: int
|
|
59
|
-
_message_id: str
|
|
60
|
-
_src_node_id: int
|
|
61
|
-
_dst_node_id: int
|
|
62
|
-
_reply_to_message: str
|
|
63
|
-
_group_id: str
|
|
64
|
-
_ttl: float
|
|
65
|
-
_message_type: str
|
|
66
|
-
_partition_id: int | None
|
|
67
|
-
_created_at: float # Unix timestamp (in seconds) to be set upon message creation
|
|
68
|
-
|
|
69
59
|
def __init__( # pylint: disable=too-many-arguments
|
|
70
60
|
self,
|
|
71
61
|
run_id: int,
|
|
@@ -78,95 +68,98 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
78
68
|
message_type: str,
|
|
79
69
|
partition_id: int | None = None,
|
|
80
70
|
) -> None:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
71
|
+
var_dict = {
|
|
72
|
+
"_run_id": run_id,
|
|
73
|
+
"_message_id": message_id,
|
|
74
|
+
"_src_node_id": src_node_id,
|
|
75
|
+
"_dst_node_id": dst_node_id,
|
|
76
|
+
"_reply_to_message": reply_to_message,
|
|
77
|
+
"_group_id": group_id,
|
|
78
|
+
"_ttl": ttl,
|
|
79
|
+
"_message_type": message_type,
|
|
80
|
+
"_partition_id": partition_id,
|
|
81
|
+
}
|
|
82
|
+
self.__dict__.update(var_dict)
|
|
90
83
|
|
|
91
84
|
@property
|
|
92
85
|
def run_id(self) -> int:
|
|
93
86
|
"""An identifier for the current run."""
|
|
94
|
-
return self._run_id
|
|
87
|
+
return cast(int, self.__dict__["_run_id"])
|
|
95
88
|
|
|
96
89
|
@property
|
|
97
90
|
def message_id(self) -> str:
|
|
98
91
|
"""An identifier for the current message."""
|
|
99
|
-
return self._message_id
|
|
92
|
+
return cast(str, self.__dict__["_message_id"])
|
|
100
93
|
|
|
101
94
|
@property
|
|
102
95
|
def src_node_id(self) -> int:
|
|
103
96
|
"""An identifier for the node sending this message."""
|
|
104
|
-
return self._src_node_id
|
|
97
|
+
return cast(int, self.__dict__["_src_node_id"])
|
|
105
98
|
|
|
106
99
|
@property
|
|
107
100
|
def reply_to_message(self) -> str:
|
|
108
101
|
"""An identifier for the message this message replies to."""
|
|
109
|
-
return self._reply_to_message
|
|
102
|
+
return cast(str, self.__dict__["_reply_to_message"])
|
|
110
103
|
|
|
111
104
|
@property
|
|
112
105
|
def dst_node_id(self) -> int:
|
|
113
106
|
"""An identifier for the node receiving this message."""
|
|
114
|
-
return self._dst_node_id
|
|
107
|
+
return cast(int, self.__dict__["_dst_node_id"])
|
|
115
108
|
|
|
116
109
|
@dst_node_id.setter
|
|
117
110
|
def dst_node_id(self, value: int) -> None:
|
|
118
111
|
"""Set dst_node_id."""
|
|
119
|
-
self._dst_node_id = value
|
|
112
|
+
self.__dict__["_dst_node_id"] = value
|
|
120
113
|
|
|
121
114
|
@property
|
|
122
115
|
def group_id(self) -> str:
|
|
123
116
|
"""An identifier for grouping messages."""
|
|
124
|
-
return self._group_id
|
|
117
|
+
return cast(str, self.__dict__["_group_id"])
|
|
125
118
|
|
|
126
119
|
@group_id.setter
|
|
127
120
|
def group_id(self, value: str) -> None:
|
|
128
121
|
"""Set group_id."""
|
|
129
|
-
self._group_id = value
|
|
122
|
+
self.__dict__["_group_id"] = value
|
|
130
123
|
|
|
131
124
|
@property
|
|
132
125
|
def created_at(self) -> float:
|
|
133
126
|
"""Unix timestamp when the message was created."""
|
|
134
|
-
return self._created_at
|
|
127
|
+
return cast(float, self.__dict__["_created_at"])
|
|
135
128
|
|
|
136
129
|
@created_at.setter
|
|
137
130
|
def created_at(self, value: float) -> None:
|
|
138
|
-
"""Set creation timestamp for this
|
|
139
|
-
self._created_at = value
|
|
131
|
+
"""Set creation timestamp for this message."""
|
|
132
|
+
self.__dict__["_created_at"] = value
|
|
140
133
|
|
|
141
134
|
@property
|
|
142
135
|
def ttl(self) -> float:
|
|
143
136
|
"""Time-to-live for this message."""
|
|
144
|
-
return self._ttl
|
|
137
|
+
return cast(float, self.__dict__["_ttl"])
|
|
145
138
|
|
|
146
139
|
@ttl.setter
|
|
147
140
|
def ttl(self, value: float) -> None:
|
|
148
141
|
"""Set ttl."""
|
|
149
|
-
self._ttl = value
|
|
142
|
+
self.__dict__["_ttl"] = value
|
|
150
143
|
|
|
151
144
|
@property
|
|
152
145
|
def message_type(self) -> str:
|
|
153
146
|
"""A string that encodes the action to be executed on the receiving end."""
|
|
154
|
-
return self._message_type
|
|
147
|
+
return cast(str, self.__dict__["_message_type"])
|
|
155
148
|
|
|
156
149
|
@message_type.setter
|
|
157
150
|
def message_type(self, value: str) -> None:
|
|
158
151
|
"""Set message_type."""
|
|
159
|
-
self._message_type = value
|
|
152
|
+
self.__dict__["_message_type"] = value
|
|
160
153
|
|
|
161
154
|
@property
|
|
162
155
|
def partition_id(self) -> int | None:
|
|
163
156
|
"""An identifier telling which data partition a ClientApp should use."""
|
|
164
|
-
return self._partition_id
|
|
157
|
+
return cast(int, self.__dict__["_partition_id"])
|
|
165
158
|
|
|
166
159
|
@partition_id.setter
|
|
167
160
|
def partition_id(self, value: int) -> None:
|
|
168
|
-
"""Set
|
|
169
|
-
self._partition_id = value
|
|
161
|
+
"""Set partition_id."""
|
|
162
|
+
self.__dict__["_partition_id"] = value
|
|
170
163
|
|
|
171
164
|
|
|
172
165
|
@dataclass
|
|
@@ -181,22 +174,22 @@ class Error:
|
|
|
181
174
|
A reason for why the error arose (e.g. an exception stack-trace)
|
|
182
175
|
"""
|
|
183
176
|
|
|
184
|
-
_code: int
|
|
185
|
-
_reason: str | None = None
|
|
186
|
-
|
|
187
177
|
def __init__(self, code: int, reason: str | None = None) -> None:
|
|
188
|
-
|
|
189
|
-
|
|
178
|
+
var_dict = {
|
|
179
|
+
"_code": code,
|
|
180
|
+
"_reason": reason,
|
|
181
|
+
}
|
|
182
|
+
self.__dict__.update(var_dict)
|
|
190
183
|
|
|
191
184
|
@property
|
|
192
185
|
def code(self) -> int:
|
|
193
186
|
"""Error code."""
|
|
194
|
-
return self._code
|
|
187
|
+
return cast(int, self.__dict__["_code"])
|
|
195
188
|
|
|
196
189
|
@property
|
|
197
190
|
def reason(self) -> str | None:
|
|
198
191
|
"""Reason reported about the error."""
|
|
199
|
-
return self._reason
|
|
192
|
+
return cast(Optional[str], self.__dict__["_reason"])
|
|
200
193
|
|
|
201
194
|
|
|
202
195
|
@dataclass
|
|
@@ -215,88 +208,70 @@ class Message:
|
|
|
215
208
|
when processing another message.
|
|
216
209
|
"""
|
|
217
210
|
|
|
218
|
-
_metadata: Metadata
|
|
219
|
-
_content: RecordSet | None = None
|
|
220
|
-
_error: Error | None = None
|
|
221
|
-
|
|
222
211
|
def __init__(
|
|
223
212
|
self,
|
|
224
213
|
metadata: Metadata,
|
|
225
214
|
content: RecordSet | None = None,
|
|
226
215
|
error: Error | None = None,
|
|
227
216
|
) -> None:
|
|
228
|
-
self._metadata = metadata
|
|
229
|
-
|
|
230
|
-
# Set message creation timestamp
|
|
231
|
-
self._metadata.created_at = time.time()
|
|
232
|
-
|
|
233
217
|
if not (content is None) ^ (error is None):
|
|
234
218
|
raise ValueError("Either `content` or `error` must be set, but not both.")
|
|
235
219
|
|
|
236
|
-
|
|
237
|
-
|
|
220
|
+
metadata.created_at = time.time() # Set the message creation timestamp
|
|
221
|
+
var_dict = {
|
|
222
|
+
"_metadata": metadata,
|
|
223
|
+
"_content": content,
|
|
224
|
+
"_error": error,
|
|
225
|
+
}
|
|
226
|
+
self.__dict__.update(var_dict)
|
|
238
227
|
|
|
239
228
|
@property
|
|
240
229
|
def metadata(self) -> Metadata:
|
|
241
230
|
"""A dataclass including information about the message to be executed."""
|
|
242
|
-
return self._metadata
|
|
231
|
+
return cast(Metadata, self.__dict__["_metadata"])
|
|
243
232
|
|
|
244
233
|
@property
|
|
245
234
|
def content(self) -> RecordSet:
|
|
246
235
|
"""The content of this message."""
|
|
247
|
-
if self._content is None:
|
|
236
|
+
if self.__dict__["_content"] is None:
|
|
248
237
|
raise ValueError(
|
|
249
238
|
"Message content is None. Use <message>.has_content() "
|
|
250
239
|
"to check if a message has content."
|
|
251
240
|
)
|
|
252
|
-
return self._content
|
|
241
|
+
return cast(RecordSet, self.__dict__["_content"])
|
|
253
242
|
|
|
254
243
|
@content.setter
|
|
255
244
|
def content(self, value: RecordSet) -> None:
|
|
256
245
|
"""Set content."""
|
|
257
|
-
if self._error is None:
|
|
258
|
-
self._content = value
|
|
246
|
+
if self.__dict__["_error"] is None:
|
|
247
|
+
self.__dict__["_content"] = value
|
|
259
248
|
else:
|
|
260
249
|
raise ValueError("A message with an error set cannot have content.")
|
|
261
250
|
|
|
262
251
|
@property
|
|
263
252
|
def error(self) -> Error:
|
|
264
253
|
"""Error captured by this message."""
|
|
265
|
-
if self._error is None:
|
|
254
|
+
if self.__dict__["_error"] is None:
|
|
266
255
|
raise ValueError(
|
|
267
256
|
"Message error is None. Use <message>.has_error() "
|
|
268
257
|
"to check first if a message carries an error."
|
|
269
258
|
)
|
|
270
|
-
return self._error
|
|
259
|
+
return cast(Error, self.__dict__["_error"])
|
|
271
260
|
|
|
272
261
|
@error.setter
|
|
273
262
|
def error(self, value: Error) -> None:
|
|
274
263
|
"""Set error."""
|
|
275
264
|
if self.has_content():
|
|
276
265
|
raise ValueError("A message with content set cannot carry an error.")
|
|
277
|
-
self._error = value
|
|
266
|
+
self.__dict__["_error"] = value
|
|
278
267
|
|
|
279
268
|
def has_content(self) -> bool:
|
|
280
269
|
"""Return True if message has content, else False."""
|
|
281
|
-
return self._content is not None
|
|
270
|
+
return self.__dict__["_content"] is not None
|
|
282
271
|
|
|
283
272
|
def has_error(self) -> bool:
|
|
284
273
|
"""Return True if message has an error, else False."""
|
|
285
|
-
return self._error is not None
|
|
286
|
-
|
|
287
|
-
def _create_reply_metadata(self, ttl: float) -> Metadata:
|
|
288
|
-
"""Construct metadata for a reply message."""
|
|
289
|
-
return Metadata(
|
|
290
|
-
run_id=self.metadata.run_id,
|
|
291
|
-
message_id="",
|
|
292
|
-
src_node_id=self.metadata.dst_node_id,
|
|
293
|
-
dst_node_id=self.metadata.src_node_id,
|
|
294
|
-
reply_to_message=self.metadata.message_id,
|
|
295
|
-
group_id=self.metadata.group_id,
|
|
296
|
-
ttl=ttl,
|
|
297
|
-
message_type=self.metadata.message_type,
|
|
298
|
-
partition_id=self.metadata.partition_id,
|
|
299
|
-
)
|
|
274
|
+
return self.__dict__["_error"] is not None
|
|
300
275
|
|
|
301
276
|
def create_error_reply(self, error: Error, ttl: float | None = None) -> Message:
|
|
302
277
|
"""Construct a reply message indicating an error happened.
|
|
@@ -323,7 +298,7 @@ class Message:
|
|
|
323
298
|
# message creation)
|
|
324
299
|
ttl_ = DEFAULT_TTL if ttl is None else ttl
|
|
325
300
|
# Create reply with error
|
|
326
|
-
message = Message(metadata=
|
|
301
|
+
message = Message(metadata=_create_reply_metadata(self, ttl_), error=error)
|
|
327
302
|
|
|
328
303
|
if ttl is None:
|
|
329
304
|
# Set TTL equal to the remaining time for the received message to expire
|
|
@@ -369,7 +344,7 @@ class Message:
|
|
|
369
344
|
ttl_ = DEFAULT_TTL if ttl is None else ttl
|
|
370
345
|
|
|
371
346
|
message = Message(
|
|
372
|
-
metadata=
|
|
347
|
+
metadata=_create_reply_metadata(self, ttl_),
|
|
373
348
|
content=content,
|
|
374
349
|
)
|
|
375
350
|
|
|
@@ -381,3 +356,18 @@ class Message:
|
|
|
381
356
|
message.metadata.ttl = ttl
|
|
382
357
|
|
|
383
358
|
return message
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _create_reply_metadata(msg: Message, ttl: float) -> Metadata:
|
|
362
|
+
"""Construct metadata for a reply message."""
|
|
363
|
+
return Metadata(
|
|
364
|
+
run_id=msg.metadata.run_id,
|
|
365
|
+
message_id="",
|
|
366
|
+
src_node_id=msg.metadata.dst_node_id,
|
|
367
|
+
dst_node_id=msg.metadata.src_node_id,
|
|
368
|
+
reply_to_message=msg.metadata.message_id,
|
|
369
|
+
group_id=msg.metadata.group_id,
|
|
370
|
+
ttl=ttl,
|
|
371
|
+
message_type=msg.metadata.message_type,
|
|
372
|
+
partition_id=msg.metadata.partition_id,
|
|
373
|
+
)
|
flwr/common/record/recordset.py
CHANGED
|
@@ -24,6 +24,7 @@ from .parametersrecord import ParametersRecord
|
|
|
24
24
|
from .typeddict import TypedDict
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
@dataclass
|
|
27
28
|
class RecordSetData:
|
|
28
29
|
"""Inner data container for the RecordSet class."""
|
|
29
30
|
|
|
@@ -97,22 +98,22 @@ class RecordSet:
|
|
|
97
98
|
metrics_records=metrics_records,
|
|
98
99
|
configs_records=configs_records,
|
|
99
100
|
)
|
|
100
|
-
|
|
101
|
+
self.__dict__["_data"] = data
|
|
101
102
|
|
|
102
103
|
@property
|
|
103
104
|
def parameters_records(self) -> TypedDict[str, ParametersRecord]:
|
|
104
105
|
"""Dictionary holding ParametersRecord instances."""
|
|
105
|
-
data = cast(RecordSetData,
|
|
106
|
+
data = cast(RecordSetData, self.__dict__["_data"])
|
|
106
107
|
return data.parameters_records
|
|
107
108
|
|
|
108
109
|
@property
|
|
109
110
|
def metrics_records(self) -> TypedDict[str, MetricsRecord]:
|
|
110
111
|
"""Dictionary holding MetricsRecord instances."""
|
|
111
|
-
data = cast(RecordSetData,
|
|
112
|
+
data = cast(RecordSetData, self.__dict__["_data"])
|
|
112
113
|
return data.metrics_records
|
|
113
114
|
|
|
114
115
|
@property
|
|
115
116
|
def configs_records(self) -> TypedDict[str, ConfigsRecord]:
|
|
116
117
|
"""Dictionary holding ConfigsRecord instances."""
|
|
117
|
-
data = cast(RecordSetData,
|
|
118
|
+
data = cast(RecordSetData, self.__dict__["_data"])
|
|
118
119
|
return data.configs_records
|
|
@@ -200,7 +200,7 @@ class DifferentialPrivacyServerSideAdaptiveClipping(Strategy):
|
|
|
200
200
|
|
|
201
201
|
log(
|
|
202
202
|
INFO,
|
|
203
|
-
"aggregate_fit: parameters are clipped by value:
|
|
203
|
+
"aggregate_fit: parameters are clipped by value: %.4f.",
|
|
204
204
|
self.clipping_norm,
|
|
205
205
|
)
|
|
206
206
|
|
|
@@ -234,7 +234,8 @@ class DifferentialPrivacyServerSideAdaptiveClipping(Strategy):
|
|
|
234
234
|
)
|
|
235
235
|
log(
|
|
236
236
|
INFO,
|
|
237
|
-
"aggregate_fit: central DP noise with
|
|
237
|
+
"aggregate_fit: central DP noise with "
|
|
238
|
+
"standard deviation: %.4f added to parameters.",
|
|
238
239
|
compute_stdv(
|
|
239
240
|
self.noise_multiplier, self.clipping_norm, self.num_sampled_clients
|
|
240
241
|
),
|
|
@@ -424,7 +425,8 @@ class DifferentialPrivacyClientSideAdaptiveClipping(Strategy):
|
|
|
424
425
|
)
|
|
425
426
|
log(
|
|
426
427
|
INFO,
|
|
427
|
-
"aggregate_fit: central DP noise with
|
|
428
|
+
"aggregate_fit: central DP noise with "
|
|
429
|
+
"standard deviation: %.4f added to parameters.",
|
|
428
430
|
compute_stdv(
|
|
429
431
|
self.noise_multiplier, self.clipping_norm, self.num_sampled_clients
|
|
430
432
|
),
|
|
@@ -158,7 +158,7 @@ class DifferentialPrivacyServerSideFixedClipping(Strategy):
|
|
|
158
158
|
)
|
|
159
159
|
log(
|
|
160
160
|
INFO,
|
|
161
|
-
"aggregate_fit: parameters are clipped by value:
|
|
161
|
+
"aggregate_fit: parameters are clipped by value: %.4f.",
|
|
162
162
|
self.clipping_norm,
|
|
163
163
|
)
|
|
164
164
|
# Convert back to parameters
|
|
@@ -180,7 +180,8 @@ class DifferentialPrivacyServerSideFixedClipping(Strategy):
|
|
|
180
180
|
|
|
181
181
|
log(
|
|
182
182
|
INFO,
|
|
183
|
-
"aggregate_fit: central DP noise with
|
|
183
|
+
"aggregate_fit: central DP noise with "
|
|
184
|
+
"standard deviation: %.4f added to parameters.",
|
|
184
185
|
compute_stdv(
|
|
185
186
|
self.noise_multiplier, self.clipping_norm, self.num_sampled_clients
|
|
186
187
|
),
|
|
@@ -337,11 +338,13 @@ class DifferentialPrivacyClientSideFixedClipping(Strategy):
|
|
|
337
338
|
)
|
|
338
339
|
log(
|
|
339
340
|
INFO,
|
|
340
|
-
"aggregate_fit: central DP noise with
|
|
341
|
+
"aggregate_fit: central DP noise with "
|
|
342
|
+
"standard deviation: %.4f added to parameters.",
|
|
341
343
|
compute_stdv(
|
|
342
344
|
self.noise_multiplier, self.clipping_norm, self.num_sampled_clients
|
|
343
345
|
),
|
|
344
346
|
)
|
|
347
|
+
|
|
345
348
|
return aggregated_params, metrics
|
|
346
349
|
|
|
347
350
|
def aggregate_evaluate(
|
|
@@ -36,6 +36,8 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
|
36
36
|
DeleteNodeResponse,
|
|
37
37
|
GetRunRequest,
|
|
38
38
|
GetRunResponse,
|
|
39
|
+
PingRequest,
|
|
40
|
+
PingResponse,
|
|
39
41
|
PullTaskInsRequest,
|
|
40
42
|
PullTaskInsResponse,
|
|
41
43
|
PushTaskResRequest,
|
|
@@ -51,6 +53,7 @@ Request = Union[
|
|
|
51
53
|
PullTaskInsRequest,
|
|
52
54
|
PushTaskResRequest,
|
|
53
55
|
GetRunRequest,
|
|
56
|
+
PingRequest,
|
|
54
57
|
]
|
|
55
58
|
|
|
56
59
|
Response = Union[
|
|
@@ -59,6 +62,7 @@ Response = Union[
|
|
|
59
62
|
PullTaskInsResponse,
|
|
60
63
|
PushTaskResResponse,
|
|
61
64
|
GetRunResponse,
|
|
65
|
+
PingResponse,
|
|
62
66
|
]
|
|
63
67
|
|
|
64
68
|
|
|
@@ -140,6 +144,7 @@ class AuthenticateServerInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
140
144
|
PullTaskInsRequest,
|
|
141
145
|
PushTaskResRequest,
|
|
142
146
|
GetRunRequest,
|
|
147
|
+
PingRequest,
|
|
143
148
|
),
|
|
144
149
|
):
|
|
145
150
|
hmac_value = base64.urlsafe_b64decode(
|
flwr/simulation/__init__.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import importlib
|
|
19
19
|
|
|
20
|
-
from flwr.simulation.run_simulation import run_simulation
|
|
20
|
+
from flwr.simulation.run_simulation import run_simulation
|
|
21
21
|
|
|
22
22
|
is_ray_installed = importlib.util.find_spec("ray") is not None
|
|
23
23
|
|
|
@@ -36,4 +36,4 @@ To install the necessary dependencies, install `flwr` with the `simulation` extr
|
|
|
36
36
|
raise ImportError(RAY_IMPORT_ERROR)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
__all__ = ["start_simulation", "
|
|
39
|
+
__all__ = ["start_simulation", "run_simulation"]
|
flwr/simulation/app.py
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"""Flower simulation app."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
import asyncio
|
|
19
|
+
import logging
|
|
18
20
|
import sys
|
|
19
21
|
import threading
|
|
20
22
|
import traceback
|
|
@@ -27,7 +29,7 @@ from ray.util.scheduling_strategies import NodeAffinitySchedulingStrategy
|
|
|
27
29
|
|
|
28
30
|
from flwr.client import ClientFn
|
|
29
31
|
from flwr.common import EventType, event
|
|
30
|
-
from flwr.common.logger import log
|
|
32
|
+
from flwr.common.logger import log, set_logger_propagation
|
|
31
33
|
from flwr.server.client_manager import ClientManager
|
|
32
34
|
from flwr.server.history import History
|
|
33
35
|
from flwr.server.server import Server, init_defaults, run_fl
|
|
@@ -156,6 +158,7 @@ def start_simulation(
|
|
|
156
158
|
is an advanced feature. For all details, please refer to the Ray documentation:
|
|
157
159
|
https://docs.ray.io/en/latest/ray-core/scheduling/index.html
|
|
158
160
|
|
|
161
|
+
|
|
159
162
|
Returns
|
|
160
163
|
-------
|
|
161
164
|
hist : flwr.server.history.History
|
|
@@ -167,6 +170,18 @@ def start_simulation(
|
|
|
167
170
|
{"num_clients": len(clients_ids) if clients_ids is not None else num_clients},
|
|
168
171
|
)
|
|
169
172
|
|
|
173
|
+
# Set logger propagation
|
|
174
|
+
loop: Optional[asyncio.AbstractEventLoop] = None
|
|
175
|
+
try:
|
|
176
|
+
loop = asyncio.get_running_loop()
|
|
177
|
+
except RuntimeError:
|
|
178
|
+
loop = None
|
|
179
|
+
finally:
|
|
180
|
+
if loop and loop.is_running():
|
|
181
|
+
# Set logger propagation to False to prevent duplicated log output in Colab.
|
|
182
|
+
logger = logging.getLogger("flwr")
|
|
183
|
+
_ = set_logger_propagation(logger, False)
|
|
184
|
+
|
|
170
185
|
# Initialize server and server config
|
|
171
186
|
initialized_server, initialized_config = init_defaults(
|
|
172
187
|
server=server,
|
|
@@ -28,6 +28,7 @@ import grpc
|
|
|
28
28
|
|
|
29
29
|
from flwr.client import ClientApp
|
|
30
30
|
from flwr.common import EventType, event, log
|
|
31
|
+
from flwr.common.logger import set_logger_propagation
|
|
31
32
|
from flwr.common.typing import ConfigsRecordValues
|
|
32
33
|
from flwr.server.driver import Driver, GrpcDriver
|
|
33
34
|
from flwr.server.run_serverapp import run
|
|
@@ -364,6 +365,8 @@ def _run_simulation(
|
|
|
364
365
|
|
|
365
366
|
finally:
|
|
366
367
|
if run_in_thread:
|
|
368
|
+
# Set logger propagation to False to prevent duplicated log output in Colab.
|
|
369
|
+
logger = set_logger_propagation(logger, False)
|
|
367
370
|
log(DEBUG, "Starting Simulation Engine on a new thread.")
|
|
368
371
|
simulation_engine_th = threading.Thread(target=_main_loop, args=args)
|
|
369
372
|
simulation_engine_th.start()
|
{flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flwr-nightly
|
|
3
|
-
Version: 1.9.0.
|
|
3
|
+
Version: 1.9.0.dev20240501
|
|
4
4
|
Summary: Flower: A Friendly Federated Learning Framework
|
|
5
5
|
Home-page: https://flower.ai
|
|
6
6
|
License: Apache-2.0
|
|
@@ -36,6 +36,7 @@ Requires-Dist: cryptography (>=42.0.4,<43.0.0)
|
|
|
36
36
|
Requires-Dist: grpcio (>=1.60.0,<2.0.0)
|
|
37
37
|
Requires-Dist: iterators (>=0.0.2,<0.0.3)
|
|
38
38
|
Requires-Dist: numpy (>=1.21.0,<2.0.0)
|
|
39
|
+
Requires-Dist: pathspec (>=0.12.1,<0.13.0)
|
|
39
40
|
Requires-Dist: protobuf (>=4.25.2,<5.0.0)
|
|
40
41
|
Requires-Dist: pycryptodome (>=3.18.0,<4.0.0)
|
|
41
42
|
Requires-Dist: ray (==2.6.3) ; (python_version >= "3.8" and python_version < "3.12") and (extra == "simulation")
|
{flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/RECORD
RENAMED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
flwr/__init__.py,sha256=VmBWedrCxqmt4QvUHBLqyVEH6p7zaFMD_oCHerXHSVw,937
|
|
2
2
|
flwr/cli/__init__.py,sha256=cZJVgozlkC6Ni2Hd_FAIrqefrkCGOV18fikToq-6iLw,720
|
|
3
|
-
flwr/cli/app.py,sha256=
|
|
3
|
+
flwr/cli/app.py,sha256=IFu7V_xdexF1T9sUsvgYWVFW5wEPHHcBWHJRPdlX38U,1141
|
|
4
|
+
flwr/cli/build.py,sha256=W30wnPSgFuHRnGB9G_vKO14rsaibWk7m-jv9r8rDqo4,5106
|
|
4
5
|
flwr/cli/config_utils.py,sha256=Hql5A5hbSpJ51hgpwaTkKqfPoaZN4Zq7FZfBuQYLMcQ,4899
|
|
5
6
|
flwr/cli/example.py,sha256=1bGDYll3BXQY2kRqSN-oICqS5n1b9m0g0RvXTopXHl4,2215
|
|
6
7
|
flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
|
|
7
|
-
flwr/cli/new/new.py,sha256=
|
|
8
|
+
flwr/cli/new/new.py,sha256=x0cYNCYTCwbWiM7K58y4ViJl-Hd_pZ7jUmgaCNSP9v8,6035
|
|
8
9
|
flwr/cli/new/templates/__init__.py,sha256=4luU8RL-CK8JJCstQ_ON809W9bNTkY1l9zSaPKBkgwY,725
|
|
9
10
|
flwr/cli/new/templates/app/.gitignore.tpl,sha256=XixnHdyeMB2vwkGtGnwHqoWpH-9WChdyG0GXe57duhc,3078
|
|
10
|
-
flwr/cli/new/templates/app/README.md.tpl,sha256=
|
|
11
|
+
flwr/cli/new/templates/app/README.md.tpl,sha256=_qGtgpKYKoCJVjQnvlBMKvFs_1gzTcL908I3KJg0oAM,668
|
|
11
12
|
flwr/cli/new/templates/app/__init__.py,sha256=DU7QMY7IhMQyuwm_tja66xU0KXTWQFqzfTqwg-_NJdE,729
|
|
12
13
|
flwr/cli/new/templates/app/code/__init__.py,sha256=EM6vfvgAILKPaPn7H1wMV1Wi01WyZCP_Eg6NxD6oWg8,736
|
|
13
14
|
flwr/cli/new/templates/app/code/__init__.py.tpl,sha256=olwrBeJemHNBWvjc6gJURloFRqW40dAy7FRQA5pDqHU,21
|
|
14
15
|
flwr/cli/new/templates/app/code/client.numpy.py.tpl,sha256=mTh7Y_jOJrPUvDYHVJy4wJCnjXZV_q-jlDkB07U5GSk,521
|
|
15
16
|
flwr/cli/new/templates/app/code/client.pytorch.py.tpl,sha256=MgCtMSv1Th16Faod11HubVaARkLYt7vS9RYH962-2pk,1172
|
|
16
17
|
flwr/cli/new/templates/app/code/client.sklearn.py.tpl,sha256=S71SZiHaRXtKqUk3m5Elc_c6HhKAIKLalrKOQ3p20No,2801
|
|
17
|
-
flwr/cli/new/templates/app/code/client.tensorflow.py.tpl,sha256=
|
|
18
|
+
flwr/cli/new/templates/app/code/client.tensorflow.py.tpl,sha256=dxrTO9JwYrDBjLsmCiRLetN9KxbnWRTeGA0BQbnOu_A,1280
|
|
18
19
|
flwr/cli/new/templates/app/code/server.numpy.py.tpl,sha256=fRxrDXV7pB1aDhQUXMBmrCsC1zp0uKwsBxZBx1JzbHA,248
|
|
19
20
|
flwr/cli/new/templates/app/code/server.pytorch.py.tpl,sha256=ltdsnFSvFGPcycVmRL4ITlr-TV0CmmXcperZe7Vamow,593
|
|
20
21
|
flwr/cli/new/templates/app/code/server.sklearn.py.tpl,sha256=cLzOpQzGIUzEazuFsjBpXAQUNPy6in6zR33SCqhix6o,341
|
|
21
|
-
flwr/cli/new/templates/app/code/server.tensorflow.py.tpl,sha256=
|
|
22
|
+
flwr/cli/new/templates/app/code/server.tensorflow.py.tpl,sha256=gsNrWCKTU77_65_gw9nlp1LSQojgP5QQIWILvqdjx2s,579
|
|
22
23
|
flwr/cli/new/templates/app/code/task.pytorch.py.tpl,sha256=NvajdZN-eTyfdqKK0v2MrvWITXw9BjJ3Ri5c1haPJDs,3684
|
|
23
|
-
flwr/cli/new/templates/app/
|
|
24
|
-
flwr/cli/new/templates/app/pyproject.
|
|
25
|
-
flwr/cli/new/templates/app/pyproject.
|
|
26
|
-
flwr/cli/new/templates/app/pyproject.
|
|
24
|
+
flwr/cli/new/templates/app/code/task.tensorflow.py.tpl,sha256=cPOUUS07QbblT9PGFucwu9lY1clRA4-W4DQGA7cpcao,1044
|
|
25
|
+
flwr/cli/new/templates/app/pyproject.numpy.toml.tpl,sha256=m276SKsjOZ4awGdXasUKvLim66agrpAsPNP9-PN6q4I,523
|
|
26
|
+
flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=QikP3u5ht6qr2BkgcnvB3rCYK7jt1cS0nAm7V8g_zFc,592
|
|
27
|
+
flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=IO5iIlyKSBxZCCf48iqEyRWeG1jmVx2tO_s2iE7FpHo,572
|
|
28
|
+
flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=CpjCOHyJ3zdIlkXQ1An6fEKN9l7rIStx43g4SsIwbkw,571
|
|
27
29
|
flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
|
|
28
30
|
flwr/cli/run/run.py,sha256=jr_J7Cbcyuj1MXIbuwU86htHdFI7XogsBrdGl7P4aYY,2334
|
|
29
31
|
flwr/cli/utils.py,sha256=px-M-IlBLu6Ez-Sc9tWhsJRjWurRaZTmxB9ASz8wurk,4119
|
|
@@ -35,16 +37,16 @@ flwr/client/dpfedavg_numpy_client.py,sha256=9Tnig4iml2J88HBKNahegjXjbfvIQyBtaIQa
|
|
|
35
37
|
flwr/client/grpc_client/__init__.py,sha256=LsnbqXiJhgQcB0XzAlUQgPx011Uf7Y7yabIC1HxivJ8,735
|
|
36
38
|
flwr/client/grpc_client/connection.py,sha256=KWbBwuvn1-2wjrAKteydGCZC_7A2zmEjk3DycQWafrA,8993
|
|
37
39
|
flwr/client/grpc_rere_client/__init__.py,sha256=avn6W_vHEM_yZEB1S7hCZgnTbXb6ZujqRP_vAzyXu-0,752
|
|
38
|
-
flwr/client/grpc_rere_client/client_interceptor.py,sha256=
|
|
40
|
+
flwr/client/grpc_rere_client/client_interceptor.py,sha256=rDBXRVo-d-rflxJ6Kw3eDfBmvChdUHkzRw5eP-bpe6Y,4903
|
|
39
41
|
flwr/client/grpc_rere_client/connection.py,sha256=gSSJJ9pSe5SgUb1Ey-xcrVK6xArUkwq0yGdav0h2kww,9597
|
|
40
42
|
flwr/client/heartbeat.py,sha256=cx37mJBH8LyoIN4Lks85wtqT1mnU5GulQnr4pGCvAq0,2404
|
|
41
43
|
flwr/client/message_handler/__init__.py,sha256=abHvBRJJiiaAMNgeILQbMOa6h8WqMK2BcnvxwQZFpic,719
|
|
42
44
|
flwr/client/message_handler/message_handler.py,sha256=ml_FlduAJ5pxO31n1tKRrWfQRSxkMgKLbwXXcRsNSos,6553
|
|
43
45
|
flwr/client/message_handler/task_handler.py,sha256=ZDJBKmrn2grRMNl1rU1iGs7FiMHL5VmZiSp_6h9GHVU,1824
|
|
44
46
|
flwr/client/mod/__init__.py,sha256=apqhs7bslrGgQK91JR56mEcwj5JihL0NF_XKQwqaQuo,1143
|
|
45
|
-
flwr/client/mod/centraldp_mods.py,sha256=
|
|
47
|
+
flwr/client/mod/centraldp_mods.py,sha256=UGwNuqpmOWfLdfJITFgdi1TG-nLjuSb-cbEyoyfDgxQ,5415
|
|
46
48
|
flwr/client/mod/comms_mods.py,sha256=hCj2mSey12D8rehhyGl4JwmVq8iFd-wlzZj8lzexGOs,2623
|
|
47
|
-
flwr/client/mod/localdp_mod.py,sha256=
|
|
49
|
+
flwr/client/mod/localdp_mod.py,sha256=K5kZnv9wQUqLYBrE2gExthbcl2YCNRK4VTbmYYqTNOs,5012
|
|
48
50
|
flwr/client/mod/secure_aggregation/__init__.py,sha256=Qo2R-NqsyoP0oX73TyDfQRu9P6DCNXhgqGbhmGIBaJA,849
|
|
49
51
|
flwr/client/mod/secure_aggregation/secagg_mod.py,sha256=wI9tuIEvMUETz-wVIEbPYvh-1nK9CEylBLGoVpNhL94,1095
|
|
50
52
|
flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=fZTfIELkYS64lpgxQKL66s-QHjCn-159qfLoNoIMJjc,19699
|
|
@@ -67,8 +69,8 @@ flwr/common/differential_privacy_constants.py,sha256=c7b7tqgvT7yMK0XN9ndiTBs4mQf
|
|
|
67
69
|
flwr/common/dp.py,sha256=Hc3lLHihjexbJaD_ft31gdv9XRcwOTgDBwJzICuok3A,2004
|
|
68
70
|
flwr/common/exit_handlers.py,sha256=2Nt0wLhc17KQQsLPFSRAjjhUiEFfJK6tNozdGiIY4Fs,2812
|
|
69
71
|
flwr/common/grpc.py,sha256=Yx_YFK24cU4U81RpXrdVwEVY_jTy4RE19cHtBxE2XOE,2460
|
|
70
|
-
flwr/common/logger.py,sha256=
|
|
71
|
-
flwr/common/message.py,sha256=
|
|
72
|
+
flwr/common/logger.py,sha256=s1ZTh6nCeGZh0MxbCG8zHYZAeQ9JNDpw8k6qtxSCCzM,6920
|
|
73
|
+
flwr/common/message.py,sha256=78SN-HZpqbsnap5JFbdnmqR1dL0juK1zDRiixg3Jbl4,12686
|
|
72
74
|
flwr/common/object_ref.py,sha256=ELoUCAFO-vbjJC41CGpa-WBG2SLYe3ErW-d9YCG3zqA,4961
|
|
73
75
|
flwr/common/parameter.py,sha256=-bFAUayToYDF50FZGrBC1hQYJCQDtB2bbr3ZuVLMtdE,2095
|
|
74
76
|
flwr/common/pyproject.py,sha256=EI_ovbCHGmhYrdPx0RSDi5EkFZFof-8m1PA54c0ZTjc,1385
|
|
@@ -77,7 +79,7 @@ flwr/common/record/configsrecord.py,sha256=VKeFEYa6cneyStqQlUOaKj12by5ZI_NXYR25L
|
|
|
77
79
|
flwr/common/record/conversion_utils.py,sha256=n3I3SI2P6hUjyxbWNc0QAch-SEhfMK6Hm-UUaplAlUc,1393
|
|
78
80
|
flwr/common/record/metricsrecord.py,sha256=Yv99oRa3LzFgSfwl903S8sB8rAgr3Sv6i6ovW7pdHsA,3923
|
|
79
81
|
flwr/common/record/parametersrecord.py,sha256=WSqtRrYvI-mRzkEwv5s-EG-yE5uizJ8zy9aczwRG-1E,4849
|
|
80
|
-
flwr/common/record/recordset.py,sha256=
|
|
82
|
+
flwr/common/record/recordset.py,sha256=ZIDX_ZMpSRwFyD9PZQBDSM_UhRzzafgna8hDJj-egZ4,4533
|
|
81
83
|
flwr/common/record/typeddict.py,sha256=2NW8JF27p1uNWaqDbJ7bMkItA5x4ygYT8aHrf8NaqnE,3879
|
|
82
84
|
flwr/common/recordset_compat.py,sha256=BjxeuvlCaP94yIiKOyFFTRBUH_lprFWSLo8U8q3BDbs,13798
|
|
83
85
|
flwr/common/retry_invoker.py,sha256=dQY5fPIKhy9OiFswZhLxA9fB455u-DYCvDVcFJmrPDk,11707
|
|
@@ -144,8 +146,8 @@ flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,
|
|
|
144
146
|
flwr/server/strategy/__init__.py,sha256=7eVZ3hQEg2BgA_usAeL6tsLp9T6XI1VYYoFy08Xn-ew,2836
|
|
145
147
|
flwr/server/strategy/aggregate.py,sha256=QyRIJtI5gnuY1NbgrcrOvkHxGIxBvApq7d9Y4xl-6W4,13468
|
|
146
148
|
flwr/server/strategy/bulyan.py,sha256=8GsSVJzRSoSWE2zQUKqC3Z795grdN9xpmc3MSGGXnzM,6532
|
|
147
|
-
flwr/server/strategy/dp_adaptive_clipping.py,sha256=
|
|
148
|
-
flwr/server/strategy/dp_fixed_clipping.py,sha256=
|
|
149
|
+
flwr/server/strategy/dp_adaptive_clipping.py,sha256=AOI6Y5hUMSdEShWNvlZtRZasvIkfpl6gXNXk-wbpBKY,17502
|
|
150
|
+
flwr/server/strategy/dp_fixed_clipping.py,sha256=PhQOKI5gJrzlCTswU8ygGZzLBVMnn15U3Wafp3I-xCo,12949
|
|
149
151
|
flwr/server/strategy/dpfedavg_adaptive.py,sha256=hLJkPQJl1bHjwrBNg3PSRFKf3no0hg5EHiFaWhHlWqw,4877
|
|
150
152
|
flwr/server/strategy/dpfedavg_fixed.py,sha256=G0yYxrPoM-MHQ889DYN3OeNiEeU0yQrjgAzcq0G653w,7219
|
|
151
153
|
flwr/server/strategy/fault_tolerant_fedavg.py,sha256=veGcehB6rXT_MihNDrD1v5JY-TxJi7fybdDl-OZooDQ,5900
|
|
@@ -177,7 +179,7 @@ flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py,sha256=kuD7R1yB1Ite0s
|
|
|
177
179
|
flwr/server/superlink/fleet/grpc_bidi/grpc_server.py,sha256=_zWknjP7CRjwLDvofzmv1QoSI8Qq1cZC5nNw9nkSS7I,11932
|
|
178
180
|
flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=bEJOMWbSlqkw-y5ZHtEXczhoSlAxErcRYffmTMQAV8M,758
|
|
179
181
|
flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=YGn1IPpuX-6NDgaG1UbyREbI9iAyKDimZuNeWxbG6s0,3387
|
|
180
|
-
flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=
|
|
182
|
+
flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=3EuoVOb5pXrN52XlDGjEfJdXYmcfFunZeYYMlDlu7ug,5902
|
|
181
183
|
flwr/server/superlink/fleet/message_handler/__init__.py,sha256=hEY0l61ojH8Iz30_K1btm1HJ6J49iZJSFUsVYqUTw3A,731
|
|
182
184
|
flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=lG3BkiONcikDVowK0An06V7p2SNkwGbWE5hfN2xlsZw,3622
|
|
183
185
|
flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=VKDvDq5H8koOUztpmQacVzGJXPLEEkL1Vmolxt3mvnY,735
|
|
@@ -203,15 +205,15 @@ flwr/server/workflow/default_workflows.py,sha256=ROJNsY538jSGMaNyF7GHwXMtV7us1Vx
|
|
|
203
205
|
flwr/server/workflow/secure_aggregation/__init__.py,sha256=3XlgDOjD_hcukTGl6Bc1B-8M_dPlVSJuTbvXIbiO-Ic,880
|
|
204
206
|
flwr/server/workflow/secure_aggregation/secagg_workflow.py,sha256=wpAkYPId0nfK6SgpUAtsCni4_MQLd-uqJ81tUKu3xlI,5838
|
|
205
207
|
flwr/server/workflow/secure_aggregation/secaggplus_workflow.py,sha256=BRqhlnVe8CYNoUvb_KCfRXay02NTT6a-pCrMaOqAxGc,29038
|
|
206
|
-
flwr/simulation/__init__.py,sha256=
|
|
207
|
-
flwr/simulation/app.py,sha256=
|
|
208
|
+
flwr/simulation/__init__.py,sha256=1SVOWQGPA6dWlUMR_ZeiPBYXURNb1rx9VOi6kSW6zrs,1348
|
|
209
|
+
flwr/simulation/app.py,sha256=Fjq3krpzx9GUW1oYLHPn_Wq2tzjyqVBa9bxRbtuNVBQ,14380
|
|
208
210
|
flwr/simulation/ray_transport/__init__.py,sha256=FsaAnzC4cw4DqoouBCix6496k29jACkfeIam55BvW9g,734
|
|
209
211
|
flwr/simulation/ray_transport/ray_actor.py,sha256=_wv2eP7qxkCZ-6rMyYWnjLrGPBZRxjvTPjaVk8zIaQ4,19367
|
|
210
212
|
flwr/simulation/ray_transport/ray_client_proxy.py,sha256=oDu4sEPIOu39vrNi-fqDAe10xtNUXMO49bM2RWfRcyw,6738
|
|
211
213
|
flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
|
|
212
|
-
flwr/simulation/run_simulation.py,sha256=
|
|
213
|
-
flwr_nightly-1.9.0.
|
|
214
|
-
flwr_nightly-1.9.0.
|
|
215
|
-
flwr_nightly-1.9.0.
|
|
216
|
-
flwr_nightly-1.9.0.
|
|
217
|
-
flwr_nightly-1.9.0.
|
|
214
|
+
flwr/simulation/run_simulation.py,sha256=jSzL7vZJ6Cg76yAyuWt7X8WzwkZ26krpurRAIHXhrNk,15896
|
|
215
|
+
flwr_nightly-1.9.0.dev20240501.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
216
|
+
flwr_nightly-1.9.0.dev20240501.dist-info/METADATA,sha256=xx9zVqrtYze9IkQZ6S2obortCJOalnsGcQnuhStB9Fs,15303
|
|
217
|
+
flwr_nightly-1.9.0.dev20240501.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
218
|
+
flwr_nightly-1.9.0.dev20240501.dist-info/entry_points.txt,sha256=8JJPfpqMnXz9c5V_FSt07Xwd-wCWbAO3MFUDXQ5ZGsI,378
|
|
219
|
+
flwr_nightly-1.9.0.dev20240501.dist-info/RECORD,,
|
|
@@ -3,7 +3,7 @@ flower-client-app=flwr.client:run_client_app
|
|
|
3
3
|
flower-driver-api=flwr.server:run_driver_api
|
|
4
4
|
flower-fleet-api=flwr.server:run_fleet_api
|
|
5
5
|
flower-server-app=flwr.server:run_server_app
|
|
6
|
-
flower-simulation=flwr.simulation:run_simulation_from_cli
|
|
6
|
+
flower-simulation=flwr.simulation.run_simulation:run_simulation_from_cli
|
|
7
7
|
flower-superlink=flwr.server:run_superlink
|
|
8
8
|
flower-supernode=flwr.client:run_supernode
|
|
9
9
|
flwr=flwr.cli.app:app
|
{flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|