flwr-nightly 1.20.0.dev20250620__py3-none-any.whl → 1.20.0.dev20250630__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/common/constant.py CHANGED
@@ -135,6 +135,7 @@ GC_THRESHOLD = 200_000_000 # 200 MB
135
135
  # Constants for Inflatable
136
136
  HEAD_BODY_DIVIDER = b"\x00"
137
137
  HEAD_VALUE_DIVIDER = " "
138
+ MAX_ARRAY_CHUNK_SIZE = 20_971_520 # 20 MB
138
139
 
139
140
  # Constants for serialization
140
141
  INT64_MAX_VALUE = 9223372036854775807 # (1 << 63) - 1
@@ -40,9 +40,11 @@ from .inflatable import (
40
40
  )
41
41
  from .message import Message
42
42
  from .record import Array, ArrayRecord, ConfigRecord, MetricRecord, RecordDict
43
+ from .record.arraychunk import ArrayChunk
43
44
 
44
45
  # Helper registry that maps names of classes to their type
45
46
  inflatable_class_registry: dict[str, type[InflatableObject]] = {
47
+ ArrayChunk.__qualname__: ArrayChunk,
46
48
  Array.__qualname__: Array,
47
49
  ArrayRecord.__qualname__: ArrayRecord,
48
50
  ConfigRecord.__qualname__: ConfigRecord,
@@ -17,6 +17,7 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import json
20
21
  import sys
21
22
  from dataclasses import dataclass
22
23
  from io import BytesIO
@@ -24,11 +25,15 @@ from typing import TYPE_CHECKING, Any, cast, overload
24
25
 
25
26
  import numpy as np
26
27
 
27
- from flwr.proto.recorddict_pb2 import Array as ArrayProto # pylint: disable=E0611
28
-
29
- from ..constant import SType
30
- from ..inflatable import InflatableObject, add_header_to_object_body, get_object_body
28
+ from ..constant import MAX_ARRAY_CHUNK_SIZE, SType
29
+ from ..inflatable import (
30
+ InflatableObject,
31
+ add_header_to_object_body,
32
+ get_object_body,
33
+ get_object_children_ids_from_object_content,
34
+ )
31
35
  from ..typing import NDArray
36
+ from .arraychunk import ArrayChunk
32
37
 
33
38
  if TYPE_CHECKING:
34
39
  import torch
@@ -252,16 +257,48 @@ class Array(InflatableObject):
252
257
  ndarray_deserialized = np.load(bytes_io, allow_pickle=False)
253
258
  return cast(NDArray, ndarray_deserialized)
254
259
 
260
+ @property
261
+ def children(self) -> dict[str, InflatableObject]:
262
+ """Return a dictionary of ArrayChunks with their Object IDs as keys."""
263
+ return dict(self.slice_array())
264
+
265
+ def slice_array(self) -> list[tuple[str, InflatableObject]]:
266
+ """Slice Array data and construct a list of ArrayChunks."""
267
+ children: list[tuple[str, InflatableObject]] = []
268
+ # memoryview allows for zero-copy slicing
269
+ data_view = memoryview(self.data)
270
+ for start in range(0, len(data_view), MAX_ARRAY_CHUNK_SIZE):
271
+ end = min(start + MAX_ARRAY_CHUNK_SIZE, len(data_view))
272
+ ac = ArrayChunk(data_view[start:end])
273
+ children.append((ac.object_id, ac))
274
+ return children
275
+
255
276
  def deflate(self) -> bytes:
256
277
  """Deflate the Array."""
257
- array_proto = ArrayProto(
258
- dtype=self.dtype,
259
- shape=self.shape,
260
- stype=self.stype,
261
- data=self.data,
262
- )
263
-
264
- obj_body = array_proto.SerializeToString(deterministic=True)
278
+ array_metadata: dict[str, str | tuple[int, ...] | list[int]] = {}
279
+
280
+ # We want to record all object_id even if repeated
281
+ # it can happend that chunks carry the exact same data
282
+ # for example when the array has only zeros
283
+ children_list = self.slice_array()
284
+ # Let's not save the entire object_id but a mapping to those
285
+ # that will be carried in the object head
286
+ # (replace a long object_id with a single scalar)
287
+ unique_children = list(self.children.keys())
288
+ arraychunk_ids = [unique_children.index(ch_id) for ch_id, _ in children_list]
289
+
290
+ # The deflated Array carries everything but the data
291
+ # The `arraychunk_ids` will be used during Array inflation
292
+ # to rematerialize the data from ArrayChunk objects.
293
+ array_metadata = {
294
+ "dtype": self.dtype,
295
+ "shape": self.shape,
296
+ "stype": self.stype,
297
+ "arraychunk_ids": arraychunk_ids,
298
+ }
299
+
300
+ # Serialize metadata dict
301
+ obj_body = json.dumps(array_metadata).encode("utf-8")
265
302
  return add_header_to_object_body(object_body=obj_body, obj=self)
266
303
 
267
304
  @classmethod
@@ -276,26 +313,55 @@ class Array(InflatableObject):
276
313
  The deflated object content of the Array.
277
314
 
278
315
  children : Optional[dict[str, InflatableObject]] (default: None)
279
- Must be ``None``. ``Array`` does not support child objects.
280
- Providing any children will raise a ``ValueError``.
316
+ Must be ``None``. ``Array`` must have child objects.
317
+ Providing no children will raise a ``ValueError``.
281
318
 
282
319
  Returns
283
320
  -------
284
321
  Array
285
322
  The inflated Array.
286
323
  """
287
- if children:
288
- raise ValueError("`Array` objects do not have children.")
324
+ if not children:
325
+ raise ValueError("`Array` objects must have children.")
289
326
 
290
327
  obj_body = get_object_body(object_content, cls)
291
- proto_array = ArrayProto.FromString(obj_body)
292
- return cls(
293
- dtype=proto_array.dtype,
294
- shape=tuple(proto_array.shape),
295
- stype=proto_array.stype,
296
- data=proto_array.data,
328
+
329
+ # Extract children IDs from head
330
+ children_ids = get_object_children_ids_from_object_content(object_content)
331
+ # Decode the Array body
332
+ array_metadata: dict[str, str | tuple[int, ...] | list[int]] = json.loads(
333
+ obj_body.decode(encoding="utf-8")
297
334
  )
298
335
 
336
+ # Verify children ids in body match those passed for inflation
337
+ chunk_ids_indices = cast(list[int], array_metadata["arraychunk_ids"])
338
+ # Convert indices back to IDs
339
+ chunk_ids = [children_ids[i] for i in chunk_ids_indices]
340
+ # Check consistency
341
+ unique_arrayschunks = set(chunk_ids)
342
+ children_obj_ids = set(children.keys())
343
+ if unique_arrayschunks != children_obj_ids:
344
+ raise ValueError(
345
+ "Unexpected set of `children`. "
346
+ f"Expected {unique_arrayschunks} but got {children_obj_ids}."
347
+ )
348
+
349
+ # Materialize Array with empty data
350
+ array = cls(
351
+ dtype=cast(str, array_metadata["dtype"]),
352
+ shape=cast(tuple[int], tuple(array_metadata["shape"])),
353
+ stype=cast(str, array_metadata["stype"]),
354
+ data=b"",
355
+ )
356
+
357
+ # Now inject data from chunks
358
+ buff = bytearray()
359
+ for ch_id in chunk_ids:
360
+ buff += cast(ArrayChunk, children[ch_id]).data
361
+
362
+ array.data = bytes(buff)
363
+ return array
364
+
299
365
  @property
300
366
  def object_id(self) -> str:
301
367
  """Get object ID."""
@@ -0,0 +1,62 @@
1
+ # Copyright 2025 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
+ """ArrayChunk."""
16
+
17
+
18
+ from __future__ import annotations
19
+
20
+ from dataclasses import dataclass
21
+
22
+ from ..inflatable import InflatableObject, add_header_to_object_body, get_object_body
23
+
24
+
25
+ @dataclass
26
+ class ArrayChunk(InflatableObject):
27
+ """ArrayChunk type."""
28
+
29
+ data: memoryview
30
+
31
+ def __init__(self, data: bytes) -> None:
32
+ self.data = memoryview(data)
33
+
34
+ def deflate(self) -> bytes:
35
+ """Deflate the ArrayChunk."""
36
+ return add_header_to_object_body(object_body=self.data, obj=self)
37
+
38
+ @classmethod
39
+ def inflate(
40
+ cls, object_content: bytes, children: dict[str, InflatableObject] | None = None
41
+ ) -> ArrayChunk:
42
+ """Inflate an ArrayChunk from bytes.
43
+
44
+ Parameters
45
+ ----------
46
+ object_content : bytes
47
+ The deflated object content of the ArrayChunk.
48
+
49
+ children : Optional[dict[str, InflatableObject]] (default: None)
50
+ Must be ``None``. ``ArrayChunk`` does not support child objects.
51
+ Providing any children will raise a ``ValueError``.
52
+
53
+ Returns
54
+ -------
55
+ ArrayChunk
56
+ The inflated ArrayChunk.
57
+ """
58
+ if children:
59
+ raise ValueError("`ArrayChunk` objects do not have children.")
60
+
61
+ obj_body = get_object_body(object_content, cls)
62
+ return cls(data=memoryview(obj_body))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.20.0.dev20250620
3
+ Version: 1.20.0.dev20250630
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  License: Apache-2.0
6
6
  Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
@@ -108,7 +108,7 @@ flwr/common/args.py,sha256=-aX_jVnSaDrJR2KZ8Wq0Y3dQHII4R4MJtJOIXzVUA0c,5417
108
108
  flwr/common/auth_plugin/__init__.py,sha256=3rzPkVLn9WyB5n7HLk1XGDw3SLCqRWAU1_CnglcWPfw,970
109
109
  flwr/common/auth_plugin/auth_plugin.py,sha256=kXx5o39vJchaPv28sK9qO6H_UXSWym6zRBbCa7sUwtQ,4825
110
110
  flwr/common/config.py,sha256=glcZDjco-amw1YfQcYTFJ4S1pt9APoexT-mf1QscuHs,13960
111
- flwr/common/constant.py,sha256=HO1y0YYHZUCIDt5QQnvCTSYVeKhkIJve0RbQ3nW7jHU,8191
111
+ flwr/common/constant.py,sha256=HMLDvtiWGcGUfHpyyMx1rau34eXdMuBsvn-koRD8-pQ,8234
112
112
  flwr/common/context.py,sha256=Be8obQR_OvEDy1OmshuUKxGRQ7Qx89mf5F4xlhkR10s,2407
113
113
  flwr/common/date.py,sha256=1ZT2cRSpC2DJqprOVTLXYCR_O2_OZR0zXO_brJ3LqWc,1554
114
114
  flwr/common/differential_privacy.py,sha256=FdlpdpPl_H_2HJa8CQM1iCUGBBQ5Dc8CzxmHERM-EoE,6148
@@ -125,14 +125,15 @@ flwr/common/heartbeat.py,sha256=SyEpNDnmJ0lni0cWO67rcoJVKasCLmkNHm3dKLeNrLU,5749
125
125
  flwr/common/inflatable.py,sha256=GDL9oBKs16_yyVdlH6kBf493O5xll_h9V7XB5Mpx1Hc,9524
126
126
  flwr/common/inflatable_grpc_utils.py,sha256=ZpwtgF1tGD6NwQkCidbhbeBPDBZ1Nx9eGMHQ04eNEE8,3554
127
127
  flwr/common/inflatable_rest_utils.py,sha256=KiZd06XRiXcl_WewOrag0JTvUQt5kZ74UIsQ3FCAXGc,3580
128
- flwr/common/inflatable_utils.py,sha256=-GTdgR1zLS9WtXrbOGJMpaoyVEL8KmoQ2yF4HeLxTI0,12406
128
+ flwr/common/inflatable_utils.py,sha256=XfgkPl8GKuR7tuOpYVZ3zK4n-LbgMZtwoNxMfUbpiCE,12489
129
129
  flwr/common/logger.py,sha256=JbRf6E2vQxXzpDBq1T8IDUJo_usu3gjWEBPQ6uKcmdg,13049
130
130
  flwr/common/message.py,sha256=xAL7iZN5-n-xPQpgoSFvxNrzs8fmiiPfoU0DjNQEhRw,19953
131
131
  flwr/common/object_ref.py,sha256=p3SfTeqo3Aj16SkB-vsnNn01zswOPdGNBitcbRnqmUk,9134
132
132
  flwr/common/parameter.py,sha256=UVw6sOgehEFhFs4uUCMl2kfVq1PD6ncmWgPLMsZPKPE,2095
133
133
  flwr/common/pyproject.py,sha256=2SU6yJW7059SbMXgzjOdK1GZRWO6AixDH7BmdxbMvHI,1386
134
134
  flwr/common/record/__init__.py,sha256=cNGccdDoxttqgnUgyKRIqLWULjW-NaSmOufVxtXq-sw,1197
135
- flwr/common/record/array.py,sha256=3K01tAf_jedub2r2-vkRshbsjBSiKErAO4KqDgdDaSo,11776
135
+ flwr/common/record/array.py,sha256=oApWRwxh1bY2MivQcAz9YHkrspkYZVvy1OkBsWolMec,14541
136
+ flwr/common/record/arraychunk.py,sha256=2ubMzsRNZFL4cc-wXNrJkcTSJUD3nL8CeX5PZpEhNSo,2019
136
137
  flwr/common/record/arrayrecord.py,sha256=CpoqYXM6Iv4XEc9SryCMYmw-bIvP8ut6xWJzRwYJzdU,18008
137
138
  flwr/common/record/configrecord.py,sha256=G7U0q39kB0Kyi0zMxFmPxcVemL9NgwVS1qjvI4BRQuU,9763
138
139
  flwr/common/record/conversion_utils.py,sha256=wbNCzy7oAqaA3-arhls_EqRZYXRC4YrWIoE-Gy82fJ0,1191
@@ -360,7 +361,7 @@ flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca
360
361
  flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
361
362
  flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=d3GdIabycUoDBDL_eVlt513knGSjQW3-9lG6Cw4QEk4,5719
362
363
  flwr/supernode/start_client_internal.py,sha256=DAXuReZ1FCXt9Y1KbM0p-dI50ROWPEJXzfKrl14OE6k,18233
363
- flwr_nightly-1.20.0.dev20250620.dist-info/METADATA,sha256=YNBc0zQFwQfdbdsas9XYKH5jPgEyt8gQR_Jrh1wkh0A,15910
364
- flwr_nightly-1.20.0.dev20250620.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
365
- flwr_nightly-1.20.0.dev20250620.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
366
- flwr_nightly-1.20.0.dev20250620.dist-info/RECORD,,
364
+ flwr_nightly-1.20.0.dev20250630.dist-info/METADATA,sha256=B3mOdd5CdmCCEJsFc4A65ifNMx4_vvr7UHDi-OuIAzY,15910
365
+ flwr_nightly-1.20.0.dev20250630.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
366
+ flwr_nightly-1.20.0.dev20250630.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
367
+ flwr_nightly-1.20.0.dev20250630.dist-info/RECORD,,