diff --git a/docs/source/howto-custom-algorithm.rst b/docs/source/howto-custom-algorithm.rst new file mode 100644 index 00000000..559f97b7 --- /dev/null +++ b/docs/source/howto-custom-algorithm.rst @@ -0,0 +1,158 @@ +.. _howto-custom-algorithm: + +How to: implement a custom algorithm +==================================== + +Built-in algorithms cover the classical methods; research often requires +variants. This guide implements a complete algorithm — a FedProx +variant with named parameter exchange and optional variance reduction — +walking through every file you need and every hook you can override. +The running example is ``test-files/custom_alg.py``; the code shown +below is included from that file, so it is exercised in CI (the CLI runs +it via ``test-files/custom_conf.yml``). + +The one-sentence model: **an algorithm is two config classes plus hook +overrides on the Server and Client classes** — the training loop, the +communication schedule scaffolding, logging, evaluation and the parallel +executors all stay untouched (:doc:`concepts` explains why). + +When is a custom algorithm needed? +---------------------------------- + +- your algorithm changes **what clients compute** locally (new local + objective, extra local state) → override client hooks; +- it changes **what is exchanged or how replies are aggregated** → + override server hooks; +- it needs **new hyperparameters** → add config fields. + +If you only need different *values* for existing hyperparameters, no +code is needed — use the built-in algorithm's config. + +The config classes +------------------ + +Two pydantic models, one for each side. New fields become CLI/CLI-YAML +options automatically; ``server_config_kw_doc`` / ``client_config_kw_doc`` +append the inherited fields' docs so the API reference stays complete: + +.. literalinclude:: ../../test-files/custom_alg.py + :language: python + :start-after: # --- begin: configs --- + :end-before: # --- end: configs --- + +The ``model_validator`` here fixes the local optimizer from the ``vr`` +flag — config-level invariants belong in validators, so invalid +combinations fail at construction, not mid-run. + +The server hooks +---------------- + +The server customizes the **downlink** (``communicate``) and the +**aggregation** (``update``). Everything else — sampling, the round +loop, metrics — is inherited: + +.. literalinclude:: ../../test-files/custom_alg.py + :language: python + :start-after: # --- begin: server --- + :end-before: # --- end: server --- + +Points worth noting: + +- ``communicate(target)`` builds a typed :class:`~fl_sim.messages.ServerToClient` + message; ``extras`` carries algorithm-specific payloads (here, + server-side gradients for variance reduction) alongside the model. +- ``update()`` runs on the main thread after the sampled clients' + replies arrive in ``self._received_messages`` — in sampled-client + order, which is what keeps aggregation bitwise identical across + :doc:`parallel_execution` backends. +- ``client_cls`` and ``config_cls`` wire the custom classes together; + ``@register_algorithm()`` (shown above each class) makes the algorithm + usable by name. + +The client hooks +---------------- + +The client customizes the **local training** (``update``) and the +**uplink** (``communicate``): + +.. literalinclude:: ../../test-files/custom_alg.py + :language: python + :start-after: # --- begin: client --- + :end-before: # --- end: client --- + +The most important contract on this page is in ``communicate``: it +**builds and returns** a :class:`~fl_sim.messages.ClientToServer` and +touches nothing but the client's own state. The framework sends the +message and maintains the communication counters. That is what makes +the hook safe to run inside a worker thread when +:doc:`parallel execution ` is on — a hook that +reached into server state would only work sequentially. + +Named exchange, by the way +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example exchanges parameters **by name** (a ``model_state`` +mapping from parameter name to tensor, i.e. :data:`~fl_sim.messages.ModelState`) +instead of the legacy flat positional list. Names survive reordering +and partial modules, and the loading side (``accept_parameters``, +``update``) looks parameters up by name. The flat-list style still +works everywhere; prefer names for new algorithms. + +Register and run +---------------- + +``@register_algorithm()`` (applied to all four classes above) puts the +algorithm into the registry; from then on the CLI and YAML matrices +accept it like any built-in: + +.. code-block:: yaml + + algorithm: + name: Custom + server: + num_iters: 100 + vr: true + client: + num_epochs: 5 + +The same four classes work programmatically, exactly like +:doc:`quickstart`: + +.. code-block:: python + + server = CustomServer( + model, dataset, CustomServerConfig(num_iters=5), CustomClientConfig(num_epochs=1) + ) + server.train() + +Safeguards for registered algorithms +------------------------------------ + +Two layers guard a registered algorithm: + +- the **golden regression** suite (``test/test_parallel_equivalence/``) + pins each algorithm's observable trajectory (messages, decisions, + counters, metrics positions) so refactors cannot silently change + behavior; +- the **hook contract test** (``test/test_hook_contract.py``) asserts + that client hooks touch no server state — the parallel-safety + property ``communicate`` above was written for. + +When you add an algorithm to the repository itself, add it to both. + +Legacy hook style +----------------- + +Older code overrides ``communicate(self, target)`` on the *client* and +mutates server state directly. It still works under the sequential +backend (with a ``DeprecationWarning``), but it is incompatible with +parallel backends and will be removed; new algorithms should build and +return the typed message as shown above. + +Next steps +---------- + +- :ref:`howto-custom-dataset` — bring your own data partitioning. +- :ref:`howto-custom-optimizer` — add a local optimizer. +- :doc:`parallel_execution` — the full hook contract and backends. +- :doc:`api/nodes` — the complete Server/Client API. diff --git a/docs/source/howto-custom-dataset.rst b/docs/source/howto-custom-dataset.rst new file mode 100644 index 00000000..5fc6732e --- /dev/null +++ b/docs/source/howto-custom-dataset.rst @@ -0,0 +1,200 @@ +.. _howto-custom-dataset: + +How to: add a custom dataset +============================ + +A federated dataset in fl-sim combines two components: the **raw data** +and a **partitioning scheme** that spreads it across the simulated +clients. This guide covers the complete example in +``test-files/custom_dataset.py`` (a re-implementation of the FedProx +paper's FEMNIST variant, exercised in CI via +``test-files/custom_conf.yml``) and then derives a minimal template +starting from NumPy arrays. + +Choose the base class +--------------------- + +- :class:`~fl_sim.data_processing.fed_dataset.FedVisionDataset` — image + data with the usual ``TensorDataset`` plumbing; what the example uses; +- :class:`~fl_sim.data_processing.fed_dataset.FedDataset` — the fully + abstract base, for anything non-vision. + +The required interface is small: load data in ``_preload``, hand out +dataloaders per client in ``get_dataloader``, and score predictions in +``evaluate``. + +The full example +---------------- + +.. literalinclude:: ../../test-files/custom_dataset.py + :language: python + :start-after: # --- begin: dataset --- + :end-before: # --- end: preload --- + +``_preload`` runs once at construction: resolve the data directory, +declare the defaults the framework queries (client counts, batch size, +the file layout), set the ``criterion``, download if needed, and read +the raw files into memory. fl-sim datasets are **immutable after +construction** — partitioning happens here, once. + +.. literalinclude:: ../../test-files/custom_dataset.py + :language: python + :start-after: # --- begin: dataloader --- + :end-before: # --- end: dataloader --- + +The contract of ``get_dataloader``: given a client index (or ``None`` +for "everything"), return a **pair** of dataloaders ``(train, test)`` +with shuffling enabled — the framework re-requests them every round, +and under ``rng_mode="per_client"`` (:doc:`tutorial-parallel-reproducible`) +their shuffling is what gets re-seeded per client. + +.. literalinclude:: ../../test-files/custom_dataset.py + :language: python + :start-after: # --- begin: evaluate --- + :end-before: # --- end: evaluate --- + +``evaluate`` turns a batch of predictions and labels into the metric +dictionary that lands in the logs (``acc``, ``loss``, ``num_samples``, +... — the same keys :doc:`quickstart` read back). + +.. literalinclude:: ../../test-files/custom_dataset.py + :language: python + :start-after: # --- begin: mirrors-and-models --- + :end-before: # --- end: mirrors-and-models --- + +The remaining properties are conventions rather than requirements: +``mirrors`` / ``url`` feed the download mechanism (fallback URLs tried +in order — see the :meth:`~fl_sim.data_processing.fed_dataset.FedDataset.mirrors` +API for the protocol), ``candidate_models`` declares the models that +fit this data (the CLI's ``model: name:`` picks from here), +``label_map`` names the classes, ``doi`` carries citations. + +The minimal template +-------------------- + +From NumPy arrays to a usable federated dataset. The template below has +been verified by end-to-end execution; three conventions it encodes are +easy to miss and costly to debug: + +- **define your own ``__init__``**: the abstract + :class:`~fl_sim.data_processing.fed_dataset.FedDataset` base has no + constructor of its own (the vision subclass supplies one); accept + ``(datadir, transform, seed, **extra_config)`` and re-seed via + ``set_seed`` so every constructor re-seeds the global RNG exactly + like the built-ins do; +- **the framework requests global loaders too**: ``get_dataloader`` + must handle ``client_idx=None`` ("all clients"), not just per-client + indices; +- **``candidate_models`` should return fl-sim models** (they carry the + ``.device`` attribute the device-allocation step relies on). + +Two ``load_partition_data*`` methods appear required but are a legacy +tff-style bulk-loading API that the training loop never calls — stub +implementations satisfy the abstract interface: + +.. code-block:: python + + import numpy as np + import torch + import torch.utils.data as torchdata + + from fl_sim.models import nn as mnn + from fl_sim.utils.misc import set_seed + from fl_sim.data_processing._register import register_fed_dataset + from fl_sim.data_processing.fed_dataset import FedDataset + + + @register_fed_dataset() + class MyArrays(FedDataset): + __name__ = "MyArrays" + + def __init__(self, datadir=None, transform="none", seed=0, **extra_config): + super().__init__() + self.datadir = datadir + self.transform = transform + self.seed = seed + set_seed(seed) # so that every constructor re-seeds, as the built-ins do + for k, v in extra_config.items(): + setattr(self, k, v) + self._preload() + + def _preload(self, datadir=None): + rng = np.random.default_rng(self.seed) + n_clients = 30 + self._parts = [ # one (features, labels) pair per client + ( + rng.normal(size=(100, 20)).astype(np.float32), + rng.integers(0, 2, size=100).astype(np.int64), + ) + for _ in range(n_clients) + ] + self.DEFAULT_TRAIN_CLIENTS_NUM = n_clients + self.DEFAULT_TEST_CLIENTS_NUM = n_clients + self.DEFAULT_BATCH_SIZE = 32 + self.criterion = torch.nn.CrossEntropyLoss() + + def get_dataloader(self, train_bs=None, test_bs=None, client_idx=None): + parts = self._parts if client_idx is None else [self._parts[client_idx]] + x = np.concatenate([p[0] for p in parts]) + y = np.concatenate([p[1] for p in parts]) + ds = torchdata.TensorDataset(torch.from_numpy(x), torch.from_numpy(y)) + dl = torchdata.DataLoader(ds, batch_size=train_bs or self.DEFAULT_BATCH_SIZE, shuffle=True) + return dl, dl # train and test loaders; a real dataset separates them + + def evaluate(self, probs, truths): + return { + "acc": (probs.argmax(1) == truths).float().mean().item(), + "loss": self.criterion(probs, truths).item(), + "num_samples": probs.shape[0], + } + + # legacy tff-style bulk loaders; the training loop never calls them + def load_partition_data(self, batch_size=None): + raise NotImplementedError + + def load_partition_data_distributed(self, process_id, batch_size=None): + raise NotImplementedError + + @property + def url(self): + return "" # nothing to download + + @property + def candidate_models(self): + # fl-sim models carry a .device attribute the framework relies on + return {"mlp": mnn.MLP(dim_in=20, dim_out=2, ndim=0)} + + @property + def doi(self): + return [] + +Usage +----- + +With the ``@register_fed_dataset()`` decorator applied, the dataset is +constructible by name once its module has been imported (or via the +module *path* as the dataset ``name`` — see +:class:`~fl_sim.data_processing.fed_data_args.FedDataArgs`), and every +built-in works on it unchanged: + +.. code-block:: python + + import my_arrays # the module above; registers the dataset + + from fl_sim.data_processing import FedDataArgs + from fl_sim.algorithms.fedopt import FedAvgClientConfig, FedAvgServer, FedAvgServerConfig + + dataset = FedDataArgs(name="MyArrays")._create_fed_dataset() + server = FedAvgServer( + dataset.candidate_models["mlp"], + dataset, + FedAvgServerConfig(num_iters=5, num_clients=10, clients_sample_ratio=1.0), + FedAvgClientConfig(num_epochs=1, batch_size=32), + ) + server.train() # identical to :doc:`quickstart` from here on + +Next steps +---------- + +- :ref:`howto-custom-algorithm` — the algorithm side of the contract. +- :doc:`api/datasets` — the full FedDataset API reference. diff --git a/docs/source/howto-custom-optimizer.rst b/docs/source/howto-custom-optimizer.rst new file mode 100644 index 00000000..b8e386ec --- /dev/null +++ b/docs/source/howto-custom-optimizer.rst @@ -0,0 +1,75 @@ +.. _howto-custom-optimizer: + +How to: add a custom optimizer +============================== + +The local training step is pluggable: fl-sim keeps its own optimizer +registry, and any optimizer registered there can be selected by name in +the client config. This is the shortest of the three guides; the +complete example is 48 lines (``test-files/custom_optimizer.py``, +exercised in CI via ``test-files/custom_conf.yml``). + +Where optimizers plug in +------------------------ + +fl-sim's local optimizers live in :mod:`fl_sim.optimizers` and +subclass :class:`~fl_sim.optimizers.base.ProxSGD` — the proximal SGD +base that supports the extra local-training knobs federated algorithms +need (proximal terms, variance-reduction buffers passed by +``optimizer.step(...)``, per the custom algorithm in +:ref:`howto-custom-algorithm`). + +Registration makes the optimizer discoverable by name — the same +pattern as algorithms and datasets: + +.. literalinclude:: ../../test-files/custom_optimizer.py + :language: python + :start-after: # --- begin: optimizer --- + :end-before: # --- end: optimizer --- + +This example wraps ``FedProxOptimizer``'s behavior under a new name; +a genuinely new optimizer overrides the ``ProxSGD`` step methods +instead of the constructor. + +Usage +----- + +Registered optimizers are referenced by name in the client config — +programmatically: + +.. code-block:: python + + FedAvgClientConfig(optimizer="CustomOptimizer", lr=1e-2) + +or in a YAML experiment matrix: + +.. code-block:: yaml + + algorithm: + client: + optimizer: CustomOptimizer + +The name is resolved through :func:`~fl_sim.optimizers.get_optimizer` +at client setup (a trailing ``Optimizer`` suffix is optional); an +unregistered name fails there with ``Optimizer ... is not registered``. + +Caveats +------- + +- the optimizer is constructed **once per client per run**; anything + stateful it keeps lives for the whole federated run; +- some algorithms fix the client optimizer in their client-config + validators (e.g. the FedProx family selects ``FedProx`` / + ``FedProx_VR`` from its ``vr`` flag) — for those algorithms the + ``optimizer`` config field is ignored by design; +- the server-side optimizers (FedAdam, FedYogi, … in the FedOpt + family) are a different mechanism — those are selected through the + server config's ``optimizer`` field and are not part of this + registry. + +Next steps +---------- + +- :ref:`howto-custom-algorithm` — when the *algorithm*, not just the + optimizer, is what's new. +- :doc:`api/optimizers` — the optimizer API reference. diff --git a/docs/source/index.rst b/docs/source/index.rst index 6fb62e21..5ed85680 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -27,6 +27,14 @@ Project Links: `GitHub `_ | `gitee , 2026. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: fl-sim\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-10-29 00:10+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: zh_CN \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/howto-custom-algorithm.rst:4 +msgid "How to: implement a custom algorithm" +msgstr "操作指南:实现一个自定义算法" + +#: ../../source/howto-custom-algorithm.rst:6 +msgid "" +"Built-in algorithms cover the classical methods; research often requires variants. This guide implements a complete algorithm — a FedProx variant with named parameter exchange and optional variance " +"reduction — walking through every file you need and every hook you can override. The running example is ``test-files/custom_alg.py``; the code shown below is included from that file, so it is " +"exercised in CI (the CLI runs it via ``test-files/custom_conf.yml``)." +msgstr "内置算法覆盖了经典方法,而研究往往需要变体。本指南实现一个完整算法——带按名参数交换与可选方差削减的 FedProx 变体——走遍你需要的每个文件与每个可覆写的钩子。贯穿示例是 ``test-files/custom_alg.py``;下文展示的代码都从该文件引入,因此在 CI 中真实执行(CLI 经 ``test-files/custom_conf.yml`` 运行它)。" + +#: ../../source/howto-custom-algorithm.rst:14 +msgid "" +"The one-sentence model: **an algorithm is two config classes plus hook overrides on the Server and Client classes** — the training loop, the communication schedule scaffolding, logging, evaluation " +"and the parallel executors all stay untouched (:doc:`concepts` explains why)." +msgstr "一句话模型: **算法 = 一对配置类 + Server/Client 类上的钩子覆写** ——训练循环、通信调度脚手架、日志、评估、并行执行器全部原样( :doc:`concepts` 解释了为什么)。" + +#: ../../source/howto-custom-algorithm.rst:20 +msgid "When is a custom algorithm needed?" +msgstr "何时需要自定义算法" + +#: ../../source/howto-custom-algorithm.rst:22 +msgid "your algorithm changes **what clients compute** locally (new local objective, extra local state) → override client hooks;" +msgstr "算法改变了客户端 **本地计算的内容** (新的本地目标、额外的本地状态)→ 覆写客户端钩子;" + +#: ../../source/howto-custom-algorithm.rst:24 +msgid "it changes **what is exchanged or how replies are aggregated** → override server hooks;" +msgstr "算法改变了 **交换的内容或回复的聚合方式** → 覆写服务器钩子;" + +#: ../../source/howto-custom-algorithm.rst:26 +msgid "it needs **new hyperparameters** → add config fields." +msgstr "算法需要 **新的超参数** → 添加配置字段。" + +#: ../../source/howto-custom-algorithm.rst:28 +msgid "If you only need different *values* for existing hyperparameters, no code is needed — use the built-in algorithm's config." +msgstr "如果只是想给既有超参数设不同的 *值* ,不需要写任何代码——直接用内置算法的配置。" + +#: ../../source/howto-custom-algorithm.rst:32 +msgid "The config classes" +msgstr "配置类" + +#: ../../source/howto-custom-algorithm.rst:34 +msgid "" +"Two pydantic models, one for each side. New fields become CLI/CLI-YAML options automatically; ``server_config_kw_doc`` / ``client_config_kw_doc`` append the inherited fields' docs so the API " +"reference stays complete:" +msgstr "两个 pydantic 模型,各管一侧。新字段自动成为 CLI/YAML 选项; ``server_config_kw_doc`` / ``client_config_kw_doc`` 把继承字段的文档追加进来,API 参考得以保持完整:" + +#: ../../source/howto-custom-algorithm.rst:43 +msgid "The ``model_validator`` here fixes the local optimizer from the ``vr`` flag — config-level invariants belong in validators, so invalid combinations fail at construction, not mid-run." +msgstr "这里的 ``model_validator`` 由 ``vr`` 标志固定本地优化器——配置级不变式就该放在 validator 里,让非法组合在构造时失败,而不是跑到一半。" + +#: ../../source/howto-custom-algorithm.rst:48 +msgid "The server hooks" +msgstr "服务器钩子" + +#: ../../source/howto-custom-algorithm.rst:50 +msgid "The server customizes the **downlink** (``communicate``) and the **aggregation** (``update``). Everything else — sampling, the round loop, metrics — is inherited:" +msgstr "服务器定制 **下行** ( ``communicate`` )与 **聚合** ( ``update`` )。其余一切——采样、轮循环、指标——全部继承:" + +#: ../../source/howto-custom-algorithm.rst:59 +msgid "Points worth noting:" +msgstr "值得注意的点:" + +#: ../../source/howto-custom-algorithm.rst:61 +msgid "" +"``communicate(target)`` builds a typed :class:`~fl_sim.messages.ServerToClient` message; ``extras`` carries algorithm-specific payloads (here, server-side gradients for variance reduction) alongside" +" the model." +msgstr "``communicate(target)`` 构建带类型的 :class:`~fl_sim.messages.ServerToClient` 消息;``extras`` 携带算法特有载荷(此处是方差削减用的服务器侧梯度),与模型并列。" + +#: ../../source/howto-custom-algorithm.rst:64 +msgid "" +"``update()`` runs on the main thread after the sampled clients' replies arrive in ``self._received_messages`` — in sampled-client order, which is what keeps aggregation bitwise identical across " +":doc:`parallel_execution` backends." +msgstr "``update()`` 在主线程上运行,此时被采样客户端的回复已进入 ``self._received_messages``——按被采样客户端顺序排列,这正是聚合在所有 :doc:`parallel_execution` 后端上逐位一致的原因。" + +#: ../../source/howto-custom-algorithm.rst:68 +msgid "``client_cls`` and ``config_cls`` wire the custom classes together; ``@register_algorithm()`` (shown above each class) makes the algorithm usable by name." +msgstr "``client_cls`` 与 ``config_cls`` 把自定义类互相接起来; ``@register_algorithm()`` (见各类上方)让算法可按名字使用。" + +#: ../../source/howto-custom-algorithm.rst:73 +msgid "The client hooks" +msgstr "客户端钩子" + +#: ../../source/howto-custom-algorithm.rst:75 +msgid "The client customizes the **local training** (``update``) and the **uplink** (``communicate``):" +msgstr "客户端定制 **本地训练** ( ``update`` )与 **上行** ( ``communicate`` ):" + +#: ../../source/howto-custom-algorithm.rst:83 +msgid "" +"The most important contract on this page is in ``communicate``: it **builds and returns** a :class:`~fl_sim.messages.ClientToServer` and touches nothing but the client's own state. The framework " +"sends the message and maintains the communication counters. That is what makes the hook safe to run inside a worker thread when :doc:`parallel execution ` is on — a hook that " +"reached into server state would only work sequentially." +msgstr "" +"本页最重要的契约在 ``communicate`` 里:它 **构建并返回** 一个 :class:`~fl_sim.messages.ClientToServer`,除客户端自身状态外不碰任何东西。消息的发送与通信计数由框架维护。正因如此,开启 :doc:`并行执行 ` " +"时该钩子才能安全地跑在工作线程里——一个伸手去碰服务器状态的钩子只能在串行模式下工作。" + +#: ../../source/howto-custom-algorithm.rst:92 +msgid "Named exchange, by the way" +msgstr "顺带说一句:按名交换" + +#: ../../source/howto-custom-algorithm.rst:94 +msgid "" +"This example exchanges parameters **by name** (a ``model_state`` mapping from parameter name to tensor, i.e. :data:`~fl_sim.messages.ModelState`) instead of the legacy flat positional list. Names " +"survive reordering and partial modules, and the loading side (``accept_parameters``, ``update``) looks parameters up by name. The flat-list style still works everywhere; prefer names for new " +"algorithms." +msgstr "本示例按 **参数名** 交换参数( ``model_state`` 映射:参数名 → 张量,即 :data:`~fl_sim.messages.ModelState` ),而不是遗留的扁平位置列表。名字能扛住重排序与部分模块,加载侧( ``accept_parameters`` 、 ``update`` )按名查找。扁平列表写法依然处处可用;但新算法请优先用名字。" + +#: ../../source/howto-custom-algorithm.rst:102 +msgid "Register and run" +msgstr "注册与运行" + +#: ../../source/howto-custom-algorithm.rst:104 +msgid "``@register_algorithm()`` (applied to all four classes above) puts the algorithm into the registry; from then on the CLI and YAML matrices accept it like any built-in:" +msgstr "``@register_algorithm()`` (上面四个类都应用了)把算法送进注册表;此后 CLI 与 YAML 矩阵像对待内置算法一样接受它:" + +#: ../../source/howto-custom-algorithm.rst:118 +msgid "The same four classes work programmatically, exactly like :doc:`quickstart`:" +msgstr "同样这四个类也可以编程方式使用,与 :doc:`quickstart` 完全一致:" + +#: ../../source/howto-custom-algorithm.rst:129 +msgid "Safeguards for registered algorithms" +msgstr "注册算法的行为保障" + +#: ../../source/howto-custom-algorithm.rst:131 +msgid "Two layers guard a registered algorithm:" +msgstr "两层机制守护一个注册算法:" + +#: ../../source/howto-custom-algorithm.rst:133 +msgid "" +"the **golden regression** suite (``test/test_parallel_equivalence/``) pins each algorithm's observable trajectory (messages, decisions, counters, metrics positions) so refactors cannot silently " +"change behavior;" +msgstr " **golden 回归** 套件( ``test/test_parallel_equivalence/`` )钉住每个算法的可观测轨迹(消息、决策、计数器、指标位置),重构无法悄悄改变行为;" + +#: ../../source/howto-custom-algorithm.rst:137 +msgid "the **hook contract test** (``test/test_hook_contract.py``) asserts that client hooks touch no server state — the parallel-safety property ``communicate`` above was written for." +msgstr " **钩子契约测试** ( ``test/test_hook_contract.py`` )断言客户端钩子不触碰服务器状态——上面 ``communicate`` 的并行安全性质正是为此而写。" + +#: ../../source/howto-custom-algorithm.rst:141 +msgid "When you add an algorithm to the repository itself, add it to both." +msgstr "把算法合入本仓库时,两层都要接入。" + +#: ../../source/howto-custom-algorithm.rst:144 +msgid "Legacy hook style" +msgstr "遗留钩子写法" + +#: ../../source/howto-custom-algorithm.rst:146 +msgid "" +"Older code overrides ``communicate(self, target)`` on the *client* and mutates server state directly. It still works under the sequential backend (with a ``DeprecationWarning``), but it is " +"incompatible with parallel backends and will be removed; new algorithms should build and return the typed message as shown above." +msgstr "更老的代码在 *客户端* 上覆写 ``communicate(self, target)`` 并直接改动服务器状态。它在串行后端下仍然可用(会带 ``DeprecationWarning`` ),但与并行后端不兼容,终将移除;新算法请按上文所示构建并返回带类型的消息。" + +#: ../../source/howto-custom-algorithm.rst:153 +msgid "Next steps" +msgstr "下一步" + +#: ../../source/howto-custom-algorithm.rst:155 +msgid ":ref:`howto-custom-dataset` — bring your own data partitioning." +msgstr ":ref:`howto-custom-dataset`——带上你自己的数据划分。" + +#: ../../source/howto-custom-algorithm.rst:156 +msgid ":ref:`howto-custom-optimizer` — add a local optimizer." +msgstr ":ref:`howto-custom-optimizer`——添加一个本地优化器。" + +#: ../../source/howto-custom-algorithm.rst:157 +msgid ":doc:`parallel_execution` — the full hook contract and backends." +msgstr ":doc:`parallel_execution`——完整的钩子契约与各后端。" + +#: ../../source/howto-custom-algorithm.rst:158 +msgid ":doc:`api/nodes` — the complete Server/Client API." +msgstr ":doc:`api/nodes`——完整的 Server/Client API。" + +#~ msgid "When do you need this?" +#~ msgstr "什么时候需要它?" + +#~ msgid "How your algorithm is kept honest" +#~ msgstr "你的算法如何被守护" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/howto-custom-dataset.po b/docs/source/locale/zh_CN/LC_MESSAGES/howto-custom-dataset.po new file mode 100644 index 00000000..5bd71aeb --- /dev/null +++ b/docs/source/locale/zh_CN/LC_MESSAGES/howto-custom-dataset.po @@ -0,0 +1,129 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2023, WEN Hao +# This file is distributed under the same license as the fl-sim package. +# FIRST AUTHOR , 2026. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: fl-sim\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-10-29 00:10+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: zh_CN \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/howto-custom-dataset.rst:4 +msgid "How to: add a custom dataset" +msgstr "操作指南:添加自定义数据集" + +#: ../../source/howto-custom-dataset.rst:6 +msgid "" +"A federated dataset in fl-sim combines two components: the **raw data** and a **partitioning scheme** that spreads it across the simulated clients. This guide covers the complete example in ``test-" +"files/custom_dataset.py`` (a re-implementation of the FedProx paper's FEMNIST variant, exercised in CI via ``test-files/custom_conf.yml``) and then derives a minimal template starting from NumPy " +"arrays." +msgstr "fl-sim 中的联邦数据集由两部分组成: **原始数据** 与把它分散到各模拟客户端的 **划分方案** 。本指南先走完 ``test-files/custom_dataset.py`` 的完整示例(FedProx 论文所用 FEMNIST 变体的复刻,经 ``test-files/custom_conf.yml`` 在 CI 中执行),再从 NumPy 数组出发给出最小模板。" + +#: ../../source/howto-custom-dataset.rst:15 +msgid "Choose the base class" +msgstr "选择基类" + +#: ../../source/howto-custom-dataset.rst:17 +msgid ":class:`~fl_sim.data_processing.fed_dataset.FedVisionDataset` — image data with the usual ``TensorDataset`` plumbing; what the example uses;" +msgstr ":class:`~fl_sim.data_processing.fed_dataset.FedVisionDataset`——图像数据,带常规的 ``TensorDataset`` 管线;示例用的就是它;" + +#: ../../source/howto-custom-dataset.rst:19 +msgid ":class:`~fl_sim.data_processing.fed_dataset.FedDataset` — the fully abstract base, for anything non-vision." +msgstr ":class:`~fl_sim.data_processing.fed_dataset.FedDataset`——完全抽象的基类,适合一切非视觉数据。" + +#: ../../source/howto-custom-dataset.rst:22 +msgid "The required interface is small: load data in ``_preload``, hand out dataloaders per client in ``get_dataloader``, and score predictions in ``evaluate``." +msgstr "必需接口很小:在 ``_preload`` 里加载数据,在 ``get_dataloader`` 里按客户端分发dataloader,在 ``evaluate`` 里为预测打分。" + +#: ../../source/howto-custom-dataset.rst:27 +msgid "The full example" +msgstr "完整示例" + +#: ../../source/howto-custom-dataset.rst:34 +msgid "" +"``_preload`` runs once at construction: resolve the data directory, declare the defaults the framework queries (client counts, batch size, the file layout), set the ``criterion``, download if " +"needed, and read the raw files into memory. fl-sim datasets are **immutable after construction** — partitioning happens here, once." +msgstr "``_preload`` 在构造时只跑一次:解析数据目录、声明框架会查询的默认值(客户端数、批大小、文件布局)、设置 ``criterion``、按需下载,并把原始文件读入内存。fl-sim 数据集 **构造后不可变** ——划分就在这里做,且只做一次。" + +#: ../../source/howto-custom-dataset.rst:45 +msgid "" +"The contract of ``get_dataloader``: given a client index (or ``None`` for \"everything\"), return a **pair** of dataloaders ``(train, test)`` with shuffling enabled — the framework re-requests them " +"every round, and under ``rng_mode=\"per_client\"`` (:doc:`tutorial-parallel-reproducible`) their shuffling is what gets re-seeded per client." +msgstr "``get_dataloader`` 的契约:给定客户端索引(或 ``None`` 表示\"全部\"),返回一对开启洗牌的 dataloader ``(train, test)`` —— 框架每轮都会重新请求它们;在 ``rng_mode=\"per_client\"`` ( :doc:`tutorial-parallel-reproducible` )下,被按客户端重新播种的正是这里的洗牌。" + +#: ../../source/howto-custom-dataset.rst:56 +msgid "``evaluate`` turns a batch of predictions and labels into the metric dictionary that lands in the logs (``acc``, ``loss``, ``num_samples``, ... — the same keys :doc:`quickstart` read back)." +msgstr "``evaluate`` 把一批预测与标签变成写进日志的指标字典( ``acc`` 、 ``loss`` 、 ``num_samples`` 等——正是 :doc:`quickstart` 读回的那些键)。" + +#: ../../source/howto-custom-dataset.rst:65 +msgid "" +"The remaining properties are conventions rather than requirements: ``mirrors`` / ``url`` feed the download mechanism (fallback URLs tried in order — see the " +":meth:`~fl_sim.data_processing.fed_dataset.FedDataset.mirrors` API for the protocol), ``candidate_models`` declares the models that fit this data (the CLI's ``model: name:`` picks from here), " +"``label_map`` names the classes, ``doi`` carries citations." +msgstr "" +"其余 property 是惯例而非硬性要求: ``mirrors`` / ``url`` 供下载机制使用(备选 URL 按序尝试——协议见 :meth:`~fl_sim.data_processing.fed_dataset.FedDataset.mirrors` API); ``candidate_models`` 声明适合本数据的模型(CLI 的 ``model: name:`` " +"从这里选取); ``label_map`` 给类别命名; ``doi`` 携带引用。" + +#: ../../source/howto-custom-dataset.rst:73 +msgid "The minimal template" +msgstr "最小模板" + +#: ../../source/howto-custom-dataset.rst:75 +msgid "From NumPy arrays to a usable federated dataset. The template below has been verified by end-to-end execution; three conventions it encodes are easy to miss and costly to debug:" +msgstr "从 NumPy 数组到可用的联邦数据集。下述模板已通过端到端执行验证;它编码的三个约定容易漏掉、调起来又贵:" + +#: ../../source/howto-custom-dataset.rst:79 +msgid "" +"**define your own ``__init__``**: the abstract :class:`~fl_sim.data_processing.fed_dataset.FedDataset` base has no constructor of its own (the vision subclass supplies one); accept ``(datadir, " +"transform, seed, **extra_config)`` and re-seed via ``set_seed`` so every constructor re-seeds the global RNG exactly like the built-ins do;" +msgstr "" +" **自己定义 ``__init__``** :抽象基类 :class:`~fl_sim.data_processing.fed_dataset.FedDataset` 本身没有构造器(视觉子类提供了一个);接受 ``(datadir, transform, seed, **extra_config)`` 并经 ``set_seed`` 重新播种,让每个构造器都和内置数据集一样重置全局 " +"RNG;" + +#: ../../source/howto-custom-dataset.rst:85 +msgid "**the framework requests global loaders too**: ``get_dataloader`` must handle ``client_idx=None`` (\"all clients\"), not just per-client indices;" +msgstr " **框架也会请求全局 loader** : ``get_dataloader`` 必须处理 ``client_idx=None`` (\"全部客户端\"),不能只处理逐客户端的索引;" + +#: ../../source/howto-custom-dataset.rst:88 +msgid "**``candidate_models`` should return fl-sim models** (they carry the ``.device`` attribute the device-allocation step relies on)." +msgstr " **``candidate_models`` 应返回 fl-sim 模型** (它们带有设备分配步骤依赖的 ``.device`` 属性)。" + +#: ../../source/howto-custom-dataset.rst:91 +msgid "Two ``load_partition_data*`` methods appear required but are a legacy tff-style bulk-loading API that the training loop never calls — stub implementations satisfy the abstract interface:" +msgstr "两个 ``load_partition_data*`` 方法看似必需,实为训练循环从不调用的遗留 tff 风格批量加载 API——桩实现即可满足抽象接口:" + +#: ../../source/howto-custom-dataset.rst:172 +msgid "Usage" +msgstr "使用" + +#: ../../source/howto-custom-dataset.rst:174 +msgid "" +"With the ``@register_fed_dataset()`` decorator applied, the dataset is constructible by name once its module has been imported (or via the module *path* as the dataset ``name`` — see " +":class:`~fl_sim.data_processing.fed_data_args.FedDataArgs`), and every built-in works on it unchanged:" +msgstr "应用 ``@register_fed_dataset()`` 装饰器后,只要模块被导入过,数据集即可按名字构造(或以模块 *路径* 作为数据集 ``name`` ——见 :class:`~fl_sim.data_processing.fed_data_args.FedDataArgs`),且所有内置组件原样可用:" + +#: ../../source/howto-custom-dataset.rst:197 +msgid "Next steps" +msgstr "下一步" + +#: ../../source/howto-custom-dataset.rst:199 +msgid ":ref:`howto-custom-algorithm` — the algorithm side of the contract." +msgstr ":ref:`howto-custom-algorithm`——契约的算法一侧。" + +#: ../../source/howto-custom-dataset.rst:200 +msgid ":doc:`api/datasets` — the full FedDataset API reference." +msgstr ":doc:`api/datasets`——完整的 FedDataset API 参考。" + +#~ msgid "Use it" +#~ msgstr "使用" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/howto-custom-optimizer.po b/docs/source/locale/zh_CN/LC_MESSAGES/howto-custom-optimizer.po new file mode 100644 index 00000000..8b92802c --- /dev/null +++ b/docs/source/locale/zh_CN/LC_MESSAGES/howto-custom-optimizer.po @@ -0,0 +1,103 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2023, WEN Hao +# This file is distributed under the same license as the fl-sim package. +# FIRST AUTHOR , 2026. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: fl-sim\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-10-29 00:10+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: zh_CN \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/howto-custom-optimizer.rst:4 +msgid "How to: add a custom optimizer" +msgstr "操作指南:添加自定义优化器" + +#: ../../source/howto-custom-optimizer.rst:6 +msgid "" +"The local training step is pluggable: fl-sim keeps its own optimizer registry, and any optimizer registered there can be selected by name in the client config. This is the shortest of the three " +"guides; the complete example is 48 lines (``test-files/custom_optimizer.py``, exercised in CI via ``test-files/custom_conf.yml``)." +msgstr "本地训练步是可插拔的:fl-sim 维护自己的优化器注册表,任何注册过的优化器都能在客户端配置里按名字选用。这是三篇指南里最短的一篇——完整示例只有 48 行( ``test-files/custom_optimizer.py`` ,经 ``test-files/custom_conf.yml`` 在 CI 中执行)。" + +#: ../../source/howto-custom-optimizer.rst:13 +msgid "Where optimizers plug in" +msgstr "优化器接在哪里" + +#: ../../source/howto-custom-optimizer.rst:15 +msgid "" +"fl-sim's local optimizers live in :mod:`fl_sim.optimizers` and subclass :class:`~fl_sim.optimizers.base.ProxSGD` — the proximal SGD base that supports the extra local-training knobs federated " +"algorithms need (proximal terms, variance-reduction buffers passed by ``optimizer.step(...)``, per the custom algorithm in :ref:`howto-custom-algorithm`)." +msgstr "" +"fl-sim 的本地优化器都在 :mod:`fl_sim.optimizers` 里,继承 :class:`~fl_sim.optimizers.base.ProxSGD`——这个近端 SGD 基类支持联邦算法需要的额外本地训练旋钮(近端项、由 ``optimizer.step(...)`` 传入的方差削减缓冲区,用法见 :ref:`howto-custom-algorithm` " +"的自定义算法)。" + +#: ../../source/howto-custom-optimizer.rst:22 +msgid "Registration makes the optimizer discoverable by name — the same pattern as algorithms and datasets:" +msgstr "注册让优化器可按名字发现——与算法、数据集是同一模式:" + +#: ../../source/howto-custom-optimizer.rst:30 +msgid "This example wraps ``FedProxOptimizer``'s behavior under a new name; a genuinely new optimizer overrides the ``ProxSGD`` step methods instead of the constructor." +msgstr "这个例子把 ``FedProxOptimizer`` 的行为换了个名字;真正全新的优化器应覆写 ``ProxSGD`` 的 step 方法而不是构造器。" + +#: ../../source/howto-custom-optimizer.rst:35 +msgid "Usage" +msgstr "使用" + +#: ../../source/howto-custom-optimizer.rst:37 +msgid "Registered optimizers are referenced by name in the client config — programmatically:" +msgstr "注册过的优化器在客户端配置里按名字引用——编程方式:" + +#: ../../source/howto-custom-optimizer.rst:44 +msgid "or in a YAML experiment matrix:" +msgstr "或在 YAML 实验矩阵里:" + +#: ../../source/howto-custom-optimizer.rst:52 +msgid "" +"The name is resolved through :func:`~fl_sim.optimizers.get_optimizer` at client setup (a trailing ``Optimizer`` suffix is optional); an unregistered name fails there with ``Optimizer ... is not " +"registered``." +msgstr "名字在客户端初始化时经 :func:`~fl_sim.optimizers.get_optimizer` 解析(结尾的 ``Optimizer`` 后缀可省略);未注册的名字会在此报错 ``Optimizer ... is not registered`` 。" + +#: ../../source/howto-custom-optimizer.rst:57 +msgid "Caveats" +msgstr "注意事项" + +#: ../../source/howto-custom-optimizer.rst:59 +msgid "the optimizer is constructed **once per client per run**; anything stateful it keeps lives for the whole federated run;" +msgstr "优化器 **每次运行中每个客户端只构造一次** ;它保存的任何状态会贯穿整个联邦运行;" + +#: ../../source/howto-custom-optimizer.rst:61 +msgid "" +"some algorithms fix the client optimizer in their client-config validators (e.g. the FedProx family selects ``FedProx`` / ``FedProx_VR`` from its ``vr`` flag) — for those algorithms the " +"``optimizer`` config field is ignored by design;" +msgstr "某些算法在客户端配置的 validator 里固定了本地优化器(如 FedProx 家族由 ``vr`` 标志选择 ``FedProx`` / ``FedProx_VR`` )——对这些算法, ``optimizer`` 配置字段被设计性地忽略;" + +#: ../../source/howto-custom-optimizer.rst:65 +msgid "" +"the server-side optimizers (FedAdam, FedYogi, … in the FedOpt family) are a different mechanism — those are selected through the server config's ``optimizer`` field and are not part of this " +"registry." +msgstr "服务器侧优化器(FedOpt 家族的 FedAdam、FedYogi 等)是另一套机制——经服务器配置的 ``optimizer`` 字段选择,不在本注册表内。" + +#: ../../source/howto-custom-optimizer.rst:71 +msgid "Next steps" +msgstr "下一步" + +#: ../../source/howto-custom-optimizer.rst:73 +msgid ":ref:`howto-custom-algorithm` — when the *algorithm*, not just the optimizer, is what's new." +msgstr ":ref:`howto-custom-algorithm`——如果新的不只是优化器,而是 *算法本身* 。" + +#: ../../source/howto-custom-optimizer.rst:75 +msgid ":doc:`api/optimizers` — the optimizer API reference." +msgstr ":doc:`api/optimizers`——优化器 API 参考。" + +#~ msgid "Use it" +#~ msgstr "使用" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/index.po b/docs/source/locale/zh_CN/LC_MESSAGES/index.po index b515f516..a199c85d 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/index.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/index.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: fl-sim\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-10-27 08:46+0800\n" +"POT-Creation-Date: 2026-10-28 08:06+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -27,11 +27,15 @@ msgstr "快速开始" msgid "Tutorials" msgstr "教程" -#: ../../source/index.rst:28 +#: ../../source/index.rst:30 +msgid "How-to guides" +msgstr "操作指南" + +#: ../../source/index.rst:38 msgid "API Reference" msgstr "API 参考" -#: ../../source/index.rst:41 +#: ../../source/index.rst:51 msgid "Advanced Topics" msgstr "进阶主题" @@ -51,18 +55,18 @@ msgstr "" "项目链接: `GitHub `_ | `gitee " "`_" -#: ../../source/index.rst:51 +#: ../../source/index.rst:61 msgid "Indices and tables" msgstr "索引和表格" -#: ../../source/index.rst:53 +#: ../../source/index.rst:63 msgid ":ref:`genindex`" msgstr "" -#: ../../source/index.rst:54 +#: ../../source/index.rst:64 msgid ":ref:`modindex`" msgstr "" -#: ../../source/index.rst:55 +#: ../../source/index.rst:65 msgid ":ref:`search`" msgstr "" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/tutorial-cli-matrix.po b/docs/source/locale/zh_CN/LC_MESSAGES/tutorial-cli-matrix.po index 1573bbaf..ba28f468 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/tutorial-cli-matrix.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/tutorial-cli-matrix.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: fl-sim \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-10-27 08:46+0800\n" +"POT-Creation-Date: 2026-10-28 08:06+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -31,17 +31,14 @@ msgid "" "and executes every combination. This tutorial builds a four-run matrix on" " synthetic data and shows where the results land." msgstr "" -"每个实验写一个 Python 脚本是无法扩展的。fl-sim 的命令行接口能用单个 YAML 文件跑起" -"一整个 **实验矩阵** :你把配置网格描述一次,CLI 负责展开并执行每个组合。本教程在" -"合成数据上构建一个四次运行的矩阵,并展示结果落在哪里。" +"每个实验写一个 Python 脚本是无法扩展的。fl-sim 的命令行接口能用单个 YAML 文件跑起一整个 **实验矩阵** " +":你把配置网格描述一次,CLI 负责展开并执行每个组合。本教程在合成数据上构建一个四次运行的矩阵,并展示结果落在哪里。" #: ../../source/tutorial-cli-matrix.rst:12 msgid "" "The YAML syntax reference lives in :doc:`cli`; this page is the workflow " "narrative. Prerequisites: :doc:`tutorial-first-experiment`." -msgstr "" -"YAML 语法参考见 :doc:`cli` ;本页讲的是工作流程。前置阅读:" -":doc:`tutorial-first-experiment` 。" +msgstr "YAML 语法参考见 :doc:`cli` ;本页讲的是工作流程。前置阅读::doc:`tutorial-first-experiment` 。" #: ../../source/tutorial-cli-matrix.rst:16 msgid "The matrix file" @@ -61,9 +58,7 @@ msgstr "三个关键部分:" msgid "" "``strategy.matrix`` — the grid. Every combination of the listed values is" " run once: 2 sampling ratios × 2 seeds = 4 experiments." -msgstr "" -"``strategy.matrix``——网格。列出的值每个组合各跑一次:2 个采样比例 × 2 个种子" -"= 4 个实验。" +msgstr "``strategy.matrix``——网格。列出的值每个组合各跑一次:2 个采样比例 × 2 个种子= 4 个实验。" #: ../../source/tutorial-cli-matrix.rst:28 msgid "" @@ -71,8 +66,8 @@ msgid "" "the CLI substitutes the current combination's value. ``seed`` sits at the" " top level, so it seeds the dataset *and* the server config of that run." msgstr "" -"``${{ matrix. }}``——插值。该模式出现之处,CLI 都会代入当前组合的值。" -"``seed`` 位于顶层,因此它同时为该次运行的数据集 *和* 服务器配置设定种子。" +"``${{ matrix. }}``——插值。该模式出现之处,CLI 都会代入当前组合的值。``seed`` " +"位于顶层,因此它同时为该次运行的数据集 *和* 服务器配置设定种子。" #: ../../source/tutorial-cli-matrix.rst:32 msgid "" @@ -83,10 +78,10 @@ msgid "" ":class:`~fl_sim.data_processing.fed_synthetic.FedSynthetic`) are passed " "straight through." msgstr "" -"其余部分—— ``algorithm`` (名字加服务器/客户端配置)、 ``dataset`` 、 ``model`` ——" -"与你在 Python 里传入的配置(见 :doc:`quickstart` )完全一致,只是改写成 YAML。" -"数据集各自的构造参数( :class:`~fl_sim.data_processing.fed_synthetic.FedSynthetic` " -"的 ``alpha`` 、 ``beta`` 、 ``iid`` 、 ``num_clients`` )原样透传。" +"其余部分—— ``algorithm`` (名字加服务器/客户端配置)、 ``dataset`` 、 ``model`` ——与你在 Python" +" 里传入的配置(见 :doc:`quickstart` )完全一致,只是改写成 YAML。数据集各自的构造参数( " +":class:`~fl_sim.data_processing.fed_synthetic.FedSynthetic` 的 ``alpha`` 、" +" ``beta`` 、 ``iid`` 、 ``num_clients`` )原样透传。" #: ../../source/tutorial-cli-matrix.rst:40 msgid "Run it" @@ -97,9 +92,7 @@ msgid "" "You will see the four runs execute in sequence, each logging its " "combination (``ratio=0.5 seed=0``, ...). Total time: seconds — no " "downloads." -msgstr "" -"你会看到四次运行依次执行,每次都会记录自己的组合(``ratio=0.5 seed=0`` 等)。" -"总耗时:几秒——无下载。" +msgstr "你会看到四次运行依次执行,每次都会记录自己的组合(``ratio=0.5 seed=0`` 等)。总耗时:几秒——无下载。" #: ../../source/tutorial-cli-matrix.rst:51 msgid "Where the results land" @@ -114,10 +107,10 @@ msgid "" "recent run (a previous ``config.json`` is rotated to " "``config.json.bak``)." msgstr "" -"所有运行都写入 ``~/.cache/fl-sim/.logs/fedsynthetic-demo/``(本配置的 ``log_dir`` )。" -"每次运行各有一份 ``.txt`` 与一份 ``.json`` 日志——即 :doc:`quickstart` 描述过的" -"那些文件——另有一份 ``config.json`` 保存最近一次运行的确切服务器/客户端配置" -"(已有的 ``config.json`` 会被轮转为 ``config.json.bak`` )。" +"所有运行都写入 ``~/.cache/fl-sim/.logs/fedsynthetic-demo/``(本配置的 ``log_dir`` " +")。每次运行各有一份 ``.txt`` 与一份 ``.json`` 日志——即 :doc:`quickstart` 描述过的那些文件——另有一份 " +"``config.json`` 保存最近一次运行的确切服务器/客户端配置(已有的 ``config.json`` 会被轮转为 " +"``config.json.bak`` )。" #: ../../source/tutorial-cli-matrix.rst:60 msgid "" @@ -127,9 +120,9 @@ msgid "" "other. That is what the ``tag: ratio_${{ ... }}_seed_${{ ... }}`` line in" " the config above is for — give every matrix arm its own tag." msgstr "" -"一个在多次运行同一分钟内结束时就会生效的细节:日志文件名带有时间戳 **加上运行的 " -"``tag``** 。若每个矩阵组合没有独特的 tag,同名的日志会互相覆盖。上面配置里的 " -"``tag: ratio_${{ ... }}_seed_${{ ... }}`` 正是为此——给矩阵的每个分支一个专属 tag。" +"一个在多次运行同一分钟内结束时就会生效的细节:日志文件名带有时间戳 **加上运行的 ``tag``** 。若每个矩阵组合没有独特的 " +"tag,同名的日志会互相覆盖。上面配置里的 ``tag: ratio_${{ ... }}_seed_${{ ... }}`` " +"正是为此——给矩阵的每个分支一个专属 tag。" #: ../../source/tutorial-cli-matrix.rst:67 msgid "Beyond serial execution" @@ -146,11 +139,10 @@ msgid "" "two are orthogonal: process-level parallelism across matrix arms, " "executor-level parallelism across clients." msgstr "" -"长实验组成的矩阵需要不止一个进程。 ``strategy`` 节还接受并行选项(``n_parallel``," -"或 ``parallel: {mode: parallel_task, num_workers: N}`` 块),把矩阵分支派发给工作" -"进程——完整示例见 ``example-configs/multiprocessing-demo.yml``;进程内把 *单次运行* " -"的客户端并行化的执行器后端见 :doc:`parallel_execution` 。两者正交:进程级并行作用于" -"矩阵分支之间,执行器级并行作用于客户端之间。" +"长实验组成的矩阵需要不止一个进程。 ``strategy`` 节还接受并行选项(``n_parallel``,或 ``parallel: " +"{mode: parallel_task, num_workers: N}`` 块),把矩阵分支派发给工作进程——完整示例见 ``example-" +"configs/multiprocessing-demo.yml``;进程内把 *单次运行* 的客户端并行化的执行器后端见 " +":doc:`parallel_execution` 。两者正交:进程级并行作用于矩阵分支之间,执行器级并行作用于客户端之间。" #: ../../source/tutorial-cli-matrix.rst:79 msgid "Next steps" @@ -166,11 +158,21 @@ msgstr ":doc:`parallel_execution`——加速每一次单独的运行。" #: ../../source/tutorial-cli-matrix.rst:83 msgid "" -"How-to guides on custom algorithms, datasets and optimizers are on the " -"way; until then, ``test-files/custom_alg.py``, ``test-" -"files/custom_dataset.py`` and ``test-files/custom_optimizer.py`` are " -"complete, runnable examples." +":ref:`howto-custom-algorithm`, :ref:`howto-custom-dataset` and :ref" +":`howto-custom-optimizer` — extend the framework itself; the ``test-" +"files/custom_*.py`` files they walk through are complete, runnable " +"examples that the CLI accepts as algorithm/dataset paths." msgstr "" -"自定义算法、数据集与优化器的 how-to 指南正在准备中;在那之前," -"``test-files/custom_alg.py``、``test-files/custom_dataset.py`` 与 " -"``test-files/custom_optimizer.py`` 就是完整可运行的例子。" +":ref:`howto-custom-algorithm` 、 :ref:`howto-custom-dataset` 与 :ref:`howto-custom-optimizer` ——扩展框架本身;它们讲解的 ``test-files/custom_*.py`` 就是完整可运行的例子,CLI 可直接接受为算法/数据集路径。" + +#~ msgid "" +#~ "How-to guides on custom algorithms, " +#~ "datasets and optimizers are on the " +#~ "way; until then, ``test-files/custom_alg.py``," +#~ " ``test-files/custom_dataset.py`` and ``test-" +#~ "files/custom_optimizer.py`` are complete, runnable" +#~ " examples." +#~ msgstr "" +#~ "自定义算法、数据集与优化器的 how-to 指南正在准备中;在那之前,``test-" +#~ "files/custom_alg.py``、``test-files/custom_dataset.py`` 与" +#~ " ``test-files/custom_optimizer.py`` 就是完整可运行的例子。" diff --git a/docs/source/tutorial-cli-matrix.rst b/docs/source/tutorial-cli-matrix.rst index 316748a2..6a24a07d 100644 --- a/docs/source/tutorial-cli-matrix.rst +++ b/docs/source/tutorial-cli-matrix.rst @@ -80,7 +80,7 @@ Next steps - :doc:`cli` — the full YAML syntax reference and CLI options. - :doc:`parallel_execution` — speed up each individual run. -- How-to guides on custom algorithms, datasets and optimizers are on - the way; until then, ``test-files/custom_alg.py``, - ``test-files/custom_dataset.py`` and ``test-files/custom_optimizer.py`` - are complete, runnable examples. +- :ref:`howto-custom-algorithm`, :ref:`howto-custom-dataset` and + :ref:`howto-custom-optimizer` — extend the framework itself; the + ``test-files/custom_*.py`` files they walk through are complete, + runnable examples that the CLI accepts as algorithm/dataset paths. diff --git a/test-files/custom_alg.py b/test-files/custom_alg.py index 5de03e56..2e195bcd 100644 --- a/test-files/custom_alg.py +++ b/test-files/custom_alg.py @@ -28,6 +28,7 @@ ] +# --- begin: configs --- @register_algorithm() @add_docstring(server_config_kw_doc, "append") class CustomServerConfig(ServerConfig): @@ -93,6 +94,10 @@ def _fix_optimizer(self) -> "CustomClientConfig": return self +# --- end: configs --- + + +# --- begin: server --- @register_algorithm() @add_docstring( Server.__doc__.replace( @@ -156,6 +161,10 @@ def doi(self) -> List[str]: return ["10.48550/ARXIV.1812.06127"] +# --- end: server --- + + +# --- begin: client --- @register_algorithm() @add_docstring( Client.__doc__.replace( @@ -251,3 +260,6 @@ def train(self) -> None: # free memory del X, y, output, loss self.lr_scheduler.step() + + +# --- end: client --- diff --git a/test-files/custom_dataset.py b/test-files/custom_dataset.py index 3dac0c27..62340f27 100644 --- a/test-files/custom_dataset.py +++ b/test-files/custom_dataset.py @@ -27,6 +27,7 @@ _label_mapping = {i: c for i, c in enumerate("abcdefghijklmnopqrstuvwxyz"[:10])} +# --- begin: dataset --- @register_fed_dataset() class CustomFEMNIST(FedVisionDataset): """ @@ -62,6 +63,7 @@ class CustomFEMNIST(FedVisionDataset): __name__ = "CustomFEMNIST" + # --- begin: preload --- def _preload(self, datadir: Optional[Union[str, Path]] = None) -> None: self.datadir = Path(datadir or CUSTOM_FEMNIST_DATA_DIR).expanduser().resolve() @@ -105,6 +107,9 @@ def _preload(self, datadir: Optional[Union[str, Path]] = None) -> None: ) ) + # --- end: preload --- + + # --- begin: dataloader --- def get_dataloader( self, train_bs: Optional[int] = None, @@ -152,11 +157,14 @@ def get_dataloader( return train_dl, test_dl + # --- end: dataloader --- + def extra_repr_keys(self) -> List[str]: return [ "n_class", ] + super().extra_repr_keys() + # --- begin: evaluate --- def evaluate(self, probs: torch.Tensor, truths: torch.Tensor) -> Dict[str, float]: return { "acc": top_n_accuracy(probs, truths, 1), @@ -166,6 +174,9 @@ def evaluate(self, probs: torch.Tensor, truths: torch.Tensor) -> Dict[str, float "num_samples": probs.shape[0], } + # --- end: evaluate --- + + # --- begin: mirrors-and-models --- @property def url(self) -> str: # https://drive.google.com/file/d/1tCEcJgRJ8NdRo11UJZR6WSKMNdmox4GC/view?usp=sharing @@ -205,3 +216,8 @@ def doi(self) -> List[str]: @property def label_map(self) -> dict: return _label_mapping + + # --- end: mirrors-and-models --- + + +# --- end: dataset --- diff --git a/test-files/custom_optimizer.py b/test-files/custom_optimizer.py index 5b5a91fc..34352c2b 100644 --- a/test-files/custom_optimizer.py +++ b/test-files/custom_optimizer.py @@ -10,6 +10,7 @@ ] +# --- begin: optimizer --- @register_optimizer() class CustomOptimizer(ProxSGD): """Local optimizer for ``CustomOptimizer`` using ``ProxSGD``. @@ -46,3 +47,6 @@ def __init__( ) -> None: self.mu = mu super().__init__(params, lr=lr, prox=mu, momentum=0) + + +# --- end: optimizer ---