blazefl 2.0.0.dev3__tar.gz → 2.0.0.dev5__tar.gz

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 (118) hide show
  1. blazefl-2.0.0.dev5/.python-version +1 -0
  2. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/PKG-INFO +1 -1
  3. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/config/config.yaml +2 -1
  4. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/main.py +8 -7
  5. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/algorithm/dsfl.py +43 -39
  6. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/pyproject.toml +1 -1
  7. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/contrib/__init__.py +6 -0
  8. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/contrib/fedavg.py +156 -47
  9. blazefl-2.0.0.dev5/src/blazefl/core/client_trainer.py +256 -0
  10. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/client_trainer.pyi +10 -6
  11. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/__init__.py +2 -0
  12. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/__init__.pyi +2 -1
  13. blazefl-2.0.0.dev5/src/blazefl/utils/ipc.py +33 -0
  14. blazefl-2.0.0.dev5/src/blazefl/utils/ipc.pyi +3 -0
  15. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_contrib/test_fedavg.py +128 -5
  16. blazefl-2.0.0.dev5/tests/test_core/test_client_trainer.py +126 -0
  17. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/uv.lock +1 -1
  18. blazefl-2.0.0.dev3/examples/step-by-step-dsfl/.python-version +0 -1
  19. blazefl-2.0.0.dev3/src/blazefl/core/client_trainer.py +0 -191
  20. blazefl-2.0.0.dev3/tests/test_core/test_client_trainer.py +0 -84
  21. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/FUNDING.yml +0 -0
  22. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  23. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  24. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/dependabot.yml +0 -0
  25. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/workflows/ci.yaml +0 -0
  26. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/workflows/publish.yaml +0 -0
  27. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/workflows/sphinx.yaml +0 -0
  28. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.gitignore +0 -0
  29. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.pre-commit-config.yaml +0 -0
  30. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/CODE_OF_CONDUCT.md +0 -0
  31. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/LICENSE +0 -0
  32. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/Makefile +0 -0
  33. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/README.md +0 -0
  34. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/Makefile +0 -0
  35. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/architecture.png +0 -0
  36. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/benchmark_cnn.png +0 -0
  37. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/benchmark_resnet18.png +0 -0
  38. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/logo.svg +0 -0
  39. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/logo_square.svg +0 -0
  40. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/ogp.png +0 -0
  41. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/make.bat +0 -0
  42. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/_static/favicon.ico +0 -0
  43. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/_static/logo.png +0 -0
  44. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/_static/logo_square.png +0 -0
  45. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/_templates/autosummary/class.rst +0 -0
  46. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/_templates/autosummary/module.rst +0 -0
  47. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/benchmark.rst +0 -0
  48. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/conf.py +0 -0
  49. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/contribute.rst +0 -0
  50. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/example.rst +0 -0
  51. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/index.rst +0 -0
  52. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/install.rst +0 -0
  53. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/overview.rst +0 -0
  54. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/reference.rst +0 -0
  55. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/.gitignore +0 -0
  56. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/.python-version +0 -0
  57. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/Makefile +0 -0
  58. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/README.md +0 -0
  59. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/config/config.yaml +0 -0
  60. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/dataset/__init__.py +0 -0
  61. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/dataset/dataset.py +0 -0
  62. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/dataset/functional.py +0 -0
  63. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/main.py +0 -0
  64. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/models/__init__.py +0 -0
  65. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/models/selector.py +0 -0
  66. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/pyproject.toml +0 -0
  67. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/uv.lock +0 -0
  68. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/.gitignore +0 -0
  69. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5/examples/quickstart-fedavg}/.python-version +0 -0
  70. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/Makefile +0 -0
  71. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/README.md +0 -0
  72. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/dataset/__init__.py +0 -0
  73. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/dataset/dataset.py +0 -0
  74. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/dataset/functional.py +0 -0
  75. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/models/__init__.py +0 -0
  76. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/models/selector.py +0 -0
  77. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/pyproject.toml +0 -0
  78. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/.gitignore +0 -0
  79. {blazefl-2.0.0.dev3/examples/quickstart-fedavg → blazefl-2.0.0.dev5/examples/step-by-step-dsfl}/.python-version +0 -0
  80. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/Makefile +0 -0
  81. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/README.md +0 -0
  82. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/algorithm/__init__.py +0 -0
  83. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/config/algorithm/dsfl.yaml +0 -0
  84. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/config/config.yaml +0 -0
  85. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/dataset/__init__.py +0 -0
  86. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/dataset/dataset.py +0 -0
  87. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/dataset/functional.py +0 -0
  88. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/main.py +0 -0
  89. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/models/__init__.py +0 -0
  90. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/models/cnn.py +0 -0
  91. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/models/selector.py +0 -0
  92. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/pyproject.toml +0 -0
  93. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/__init__.py +0 -0
  94. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/__init__.py +0 -0
  95. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/__init__.pyi +0 -0
  96. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/model_selector.py +0 -0
  97. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/model_selector.pyi +0 -0
  98. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/partitioned_dataset.py +0 -0
  99. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/partitioned_dataset.pyi +0 -0
  100. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/server_handler.py +0 -0
  101. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/server_handler.pyi +0 -0
  102. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/py.typed +0 -0
  103. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/dataset.py +0 -0
  104. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/dataset.pyi +0 -0
  105. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/seed.py +0 -0
  106. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/seed.pyi +0 -0
  107. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/serialize.py +0 -0
  108. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/serialize.pyi +0 -0
  109. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/__init__.py +0 -0
  110. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/conftest.py +0 -0
  111. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_contrib/__init__.py +0 -0
  112. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_core/__init__.py +0 -0
  113. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_core/test_model_selector.py +0 -0
  114. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_core/test_partitioned_dataset.py +0 -0
  115. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_utils/__init__.py +0 -0
  116. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_utils/test_dataset.py +0 -0
  117. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_utils/test_seed.py +0 -0
  118. {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_utils/test_serialize.py +0 -0
@@ -0,0 +1 @@
1
+ 3.13
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blazefl
3
- Version: 2.0.0.dev3
3
+ Version: 2.0.0.dev5
4
4
  Summary: A blazing-fast and lightweight simulation framework for Federated Learning.
5
5
  Author-email: kitsuyaazuma <kitsuyaazuma@gmail.com>
6
6
  License-File: LICENSE
@@ -14,4 +14,5 @@ dataset_root_dir: /tmp/quickstart-fedavg/dataset
14
14
  dataset_split_dir: /tmp/quickstart-fedavg/split
15
15
  share_dir: /tmp/quickstart-fedavg/share
16
16
  state_dir: /tmp/quickstart-fedavg/state
17
- serial: false
17
+ parallel: true
18
+ ipc_mode: storage
@@ -98,31 +98,32 @@ def main(cfg: DictConfig):
98
98
  batch_size=cfg.batch_size,
99
99
  )
100
100
  trainer: FedAvgBaseClientTrainer | FedAvgProcessPoolClientTrainer | None = None
101
- if cfg.serial:
102
- trainer = FedAvgBaseClientTrainer(
101
+ if cfg.parallel:
102
+ trainer = FedAvgProcessPoolClientTrainer(
103
103
  model_selector=model_selector,
104
104
  model_name=cfg.model_name,
105
105
  dataset=dataset,
106
+ share_dir=share_dir,
107
+ state_dir=state_dir,
108
+ seed=cfg.seed,
106
109
  device=device,
107
110
  num_clients=cfg.num_clients,
108
111
  epochs=cfg.epochs,
109
112
  lr=cfg.lr,
110
113
  batch_size=cfg.batch_size,
114
+ num_parallels=cfg.num_parallels,
115
+ ipc_mode=cfg.ipc_mode,
111
116
  )
112
117
  else:
113
- trainer = FedAvgProcessPoolClientTrainer(
118
+ trainer = FedAvgBaseClientTrainer(
114
119
  model_selector=model_selector,
115
120
  model_name=cfg.model_name,
116
121
  dataset=dataset,
117
- share_dir=share_dir,
118
- state_dir=state_dir,
119
- seed=cfg.seed,
120
122
  device=device,
121
123
  num_clients=cfg.num_clients,
122
124
  epochs=cfg.epochs,
123
125
  lr=cfg.lr,
124
126
  batch_size=cfg.batch_size,
125
- num_parallels=cfg.num_parallels,
126
127
  )
127
128
  pipeline = FedAvgPipeline(handler=handler, trainer=trainer, writer=writer)
128
129
  try:
@@ -233,7 +233,7 @@ class DSFLBaseServerHandler(BaseServerHandler[DSFLUplinkPackage, DSFLDownlinkPac
233
233
 
234
234
 
235
235
  @dataclass
236
- class DSFLDiskSharedData:
236
+ class DSFLClientConfig:
237
237
  model_selector: DSFLModelSelector
238
238
  model_name: str
239
239
  dataset: DSFLPartitionedDataset
@@ -245,7 +245,6 @@ class DSFLDiskSharedData:
245
245
  kd_lr: float
246
246
  cid: int
247
247
  seed: int
248
- payload: DSFLDownlinkPackage
249
248
  state_path: Path
250
249
 
251
250
 
@@ -258,7 +257,7 @@ class DSFLClientState:
258
257
 
259
258
 
260
259
  class DSFLProcessPoolClientTrainer(
261
- ProcessPoolClientTrainer[DSFLUplinkPackage, DSFLDownlinkPackage, DSFLDiskSharedData]
260
+ ProcessPoolClientTrainer[DSFLUplinkPackage, DSFLDownlinkPackage, DSFLClientConfig]
262
261
  ):
263
262
  def __init__(
264
263
  self,
@@ -300,68 +299,76 @@ class DSFLProcessPoolClientTrainer(
300
299
  self.device = device
301
300
  self.num_clients = num_clients
302
301
  self.seed = seed
302
+ self.ipc_mode = "storage"
303
303
 
304
304
  if self.device == "cuda":
305
305
  self.device_count = torch.cuda.device_count()
306
306
 
307
307
  @staticmethod
308
- def process_client(path: Path, device: str) -> Path:
309
- data = torch.load(path, weights_only=False)
310
- assert isinstance(data, DSFLDiskSharedData)
311
-
312
- model = data.model_selector.select_model(data.model_name)
313
- optimizer = torch.optim.SGD(model.parameters(), lr=data.lr)
308
+ def worker(
309
+ config: DSFLClientConfig | Path,
310
+ payload: DSFLDownlinkPackage | Path,
311
+ device: str,
312
+ ) -> Path:
313
+ assert isinstance(config, Path) and isinstance(payload, Path)
314
+ config_path, payload_path = config, payload
315
+ c = torch.load(config_path, weights_only=False)
316
+ p = torch.load(payload_path, weights_only=False)
317
+ assert isinstance(c, DSFLClientConfig) and isinstance(p, DSFLDownlinkPackage)
318
+
319
+ model = c.model_selector.select_model(c.model_name)
320
+ optimizer = torch.optim.SGD(model.parameters(), lr=c.lr)
314
321
  kd_optimizer: torch.optim.SGD | None = None
315
322
 
316
323
  state: DSFLClientState | None = None
317
- if data.state_path.exists():
318
- state = torch.load(data.state_path, weights_only=False)
324
+ if c.state_path.exists():
325
+ state = torch.load(c.state_path, weights_only=False)
319
326
  assert isinstance(state, DSFLClientState)
320
327
  RandomState.set_random_state(state.random)
321
328
  model.load_state_dict(state.model)
322
329
  optimizer.load_state_dict(state.optimizer)
323
330
  if state.kd_optimizer is not None:
324
- kd_optimizer = torch.optim.SGD(model.parameters(), lr=data.kd_lr)
331
+ kd_optimizer = torch.optim.SGD(model.parameters(), lr=c.kd_lr)
325
332
  kd_optimizer.load_state_dict(state.kd_optimizer)
326
333
  else:
327
- seed_everything(data.seed, device=device)
334
+ seed_everything(c.seed, device=device)
328
335
 
329
336
  # Distill
330
- open_dataset = data.dataset.get_dataset(type_="open", cid=None)
331
- if data.payload.indices is not None and data.payload.soft_labels is not None:
332
- global_soft_labels = list(torch.unbind(data.payload.soft_labels, dim=0))
333
- global_indices = data.payload.indices.tolist()
337
+ open_dataset = c.dataset.get_dataset(type_="open", cid=None)
338
+ if p.indices is not None and p.soft_labels is not None:
339
+ global_soft_labels = list(torch.unbind(p.soft_labels, dim=0))
340
+ global_indices = p.indices.tolist()
334
341
  if kd_optimizer is None:
335
- kd_optimizer = torch.optim.SGD(model.parameters(), lr=data.kd_lr)
342
+ kd_optimizer = torch.optim.SGD(model.parameters(), lr=c.kd_lr)
336
343
  DSFLBaseServerHandler.distill(
337
344
  model=model,
338
345
  optimizer=kd_optimizer,
339
- dataset=data.dataset,
346
+ dataset=c.dataset,
340
347
  global_soft_labels=global_soft_labels,
341
348
  global_indices=global_indices,
342
- kd_epochs=data.kd_epochs,
343
- kd_batch_size=data.kd_batch_size,
349
+ kd_epochs=c.kd_epochs,
350
+ kd_batch_size=c.kd_batch_size,
344
351
  device=device,
345
352
  )
346
353
 
347
354
  # Train
348
- train_loader = data.dataset.get_dataloader(
355
+ train_loader = c.dataset.get_dataloader(
349
356
  type_="train",
350
- cid=data.cid,
351
- batch_size=data.batch_size,
357
+ cid=c.cid,
358
+ batch_size=c.batch_size,
352
359
  )
353
360
  DSFLProcessPoolClientTrainer.train(
354
361
  model=model,
355
362
  optimizer=optimizer,
356
363
  train_loader=train_loader,
357
364
  device=device,
358
- epochs=data.epochs,
365
+ epochs=c.epochs,
359
366
  )
360
367
 
361
368
  # Predict
362
369
  open_loader = DataLoader(
363
- Subset(open_dataset, data.payload.next_indices.tolist()),
364
- batch_size=data.batch_size,
370
+ Subset(open_dataset, p.next_indices.tolist()),
371
+ batch_size=c.batch_size,
365
372
  )
366
373
  soft_labels = DSFLProcessPoolClientTrainer.predict(
367
374
  model=model,
@@ -370,10 +377,10 @@ class DSFLProcessPoolClientTrainer(
370
377
  )
371
378
 
372
379
  # Evaluate
373
- test_loader = data.dataset.get_dataloader(
380
+ test_loader = c.dataset.get_dataloader(
374
381
  type_="test",
375
- cid=data.cid,
376
- batch_size=data.batch_size,
382
+ cid=c.cid,
383
+ batch_size=c.batch_size,
377
384
  )
378
385
  loss, acc = DSFLBaseServerHandler.evaulate(
379
386
  model=model,
@@ -383,19 +390,19 @@ class DSFLProcessPoolClientTrainer(
383
390
 
384
391
  package = DSFLUplinkPackage(
385
392
  soft_labels=soft_labels,
386
- indices=data.payload.next_indices,
393
+ indices=p.next_indices,
387
394
  metadata={"loss": loss, "acc": acc},
388
395
  )
389
396
 
390
- torch.save(package, path)
397
+ torch.save(package, config_path)
391
398
  state = DSFLClientState(
392
399
  random=RandomState.get_random_state(device=device),
393
400
  model=model.state_dict(),
394
401
  optimizer=optimizer.state_dict(),
395
402
  kd_optimizer=kd_optimizer.state_dict() if kd_optimizer else None,
396
403
  )
397
- torch.save(state, data.state_path)
398
- return path
404
+ torch.save(state, c.state_path)
405
+ return config_path
399
406
 
400
407
  @staticmethod
401
408
  def train(
@@ -442,10 +449,8 @@ class DSFLProcessPoolClientTrainer(
442
449
  soft_labels = torch.cat(soft_labels_list, dim=0)
443
450
  return soft_labels.cpu()
444
451
 
445
- def get_shared_data(
446
- self, cid: int, payload: DSFLDownlinkPackage
447
- ) -> DSFLDiskSharedData:
448
- data = DSFLDiskSharedData(
452
+ def get_client_config(self, cid: int) -> DSFLClientConfig:
453
+ data = DSFLClientConfig(
449
454
  model_selector=self.model_selector,
450
455
  model_name=self.model_name,
451
456
  dataset=self.dataset,
@@ -457,7 +462,6 @@ class DSFLProcessPoolClientTrainer(
457
462
  kd_lr=self.kd_lr,
458
463
  cid=cid,
459
464
  seed=self.seed,
460
- payload=payload,
461
465
  state_path=self.state_dir.joinpath(f"{cid}.pt"),
462
466
  )
463
467
  return data
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "blazefl"
3
- version = "2.0.0.dev3"
3
+ version = "2.0.0.dev5"
4
4
  description = "A blazing-fast and lightweight simulation framework for Federated Learning."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -8,11 +8,17 @@ extending the core functionalities of BlazeFL.
8
8
  from blazefl.contrib.fedavg import (
9
9
  FedAvgBaseClientTrainer,
10
10
  FedAvgBaseServerHandler,
11
+ FedAvgDownlinkPackage,
11
12
  FedAvgProcessPoolClientTrainer,
13
+ FedAvgThreadPoolClientTrainer,
14
+ FedAvgUplinkPackage,
12
15
  )
13
16
 
14
17
  __all__ = [
15
18
  "FedAvgBaseServerHandler",
16
19
  "FedAvgProcessPoolClientTrainer",
17
20
  "FedAvgBaseClientTrainer",
21
+ "FedAvgThreadPoolClientTrainer",
22
+ "FedAvgUplinkPackage",
23
+ "FedAvgDownlinkPackage",
18
24
  ]
@@ -1,7 +1,9 @@
1
1
  import random
2
+ import threading
2
3
  from copy import deepcopy
3
4
  from dataclasses import dataclass
4
5
  from pathlib import Path
6
+ from typing import Literal
5
7
 
6
8
  import torch
7
9
  from torch.utils.data import DataLoader
@@ -13,6 +15,7 @@ from blazefl.core import (
13
15
  ModelSelector,
14
16
  PartitionedDataset,
15
17
  ProcessPoolClientTrainer,
18
+ ThreadPoolClientTrainer,
16
19
  )
17
20
  from blazefl.utils import (
18
21
  RandomState,
@@ -431,7 +434,7 @@ class FedAvgBaseClientTrainer(
431
434
 
432
435
 
433
436
  @dataclass
434
- class FedAvgDiskSharedData:
437
+ class FedAvgClientConfig:
435
438
  """
436
439
  Data structure representing shared data for parallel client training
437
440
  in the Federated Averaging (FedAvg) algorithm.
@@ -448,7 +451,6 @@ class FedAvgDiskSharedData:
448
451
  lr (float): Learning rate for the optimizer.
449
452
  cid (int): Client ID.
450
453
  seed (int): Seed for reproducibility.
451
- payload (FedAvgDownlinkPackage): Downlink package with global model parameters.
452
454
  state_path (Path): Path to save the client's random state.
453
455
  """
454
456
 
@@ -460,13 +462,12 @@ class FedAvgDiskSharedData:
460
462
  lr: float
461
463
  cid: int
462
464
  seed: int
463
- payload: FedAvgDownlinkPackage
464
465
  state_path: Path
465
466
 
466
467
 
467
468
  class FedAvgProcessPoolClientTrainer(
468
469
  ProcessPoolClientTrainer[
469
- FedAvgUplinkPackage, FedAvgDownlinkPackage, FedAvgDiskSharedData
470
+ FedAvgUplinkPackage, FedAvgDownlinkPackage, FedAvgClientConfig
470
471
  ]
471
472
  ):
472
473
  """
@@ -488,6 +489,8 @@ class FedAvgProcessPoolClientTrainer(
488
489
  lr (float): Learning rate for the optimizer.
489
490
  seed (int): Seed for reproducibility.
490
491
  num_parallels (int): Number of parallel processes for training.
492
+ ipc_mode (Literal["storage", "shared_memory"]):
493
+ Inter-process communication mode.
491
494
  device_count (int | None): Number of CUDA devices available (if using GPU).
492
495
  """
493
496
 
@@ -505,6 +508,7 @@ class FedAvgProcessPoolClientTrainer(
505
508
  lr: float,
506
509
  seed: int,
507
510
  num_parallels: int,
511
+ ipc_mode: Literal["storage", "shared_memory"],
508
512
  ) -> None:
509
513
  """
510
514
  Initialize the FedAvgParalleClientTrainer.
@@ -542,50 +546,93 @@ class FedAvgProcessPoolClientTrainer(
542
546
  self.device = device
543
547
  self.num_clients = num_clients
544
548
  self.seed = seed
549
+ self.ipc_mode = ipc_mode
545
550
 
546
551
  @staticmethod
547
- def process_client(path: Path, device: str) -> Path:
552
+ def worker(
553
+ config: FedAvgClientConfig | Path,
554
+ payload: FedAvgDownlinkPackage | Path,
555
+ device: str,
556
+ ) -> FedAvgUplinkPackage | Path:
548
557
  """
549
558
  Process a single client's local training and evaluation.
550
559
 
551
- This method is executed by a parallel process and handles data loading,
552
- training, evaluation, and saving results to a shared file.
560
+ This method is executed by a worker process and handles loading client
561
+ configuration and payload, performing the client-specific training,
562
+ and returning the result.
553
563
 
554
564
  Args:
555
- path (Path): Path to the shared data file containing client-specific
556
- information.
557
- device (str): Device to use for processing.
565
+ config (FedAvgClientConfig | Path):
566
+ The client's configuration data, or a path to a file containing
567
+ the configuration if `ipc_mode` is "storage".
568
+ payload (FedAvgDownlinkPackage | Path):
569
+ The downlink payload from the server, or a path to a file
570
+ containing the payload if `ipc_mode` is "storage".
571
+ device (str): Device to use for processing (e.g., "cpu", "cuda:0").
558
572
 
559
573
  Returns:
560
- Path: Path to the file with the processed results.
561
- """
562
- data = torch.load(path, weights_only=False)
563
- assert isinstance(data, FedAvgDiskSharedData)
564
-
565
- if data.state_path.exists():
566
- state = torch.load(data.state_path, weights_only=False)
567
- assert isinstance(state, RandomState)
568
- RandomState.set_random_state(state)
574
+ FedAvgUplinkPackage | Path:
575
+ The uplink package containing the client's results, or a path to
576
+ a file containing the package if `ipc_mode` is "storage".
577
+ """
578
+
579
+ def _storage_worker(
580
+ config_path: Path,
581
+ payload_path: Path,
582
+ device: str,
583
+ ) -> Path:
584
+ config = torch.load(config_path, weights_only=False)
585
+ assert isinstance(config, FedAvgClientConfig)
586
+ payload = torch.load(payload_path, weights_only=False)
587
+ assert isinstance(payload, FedAvgDownlinkPackage)
588
+ package = _shared_memory_worker(
589
+ config=config,
590
+ payload=payload,
591
+ device=device,
592
+ )
593
+ torch.save(package, config_path)
594
+ return config_path
595
+
596
+ def _shared_memory_worker(
597
+ config: FedAvgClientConfig,
598
+ payload: FedAvgDownlinkPackage,
599
+ device: str,
600
+ ) -> FedAvgUplinkPackage:
601
+ if config.state_path.exists():
602
+ state = torch.load(config.state_path, weights_only=False)
603
+ assert isinstance(state, RandomState)
604
+ RandomState.set_random_state(state)
605
+ else:
606
+ seed_everything(config.seed, device=device)
607
+
608
+ model = config.model_selector.select_model(config.model_name)
609
+ train_loader = config.dataset.get_dataloader(
610
+ type_="train",
611
+ cid=config.cid,
612
+ batch_size=config.batch_size,
613
+ )
614
+ package = FedAvgProcessPoolClientTrainer.train(
615
+ model=model,
616
+ model_parameters=payload.model_parameters,
617
+ train_loader=train_loader,
618
+ device=device,
619
+ epochs=config.epochs,
620
+ lr=config.lr,
621
+ )
622
+ torch.save(RandomState.get_random_state(device=device), config.state_path)
623
+ return package
624
+
625
+ if isinstance(config, Path) and isinstance(payload, Path):
626
+ return _storage_worker(config, payload, device)
627
+ elif isinstance(config, FedAvgClientConfig) and isinstance(
628
+ payload, FedAvgDownlinkPackage
629
+ ):
630
+ return _shared_memory_worker(config, payload, device)
569
631
  else:
570
- seed_everything(data.seed, device=device)
571
-
572
- model = data.model_selector.select_model(data.model_name)
573
- train_loader = data.dataset.get_dataloader(
574
- type_="train",
575
- cid=data.cid,
576
- batch_size=data.batch_size,
577
- )
578
- package = FedAvgProcessPoolClientTrainer.train(
579
- model=model,
580
- model_parameters=data.payload.model_parameters,
581
- train_loader=train_loader,
582
- device=device,
583
- epochs=data.epochs,
584
- lr=data.lr,
585
- )
586
- torch.save(package, path)
587
- torch.save(RandomState.get_random_state(device=device), data.state_path)
588
- return path
632
+ raise TypeError(
633
+ "Invalid types for config and payload."
634
+ " Expected FedAvgClientConfig and FedAvgDownlinkPackage or Path."
635
+ )
589
636
 
590
637
  @staticmethod
591
638
  def train(
@@ -595,6 +642,7 @@ class FedAvgProcessPoolClientTrainer(
595
642
  device: str,
596
643
  epochs: int,
597
644
  lr: float,
645
+ stop_event: threading.Event | None = None,
598
646
  ) -> FedAvgUplinkPackage:
599
647
  """
600
648
  Train the model with the given training data loader.
@@ -619,6 +667,8 @@ class FedAvgProcessPoolClientTrainer(
619
667
 
620
668
  data_size = 0
621
669
  for _ in range(epochs):
670
+ if stop_event is not None and stop_event.is_set():
671
+ break
622
672
  for data, target in train_loader:
623
673
  data = data.to(device)
624
674
  target = target.to(device)
@@ -636,21 +686,17 @@ class FedAvgProcessPoolClientTrainer(
636
686
 
637
687
  return FedAvgUplinkPackage(model_parameters, data_size)
638
688
 
639
- def get_shared_data(
640
- self, cid: int, payload: FedAvgDownlinkPackage
641
- ) -> FedAvgDiskSharedData:
689
+ def get_client_config(self, cid: int) -> FedAvgClientConfig:
642
690
  """
643
- Generate the shared data for a specific client.
691
+ Generate the client configuration for a specific client.
644
692
 
645
693
  Args:
646
694
  cid (int): Client ID.
647
- payload (FedAvgDownlinkPackage): Downlink package with global model
648
- parameters.
649
695
 
650
696
  Returns:
651
- FedAvgDiskSharedData: Shared data structure for the client.
697
+ FedAvgClientConfig: Client configuration data structure.
652
698
  """
653
- data = FedAvgDiskSharedData(
699
+ data = FedAvgClientConfig(
654
700
  model_selector=self.model_selector,
655
701
  model_name=self.model_name,
656
702
  dataset=self.dataset,
@@ -659,7 +705,6 @@ class FedAvgProcessPoolClientTrainer(
659
705
  lr=self.lr,
660
706
  cid=cid,
661
707
  seed=self.seed,
662
- payload=payload,
663
708
  state_path=self.state_dir.joinpath(f"{cid}.pt"),
664
709
  )
665
710
  return data
@@ -674,3 +719,67 @@ class FedAvgProcessPoolClientTrainer(
674
719
  package = deepcopy(self.cache)
675
720
  self.cache = []
676
721
  return package
722
+
723
+
724
+ class FedAvgThreadPoolClientTrainer(
725
+ ThreadPoolClientTrainer[
726
+ FedAvgUplinkPackage,
727
+ FedAvgDownlinkPackage,
728
+ ]
729
+ ):
730
+ def __init__(
731
+ self,
732
+ model_selector: ModelSelector,
733
+ model_name: str,
734
+ dataset: PartitionedDataset,
735
+ device: str,
736
+ num_clients: int,
737
+ epochs: int,
738
+ batch_size: int,
739
+ lr: float,
740
+ seed: int,
741
+ num_parallels: int,
742
+ ) -> None:
743
+ self.num_parallels = num_parallels
744
+ self.device = device
745
+ if self.device == "cuda":
746
+ self.device_count = torch.cuda.device_count()
747
+ self.cache: list[FedAvgUplinkPackage] = []
748
+
749
+ self.model_selector = model_selector
750
+ self.model_name = model_name
751
+ self.dataset = dataset
752
+ self.epochs = epochs
753
+ self.batch_size = batch_size
754
+ self.lr = lr
755
+ self.num_clients = num_clients
756
+ self.seed = seed
757
+ self.stop_event = None
758
+
759
+ def worker(
760
+ self,
761
+ cid: int,
762
+ device: str,
763
+ payload: FedAvgDownlinkPackage,
764
+ ) -> FedAvgUplinkPackage:
765
+ model = self.model_selector.select_model(self.model_name)
766
+ train_loader = self.dataset.get_dataloader(
767
+ type_="train",
768
+ cid=cid,
769
+ batch_size=self.batch_size,
770
+ )
771
+ package = FedAvgProcessPoolClientTrainer.train(
772
+ model=model,
773
+ model_parameters=payload.model_parameters,
774
+ train_loader=train_loader,
775
+ device=device,
776
+ epochs=self.epochs,
777
+ lr=self.lr,
778
+ stop_event=self.stop_event,
779
+ )
780
+ return package
781
+
782
+ def uplink_package(self) -> list[FedAvgUplinkPackage]:
783
+ package = deepcopy(self.cache)
784
+ self.cache = []
785
+ return package