flwr-nightly 1.8.0.dev20240310__py3-none-any.whl → 1.8.0.dev20240311__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.
- 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
|