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.
- blazefl-2.0.0.dev5/.python-version +1 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/PKG-INFO +1 -1
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/config/config.yaml +2 -1
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/main.py +8 -7
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/algorithm/dsfl.py +43 -39
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/pyproject.toml +1 -1
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/contrib/__init__.py +6 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/contrib/fedavg.py +156 -47
- blazefl-2.0.0.dev5/src/blazefl/core/client_trainer.py +256 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/client_trainer.pyi +10 -6
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/__init__.py +2 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/__init__.pyi +2 -1
- blazefl-2.0.0.dev5/src/blazefl/utils/ipc.py +33 -0
- blazefl-2.0.0.dev5/src/blazefl/utils/ipc.pyi +3 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_contrib/test_fedavg.py +128 -5
- blazefl-2.0.0.dev5/tests/test_core/test_client_trainer.py +126 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/uv.lock +1 -1
- blazefl-2.0.0.dev3/examples/step-by-step-dsfl/.python-version +0 -1
- blazefl-2.0.0.dev3/src/blazefl/core/client_trainer.py +0 -191
- blazefl-2.0.0.dev3/tests/test_core/test_client_trainer.py +0 -84
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/FUNDING.yml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/dependabot.yml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/workflows/ci.yaml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/workflows/publish.yaml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.github/workflows/sphinx.yaml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.gitignore +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/.pre-commit-config.yaml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/CODE_OF_CONDUCT.md +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/LICENSE +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/Makefile +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/README.md +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/Makefile +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/architecture.png +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/benchmark_cnn.png +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/benchmark_resnet18.png +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/logo.svg +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/logo_square.svg +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/imgs/ogp.png +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/make.bat +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/_static/favicon.ico +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/_static/logo.png +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/_static/logo_square.png +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/_templates/autosummary/class.rst +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/_templates/autosummary/module.rst +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/benchmark.rst +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/conf.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/contribute.rst +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/example.rst +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/index.rst +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/install.rst +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/overview.rst +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/docs/source/reference.rst +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/.gitignore +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/.python-version +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/Makefile +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/README.md +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/config/config.yaml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/dataset/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/dataset/dataset.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/dataset/functional.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/main.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/models/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/models/selector.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/pyproject.toml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/experimental-freethreaded/uv.lock +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/.gitignore +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5/examples/quickstart-fedavg}/.python-version +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/Makefile +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/README.md +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/dataset/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/dataset/dataset.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/dataset/functional.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/models/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/models/selector.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/quickstart-fedavg/pyproject.toml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/.gitignore +0 -0
- {blazefl-2.0.0.dev3/examples/quickstart-fedavg → blazefl-2.0.0.dev5/examples/step-by-step-dsfl}/.python-version +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/Makefile +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/README.md +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/algorithm/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/config/algorithm/dsfl.yaml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/config/config.yaml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/dataset/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/dataset/dataset.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/dataset/functional.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/main.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/models/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/models/cnn.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/models/selector.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/examples/step-by-step-dsfl/pyproject.toml +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/__init__.pyi +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/model_selector.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/model_selector.pyi +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/partitioned_dataset.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/partitioned_dataset.pyi +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/server_handler.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/core/server_handler.pyi +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/py.typed +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/dataset.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/dataset.pyi +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/seed.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/seed.pyi +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/serialize.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/src/blazefl/utils/serialize.pyi +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/conftest.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_contrib/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_core/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_core/test_model_selector.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_core/test_partitioned_dataset.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_utils/__init__.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_utils/test_dataset.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_utils/test_seed.py +0 -0
- {blazefl-2.0.0.dev3 → blazefl-2.0.0.dev5}/tests/test_utils/test_serialize.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -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.
|
|
102
|
-
trainer =
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
|
318
|
-
state = torch.load(
|
|
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=
|
|
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(
|
|
334
|
+
seed_everything(c.seed, device=device)
|
|
328
335
|
|
|
329
336
|
# Distill
|
|
330
|
-
open_dataset =
|
|
331
|
-
if
|
|
332
|
-
global_soft_labels = list(torch.unbind(
|
|
333
|
-
global_indices =
|
|
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=
|
|
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=
|
|
346
|
+
dataset=c.dataset,
|
|
340
347
|
global_soft_labels=global_soft_labels,
|
|
341
348
|
global_indices=global_indices,
|
|
342
|
-
kd_epochs=
|
|
343
|
-
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 =
|
|
355
|
+
train_loader = c.dataset.get_dataloader(
|
|
349
356
|
type_="train",
|
|
350
|
-
cid=
|
|
351
|
-
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=
|
|
365
|
+
epochs=c.epochs,
|
|
359
366
|
)
|
|
360
367
|
|
|
361
368
|
# Predict
|
|
362
369
|
open_loader = DataLoader(
|
|
363
|
-
Subset(open_dataset,
|
|
364
|
-
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 =
|
|
380
|
+
test_loader = c.dataset.get_dataloader(
|
|
374
381
|
type_="test",
|
|
375
|
-
cid=
|
|
376
|
-
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=
|
|
393
|
+
indices=p.next_indices,
|
|
387
394
|
metadata={"loss": loss, "acc": acc},
|
|
388
395
|
)
|
|
389
396
|
|
|
390
|
-
torch.save(package,
|
|
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,
|
|
398
|
-
return
|
|
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
|
|
446
|
-
|
|
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
|
|
@@ -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
|
|
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,
|
|
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
|
|
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
|
|
552
|
-
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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:
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
|
640
|
-
self, cid: int, payload: FedAvgDownlinkPackage
|
|
641
|
-
) -> FedAvgDiskSharedData:
|
|
689
|
+
def get_client_config(self, cid: int) -> FedAvgClientConfig:
|
|
642
690
|
"""
|
|
643
|
-
Generate the
|
|
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
|
-
|
|
697
|
+
FedAvgClientConfig: Client configuration data structure.
|
|
652
698
|
"""
|
|
653
|
-
data =
|
|
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
|