flwr-nightly 1.8.0.dev20240310__py3-none-any.whl → 1.8.0.dev20240311__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- flwr/cli/new/new.py +6 -3
- flwr/cli/utils.py +14 -1
- flwr/client/app.py +25 -2
- flwr/client/mod/__init__.py +2 -1
- flwr/client/mod/secure_aggregation/__init__.py +2 -0
- flwr/client/mod/secure_aggregation/secagg_mod.py +30 -0
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +12 -12
- flwr/common/logger.py +6 -8
- flwr/common/secure_aggregation/secaggplus_constants.py +2 -2
- flwr/server/superlink/state/in_memory_state.py +34 -32
- flwr/server/workflow/__init__.py +2 -1
- flwr/server/workflow/default_workflows.py +36 -37
- flwr/server/workflow/secure_aggregation/__init__.py +2 -0
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +112 -0
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +85 -10
- {flwr_nightly-1.8.0.dev20240310.dist-info → flwr_nightly-1.8.0.dev20240311.dist-info}/METADATA +1 -1
- {flwr_nightly-1.8.0.dev20240310.dist-info → flwr_nightly-1.8.0.dev20240311.dist-info}/RECORD +20 -18
- {flwr_nightly-1.8.0.dev20240310.dist-info → flwr_nightly-1.8.0.dev20240311.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.8.0.dev20240310.dist-info → flwr_nightly-1.8.0.dev20240311.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.8.0.dev20240310.dist-info → flwr_nightly-1.8.0.dev20240311.dist-info}/entry_points.txt +0 -0
flwr/cli/new/new.py
CHANGED
@@ -22,7 +22,7 @@ from typing import Dict, Optional
|
|
22
22
|
import typer
|
23
23
|
from typing_extensions import Annotated
|
24
24
|
|
25
|
-
from ..utils import prompt_options
|
25
|
+
from ..utils import prompt_options, prompt_text
|
26
26
|
|
27
27
|
|
28
28
|
class MlFramework(str, Enum):
|
@@ -72,9 +72,9 @@ def render_and_create(file_path: str, template: str, context: Dict[str, str]) ->
|
|
72
72
|
|
73
73
|
def new(
|
74
74
|
project_name: Annotated[
|
75
|
-
str,
|
75
|
+
Optional[str],
|
76
76
|
typer.Argument(metavar="project_name", help="The name of the project"),
|
77
|
-
],
|
77
|
+
] = None,
|
78
78
|
framework: Annotated[
|
79
79
|
Optional[MlFramework],
|
80
80
|
typer.Option(case_sensitive=False, help="The ML framework to use"),
|
@@ -83,6 +83,9 @@ def new(
|
|
83
83
|
"""Create new Flower project."""
|
84
84
|
print(f"Creating Flower project {project_name}...")
|
85
85
|
|
86
|
+
if project_name is None:
|
87
|
+
project_name = prompt_text("Please provide project name")
|
88
|
+
|
86
89
|
if framework is not None:
|
87
90
|
framework_str = str(framework.value)
|
88
91
|
else:
|
flwr/cli/utils.py
CHANGED
@@ -14,11 +14,24 @@
|
|
14
14
|
# ==============================================================================
|
15
15
|
"""Flower command line interface utils."""
|
16
16
|
|
17
|
-
from typing import List
|
17
|
+
from typing import List, cast
|
18
18
|
|
19
19
|
import typer
|
20
20
|
|
21
21
|
|
22
|
+
def prompt_text(text: str) -> str:
|
23
|
+
"""Ask user to enter text input."""
|
24
|
+
while True:
|
25
|
+
result = typer.prompt(
|
26
|
+
typer.style(f"\n💬 {text}", fg=typer.colors.MAGENTA, bold=True)
|
27
|
+
)
|
28
|
+
if len(result) > 0:
|
29
|
+
break
|
30
|
+
print(typer.style("❌ Invalid entry", fg=typer.colors.RED, bold=True))
|
31
|
+
|
32
|
+
return cast(str, result)
|
33
|
+
|
34
|
+
|
22
35
|
def prompt_options(text: str, options: List[str]) -> str:
|
23
36
|
"""Ask user to select one of the given options and return the selected item."""
|
24
37
|
# Turn options into a list with index as in " [ 0] quickstart-pytorch"
|
flwr/client/app.py
CHANGED
@@ -456,7 +456,19 @@ def _start_client_internal(
|
|
456
456
|
time.sleep(3) # Wait for 3s before asking again
|
457
457
|
continue
|
458
458
|
|
459
|
-
log(INFO, "
|
459
|
+
log(INFO, "")
|
460
|
+
log(
|
461
|
+
INFO,
|
462
|
+
"[RUN %s, ROUND %s]",
|
463
|
+
message.metadata.run_id,
|
464
|
+
message.metadata.group_id,
|
465
|
+
)
|
466
|
+
log(
|
467
|
+
INFO,
|
468
|
+
"Received: %s message %s",
|
469
|
+
message.metadata.message_type,
|
470
|
+
message.metadata.message_id,
|
471
|
+
)
|
460
472
|
|
461
473
|
# Handle control message
|
462
474
|
out_message, sleep_duration = handle_control_message(message)
|
@@ -484,7 +496,18 @@ def _start_client_internal(
|
|
484
496
|
|
485
497
|
# Send
|
486
498
|
send(out_message)
|
487
|
-
log(
|
499
|
+
log(
|
500
|
+
INFO,
|
501
|
+
"[RUN %s, ROUND %s]",
|
502
|
+
out_message.metadata.run_id,
|
503
|
+
out_message.metadata.group_id,
|
504
|
+
)
|
505
|
+
log(
|
506
|
+
INFO,
|
507
|
+
"Sent: %s reply to message %s",
|
508
|
+
out_message.metadata.message_type,
|
509
|
+
message.metadata.message_id,
|
510
|
+
)
|
488
511
|
|
489
512
|
# Unregister node
|
490
513
|
if delete_node is not None:
|
flwr/client/mod/__init__.py
CHANGED
@@ -17,7 +17,7 @@
|
|
17
17
|
|
18
18
|
from .centraldp_mods import adaptiveclipping_mod, fixedclipping_mod
|
19
19
|
from .localdp_mod import LocalDpMod
|
20
|
-
from .secure_aggregation
|
20
|
+
from .secure_aggregation import secagg_mod, secaggplus_mod
|
21
21
|
from .utils import make_ffn
|
22
22
|
|
23
23
|
__all__ = [
|
@@ -25,5 +25,6 @@ __all__ = [
|
|
25
25
|
"fixedclipping_mod",
|
26
26
|
"LocalDpMod",
|
27
27
|
"make_ffn",
|
28
|
+
"secagg_mod",
|
28
29
|
"secaggplus_mod",
|
29
30
|
]
|
@@ -0,0 +1,30 @@
|
|
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
|
+
"""Modifier for the SecAgg protocol."""
|
16
|
+
|
17
|
+
|
18
|
+
from flwr.client.typing import ClientAppCallable
|
19
|
+
from flwr.common import Context, Message
|
20
|
+
|
21
|
+
from .secaggplus_mod import secaggplus_mod
|
22
|
+
|
23
|
+
|
24
|
+
def secagg_mod(
|
25
|
+
msg: Message,
|
26
|
+
ctxt: Context,
|
27
|
+
call_next: ClientAppCallable,
|
28
|
+
) -> Message:
|
29
|
+
"""Handle incoming message and return results, following the SecAgg protocol."""
|
30
|
+
return secaggplus_mod(msg, ctxt, call_next)
|
@@ -178,9 +178,9 @@ def secaggplus_mod(
|
|
178
178
|
res = _setup(state, configs)
|
179
179
|
elif state.current_stage == Stage.SHARE_KEYS:
|
180
180
|
res = _share_keys(state, configs)
|
181
|
-
elif state.current_stage == Stage.
|
181
|
+
elif state.current_stage == Stage.COLLECT_MASKED_VECTORS:
|
182
182
|
fit = _get_fit_fn(msg, ctxt, call_next)
|
183
|
-
res =
|
183
|
+
res = _collect_masked_vectors(state, configs, fit)
|
184
184
|
elif state.current_stage == Stage.UNMASK:
|
185
185
|
res = _unmask(state, configs)
|
186
186
|
else:
|
@@ -199,7 +199,7 @@ def check_stage(current_stage: str, configs: ConfigsRecord) -> None:
|
|
199
199
|
# Check the existence of Config.STAGE
|
200
200
|
if Key.STAGE not in configs:
|
201
201
|
raise KeyError(
|
202
|
-
f"The required key '{Key.STAGE}' is missing from the
|
202
|
+
f"The required key '{Key.STAGE}' is missing from the ConfigsRecord."
|
203
203
|
)
|
204
204
|
|
205
205
|
# Check the value type of the Config.STAGE
|
@@ -215,7 +215,7 @@ def check_stage(current_stage: str, configs: ConfigsRecord) -> None:
|
|
215
215
|
if current_stage != Stage.UNMASK:
|
216
216
|
log(WARNING, "Restart from the setup stage")
|
217
217
|
# If stage is not "setup",
|
218
|
-
# the stage from
|
218
|
+
# the stage from configs should be the expected next stage
|
219
219
|
else:
|
220
220
|
stages = Stage.all()
|
221
221
|
expected_next_stage = stages[(stages.index(current_stage) + 1) % len(stages)]
|
@@ -229,7 +229,7 @@ def check_stage(current_stage: str, configs: ConfigsRecord) -> None:
|
|
229
229
|
# pylint: disable-next=too-many-branches
|
230
230
|
def check_configs(stage: str, configs: ConfigsRecord) -> None:
|
231
231
|
"""Check the validity of the configs."""
|
232
|
-
# Check
|
232
|
+
# Check configs for the setup stage
|
233
233
|
if stage == Stage.SETUP:
|
234
234
|
key_type_pairs = [
|
235
235
|
(Key.SAMPLE_NUMBER, int),
|
@@ -243,7 +243,7 @@ def check_configs(stage: str, configs: ConfigsRecord) -> None:
|
|
243
243
|
if key not in configs:
|
244
244
|
raise KeyError(
|
245
245
|
f"Stage {Stage.SETUP}: the required key '{key}' is "
|
246
|
-
"missing from the
|
246
|
+
"missing from the ConfigsRecord."
|
247
247
|
)
|
248
248
|
# Bool is a subclass of int in Python,
|
249
249
|
# so `isinstance(v, int)` will return True even if v is a boolean.
|
@@ -266,7 +266,7 @@ def check_configs(stage: str, configs: ConfigsRecord) -> None:
|
|
266
266
|
f"Stage {Stage.SHARE_KEYS}: "
|
267
267
|
f"the value for the key '{key}' must be a list of two bytes."
|
268
268
|
)
|
269
|
-
elif stage == Stage.
|
269
|
+
elif stage == Stage.COLLECT_MASKED_VECTORS:
|
270
270
|
key_type_pairs = [
|
271
271
|
(Key.CIPHERTEXT_LIST, bytes),
|
272
272
|
(Key.SOURCE_LIST, int),
|
@@ -274,9 +274,9 @@ def check_configs(stage: str, configs: ConfigsRecord) -> None:
|
|
274
274
|
for key, expected_type in key_type_pairs:
|
275
275
|
if key not in configs:
|
276
276
|
raise KeyError(
|
277
|
-
f"Stage {Stage.
|
277
|
+
f"Stage {Stage.COLLECT_MASKED_VECTORS}: "
|
278
278
|
f"the required key '{key}' is "
|
279
|
-
"missing from the
|
279
|
+
"missing from the ConfigsRecord."
|
280
280
|
)
|
281
281
|
if not isinstance(configs[key], list) or any(
|
282
282
|
elm
|
@@ -285,7 +285,7 @@ def check_configs(stage: str, configs: ConfigsRecord) -> None:
|
|
285
285
|
if type(elm) is not expected_type
|
286
286
|
):
|
287
287
|
raise TypeError(
|
288
|
-
f"Stage {Stage.
|
288
|
+
f"Stage {Stage.COLLECT_MASKED_VECTORS}: "
|
289
289
|
f"the value for the key '{key}' "
|
290
290
|
f"must be of type List[{expected_type.__name__}]"
|
291
291
|
)
|
@@ -299,7 +299,7 @@ def check_configs(stage: str, configs: ConfigsRecord) -> None:
|
|
299
299
|
raise KeyError(
|
300
300
|
f"Stage {Stage.UNMASK}: "
|
301
301
|
f"the required key '{key}' is "
|
302
|
-
"missing from the
|
302
|
+
"missing from the ConfigsRecord."
|
303
303
|
)
|
304
304
|
if not isinstance(configs[key], list) or any(
|
305
305
|
elm
|
@@ -414,7 +414,7 @@ def _share_keys(
|
|
414
414
|
|
415
415
|
|
416
416
|
# pylint: disable-next=too-many-locals
|
417
|
-
def
|
417
|
+
def _collect_masked_vectors(
|
418
418
|
state: SecAggPlusState,
|
419
419
|
configs: ConfigsRecord,
|
420
420
|
fit: Callable[[], FitRes],
|
flwr/common/logger.py
CHANGED
@@ -168,11 +168,10 @@ def warn_experimental_feature(name: str) -> None:
|
|
168
168
|
"""Warn the user when they use an experimental feature."""
|
169
169
|
log(
|
170
170
|
WARN,
|
171
|
-
"""
|
172
|
-
EXPERIMENTAL FEATURE: %s
|
171
|
+
"""EXPERIMENTAL FEATURE: %s
|
173
172
|
|
174
|
-
|
175
|
-
|
173
|
+
This is an experimental feature. It could change significantly or be removed
|
174
|
+
entirely in future versions of Flower.
|
176
175
|
""",
|
177
176
|
name,
|
178
177
|
)
|
@@ -182,11 +181,10 @@ def warn_deprecated_feature(name: str) -> None:
|
|
182
181
|
"""Warn the user when they use a deprecated feature."""
|
183
182
|
log(
|
184
183
|
WARN,
|
185
|
-
"""
|
186
|
-
DEPRECATED FEATURE: %s
|
184
|
+
"""DEPRECATED FEATURE: %s
|
187
185
|
|
188
|
-
|
189
|
-
|
186
|
+
This is a deprecated feature. It will be removed
|
187
|
+
entirely in future versions of Flower.
|
190
188
|
""",
|
191
189
|
name,
|
192
190
|
)
|
@@ -27,9 +27,9 @@ class Stage:
|
|
27
27
|
|
28
28
|
SETUP = "setup"
|
29
29
|
SHARE_KEYS = "share_keys"
|
30
|
-
|
30
|
+
COLLECT_MASKED_VECTORS = "collect_masked_vectors"
|
31
31
|
UNMASK = "unmask"
|
32
|
-
_stages = (SETUP, SHARE_KEYS,
|
32
|
+
_stages = (SETUP, SHARE_KEYS, COLLECT_MASKED_VECTORS, UNMASK)
|
33
33
|
|
34
34
|
@classmethod
|
35
35
|
def all(cls) -> tuple[str, str, str, str]:
|
@@ -122,7 +122,8 @@ class InMemoryState(State):
|
|
122
122
|
task_res.task_id = str(task_id)
|
123
123
|
task_res.task.created_at = created_at.isoformat()
|
124
124
|
task_res.task.ttl = ttl.isoformat()
|
125
|
-
self.
|
125
|
+
with self.lock:
|
126
|
+
self.task_res_store[task_id] = task_res
|
126
127
|
|
127
128
|
# Return the new task_id
|
128
129
|
return task_id
|
@@ -132,46 +133,47 @@ class InMemoryState(State):
|
|
132
133
|
if limit is not None and limit < 1:
|
133
134
|
raise AssertionError("`limit` must be >= 1")
|
134
135
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
136
|
+
with self.lock:
|
137
|
+
# Find TaskRes that were not delivered yet
|
138
|
+
task_res_list: List[TaskRes] = []
|
139
|
+
for _, task_res in self.task_res_store.items():
|
140
|
+
if (
|
141
|
+
UUID(task_res.task.ancestry[0]) in task_ids
|
142
|
+
and task_res.task.delivered_at == ""
|
143
|
+
):
|
144
|
+
task_res_list.append(task_res)
|
145
|
+
if limit and len(task_res_list) == limit:
|
146
|
+
break
|
145
147
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
148
|
+
# Mark all of them as delivered
|
149
|
+
delivered_at = now().isoformat()
|
150
|
+
for task_res in task_res_list:
|
151
|
+
task_res.task.delivered_at = delivered_at
|
150
152
|
|
151
|
-
|
152
|
-
|
153
|
+
# Return TaskRes
|
154
|
+
return task_res_list
|
153
155
|
|
154
156
|
def delete_tasks(self, task_ids: Set[UUID]) -> None:
|
155
157
|
"""Delete all delivered TaskIns/TaskRes pairs."""
|
156
158
|
task_ins_to_be_deleted: Set[UUID] = set()
|
157
159
|
task_res_to_be_deleted: Set[UUID] = set()
|
158
160
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
161
|
+
with self.lock:
|
162
|
+
for task_ins_id in task_ids:
|
163
|
+
# Find the task_id of the matching task_res
|
164
|
+
for task_res_id, task_res in self.task_res_store.items():
|
165
|
+
if UUID(task_res.task.ancestry[0]) != task_ins_id:
|
166
|
+
continue
|
167
|
+
if task_res.task.delivered_at == "":
|
168
|
+
continue
|
169
|
+
|
170
|
+
task_ins_to_be_deleted.add(task_ins_id)
|
171
|
+
task_res_to_be_deleted.add(task_res_id)
|
172
|
+
|
173
|
+
for task_id in task_ins_to_be_deleted:
|
172
174
|
del self.task_ins_store[task_id]
|
173
|
-
|
174
|
-
|
175
|
+
for task_id in task_res_to_be_deleted:
|
176
|
+
del self.task_res_store[task_id]
|
175
177
|
|
176
178
|
def num_task_ins(self) -> int:
|
177
179
|
"""Calculate the number of task_ins in store.
|
flwr/server/workflow/__init__.py
CHANGED
@@ -16,9 +16,10 @@
|
|
16
16
|
|
17
17
|
|
18
18
|
from .default_workflows import DefaultWorkflow
|
19
|
-
from .secure_aggregation import SecAggPlusWorkflow
|
19
|
+
from .secure_aggregation import SecAggPlusWorkflow, SecAggWorkflow
|
20
20
|
|
21
21
|
__all__ = [
|
22
22
|
"DefaultWorkflow",
|
23
23
|
"SecAggPlusWorkflow",
|
24
|
+
"SecAggWorkflow",
|
24
25
|
]
|
@@ -15,8 +15,9 @@
|
|
15
15
|
"""Legacy default workflows."""
|
16
16
|
|
17
17
|
|
18
|
+
import io
|
18
19
|
import timeit
|
19
|
-
from logging import
|
20
|
+
from logging import INFO
|
20
21
|
from typing import Optional, cast
|
21
22
|
|
22
23
|
import flwr.common.recordset_compat as compat
|
@@ -58,16 +59,18 @@ class DefaultWorkflow:
|
|
58
59
|
)
|
59
60
|
|
60
61
|
# Initialize parameters
|
62
|
+
log(INFO, "[INIT]")
|
61
63
|
default_init_params_workflow(driver, context)
|
62
64
|
|
63
65
|
# Run federated learning for num_rounds
|
64
|
-
log(INFO, "FL starting")
|
65
66
|
start_time = timeit.default_timer()
|
66
67
|
cfg = ConfigsRecord()
|
67
68
|
cfg[Key.START_TIME] = start_time
|
68
69
|
context.state.configs_records[MAIN_CONFIGS_RECORD] = cfg
|
69
70
|
|
70
71
|
for current_round in range(1, context.config.num_rounds + 1):
|
72
|
+
log(INFO, "")
|
73
|
+
log(INFO, "[ROUND %s]", current_round)
|
71
74
|
cfg[Key.CURRENT_ROUND] = current_round
|
72
75
|
|
73
76
|
# Fit round
|
@@ -79,22 +82,19 @@ class DefaultWorkflow:
|
|
79
82
|
# Evaluate round
|
80
83
|
self.evaluate_workflow(driver, context)
|
81
84
|
|
82
|
-
# Bookkeeping
|
85
|
+
# Bookkeeping and log results
|
83
86
|
end_time = timeit.default_timer()
|
84
87
|
elapsed = end_time - start_time
|
85
|
-
log(INFO, "FL finished in %s", elapsed)
|
86
|
-
|
87
|
-
# Log results
|
88
88
|
hist = context.history
|
89
|
-
log(INFO, "
|
90
|
-
log(
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
log(INFO, "
|
89
|
+
log(INFO, "")
|
90
|
+
log(INFO, "[SUMMARY]")
|
91
|
+
log(INFO, "Run finished %s rounds in %.2fs", context.config.num_rounds, elapsed)
|
92
|
+
for idx, line in enumerate(io.StringIO(str(hist))):
|
93
|
+
if idx == 0:
|
94
|
+
log(INFO, "%s", line.strip("\n"))
|
95
|
+
else:
|
96
|
+
log(INFO, "\t%s", line.strip("\n"))
|
97
|
+
log(INFO, "")
|
98
98
|
|
99
99
|
# Terminate the thread
|
100
100
|
f_stop.set()
|
@@ -107,12 +107,11 @@ def default_init_params_workflow(driver: Driver, context: Context) -> None:
|
|
107
107
|
if not isinstance(context, LegacyContext):
|
108
108
|
raise TypeError(f"Expect a LegacyContext, but get {type(context).__name__}.")
|
109
109
|
|
110
|
-
log(INFO, "Initializing global parameters")
|
111
110
|
parameters = context.strategy.initialize_parameters(
|
112
111
|
client_manager=context.client_manager
|
113
112
|
)
|
114
113
|
if parameters is not None:
|
115
|
-
log(INFO, "Using initial parameters provided by strategy")
|
114
|
+
log(INFO, "Using initial global parameters provided by strategy")
|
116
115
|
paramsrecord = compat.parameters_to_parametersrecord(
|
117
116
|
parameters, keep_input=True
|
118
117
|
)
|
@@ -140,7 +139,7 @@ def default_init_params_workflow(driver: Driver, context: Context) -> None:
|
|
140
139
|
context.state.parameters_records[MAIN_PARAMS_RECORD] = paramsrecord
|
141
140
|
|
142
141
|
# Evaluate initial parameters
|
143
|
-
log(INFO, "Evaluating initial parameters")
|
142
|
+
log(INFO, "Evaluating initial global parameters")
|
144
143
|
parameters = compat.parametersrecord_to_parameters(paramsrecord, keep_input=True)
|
145
144
|
res = context.strategy.evaluate(0, parameters=parameters)
|
146
145
|
if res is not None:
|
@@ -186,7 +185,9 @@ def default_centralized_evaluation_workflow(_: Driver, context: Context) -> None
|
|
186
185
|
)
|
187
186
|
|
188
187
|
|
189
|
-
def default_fit_workflow(
|
188
|
+
def default_fit_workflow( # pylint: disable=R0914
|
189
|
+
driver: Driver, context: Context
|
190
|
+
) -> None:
|
190
191
|
"""Execute the default workflow for a single fit round."""
|
191
192
|
if not isinstance(context, LegacyContext):
|
192
193
|
raise TypeError(f"Expect a LegacyContext, but get {type(context).__name__}.")
|
@@ -207,12 +208,11 @@ def default_fit_workflow(driver: Driver, context: Context) -> None:
|
|
207
208
|
)
|
208
209
|
|
209
210
|
if not client_instructions:
|
210
|
-
log(INFO, "
|
211
|
+
log(INFO, "configure_fit: no clients selected, cancel")
|
211
212
|
return
|
212
213
|
log(
|
213
|
-
|
214
|
-
"
|
215
|
-
current_round,
|
214
|
+
INFO,
|
215
|
+
"configure_fit: strategy sampled %s clients (out of %s)",
|
216
216
|
len(client_instructions),
|
217
217
|
context.client_manager.num_available(),
|
218
218
|
)
|
@@ -236,14 +236,14 @@ def default_fit_workflow(driver: Driver, context: Context) -> None:
|
|
236
236
|
# collect `fit` results from all clients participating in this round
|
237
237
|
messages = list(driver.send_and_receive(out_messages))
|
238
238
|
del out_messages
|
239
|
+
num_failures = len([msg for msg in messages if msg.has_error()])
|
239
240
|
|
240
241
|
# No exception/failure handling currently
|
241
242
|
log(
|
242
|
-
|
243
|
-
"
|
244
|
-
|
245
|
-
|
246
|
-
0,
|
243
|
+
INFO,
|
244
|
+
"aggregate_fit: received %s results and %s failures",
|
245
|
+
len(messages) - num_failures,
|
246
|
+
num_failures,
|
247
247
|
)
|
248
248
|
|
249
249
|
# Aggregate training results
|
@@ -288,12 +288,11 @@ def default_evaluate_workflow(driver: Driver, context: Context) -> None:
|
|
288
288
|
client_manager=context.client_manager,
|
289
289
|
)
|
290
290
|
if not client_instructions:
|
291
|
-
log(INFO, "
|
291
|
+
log(INFO, "configure_evaluate: no clients selected, skipping evaluation")
|
292
292
|
return
|
293
293
|
log(
|
294
|
-
|
295
|
-
"
|
296
|
-
current_round,
|
294
|
+
INFO,
|
295
|
+
"configure_evaluate: strategy sampled %s clients (out of %s)",
|
297
296
|
len(client_instructions),
|
298
297
|
context.client_manager.num_available(),
|
299
298
|
)
|
@@ -317,14 +316,14 @@ def default_evaluate_workflow(driver: Driver, context: Context) -> None:
|
|
317
316
|
# collect `evaluate` results from all clients participating in this round
|
318
317
|
messages = list(driver.send_and_receive(out_messages))
|
319
318
|
del out_messages
|
319
|
+
num_failures = len([msg for msg in messages if msg.has_error()])
|
320
320
|
|
321
321
|
# No exception/failure handling currently
|
322
322
|
log(
|
323
|
-
|
324
|
-
"
|
325
|
-
|
326
|
-
|
327
|
-
0,
|
323
|
+
INFO,
|
324
|
+
"aggregate_evaluate: received %s results and %s failures",
|
325
|
+
len(messages) - num_failures,
|
326
|
+
num_failures,
|
328
327
|
)
|
329
328
|
|
330
329
|
# Aggregate the evaluation results
|
@@ -0,0 +1,112 @@
|
|
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
|
+
"""Workflow for the SecAgg protocol."""
|
16
|
+
|
17
|
+
|
18
|
+
from typing import Optional, Union
|
19
|
+
|
20
|
+
from .secaggplus_workflow import SecAggPlusWorkflow
|
21
|
+
|
22
|
+
|
23
|
+
class SecAggWorkflow(SecAggPlusWorkflow):
|
24
|
+
"""The workflow for the SecAgg protocol.
|
25
|
+
|
26
|
+
The SecAgg protocol ensures the secure summation of integer vectors owned by
|
27
|
+
multiple parties, without accessing any individual integer vector. This workflow
|
28
|
+
allows the server to compute the weighted average of model parameters across all
|
29
|
+
clients, ensuring individual contributions remain private. This is achieved by
|
30
|
+
clients sending both, a weighting factor and a weighted version of the locally
|
31
|
+
updated parameters, both of which are masked for privacy. Specifically, each
|
32
|
+
client uploads "[w, w * params]" with masks, where weighting factor 'w' is the
|
33
|
+
number of examples ('num_examples') and 'params' represents the model parameters
|
34
|
+
('parameters') from the client's `FitRes`. The server then aggregates these
|
35
|
+
contributions to compute the weighted average of model parameters.
|
36
|
+
|
37
|
+
The protocol involves four main stages:
|
38
|
+
- 'setup': Send SecAgg configuration to clients and collect their public keys.
|
39
|
+
- 'share keys': Broadcast public keys among clients and collect encrypted secret
|
40
|
+
key shares.
|
41
|
+
- 'collect masked vectors': Forward encrypted secret key shares to target clients
|
42
|
+
and collect masked model parameters.
|
43
|
+
- 'unmask': Collect secret key shares to decrypt and aggregate the model parameters.
|
44
|
+
|
45
|
+
Only the aggregated model parameters are exposed and passed to
|
46
|
+
`Strategy.aggregate_fit`, ensuring individual data privacy.
|
47
|
+
|
48
|
+
Parameters
|
49
|
+
----------
|
50
|
+
reconstruction_threshold : Union[int, float]
|
51
|
+
The minimum number of shares required to reconstruct a client's private key,
|
52
|
+
or, if specified as a float, it represents the proportion of the total number
|
53
|
+
of shares needed for reconstruction. This threshold ensures privacy by allowing
|
54
|
+
for the recovery of contributions from dropped clients during aggregation,
|
55
|
+
without compromising individual client data.
|
56
|
+
max_weight : Optional[float] (default: 1000.0)
|
57
|
+
The maximum value of the weight that can be assigned to any single client's
|
58
|
+
update during the weighted average calculation on the server side, e.g., in the
|
59
|
+
FedAvg algorithm.
|
60
|
+
clipping_range : float, optional (default: 8.0)
|
61
|
+
The range within which model parameters are clipped before quantization.
|
62
|
+
This parameter ensures each model parameter is bounded within
|
63
|
+
[-clipping_range, clipping_range], facilitating quantization.
|
64
|
+
quantization_range : int, optional (default: 4194304, this equals 2**22)
|
65
|
+
The size of the range into which floating-point model parameters are quantized,
|
66
|
+
mapping each parameter to an integer in [0, quantization_range-1]. This
|
67
|
+
facilitates cryptographic operations on the model updates.
|
68
|
+
modulus_range : int, optional (default: 4294967296, this equals 2**32)
|
69
|
+
The range of values from which random mask entries are uniformly sampled
|
70
|
+
([0, modulus_range-1]). `modulus_range` must be less than 4294967296.
|
71
|
+
Please use 2**n values for `modulus_range` to prevent overflow issues.
|
72
|
+
timeout : Optional[float] (default: None)
|
73
|
+
The timeout duration in seconds. If specified, the workflow will wait for
|
74
|
+
replies for this duration each time. If `None`, there is no time limit and
|
75
|
+
the workflow will wait until replies for all messages are received.
|
76
|
+
|
77
|
+
Notes
|
78
|
+
-----
|
79
|
+
- Each client's private key is split into N shares under the SecAgg protocol, where
|
80
|
+
N is the number of selected clients.
|
81
|
+
- Generally, higher `reconstruction_threshold` means better privacy guarantees but
|
82
|
+
less tolerance to dropouts.
|
83
|
+
- Too large `max_weight` may compromise the precision of the quantization.
|
84
|
+
- `modulus_range` must be 2**n and larger than `quantization_range`.
|
85
|
+
- When `reconstruction_threshold` is a float, it is interpreted as the proportion of
|
86
|
+
the number of all selected clients needed for the reconstruction of a private key.
|
87
|
+
This feature enables flexibility in setting the security threshold relative to the
|
88
|
+
number of selected clients.
|
89
|
+
- `reconstruction_threshold`, and the quantization parameters
|
90
|
+
(`clipping_range`, `quantization_range`, `modulus_range`) play critical roles in
|
91
|
+
balancing privacy, robustness, and efficiency within the SecAgg protocol.
|
92
|
+
"""
|
93
|
+
|
94
|
+
def __init__( # pylint: disable=R0913
|
95
|
+
self,
|
96
|
+
reconstruction_threshold: Union[int, float],
|
97
|
+
*,
|
98
|
+
max_weight: float = 1000.0,
|
99
|
+
clipping_range: float = 8.0,
|
100
|
+
quantization_range: int = 4194304,
|
101
|
+
modulus_range: int = 4294967296,
|
102
|
+
timeout: Optional[float] = None,
|
103
|
+
) -> None:
|
104
|
+
super().__init__(
|
105
|
+
num_shares=1.0,
|
106
|
+
reconstruction_threshold=reconstruction_threshold,
|
107
|
+
max_weight=max_weight,
|
108
|
+
clipping_range=clipping_range,
|
109
|
+
quantization_range=quantization_range,
|
110
|
+
modulus_range=modulus_range,
|
111
|
+
timeout=timeout,
|
112
|
+
)
|
@@ -17,7 +17,7 @@
|
|
17
17
|
|
18
18
|
import random
|
19
19
|
from dataclasses import dataclass, field
|
20
|
-
from logging import ERROR, WARN
|
20
|
+
from logging import DEBUG, ERROR, INFO, WARN
|
21
21
|
from typing import Dict, List, Optional, Set, Union, cast
|
22
22
|
|
23
23
|
import flwr.common.recordset_compat as compat
|
@@ -101,7 +101,7 @@ class SecAggPlusWorkflow:
|
|
101
101
|
- 'setup': Send SecAgg+ configuration to clients and collect their public keys.
|
102
102
|
- 'share keys': Broadcast public keys among clients and collect encrypted secret
|
103
103
|
key shares.
|
104
|
-
- 'collect masked
|
104
|
+
- 'collect masked vectors': Forward encrypted secret key shares to target clients
|
105
105
|
and collect masked model parameters.
|
106
106
|
- 'unmask': Collect secret key shares to decrypt and aggregate the model parameters.
|
107
107
|
|
@@ -195,12 +195,15 @@ class SecAggPlusWorkflow:
|
|
195
195
|
steps = (
|
196
196
|
self.setup_stage,
|
197
197
|
self.share_keys_stage,
|
198
|
-
self.
|
198
|
+
self.collect_masked_vectors_stage,
|
199
199
|
self.unmask_stage,
|
200
200
|
)
|
201
|
+
log(INFO, "Secure aggregation commencing.")
|
201
202
|
for step in steps:
|
202
203
|
if not step(driver, context, state):
|
204
|
+
log(INFO, "Secure aggregation halted.")
|
203
205
|
return
|
206
|
+
log(INFO, "Secure aggregation completed.")
|
204
207
|
|
205
208
|
def _check_init_params(self) -> None: # pylint: disable=R0912
|
206
209
|
# Check `num_shares`
|
@@ -287,6 +290,16 @@ class SecAggPlusWorkflow:
|
|
287
290
|
proxy_fitins_lst = context.strategy.configure_fit(
|
288
291
|
current_round, parameters, context.client_manager
|
289
292
|
)
|
293
|
+
if not proxy_fitins_lst:
|
294
|
+
log(INFO, "configure_fit: no clients selected, cancel")
|
295
|
+
return False
|
296
|
+
log(
|
297
|
+
INFO,
|
298
|
+
"configure_fit: strategy sampled %s clients (out of %s)",
|
299
|
+
len(proxy_fitins_lst),
|
300
|
+
context.client_manager.num_available(),
|
301
|
+
)
|
302
|
+
|
290
303
|
state.nid_to_fitins = {
|
291
304
|
proxy.node_id: compat.fitins_to_recordset(fitins, False)
|
292
305
|
for proxy, fitins in proxy_fitins_lst
|
@@ -362,12 +375,22 @@ class SecAggPlusWorkflow:
|
|
362
375
|
ttl="",
|
363
376
|
)
|
364
377
|
|
378
|
+
log(
|
379
|
+
DEBUG,
|
380
|
+
"[Stage 0] Sending configurations to %s clients.",
|
381
|
+
len(state.active_node_ids),
|
382
|
+
)
|
365
383
|
msgs = driver.send_and_receive(
|
366
384
|
[make(node_id) for node_id in state.active_node_ids], timeout=self.timeout
|
367
385
|
)
|
368
386
|
state.active_node_ids = {
|
369
387
|
msg.metadata.src_node_id for msg in msgs if not msg.has_error()
|
370
388
|
}
|
389
|
+
log(
|
390
|
+
DEBUG,
|
391
|
+
"[Stage 0] Received public keys from %s clients.",
|
392
|
+
len(state.active_node_ids),
|
393
|
+
)
|
371
394
|
|
372
395
|
for msg in msgs:
|
373
396
|
if msg.has_error():
|
@@ -401,12 +424,22 @@ class SecAggPlusWorkflow:
|
|
401
424
|
)
|
402
425
|
|
403
426
|
# Broadcast public keys to clients and receive secret key shares
|
427
|
+
log(
|
428
|
+
DEBUG,
|
429
|
+
"[Stage 1] Forwarding public keys to %s clients.",
|
430
|
+
len(state.active_node_ids),
|
431
|
+
)
|
404
432
|
msgs = driver.send_and_receive(
|
405
433
|
[make(node_id) for node_id in state.active_node_ids], timeout=self.timeout
|
406
434
|
)
|
407
435
|
state.active_node_ids = {
|
408
436
|
msg.metadata.src_node_id for msg in msgs if not msg.has_error()
|
409
437
|
}
|
438
|
+
log(
|
439
|
+
DEBUG,
|
440
|
+
"[Stage 1] Received encrypted key shares from %s clients.",
|
441
|
+
len(state.active_node_ids),
|
442
|
+
)
|
410
443
|
|
411
444
|
# Build forward packet list dictionary
|
412
445
|
srcs: List[int] = []
|
@@ -437,16 +470,16 @@ class SecAggPlusWorkflow:
|
|
437
470
|
|
438
471
|
return self._check_threshold(state)
|
439
472
|
|
440
|
-
def
|
473
|
+
def collect_masked_vectors_stage(
|
441
474
|
self, driver: Driver, context: LegacyContext, state: WorkflowState
|
442
475
|
) -> bool:
|
443
|
-
"""Execute the 'collect masked
|
476
|
+
"""Execute the 'collect masked vectors' stage."""
|
444
477
|
cfg = context.state.configs_records[MAIN_CONFIGS_RECORD]
|
445
478
|
|
446
|
-
# Send secret key shares to clients (plus FitIns) and collect masked
|
479
|
+
# Send secret key shares to clients (plus FitIns) and collect masked vectors
|
447
480
|
def make(nid: int) -> Message:
|
448
481
|
cfgs_dict = {
|
449
|
-
Key.STAGE: Stage.
|
482
|
+
Key.STAGE: Stage.COLLECT_MASKED_VECTORS,
|
450
483
|
Key.CIPHERTEXT_LIST: state.forward_ciphertexts[nid],
|
451
484
|
Key.SOURCE_LIST: state.forward_srcs[nid],
|
452
485
|
}
|
@@ -461,12 +494,22 @@ class SecAggPlusWorkflow:
|
|
461
494
|
ttl="",
|
462
495
|
)
|
463
496
|
|
497
|
+
log(
|
498
|
+
DEBUG,
|
499
|
+
"[Stage 2] Forwarding encrypted key shares to %s clients.",
|
500
|
+
len(state.active_node_ids),
|
501
|
+
)
|
464
502
|
msgs = driver.send_and_receive(
|
465
503
|
[make(node_id) for node_id in state.active_node_ids], timeout=self.timeout
|
466
504
|
)
|
467
505
|
state.active_node_ids = {
|
468
506
|
msg.metadata.src_node_id for msg in msgs if not msg.has_error()
|
469
507
|
}
|
508
|
+
log(
|
509
|
+
DEBUG,
|
510
|
+
"[Stage 2] Received masked vectors from %s clients.",
|
511
|
+
len(state.active_node_ids),
|
512
|
+
)
|
470
513
|
|
471
514
|
# Clear cache
|
472
515
|
del state.forward_ciphertexts, state.forward_srcs, state.nid_to_fitins
|
@@ -487,7 +530,7 @@ class SecAggPlusWorkflow:
|
|
487
530
|
|
488
531
|
return self._check_threshold(state)
|
489
532
|
|
490
|
-
def unmask_stage( # pylint: disable=R0912, R0914
|
533
|
+
def unmask_stage( # pylint: disable=R0912, R0914, R0915
|
491
534
|
self, driver: Driver, context: LegacyContext, state: WorkflowState
|
492
535
|
) -> bool:
|
493
536
|
"""Execute the 'unmask' stage."""
|
@@ -516,12 +559,22 @@ class SecAggPlusWorkflow:
|
|
516
559
|
ttl="",
|
517
560
|
)
|
518
561
|
|
562
|
+
log(
|
563
|
+
DEBUG,
|
564
|
+
"[Stage 3] Requesting key shares from %s clients to remove masks.",
|
565
|
+
len(state.active_node_ids),
|
566
|
+
)
|
519
567
|
msgs = driver.send_and_receive(
|
520
568
|
[make(node_id) for node_id in state.active_node_ids], timeout=self.timeout
|
521
569
|
)
|
522
570
|
state.active_node_ids = {
|
523
571
|
msg.metadata.src_node_id for msg in msgs if not msg.has_error()
|
524
572
|
}
|
573
|
+
log(
|
574
|
+
DEBUG,
|
575
|
+
"[Stage 3] Received key shares from %s clients.",
|
576
|
+
len(state.active_node_ids),
|
577
|
+
)
|
525
578
|
|
526
579
|
# Build collected shares dict
|
527
580
|
collected_shares_dict: Dict[int, List[bytes]] = {}
|
@@ -534,7 +587,7 @@ class SecAggPlusWorkflow:
|
|
534
587
|
for owner_nid, share in zip(nids, shares):
|
535
588
|
collected_shares_dict[owner_nid].append(share)
|
536
589
|
|
537
|
-
# Remove
|
590
|
+
# Remove masks for every active client after collect_masked_vectors stage
|
538
591
|
masked_vector = state.aggregate_ndarrays
|
539
592
|
del state.aggregate_ndarrays
|
540
593
|
for nid, share_list in collected_shares_dict.items():
|
@@ -585,6 +638,15 @@ class SecAggPlusWorkflow:
|
|
585
638
|
vec += offset
|
586
639
|
vec *= inv_dq_total_ratio
|
587
640
|
state.aggregate_ndarrays = aggregated_vector
|
641
|
+
|
642
|
+
# No exception/failure handling currently
|
643
|
+
log(
|
644
|
+
INFO,
|
645
|
+
"aggregate_fit: received %s results and %s failures",
|
646
|
+
1,
|
647
|
+
0,
|
648
|
+
)
|
649
|
+
|
588
650
|
final_fitres = FitRes(
|
589
651
|
status=Status(code=Code.OK, message=""),
|
590
652
|
parameters=ndarrays_to_parameters(aggregated_vector),
|
@@ -597,5 +659,18 @@ class SecAggPlusWorkflow:
|
|
597
659
|
False,
|
598
660
|
driver.run_id, # type: ignore
|
599
661
|
)
|
600
|
-
context.strategy.aggregate_fit(
|
662
|
+
aggregated_result = context.strategy.aggregate_fit(
|
663
|
+
current_round, [(empty_proxy, final_fitres)], []
|
664
|
+
)
|
665
|
+
parameters_aggregated, metrics_aggregated = aggregated_result
|
666
|
+
|
667
|
+
# Update the parameters and write history
|
668
|
+
if parameters_aggregated:
|
669
|
+
paramsrecord = compat.parameters_to_parametersrecord(
|
670
|
+
parameters_aggregated, True
|
671
|
+
)
|
672
|
+
context.state.parameters_records[MAIN_PARAMS_RECORD] = paramsrecord
|
673
|
+
context.history.add_metrics_distributed_fit(
|
674
|
+
server_round=current_round, metrics=metrics_aggregated
|
675
|
+
)
|
601
676
|
return True
|
{flwr_nightly-1.8.0.dev20240310.dist-info → flwr_nightly-1.8.0.dev20240311.dist-info}/RECORD
RENAMED
@@ -4,7 +4,7 @@ flwr/cli/app.py,sha256=38thPnMydBmNAxNE9mz4By-KdRUhJfoUgeDuAxMYF_U,1095
|
|
4
4
|
flwr/cli/example.py,sha256=EGPYLMQf2MgcYRn5aPp_eYYUA39M8dm69PUM4zhlHuk,2184
|
5
5
|
flwr/cli/flower_toml.py,sha256=KSimz9EW9SkZdPnRRSj5XquQfXkCoPZHvLXvu8b4wQ4,3785
|
6
6
|
flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
|
7
|
-
flwr/cli/new/new.py,sha256=
|
7
|
+
flwr/cli/new/new.py,sha256=hEXuk7O1caO2XwfuTYvZdxZjPEovpFekX31KLYp-WRY,4522
|
8
8
|
flwr/cli/new/templates/__init__.py,sha256=4luU8RL-CK8JJCstQ_ON809W9bNTkY1l9zSaPKBkgwY,725
|
9
9
|
flwr/cli/new/templates/app/README.md.tpl,sha256=YcrwN6q0dkHaAW5Cual0mnny2_LBs5mQ7mQsMaRzs0I,686
|
10
10
|
flwr/cli/new/templates/app/__init__.py,sha256=DU7QMY7IhMQyuwm_tja66xU0KXTWQFqzfTqwg-_NJdE,729
|
@@ -24,9 +24,9 @@ flwr/cli/new/templates/app/requirements.pytorch.txt.tpl,sha256=CiAhFk8IASOnhTbDT
|
|
24
24
|
flwr/cli/new/templates/app/requirements.tensorflow.txt.tpl,sha256=MsiO0GbUe35h5giVMaE2YykKMAhtC5ccAc_4EcmJUNs,209
|
25
25
|
flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
|
26
26
|
flwr/cli/run/run.py,sha256=6iP2XW8jvB6gePLc7qGNdueG1W4Qai-rLsev-wKkPEg,3286
|
27
|
-
flwr/cli/utils.py,sha256=
|
27
|
+
flwr/cli/utils.py,sha256=_V2BlFVNNG2naZrq227fZ8o4TxBN_hB-4fQsen9uQoo,2300
|
28
28
|
flwr/client/__init__.py,sha256=futk_IdY_N1h8BTve4Iru51bxm7H1gv58ZPIXWi5XUA,1187
|
29
|
-
flwr/client/app.py,sha256=
|
29
|
+
flwr/client/app.py,sha256=xCCpP-fMEFdTEaSWOP93JPIDfjRhx5Z1uI1h6YlJteo,24784
|
30
30
|
flwr/client/client.py,sha256=Vp9UkOkoHdNfn6iMYZsj_5m_GICiFfUlKEVaLad-YhM,8183
|
31
31
|
flwr/client/client_app.py,sha256=AzSCU8ZiBtOLgcrGEJxIU4DV4Jf7WQHxY5QxESfWo-w,8079
|
32
32
|
flwr/client/dpfedavg_numpy_client.py,sha256=9Tnig4iml2J88HBKNahegjXjbfvIQyBtaIQaqjbeqsA,7435
|
@@ -37,11 +37,12 @@ flwr/client/grpc_rere_client/connection.py,sha256=3kpnUbS06rNQ969EybGx7zZfQPc2Jm
|
|
37
37
|
flwr/client/message_handler/__init__.py,sha256=abHvBRJJiiaAMNgeILQbMOa6h8WqMK2BcnvxwQZFpic,719
|
38
38
|
flwr/client/message_handler/message_handler.py,sha256=SlIU-l6GgB3wfA1Qq2x7z1SSSCXO4SO4pM122QNyTvU,6516
|
39
39
|
flwr/client/message_handler/task_handler.py,sha256=ZDJBKmrn2grRMNl1rU1iGs7FiMHL5VmZiSp_6h9GHVU,1824
|
40
|
-
flwr/client/mod/__init__.py,sha256=
|
40
|
+
flwr/client/mod/__init__.py,sha256=OowF__U4CR0PoNSvniuLFr53RiUBVruZCiRam5uVtnk,1030
|
41
41
|
flwr/client/mod/centraldp_mods.py,sha256=8Jiy42idsYs_gF6g5kZpjeyG98vyuSoYq9E-OibyrNk,5125
|
42
42
|
flwr/client/mod/localdp_mod.py,sha256=fFLtqHAzWuMG5tL2O_Q4nrkqwGR6AzHkA82Avkajrdc,4492
|
43
|
-
flwr/client/mod/secure_aggregation/__init__.py,sha256=
|
44
|
-
flwr/client/mod/secure_aggregation/
|
43
|
+
flwr/client/mod/secure_aggregation/__init__.py,sha256=Qo2R-NqsyoP0oX73TyDfQRu9P6DCNXhgqGbhmGIBaJA,849
|
44
|
+
flwr/client/mod/secure_aggregation/secagg_mod.py,sha256=wI9tuIEvMUETz-wVIEbPYvh-1nK9CEylBLGoVpNhL94,1095
|
45
|
+
flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=qzQiePAl3cz4MT_THiNmEfUiJoawJDiMd0B62HEYegY,19892
|
45
46
|
flwr/client/mod/utils.py,sha256=lvETHcCYsSWz7h8I772hCV_kZspxqlMqzriMZ-SxmKc,1226
|
46
47
|
flwr/client/node_state.py,sha256=KTTs_l4I0jBM7IsSsbAGjhfL_yZC3QANbzyvyfZBRDM,1778
|
47
48
|
flwr/client/node_state_tests.py,sha256=gPwz0zf2iuDSa11jedkur_u3Xm7lokIDG5ALD2MCvSw,2195
|
@@ -59,7 +60,7 @@ flwr/common/differential_privacy_constants.py,sha256=c7b7tqgvT7yMK0XN9ndiTBs4mQf
|
|
59
60
|
flwr/common/dp.py,sha256=Hc3lLHihjexbJaD_ft31gdv9XRcwOTgDBwJzICuok3A,2004
|
60
61
|
flwr/common/exit_handlers.py,sha256=2Nt0wLhc17KQQsLPFSRAjjhUiEFfJK6tNozdGiIY4Fs,2812
|
61
62
|
flwr/common/grpc.py,sha256=HimjpTtIY3Vfqtlq3u-CYWjqAl9rSn0uo3A8JjhUmwQ,2273
|
62
|
-
flwr/common/logger.py,sha256=
|
63
|
+
flwr/common/logger.py,sha256=Plhm9fsi4ewb90eGALQZ9xBkR0cGEsckX5RLSMEaS3M,6118
|
63
64
|
flwr/common/message.py,sha256=Z-k3a1HgoyWBm3NS0_bphfYbJtJ8YqxfXR68AvXXkuA,9808
|
64
65
|
flwr/common/object_ref.py,sha256=ELoUCAFO-vbjJC41CGpa-WBG2SLYe3ErW-d9YCG3zqA,4961
|
65
66
|
flwr/common/parameter.py,sha256=-bFAUayToYDF50FZGrBC1hQYJCQDtB2bbr3ZuVLMtdE,2095
|
@@ -78,7 +79,7 @@ flwr/common/secure_aggregation/crypto/shamir.py,sha256=yY35ZgHlB4YyGW_buG-1X-0M-
|
|
78
79
|
flwr/common/secure_aggregation/crypto/symmetric_encryption.py,sha256=-zDyQoTsHHQjR7o-92FNIikg1zM_Ke9yynaD5u2BXbQ,3546
|
79
80
|
flwr/common/secure_aggregation/ndarrays_arithmetic.py,sha256=66mNQCz64r7qzvXwFrXP6zz7YMi8EkTOABN7KulkKc4,3026
|
80
81
|
flwr/common/secure_aggregation/quantization.py,sha256=appui7GGrkRPsupF59TkapeV4Na_CyPi73JtJ1pimdI,2310
|
81
|
-
flwr/common/secure_aggregation/secaggplus_constants.py,sha256=
|
82
|
+
flwr/common/secure_aggregation/secaggplus_constants.py,sha256=Fh7-n6pgL4TUnHpNYXo8iW-n5cOGQgQa-c7RcU80tqQ,2183
|
82
83
|
flwr/common/secure_aggregation/secaggplus_utils.py,sha256=87bNZX6CmQekj935R4u3m5hsaEkkfKtGSA-VG2c-O9w,3221
|
83
84
|
flwr/common/serde.py,sha256=tmREmZtV3af0tnx9vp45KWGlUUqoVQyJMszkUNvVkKM,22014
|
84
85
|
flwr/common/telemetry.py,sha256=JkFB6WBOskqAJfzSM-l6tQfRiSi2oiysClfg0-5T7NY,7782
|
@@ -178,7 +179,7 @@ flwr/server/superlink/fleet/vce/backend/backend.py,sha256=LJsKl7oixVvptcG98Rd9ej
|
|
178
179
|
flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=BYgzVH1uz8nk6mOP6GhgSxjrdCe7xtkzb7nhPbKStFM,6317
|
179
180
|
flwr/server/superlink/fleet/vce/vce_api.py,sha256=Yq4i9fduafnoWSHCLn0mmkCTS9oZqwycH8gbKa4bPXo,11168
|
180
181
|
flwr/server/superlink/state/__init__.py,sha256=ij-7Ms-hyordQdRmGQxY1-nVa4OhixJ0jr7_YDkys0s,1003
|
181
|
-
flwr/server/superlink/state/in_memory_state.py,sha256
|
182
|
+
flwr/server/superlink/state/in_memory_state.py,sha256=-dEhGm_y5zdamyRr57FrEJDgnNko2dytyqc8vW_q7vw,8265
|
182
183
|
flwr/server/superlink/state/sqlite_state.py,sha256=Adc2g1DecAN9Cl9F8lekuTb885mIHiOi6sQv4nxbmSc,21203
|
183
184
|
flwr/server/superlink/state/state.py,sha256=JtsI92HfdKd8KzBQ9Om7A7xwngDXVxtET2Bk9aQ7nao,5316
|
184
185
|
flwr/server/superlink/state/state_factory.py,sha256=91cSB-KOAFM37z7T098WxTkVeKNaAZ_mTI75snn2_tk,1654
|
@@ -186,11 +187,12 @@ flwr/server/typing.py,sha256=2zSG-KuDAgwFPuzgVjTLDaEqJ8gXXGqFR2RD-qIk730,913
|
|
186
187
|
flwr/server/utils/__init__.py,sha256=RQVbo-bcsVtp_lJBf7dL5w01FbLrr7v3YedeGp5_YMs,908
|
187
188
|
flwr/server/utils/tensorboard.py,sha256=k0G6bqsLx7wfYbH2KtXsDYcOCfyIeE12-hefXA7lZdg,5485
|
188
189
|
flwr/server/utils/validator.py,sha256=IJN2475yyD_i_9kg_SJ_JodIuZh58ufpWGUDQRAqu2s,4740
|
189
|
-
flwr/server/workflow/__init__.py,sha256=
|
190
|
+
flwr/server/workflow/__init__.py,sha256=SXY0XkwbkezFBxxrFB5hKUtmtAgnYISBkPouR1V71ss,902
|
190
191
|
flwr/server/workflow/constant.py,sha256=q4DLdR8Krlxuewq2AQjwTL75hphxE5ODNz4AhViHMXk,1082
|
191
|
-
flwr/server/workflow/default_workflows.py,sha256=
|
192
|
-
flwr/server/workflow/secure_aggregation/__init__.py,sha256=
|
193
|
-
flwr/server/workflow/secure_aggregation/
|
192
|
+
flwr/server/workflow/default_workflows.py,sha256=fOGQiY0LrRPOWLjr3zlZoEapts3dgg4XdHravb4-shE,12601
|
193
|
+
flwr/server/workflow/secure_aggregation/__init__.py,sha256=3XlgDOjD_hcukTGl6Bc1B-8M_dPlVSJuTbvXIbiO-Ic,880
|
194
|
+
flwr/server/workflow/secure_aggregation/secagg_workflow.py,sha256=wpAkYPId0nfK6SgpUAtsCni4_MQLd-uqJ81tUKu3xlI,5838
|
195
|
+
flwr/server/workflow/secure_aggregation/secaggplus_workflow.py,sha256=9LxvdTgaJ9AG3E3z30a-Z556wiR9kXNljEUzW-GjHsE,28954
|
194
196
|
flwr/simulation/__init__.py,sha256=hpoKzdovrH0_Cf8HIcXxQxyUUb3BiSk-WUNLf5STHcc,1400
|
195
197
|
flwr/simulation/app.py,sha256=WqJxdXTEuehwMW605p5NMmvBbKYx5tuqnV3Mp7jSWXM,13904
|
196
198
|
flwr/simulation/ray_transport/__init__.py,sha256=FsaAnzC4cw4DqoouBCix6496k29jACkfeIam55BvW9g,734
|
@@ -198,8 +200,8 @@ flwr/simulation/ray_transport/ray_actor.py,sha256=zRETW_xuCAOLRFaYnQ-q3IBSz0LIv_
|
|
198
200
|
flwr/simulation/ray_transport/ray_client_proxy.py,sha256=L49gtigsf4vTQgRiqzOgcPEuS_l-EuTj29Ohw6ekbSI,6721
|
199
201
|
flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
|
200
202
|
flwr/simulation/run_simulation.py,sha256=1JMP5nYFeGrZzcpw_Q0aDvyla2AvRz5aFJw1i1InSvs,15681
|
201
|
-
flwr_nightly-1.8.0.
|
202
|
-
flwr_nightly-1.8.0.
|
203
|
-
flwr_nightly-1.8.0.
|
204
|
-
flwr_nightly-1.8.0.
|
205
|
-
flwr_nightly-1.8.0.
|
203
|
+
flwr_nightly-1.8.0.dev20240311.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
204
|
+
flwr_nightly-1.8.0.dev20240311.dist-info/METADATA,sha256=BCxj-WJIOnMV5R2uqtLoBFk1KLRmgsLjScmSY0YPscI,15257
|
205
|
+
flwr_nightly-1.8.0.dev20240311.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
206
|
+
flwr_nightly-1.8.0.dev20240311.dist-info/entry_points.txt,sha256=utu2wybGyYJSTtsB2ktY_gmy-XtMFo9EFZdishX0zR4,320
|
207
|
+
flwr_nightly-1.8.0.dev20240311.dist-info/RECORD,,
|
{flwr_nightly-1.8.0.dev20240310.dist-info → flwr_nightly-1.8.0.dev20240311.dist-info}/LICENSE
RENAMED
File without changes
|
File without changes
|
File without changes
|