flwr 1.16.0__py3-none-any.whl → 1.18.0__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.
Files changed (248) hide show
  1. flwr/__init__.py +1 -1
  2. flwr/cli/__init__.py +1 -1
  3. flwr/cli/app.py +21 -2
  4. flwr/cli/build.py +1 -1
  5. flwr/cli/cli_user_auth_interceptor.py +1 -1
  6. flwr/cli/config_utils.py +53 -17
  7. flwr/cli/example.py +1 -1
  8. flwr/cli/install.py +1 -1
  9. flwr/cli/log.py +1 -1
  10. flwr/cli/login/__init__.py +1 -1
  11. flwr/cli/login/login.py +12 -1
  12. flwr/cli/ls.py +1 -1
  13. flwr/cli/new/__init__.py +1 -1
  14. flwr/cli/new/new.py +4 -4
  15. flwr/cli/new/templates/__init__.py +1 -1
  16. flwr/cli/new/templates/app/__init__.py +1 -1
  17. flwr/cli/new/templates/app/code/__init__.py +1 -1
  18. flwr/cli/new/templates/app/code/flwr_tune/__init__.py +1 -1
  19. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +5 -5
  20. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +1 -1
  21. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  22. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +4 -4
  23. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  24. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  25. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  26. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  27. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  28. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  29. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  30. flwr/cli/run/__init__.py +1 -1
  31. flwr/cli/run/run.py +6 -10
  32. flwr/cli/stop.py +1 -1
  33. flwr/cli/utils.py +11 -12
  34. flwr/client/__init__.py +1 -1
  35. flwr/client/app.py +58 -56
  36. flwr/client/client.py +1 -1
  37. flwr/client/client_app.py +231 -166
  38. flwr/client/clientapp/__init__.py +1 -1
  39. flwr/client/clientapp/app.py +3 -3
  40. flwr/client/clientapp/clientappio_servicer.py +1 -1
  41. flwr/client/clientapp/utils.py +1 -1
  42. flwr/client/dpfedavg_numpy_client.py +1 -1
  43. flwr/client/grpc_adapter_client/__init__.py +1 -1
  44. flwr/client/grpc_adapter_client/connection.py +1 -1
  45. flwr/client/grpc_client/__init__.py +1 -1
  46. flwr/client/grpc_client/connection.py +37 -34
  47. flwr/client/grpc_rere_client/__init__.py +1 -1
  48. flwr/client/grpc_rere_client/client_interceptor.py +1 -1
  49. flwr/client/grpc_rere_client/connection.py +1 -1
  50. flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
  51. flwr/client/heartbeat.py +1 -1
  52. flwr/client/message_handler/__init__.py +1 -1
  53. flwr/client/message_handler/message_handler.py +28 -28
  54. flwr/client/mod/__init__.py +3 -3
  55. flwr/client/mod/centraldp_mods.py +8 -8
  56. flwr/client/mod/comms_mods.py +17 -23
  57. flwr/client/mod/localdp_mod.py +10 -10
  58. flwr/client/mod/secure_aggregation/__init__.py +1 -1
  59. flwr/client/mod/secure_aggregation/secagg_mod.py +1 -1
  60. flwr/client/mod/secure_aggregation/secaggplus_mod.py +32 -32
  61. flwr/client/mod/utils.py +1 -1
  62. flwr/client/nodestate/__init__.py +1 -1
  63. flwr/client/nodestate/in_memory_nodestate.py +1 -1
  64. flwr/client/nodestate/nodestate.py +1 -1
  65. flwr/client/nodestate/nodestate_factory.py +1 -1
  66. flwr/client/numpy_client.py +1 -1
  67. flwr/client/rest_client/__init__.py +1 -1
  68. flwr/client/rest_client/connection.py +1 -1
  69. flwr/client/run_info_store.py +3 -3
  70. flwr/client/supernode/__init__.py +1 -1
  71. flwr/client/supernode/app.py +1 -1
  72. flwr/client/typing.py +1 -1
  73. flwr/common/__init__.py +13 -5
  74. flwr/common/address.py +1 -1
  75. flwr/common/args.py +1 -1
  76. flwr/common/auth_plugin/__init__.py +1 -1
  77. flwr/common/auth_plugin/auth_plugin.py +1 -1
  78. flwr/common/config.py +5 -5
  79. flwr/common/constant.py +7 -7
  80. flwr/common/context.py +5 -5
  81. flwr/common/date.py +1 -1
  82. flwr/common/differential_privacy.py +1 -1
  83. flwr/common/differential_privacy_constants.py +1 -1
  84. flwr/common/dp.py +1 -1
  85. flwr/common/event_log_plugin/event_log_plugin.py +3 -3
  86. flwr/common/exit/exit.py +6 -6
  87. flwr/common/exit_handlers.py +1 -1
  88. flwr/common/grpc.py +1 -1
  89. flwr/common/logger.py +3 -3
  90. flwr/common/message.py +344 -102
  91. flwr/common/object_ref.py +1 -1
  92. flwr/common/parameter.py +1 -1
  93. flwr/common/pyproject.py +1 -1
  94. flwr/common/record/__init__.py +9 -5
  95. flwr/common/record/arrayrecord.py +626 -0
  96. flwr/common/record/{configsrecord.py → configrecord.py} +83 -37
  97. flwr/common/record/conversion_utils.py +2 -2
  98. flwr/common/record/{metricsrecord.py → metricrecord.py} +90 -44
  99. flwr/common/record/recorddict.py +337 -0
  100. flwr/common/record/typeddict.py +1 -1
  101. flwr/common/recorddict_compat.py +410 -0
  102. flwr/common/retry_invoker.py +10 -10
  103. flwr/common/secure_aggregation/__init__.py +1 -1
  104. flwr/common/secure_aggregation/crypto/__init__.py +1 -1
  105. flwr/common/secure_aggregation/crypto/shamir.py +52 -30
  106. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -1
  107. flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
  108. flwr/common/secure_aggregation/quantization.py +1 -1
  109. flwr/common/secure_aggregation/secaggplus_constants.py +2 -2
  110. flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
  111. flwr/common/serde.py +67 -72
  112. flwr/common/telemetry.py +2 -2
  113. flwr/common/typing.py +9 -9
  114. flwr/common/version.py +1 -1
  115. flwr/proto/__init__.py +1 -1
  116. flwr/proto/exec_pb2.py +3 -3
  117. flwr/proto/exec_pb2.pyi +3 -3
  118. flwr/proto/message_pb2.py +12 -12
  119. flwr/proto/message_pb2.pyi +9 -9
  120. flwr/proto/recorddict_pb2.py +70 -0
  121. flwr/proto/{recordset_pb2.pyi → recorddict_pb2.pyi} +35 -35
  122. flwr/proto/run_pb2.py +31 -31
  123. flwr/proto/run_pb2.pyi +3 -3
  124. flwr/server/__init__.py +4 -2
  125. flwr/server/app.py +67 -12
  126. flwr/server/client_manager.py +1 -1
  127. flwr/server/client_proxy.py +1 -1
  128. flwr/server/compat/__init__.py +3 -3
  129. flwr/server/compat/app.py +12 -12
  130. flwr/server/compat/app_utils.py +17 -17
  131. flwr/server/compat/{driver_client_proxy.py → grid_client_proxy.py} +39 -39
  132. flwr/server/compat/legacy_context.py +1 -1
  133. flwr/server/criterion.py +1 -1
  134. flwr/server/fleet_event_log_interceptor.py +94 -0
  135. flwr/server/{driver → grid}/__init__.py +8 -7
  136. flwr/server/{driver/driver.py → grid/grid.py} +48 -19
  137. flwr/server/{driver/grpc_driver.py → grid/grpc_grid.py} +87 -64
  138. flwr/server/{driver/inmemory_driver.py → grid/inmemory_grid.py} +24 -34
  139. flwr/server/history.py +1 -1
  140. flwr/server/run_serverapp.py +5 -5
  141. flwr/server/server.py +1 -1
  142. flwr/server/server_app.py +98 -71
  143. flwr/server/server_config.py +1 -1
  144. flwr/server/serverapp/__init__.py +1 -1
  145. flwr/server/serverapp/app.py +11 -11
  146. flwr/server/serverapp_components.py +1 -1
  147. flwr/server/strategy/__init__.py +1 -1
  148. flwr/server/strategy/aggregate.py +1 -1
  149. flwr/server/strategy/bulyan.py +2 -2
  150. flwr/server/strategy/dp_adaptive_clipping.py +17 -17
  151. flwr/server/strategy/dp_fixed_clipping.py +17 -17
  152. flwr/server/strategy/dpfedavg_adaptive.py +1 -1
  153. flwr/server/strategy/dpfedavg_fixed.py +1 -1
  154. flwr/server/strategy/fault_tolerant_fedavg.py +1 -1
  155. flwr/server/strategy/fedadagrad.py +1 -1
  156. flwr/server/strategy/fedadam.py +1 -1
  157. flwr/server/strategy/fedavg.py +1 -1
  158. flwr/server/strategy/fedavg_android.py +1 -1
  159. flwr/server/strategy/fedavgm.py +1 -1
  160. flwr/server/strategy/fedmedian.py +1 -1
  161. flwr/server/strategy/fedopt.py +1 -1
  162. flwr/server/strategy/fedprox.py +1 -1
  163. flwr/server/strategy/fedtrimmedavg.py +1 -1
  164. flwr/server/strategy/fedxgb_bagging.py +1 -1
  165. flwr/server/strategy/fedxgb_cyclic.py +1 -1
  166. flwr/server/strategy/fedxgb_nn_avg.py +3 -2
  167. flwr/server/strategy/fedyogi.py +1 -1
  168. flwr/server/strategy/krum.py +1 -1
  169. flwr/server/strategy/qfedavg.py +1 -1
  170. flwr/server/strategy/strategy.py +1 -1
  171. flwr/server/superlink/__init__.py +1 -1
  172. flwr/server/superlink/ffs/__init__.py +1 -1
  173. flwr/server/superlink/ffs/disk_ffs.py +1 -1
  174. flwr/server/superlink/ffs/ffs.py +1 -1
  175. flwr/server/superlink/ffs/ffs_factory.py +1 -1
  176. flwr/server/superlink/fleet/__init__.py +1 -1
  177. flwr/server/superlink/fleet/grpc_adapter/__init__.py +1 -1
  178. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
  179. flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
  180. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
  181. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
  182. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
  183. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +13 -13
  184. flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
  185. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
  186. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +1 -1
  187. flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
  188. flwr/server/superlink/fleet/message_handler/message_handler.py +1 -1
  189. flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
  190. flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -1
  191. flwr/server/superlink/fleet/vce/__init__.py +1 -1
  192. flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
  193. flwr/server/superlink/fleet/vce/backend/backend.py +3 -3
  194. flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -3
  195. flwr/server/superlink/fleet/vce/vce_api.py +2 -4
  196. flwr/server/superlink/linkstate/__init__.py +1 -1
  197. flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -9
  198. flwr/server/superlink/linkstate/linkstate.py +5 -5
  199. flwr/server/superlink/linkstate/linkstate_factory.py +1 -1
  200. flwr/server/superlink/linkstate/sqlite_linkstate.py +62 -28
  201. flwr/server/superlink/linkstate/utils.py +94 -28
  202. flwr/server/superlink/{driver → serverappio}/__init__.py +1 -1
  203. flwr/server/superlink/{driver → serverappio}/serverappio_grpc.py +1 -1
  204. flwr/server/superlink/{driver → serverappio}/serverappio_servicer.py +4 -4
  205. flwr/server/superlink/simulation/__init__.py +1 -1
  206. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  207. flwr/server/superlink/simulation/simulationio_servicer.py +3 -3
  208. flwr/server/superlink/utils.py +1 -1
  209. flwr/server/typing.py +4 -4
  210. flwr/server/utils/__init__.py +1 -1
  211. flwr/server/utils/tensorboard.py +1 -1
  212. flwr/server/utils/validator.py +5 -5
  213. flwr/server/workflow/__init__.py +1 -1
  214. flwr/server/workflow/constant.py +1 -1
  215. flwr/server/workflow/default_workflows.py +49 -58
  216. flwr/server/workflow/secure_aggregation/__init__.py +1 -1
  217. flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -1
  218. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +49 -51
  219. flwr/simulation/__init__.py +1 -1
  220. flwr/simulation/app.py +3 -3
  221. flwr/simulation/legacy_app.py +1 -1
  222. flwr/simulation/ray_transport/__init__.py +1 -1
  223. flwr/simulation/ray_transport/ray_actor.py +5 -3
  224. flwr/simulation/ray_transport/ray_client_proxy.py +35 -33
  225. flwr/simulation/ray_transport/utils.py +1 -1
  226. flwr/simulation/run_simulation.py +17 -17
  227. flwr/simulation/simulationio_connection.py +1 -1
  228. flwr/superexec/__init__.py +1 -1
  229. flwr/superexec/app.py +1 -1
  230. flwr/superexec/deployment.py +5 -5
  231. flwr/superexec/exec_event_log_interceptor.py +135 -0
  232. flwr/superexec/exec_grpc.py +11 -5
  233. flwr/superexec/exec_servicer.py +3 -3
  234. flwr/superexec/exec_user_auth_interceptor.py +19 -3
  235. flwr/superexec/executor.py +4 -4
  236. flwr/superexec/simulation.py +4 -4
  237. {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/METADATA +3 -3
  238. flwr-1.18.0.dist-info/RECORD +332 -0
  239. flwr/common/record/parametersrecord.py +0 -339
  240. flwr/common/record/recordset.py +0 -209
  241. flwr/common/recordset_compat.py +0 -418
  242. flwr/proto/recordset_pb2.py +0 -70
  243. flwr-1.16.0.dist-info/LICENSE +0 -202
  244. flwr-1.16.0.dist-info/RECORD +0 -331
  245. /flwr/proto/{recordset_pb2_grpc.py → recorddict_pb2_grpc.py} +0 -0
  246. /flwr/proto/{recordset_pb2_grpc.pyi → recorddict_pb2_grpc.pyi} +0 -0
  247. {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/WHEEL +0 -0
  248. {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/entry_points.txt +0 -0
@@ -1,339 +0,0 @@
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
- """ParametersRecord and Array."""
16
-
17
-
18
- from __future__ import annotations
19
-
20
- from collections import OrderedDict
21
- from dataclasses import dataclass
22
- from io import BytesIO
23
- from typing import Any, cast, overload
24
-
25
- import numpy as np
26
-
27
- from ..constant import SType
28
- from ..typing import NDArray
29
- from .typeddict import TypedDict
30
-
31
-
32
- def _raise_array_init_error() -> None:
33
- raise TypeError(
34
- f"Invalid arguments for {Array.__qualname__}. Expected either a "
35
- "NumPy ndarray, or explicit dtype/shape/stype/data values."
36
- )
37
-
38
-
39
- @dataclass
40
- class Array:
41
- """Array type.
42
-
43
- A dataclass containing serialized data from an array-like or tensor-like object
44
- along with metadata about it. The class can be initialized in one of two ways:
45
-
46
- 1. By specifying explicit values for `dtype`, `shape`, `stype`, and `data`.
47
- 2. By providing a NumPy ndarray (via the `ndarray` argument).
48
-
49
- In scenario (2), the `dtype`, `shape`, `stype`, and `data` are automatically
50
- derived from the input. In scenario (1), these fields must be specified manually.
51
-
52
- Parameters
53
- ----------
54
- dtype : Optional[str] (default: None)
55
- A string representing the data type of the serialized object (e.g. `"float32"`).
56
- Only required if you are not passing in a ndarray.
57
-
58
- shape : Optional[list[int]] (default: None)
59
- A list representing the shape of the unserialized array-like object. Only
60
- required if you are not passing in a ndarray.
61
-
62
- stype : Optional[str] (default: None)
63
- A string indicating the serialization mechanism used to generate the bytes in
64
- `data` from an array-like or tensor-like object. Only required if you are not
65
- passing in a ndarray.
66
-
67
- data : Optional[bytes] (default: None)
68
- A buffer of bytes containing the data. Only required if you are not passing in
69
- a ndarray.
70
-
71
- ndarray : Optional[NDArray] (default: None)
72
- A NumPy ndarray. If provided, the `dtype`, `shape`, `stype`, and `data`
73
- fields are derived automatically from it.
74
-
75
- Examples
76
- --------
77
- Initializing by specifying all fields directly:
78
-
79
- >>> arr1 = Array(
80
- >>> dtype="float32",
81
- >>> shape=[3, 3],
82
- >>> stype="numpy.ndarray",
83
- >>> data=b"serialized_data...",
84
- >>> )
85
-
86
- Initializing with a NumPy ndarray:
87
-
88
- >>> import numpy as np
89
- >>> arr2 = Array(np.random.randn(3, 3))
90
- """
91
-
92
- dtype: str
93
- shape: list[int]
94
- stype: str
95
- data: bytes
96
-
97
- @overload
98
- def __init__( # noqa: E704
99
- self, dtype: str, shape: list[int], stype: str, data: bytes
100
- ) -> None: ...
101
-
102
- @overload
103
- def __init__(self, ndarray: NDArray) -> None: ... # noqa: E704
104
-
105
- def __init__( # pylint: disable=too-many-arguments, too-many-locals
106
- self,
107
- *args: Any,
108
- dtype: str | None = None,
109
- shape: list[int] | None = None,
110
- stype: str | None = None,
111
- data: bytes | None = None,
112
- ndarray: NDArray | None = None,
113
- ) -> None:
114
- # Determine the initialization method and validate input arguments.
115
- # Support two initialization formats:
116
- # 1. Array(dtype: str, shape: list[int], stype: str, data: bytes)
117
- # 2. Array(ndarray: NDArray)
118
-
119
- # Initialize all arguments
120
- # If more than 4 positional arguments are provided, raise an error.
121
- if len(args) > 4:
122
- _raise_array_init_error()
123
- all_args = [None] * 4
124
- for i, arg in enumerate(args):
125
- all_args[i] = arg
126
- init_method: str | None = None # Track which init method is being used
127
-
128
- # Try to assign a value to all_args[index] if it's not already set.
129
- # If an initialization method is provided, update init_method.
130
- def _try_set_arg(index: int, arg: Any, method: str) -> None:
131
- # Skip if arg is None
132
- if arg is None:
133
- return
134
- # Raise an error if all_args[index] is already set
135
- if all_args[index] is not None:
136
- _raise_array_init_error()
137
- # Raise an error if a different initialization method is already set
138
- nonlocal init_method
139
- if init_method is not None and init_method != method:
140
- _raise_array_init_error()
141
- # Set init_method and all_args[index]
142
- if init_method is None:
143
- init_method = method
144
- all_args[index] = arg
145
-
146
- # Try to set keyword arguments in all_args
147
- _try_set_arg(0, dtype, "direct")
148
- _try_set_arg(1, shape, "direct")
149
- _try_set_arg(2, stype, "direct")
150
- _try_set_arg(3, data, "direct")
151
- _try_set_arg(0, ndarray, "ndarray")
152
-
153
- # Check if all arguments are correctly set
154
- all_args = [arg for arg in all_args if arg is not None]
155
-
156
- # Handle direct field initialization
157
- if not init_method or init_method == "direct":
158
- if (
159
- len(all_args) == 4 # pylint: disable=too-many-boolean-expressions
160
- and isinstance(all_args[0], str)
161
- and isinstance(all_args[1], list)
162
- and all(isinstance(i, int) for i in all_args[1])
163
- and isinstance(all_args[2], str)
164
- and isinstance(all_args[3], bytes)
165
- ):
166
- self.dtype, self.shape, self.stype, self.data = all_args
167
- return
168
-
169
- # Handle NumPy array
170
- if not init_method or init_method == "ndarray":
171
- if len(all_args) == 1 and isinstance(all_args[0], np.ndarray):
172
- self.__dict__.update(self.from_numpy_ndarray(all_args[0]).__dict__)
173
- return
174
-
175
- _raise_array_init_error()
176
-
177
- @classmethod
178
- def from_numpy_ndarray(cls, ndarray: NDArray) -> Array:
179
- """Create Array from NumPy ndarray."""
180
- assert isinstance(
181
- ndarray, np.ndarray
182
- ), f"Expected NumPy ndarray, got {type(ndarray)}"
183
- buffer = BytesIO()
184
- # WARNING: NEVER set allow_pickle to true.
185
- # Reason: loading pickled data can execute arbitrary code
186
- # Source: https://numpy.org/doc/stable/reference/generated/numpy.save.html
187
- np.save(buffer, ndarray, allow_pickle=False)
188
- data = buffer.getvalue()
189
- return Array(
190
- dtype=str(ndarray.dtype),
191
- shape=list(ndarray.shape),
192
- stype=SType.NUMPY,
193
- data=data,
194
- )
195
-
196
- def numpy(self) -> NDArray:
197
- """Return the array as a NumPy array."""
198
- if self.stype != SType.NUMPY:
199
- raise TypeError(
200
- f"Unsupported serialization type for numpy conversion: '{self.stype}'"
201
- )
202
- bytes_io = BytesIO(self.data)
203
- # WARNING: NEVER set allow_pickle to true.
204
- # Reason: loading pickled data can execute arbitrary code
205
- # Source: https://numpy.org/doc/stable/reference/generated/numpy.load.html
206
- ndarray_deserialized = np.load(bytes_io, allow_pickle=False)
207
- return cast(NDArray, ndarray_deserialized)
208
-
209
-
210
- def _check_key(key: str) -> None:
211
- """Check if key is of expected type."""
212
- if not isinstance(key, str):
213
- raise TypeError(f"Key must be of type `str` but `{type(key)}` was passed.")
214
-
215
-
216
- def _check_value(value: Array) -> None:
217
- if not isinstance(value, Array):
218
- raise TypeError(
219
- f"Value must be of type `{Array}` but `{type(value)}` was passed."
220
- )
221
-
222
-
223
- class ParametersRecord(TypedDict[str, Array]):
224
- r"""Parameters record.
225
-
226
- A dataclass storing named Arrays in order. This means that it holds entries as an
227
- OrderedDict[str, Array]. ParametersRecord objects can be viewed as an equivalent to
228
- PyTorch's state_dict, but holding serialised tensors instead. A
229
- :code:`ParametersRecord` is one of the types of records that a
230
- `flwr.common.RecordSet <flwr.common.RecordSet.html#recordset>`_ supports and
231
- can therefore be used to construct :code:`common.Message` objects.
232
-
233
- Parameters
234
- ----------
235
- array_dict : Optional[OrderedDict[str, Array]]
236
- A dictionary that stores serialized array-like or tensor-like objects.
237
- keep_input : bool (default: False)
238
- A boolean indicating whether parameters should be deleted from the input
239
- dictionary immediately after adding them to the record. If False, the
240
- dictionary passed to `set_parameters()` will be empty once exiting from that
241
- function. This is the desired behaviour when working with very large
242
- models/tensors/arrays. However, if you plan to continue working with your
243
- parameters after adding it to the record, set this flag to True. When set
244
- to True, the data is duplicated in memory.
245
-
246
- Examples
247
- --------
248
- The usage of :code:`ParametersRecord` is envisioned for storing data arrays (e.g.
249
- parameters of a machine learning model). These first need to be serialized into
250
- a :code:`flwr.common.Array` data structure.
251
-
252
- Let's see some examples:
253
-
254
- >>> import numpy as np
255
- >>> from flwr.common import ParametersRecord
256
- >>>
257
- >>> # Let's create a simple NumPy array
258
- >>> arr_np = np.random.randn(3, 3)
259
- >>>
260
- >>> # If we print it
261
- >>> array([[-1.84242409, -1.01539537, -0.46528405],
262
- >>> [ 0.32991896, 0.55540414, 0.44085534],
263
- >>> [-0.10758364, 1.97619858, -0.37120501]])
264
- >>>
265
- >>> # Let's create an Array out of it
266
- >>> arr = Array(arr_np)
267
- >>>
268
- >>> # If we print it you'll see (note the binary data)
269
- >>> Array(dtype='float64', shape=[3,3], stype='numpy.ndarray', data=b'@\x99\x18...')
270
- >>>
271
- >>> # Adding it to a ParametersRecord:
272
- >>> p_record = ParametersRecord({"my_array": arr})
273
-
274
- Now that the NumPy array is embedded into a :code:`ParametersRecord` it could be
275
- sent if added as part of a :code:`common.Message` or it could be saved as a
276
- persistent state of a :code:`ClientApp` via its context. Regardless of the usecase,
277
- we will sooner or later want to recover the array in its original NumPy
278
- representation. For the example above, where the array was serialized using the
279
- built-in utility function, deserialization can be done as follows:
280
-
281
- >>> # Use the Array's built-in method
282
- >>> arr_np_d = arr.numpy()
283
- >>>
284
- >>> # If printed, it will show the exact same data as above:
285
- >>> array([[-1.84242409, -1.01539537, -0.46528405],
286
- >>> [ 0.32991896, 0.55540414, 0.44085534],
287
- >>> [-0.10758364, 1.97619858, -0.37120501]])
288
-
289
- If you need finer control on how your arrays are serialized and deserialized, you
290
- can construct :code:`Array` objects directly like this:
291
-
292
- >>> from flwr.common import Array
293
- >>> # Serialize your array and construct Array object
294
- >>> arr = Array(
295
- >>> data=ndarray.tobytes(),
296
- >>> dtype=str(ndarray.dtype),
297
- >>> stype="", # Could be used in a deserialization function
298
- >>> shape=list(ndarray.shape),
299
- >>> )
300
- >>>
301
- >>> # Then you can deserialize it like this
302
- >>> arr_np_d = np.frombuffer(
303
- >>> buffer=array.data,
304
- >>> dtype=array.dtype,
305
- >>> ).reshape(array.shape)
306
-
307
- Note that different arrays (e.g. from PyTorch, Tensorflow) might require different
308
- serialization mechanism. Howerver, they often support a conversion to NumPy,
309
- therefore allowing to use the same or similar steps as in the example above.
310
- """
311
-
312
- def __init__(
313
- self,
314
- array_dict: OrderedDict[str, Array] | None = None,
315
- keep_input: bool = False,
316
- ) -> None:
317
- super().__init__(_check_key, _check_value)
318
- if array_dict:
319
- for k in list(array_dict.keys()):
320
- self[k] = array_dict[k]
321
- if not keep_input:
322
- del array_dict[k]
323
-
324
- def count_bytes(self) -> int:
325
- """Return number of Bytes stored in this object.
326
-
327
- Note that a small amount of Bytes might also be included in this counting that
328
- correspond to metadata of the serialized object (e.g. of NumPy array) needed for
329
- deseralization.
330
- """
331
- num_bytes = 0
332
-
333
- for k, v in self.items():
334
- num_bytes += len(v.data)
335
-
336
- # We also count the bytes footprint of the keys
337
- num_bytes += len(k)
338
-
339
- return num_bytes
@@ -1,209 +0,0 @@
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
- """RecordSet."""
16
-
17
-
18
- from __future__ import annotations
19
-
20
- from logging import WARN
21
- from textwrap import indent
22
- from typing import TypeVar, Union, cast
23
-
24
- from ..logger import log
25
- from .configsrecord import ConfigsRecord
26
- from .metricsrecord import MetricsRecord
27
- from .parametersrecord import ParametersRecord
28
- from .typeddict import TypedDict
29
-
30
- RecordType = Union[ParametersRecord, MetricsRecord, ConfigsRecord]
31
-
32
- T = TypeVar("T")
33
-
34
-
35
- def _check_key(key: str) -> None:
36
- if not isinstance(key, str):
37
- raise TypeError(
38
- f"Expected `{str.__name__}`, but "
39
- f"received `{type(key).__name__}` for the key."
40
- )
41
-
42
-
43
- def _check_value(value: RecordType) -> None:
44
- if not isinstance(value, (ParametersRecord, MetricsRecord, ConfigsRecord)):
45
- raise TypeError(
46
- f"Expected `{ParametersRecord.__name__}`, `{MetricsRecord.__name__}`, "
47
- f"or `{ConfigsRecord.__name__}` but received "
48
- f"`{type(value).__name__}` for the value."
49
- )
50
-
51
-
52
- class _SyncedDict(TypedDict[str, T]):
53
- """A synchronized dictionary that mirrors changes to an underlying RecordSet.
54
-
55
- This dictionary ensures that any modifications (set or delete operations)
56
- are automatically reflected in the associated `RecordSet`. Only values of
57
- the specified `allowed_type` are permitted.
58
- """
59
-
60
- def __init__(self, ref_recordset: RecordSet, allowed_type: type[T]) -> None:
61
- if not issubclass(
62
- allowed_type, (ParametersRecord, MetricsRecord, ConfigsRecord)
63
- ):
64
- raise TypeError(f"{allowed_type} is not a valid type.")
65
- super().__init__(_check_key, self.check_value)
66
- self.recordset = ref_recordset
67
- self.allowed_type = allowed_type
68
-
69
- def __setitem__(self, key: str, value: T) -> None:
70
- super().__setitem__(key, value)
71
- self.recordset[key] = cast(RecordType, value)
72
-
73
- def __delitem__(self, key: str) -> None:
74
- super().__delitem__(key)
75
- del self.recordset[key]
76
-
77
- def check_value(self, value: T) -> None:
78
- """Check if value is of expected type."""
79
- if not isinstance(value, self.allowed_type):
80
- raise TypeError(
81
- f"Expected `{self.allowed_type.__name__}`, but "
82
- f"received `{type(value).__name__}` for the value."
83
- )
84
-
85
-
86
- class RecordSet(TypedDict[str, RecordType]):
87
- """RecordSet stores groups of parameters, metrics and configs.
88
-
89
- A :class:`RecordSet` is the unified mechanism by which parameters,
90
- metrics and configs can be either stored as part of a :class:`Context`
91
- in your apps or communicated as part of a :class:`Message` between
92
- your apps.
93
-
94
- Parameters
95
- ----------
96
- parameters_records : Optional[Dict[str, ParametersRecord]]
97
- A dictionary of :code:`ParametersRecords` that can be used to record
98
- and communicate model parameters and high-dimensional arrays.
99
- metrics_records : Optional[Dict[str, MetricsRecord]]
100
- A dictionary of :code:`MetricsRecord` that can be used to record
101
- and communicate scalar-valued metrics that are the result of performing
102
- and action, for example, by a :code:`ClientApp`.
103
- configs_records : Optional[Dict[str, ConfigsRecord]]
104
- A dictionary of :code:`ConfigsRecord` that can be used to record
105
- and communicate configuration values to an entity (e.g. to a
106
- :code:`ClientApp`)
107
- for it to adjust how an action is performed.
108
-
109
- Examples
110
- --------
111
- A :code:`RecordSet` can hold three types of records, each designed
112
- with an specific purpose. What is common to all of them is that they
113
- are Python dictionaries designed to ensure that each key-value pair
114
- adheres to specified data types.
115
-
116
- Let's see an example.
117
-
118
- >>> from flwr.common import RecordSet
119
- >>> from flwr.common import ConfigsRecord, MetricsRecord, ParametersRecord
120
- >>>
121
- >>> # Let's begin with an empty record
122
- >>> my_recordset = RecordSet()
123
- >>>
124
- >>> # We can create a ConfigsRecord
125
- >>> c_record = ConfigsRecord({"lr": 0.1, "batch-size": 128})
126
- >>> # Adding it to the record_set would look like this
127
- >>> my_recordset["my_config"] = c_record
128
- >>>
129
- >>> # We can create a MetricsRecord following a similar process
130
- >>> m_record = MetricsRecord({"accuracy": 0.93, "losses": [0.23, 0.1]})
131
- >>> # Adding it to the record_set would look like this
132
- >>> my_recordset["my_metrics"] = m_record
133
-
134
- Adding a :code:`ParametersRecord` follows the same steps as above but first,
135
- the array needs to be serialized and represented as a :code:`flwr.common.Array`.
136
- If the array is a :code:`NumPy` array, you can use the built-in utility function
137
- `array_from_numpy <flwr.common.array_from_numpy.html>`_. It is often possible to
138
- convert an array first to :code:`NumPy` and then use the aforementioned function.
139
-
140
- >>> from flwr.common import array_from_numpy
141
- >>> # Creating a ParametersRecord would look like this
142
- >>> arr_np = np.random.randn(3, 3)
143
- >>>
144
- >>> # You can use the built-in tool to serialize the array
145
- >>> arr = array_from_numpy(arr_np)
146
- >>>
147
- >>> # Finally, create the record
148
- >>> p_record = ParametersRecord({"my_array": arr})
149
- >>>
150
- >>> # Adding it to the record_set would look like this
151
- >>> my_recordset["my_parameters"] = p_record
152
-
153
- For additional examples on how to construct each of the records types shown
154
- above, please refer to the documentation for :code:`ConfigsRecord`,
155
- :code:`MetricsRecord` and :code:`ParametersRecord`.
156
- """
157
-
158
- def __init__(self, records: dict[str, RecordType] | None = None) -> None:
159
- super().__init__(_check_key, _check_value)
160
- if records is not None:
161
- for key, record in records.items():
162
- self[key] = record
163
-
164
- @property
165
- def parameters_records(self) -> TypedDict[str, ParametersRecord]:
166
- """Dictionary holding only ParametersRecord instances."""
167
- synced_dict = _SyncedDict[ParametersRecord](self, ParametersRecord)
168
- for key, record in self.items():
169
- if isinstance(record, ParametersRecord):
170
- synced_dict[key] = record
171
- return synced_dict
172
-
173
- @property
174
- def metrics_records(self) -> TypedDict[str, MetricsRecord]:
175
- """Dictionary holding only MetricsRecord instances."""
176
- synced_dict = _SyncedDict[MetricsRecord](self, MetricsRecord)
177
- for key, record in self.items():
178
- if isinstance(record, MetricsRecord):
179
- synced_dict[key] = record
180
- return synced_dict
181
-
182
- @property
183
- def configs_records(self) -> TypedDict[str, ConfigsRecord]:
184
- """Dictionary holding only ConfigsRecord instances."""
185
- synced_dict = _SyncedDict[ConfigsRecord](self, ConfigsRecord)
186
- for key, record in self.items():
187
- if isinstance(record, ConfigsRecord):
188
- synced_dict[key] = record
189
- return synced_dict
190
-
191
- def __repr__(self) -> str:
192
- """Return a string representation of this instance."""
193
- flds = ("parameters_records", "metrics_records", "configs_records")
194
- fld_views = [f"{fld}={dict(getattr(self, fld))!r}" for fld in flds]
195
- view = indent(",\n".join(fld_views), " ")
196
- return f"{self.__class__.__qualname__}(\n{view}\n)"
197
-
198
- def __setitem__(self, key: str, value: RecordType) -> None:
199
- """Set the given key to the given value after type checking."""
200
- original_value = self.get(key, None)
201
- super().__setitem__(key, value)
202
- if original_value is not None and not isinstance(value, type(original_value)):
203
- log(
204
- WARN,
205
- "Key '%s' was overwritten: record of type `%s` replaced with type `%s`",
206
- key,
207
- type(original_value).__name__,
208
- type(value).__name__,
209
- )